Merge \"Expose external app files/cache dir from FileProvider.\"
am: 2b53fdcd54 -s ours
Change-Id: If928c67dea7ed5fba8fee2253328eeaab0310d07
diff --git a/.gitignore b/.gitignore
index a72b3dc..e1876f98 100644
--- a/.gitignore
+++ b/.gitignore
@@ -8,3 +8,4 @@
**/gen
*.iml
**/out
+buildSrc/
diff --git a/Android.mk b/Android.mk
index 8fd0693b..2ef2319 100644
--- a/Android.mk
+++ b/Android.mk
@@ -16,27 +16,68 @@
# Don't include in unbundled build.
ifeq ($(TARGET_BUILD_APPS),)
+SUPPORT_CURRENT_SDK_VERSION := current
SUPPORT_API_CHECK := $(LOCAL_PATH)/apicheck.mk
api_check_current_msg_file := $(LOCAL_PATH)/apicheck_msg_current.txt
api_check_last_msg_file := $(LOCAL_PATH)/apicheck_msg_last.txt
+###########################################################
+# Find all of the files in the given subdirs that match the
+# specified pattern but do not match another pattern. This
+# function uses $(1) instead of LOCAL_PATH as the base.
+# $(1): the base dir, relative to the root of the source tree.
+# $(2): the file name pattern to match.
+# $(3): the file name pattern to exclude.
+# $(4): a list of subdirs of the base dir.
+# Returns: a list of paths relative to the base dir.
+###########################################################
+
+define find-files-in-subdirs-exclude
+$(sort $(patsubst ./%,%, \
+ $(shell cd $(1) ; \
+ find -L $(4) -name $(2) -and -not -name $(3) -and -not -name ".*") \
+ ))
+endef
+
+###########################################################
+## Find all of the files under the named directories where
+## the file name matches the specified pattern but does not
+## match another pattern. Meant to be used like:
+## SRC_FILES := $(call all-named-files-under,.*\.h,src tests)
+###########################################################
+
+define all-named-files-under-exclude
+$(call find-files-in-subdirs-exclude,$(LOCAL_PATH),"$(1)","$(2)",$(3))
+endef
+
+###########################################################
+## Find all of the files under the current directory where
+## the file name matches the specified pattern but does not
+## match another pattern.
+###########################################################
+
+define all-subdir-named-files-exclude
+$(call all-named-files-under-exclude,$(1),$(2),.)
+endef
+
+
+# Pre-process support library AIDLs
+aidl_files := $(addprefix $(LOCAL_PATH)/, $(call all-subdir-named-files-exclude,*.aidl,I*.aidl))
+support-aidl := $(TARGET_OUT_COMMON_INTERMEDIATES)/support.aidl
+$(support-aidl): $(aidl_files) | $(AIDL)
+ $(AIDL) --preprocess $@ $(aidl_files)
+
.PHONY: update-support-api
.PHONY: check-support-api
-.PHONY: support-gradle-archive
-support-gradle-archive: PRIVATE_LOCAL_PATH := $(LOCAL_PATH)
-support-gradle-archive:
- $(PRIVATE_LOCAL_PATH)/gradlew -p $(PRIVATE_LOCAL_PATH) createArchive
-
# Run the check-support-api task on a SDK build
-sdk: check-support-api
-# Run the support-gradle-archive task on a SDK build
-sdk: support-gradle-archive
+sdk: check-support-api $(support-aidl)
# Build all support libraries
include $(call all-makefiles-under,$(LOCAL_PATH))
# Clear out variables
+SUPPORT_CURRENT_SDK_VERSION :=
SUPPORT_API_CHECK :=
api_check_current_msg_file :=
api_check_last_msg_file :=
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 317bb03..7b2174eb 100644
--- a/CleanSpec.mk
+++ b/CleanSpec.mk
@@ -45,11 +45,7 @@
#$(call add-clean-step, rm -rf $(PRODUCT_OUT)/data/*)
$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
-$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/JAVA_LIBRARIES/android-support-v*)
+$(call add-clean-step, rm -rf $(OUT_DIR)/target/common/obj/support.aidl)
# ************************************************
# NEWER CLEAN STEPS MUST BE AT THE END OF THE LIST
diff --git a/annotations/Android.mk b/annotations/Android.mk
index 025ca0e..78a29df 100644
--- a/annotations/Android.mk
+++ b/annotations/Android.mk
@@ -15,8 +15,11 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-annotations
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/annotations/AndroidManifest.xml b/annotations/AndroidManifest.xml
new file mode 100644
index 0000000..6f24ecb
--- /dev/null
+++ b/annotations/AndroidManifest.xml
@@ -0,0 +1,2 @@
+<?xml version="1.0" encoding="utf-8"?>
+<manifest package="android.support.annotations" />
diff --git a/annotations/build.gradle b/annotations/build.gradle
index 1f3bead..61d036b 100644
--- a/annotations/build.gradle
+++ b/annotations/build.gradle
@@ -1,11 +1,15 @@
apply plugin: 'java'
-
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
archivesBaseName = 'support-annotations'
sourceSets {
main.java.srcDir 'src'
}
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
+
jar {
from sourceSets.main.output
}
diff --git a/annotations/src/android/support/annotation/AnimRes.java b/annotations/src/android/support/annotation/AnimRes.java
index 906461b..e91fa81 100644
--- a/annotations/src/android/support/annotation/AnimRes.java
+++ b/annotations/src/android/support/annotation/AnimRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an anim resource reference (e.g. {@link android.R.anim#fade_in}).
+ * to be an anim resource reference (e.g. {@code android.R.anim.fade_in}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AnimatorRes.java b/annotations/src/android/support/annotation/AnimatorRes.java
index 4681236..e969356 100644
--- a/annotations/src/android/support/annotation/AnimatorRes.java
+++ b/annotations/src/android/support/annotation/AnimatorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an animator resource reference (e.g. {@link android.R.animator#fade_in}).
+ * to be an animator resource reference (e.g. {@code android.R.animator.fade_in}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AnyThread.java b/annotations/src/android/support/annotation/AnyThread.java
new file mode 100644
index 0000000..500d368
--- /dev/null
+++ b/annotations/src/android/support/annotation/AnyThread.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that the annotated method can be called from any thread (e.g. it is "thread safe".)
+ * If the annotated element is a class, then all methods in the class can be called
+ * from any thread.
+ * <p>
+ * The main purpose of this method is to indicate that you believe a method can be called
+ * from any thread; static tools can then check that nothing you call from within this method
+ * or class have more strict threading requirements.
+ * <p>
+ * Example:
+ * <pre><code>
+ * @AnyThread
+ * public void deliverResult(D data) { ... }
+ * </code></pre>
+ */
+@Documented
+@Retention(CLASS)
+@Target({METHOD,CONSTRUCTOR,TYPE})
+public @interface AnyThread {
+}
diff --git a/annotations/src/android/support/annotation/ArrayRes.java b/annotations/src/android/support/annotation/ArrayRes.java
index 347de36..758bce4 100644
--- a/annotations/src/android/support/annotation/ArrayRes.java
+++ b/annotations/src/android/support/annotation/ArrayRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an array resource reference (e.g. {@link android.R.array#phoneTypes}).
+ * to be an array resource reference (e.g. {@code android.R.array.phoneTypes}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/AttrRes.java b/annotations/src/android/support/annotation/AttrRes.java
index 7ba1f0d..2034be5 100644
--- a/annotations/src/android/support/annotation/AttrRes.java
+++ b/annotations/src/android/support/annotation/AttrRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an attribute reference (e.g. {@link android.R.attr#action}).
+ * to be an attribute reference (e.g. {@code android.R.attr.action}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/BinderThread.java b/annotations/src/android/support/annotation/BinderThread.java
index a9e5db5..db7dd3a 100644
--- a/annotations/src/android/support/annotation/BinderThread.java
+++ b/annotations/src/android/support/annotation/BinderThread.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -34,6 +35,7 @@
* public BeamShareData createBeamShareData() { ... }
* </code></pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface BinderThread {
diff --git a/annotations/src/android/support/annotation/CallSuper.java b/annotations/src/android/support/annotation/CallSuper.java
index f9f8bee..9c01cdf 100644
--- a/annotations/src/android/support/annotation/CallSuper.java
+++ b/annotations/src/android/support/annotation/CallSuper.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -30,6 +31,7 @@
* public abstract void onFocusLost();
* </code></pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CallSuper {
diff --git a/annotations/src/android/support/annotation/CheckResult.java b/annotations/src/android/support/annotation/CheckResult.java
index 405ea4b..7f40676 100644
--- a/annotations/src/android/support/annotation/CheckResult.java
+++ b/annotations/src/android/support/annotation/CheckResult.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -35,6 +36,7 @@
* s = s.trim(); // ok
* }</pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD})
public @interface CheckResult {
diff --git a/annotations/src/android/support/annotation/ColorRes.java b/annotations/src/android/support/annotation/ColorRes.java
index eb273c4..d0fd49a 100644
--- a/annotations/src/android/support/annotation/ColorRes.java
+++ b/annotations/src/android/support/annotation/ColorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a color resource reference (e.g. {@link android.R.color#black}).
+ * to be a color resource reference (e.g. {@code android.R.color.black}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/DimenRes.java b/annotations/src/android/support/annotation/DimenRes.java
index c3492a5..08dc4a9 100644
--- a/annotations/src/android/support/annotation/DimenRes.java
+++ b/annotations/src/android/support/annotation/DimenRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a dimension resource reference (e.g. {@link android.R.dimen#app_icon_size}).
+ * to be a dimension resource reference (e.g. {@code android.R.dimen.app_icon_size}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/Dimension.java b/annotations/src/android/support/annotation/Dimension.java
new file mode 100644
index 0000000..cb4fb5e
--- /dev/null
+++ b/annotations/src/android/support/annotation/Dimension.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to represent a dimension.
+ */
+@Documented
+@Retention(CLASS)
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
+public @interface Dimension {
+ @Unit
+ int unit() default PX;
+
+ int DP = 0;
+ int PX = 1;
+ int SP = 2;
+
+ @IntDef({PX, DP, SP})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface Unit {}
+}
diff --git a/annotations/src/android/support/annotation/DrawableRes.java b/annotations/src/android/support/annotation/DrawableRes.java
index 0ea1bca..f130380 100644
--- a/annotations/src/android/support/annotation/DrawableRes.java
+++ b/annotations/src/android/support/annotation/DrawableRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a drawable resource reference (e.g. {@link android.R.attr#alertDialogIcon}).
+ * to be a drawable resource reference (e.g. {@code android.R.attr.alertDialogIcon}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/FloatRange.java b/annotations/src/android/support/annotation/FloatRange.java
index f129e1b..275b5b6 100644
--- a/annotations/src/android/support/annotation/FloatRange.java
+++ b/annotations/src/android/support/annotation/FloatRange.java
@@ -18,6 +18,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
@@ -36,7 +37,7 @@
* </code></pre>
*/
@Retention(CLASS)
-@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface FloatRange {
/** Smallest value. Whether it is inclusive or not is determined
* by {@link #fromInclusive} */
diff --git a/annotations/src/android/support/annotation/IdRes.java b/annotations/src/android/support/annotation/IdRes.java
index 9a0060f7..7cb3f98 100644
--- a/annotations/src/android/support/annotation/IdRes.java
+++ b/annotations/src/android/support/annotation/IdRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an id resource reference (e.g. {@link android.R.id#copy}).
+ * to be an id resource reference (e.g. {@code android.R.id.copy}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/IntRange.java b/annotations/src/android/support/annotation/IntRange.java
index 9dd605f..d489acb 100644
--- a/annotations/src/android/support/annotation/IntRange.java
+++ b/annotations/src/android/support/annotation/IntRange.java
@@ -18,6 +18,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
@@ -36,7 +37,7 @@
* </code></pre>
*/
@Retention(CLASS)
-@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE})
+@Target({METHOD,PARAMETER,FIELD,LOCAL_VARIABLE,ANNOTATION_TYPE})
public @interface IntRange {
/** Smallest value, inclusive */
long from() default Long.MIN_VALUE;
diff --git a/annotations/src/android/support/annotation/IntegerRes.java b/annotations/src/android/support/annotation/IntegerRes.java
index 6bfcc37..d965675 100644
--- a/annotations/src/android/support/annotation/IntegerRes.java
+++ b/annotations/src/android/support/annotation/IntegerRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an integer resource reference (e.g. {@link android.R.integer#config_shortAnimTime}).
+ * to be an integer resource reference (e.g. {@code android.R.integer.config_shortAnimTime}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/InterpolatorRes.java b/annotations/src/android/support/annotation/InterpolatorRes.java
index 20f42b8..3195c57 100644
--- a/annotations/src/android/support/annotation/InterpolatorRes.java
+++ b/annotations/src/android/support/annotation/InterpolatorRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be an interpolator resource reference (e.g. {@link android.R.interpolator#cycle}).
+ * to be an interpolator resource reference (e.g. {@code android.R.interpolator.cycle}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/LayoutRes.java b/annotations/src/android/support/annotation/LayoutRes.java
index ad04ef0..0de4e84 100644
--- a/annotations/src/android/support/annotation/LayoutRes.java
+++ b/annotations/src/android/support/annotation/LayoutRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a layout resource reference (e.g. {@link android.R.layout#list_content}).
+ * to be a layout resource reference (e.g. {@code android.R.layout.list_content}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/MainThread.java b/annotations/src/android/support/annotation/MainThread.java
index dce4c71..1ce8b3c 100644
--- a/annotations/src/android/support/annotation/MainThread.java
+++ b/annotations/src/android/support/annotation/MainThread.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -34,6 +35,7 @@
* public void deliverResult(D data) { ... }
* </code></pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface MainThread {
diff --git a/annotations/src/android/support/annotation/NonNull.java b/annotations/src/android/support/annotation/NonNull.java
index 5a7c86d..226f8c3 100644
--- a/annotations/src/android/support/annotation/NonNull.java
+++ b/annotations/src/android/support/annotation/NonNull.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -28,6 +29,7 @@
* <p>
* This is a marker annotation and it has no specific attributes.
*/
+@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface NonNull {
diff --git a/annotations/src/android/support/annotation/Nullable.java b/annotations/src/android/support/annotation/Nullable.java
index c427b64..590b48f 100644
--- a/annotations/src/android/support/annotation/Nullable.java
+++ b/annotations/src/android/support/annotation/Nullable.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -35,6 +36,7 @@
* <p>
* This is a marker annotation and it has no specific attributes.
*/
+@Documented
@Retention(CLASS)
@Target({METHOD, PARAMETER, FIELD})
public @interface Nullable {
diff --git a/annotations/src/android/support/annotation/Px.java b/annotations/src/android/support/annotation/Px.java
new file mode 100644
index 0000000..e04afa0
--- /dev/null
+++ b/annotations/src/android/support/annotation/Px.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.annotation;
+
+import java.lang.annotation.Documented;
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that an integer parameter, field or method return value is expected
+ * to represent a pixel dimension.
+ */
+@Documented
+@Retention(CLASS)
+@Target({METHOD, PARAMETER, FIELD, LOCAL_VARIABLE})
+@Dimension(unit = Dimension.PX)
+public @interface Px {
+}
diff --git a/annotations/src/android/support/annotation/RequiresApi.java b/annotations/src/android/support/annotation/RequiresApi.java
new file mode 100644
index 0000000..ddd31587
--- /dev/null
+++ b/annotations/src/android/support/annotation/RequiresApi.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.annotation;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+import static java.lang.annotation.ElementType.CONSTRUCTOR;
+import static java.lang.annotation.ElementType.FIELD;
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.TYPE;
+
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+/**
+ * Denotes that the annotated element should only be called on the given API level
+ * or higher.
+ * <p>
+ * This is similar in purpose to the older {@code @TargetApi} annotation, but more
+ * clearly expresses that this is a requirement on the caller, rather than being
+ * used to "suppress" warnings within the method that exceed the {@code minSdkVersion}.
+ */
+@Retention(CLASS)
+@Target({TYPE,METHOD,CONSTRUCTOR,FIELD})
+public @interface RequiresApi {
+ /**
+ * The API level to require. Alias for {@link #api} which allows you to leave out the
+ * {@code api=} part.
+ */
+ @IntRange(from=1)
+ int value() default 1;
+
+ /** The API level to require */
+ @IntRange(from=1)
+ int api() default 1;
+}
diff --git a/annotations/src/android/support/annotation/RequiresPermission.java b/annotations/src/android/support/annotation/RequiresPermission.java
index cf2e404..1ff1964 100644
--- a/annotations/src/android/support/annotation/RequiresPermission.java
+++ b/annotations/src/android/support/annotation/RequiresPermission.java
@@ -22,11 +22,12 @@
import static java.lang.annotation.ElementType.CONSTRUCTOR;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Denotes that the annotated element requires (or may require) one or more permissions.
- * <p/>
+ * <p>
* Example of requiring a single permission:
* <pre><code>
* @RequiresPermission(Manifest.permission.SET_WALLPAPER)
@@ -50,12 +51,30 @@
* @RequiresPermission.Read(@RequiresPermission(READ_HISTORY_BOOKMARKS))
* @RequiresPermission.Write(@RequiresPermission(WRITE_HISTORY_BOOKMARKS))
* public static final Uri BOOKMARKS_URI = Uri.parse("content://browser/bookmarks");
+ * </code></pre>
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter. For example, consider
+ * {@code android.app.Activity.startActivity(android.content.Intent)}:
+ * <pre>{@code
+ * public void startActivity(@RequiresPermission Intent intent) { ... }
* }</pre>
- *
- * @hide
+ * Notice how there are no actual permission names listed in the annotation. The actual
+ * permissions required will depend on the particular intent passed in. For example,
+ * the code may look like this:
+ * <pre>{@code
+ * Intent intent = new Intent(Intent.ACTION_CALL);
+ * startActivity(intent);
+ * }</pre>
+ * and the actual permission requirement for this particular intent is described on
+ * the Intent name itself:
+ * <pre><code>
+ * @RequiresPermission(Manifest.permission.CALL_PHONE)
+ * public static final String ACTION_CALL = "android.intent.action.CALL";
+ * </code></pre>
*/
@Retention(CLASS)
-@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD})
+@Target({ANNOTATION_TYPE,METHOD,CONSTRUCTOR,FIELD,PARAMETER})
public @interface RequiresPermission {
/**
* The name of the permission that is required, if precisely one permission
@@ -87,18 +106,28 @@
boolean conditional() default false;
/**
- * Specifies that the given permission is required for read operations
+ * Specifies that the given permission is required for read operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a {@code @RequiresPermission} annotation.)
*/
- @Target(FIELD)
+ @Target({FIELD, METHOD, PARAMETER})
@interface Read {
- RequiresPermission value();
+ RequiresPermission value() default @RequiresPermission;
}
/**
- * Specifies that the given permission is required for write operations
+ * Specifies that the given permission is required for write operations.
+ * <p>
+ * When specified on a parameter, the annotation indicates that the method requires
+ * a permission which depends on the value of the parameter (and typically
+ * the corresponding field passed in will be one of a set of constants which have
+ * been annotated with a {@code @RequiresPermission} annotation.)
*/
- @Target(FIELD)
+ @Target({FIELD, METHOD, PARAMETER})
@interface Write {
- RequiresPermission value();
+ RequiresPermission value() default @RequiresPermission;
}
}
diff --git a/annotations/src/android/support/annotation/Size.java b/annotations/src/android/support/annotation/Size.java
index bd1d7bd..b1c0308 100644
--- a/annotations/src/android/support/annotation/Size.java
+++ b/annotations/src/android/support/annotation/Size.java
@@ -18,6 +18,7 @@
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
+import static java.lang.annotation.ElementType.ANNOTATION_TYPE;
import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.LOCAL_VARIABLE;
import static java.lang.annotation.ElementType.METHOD;
@@ -37,7 +38,7 @@
* }</pre>
*/
@Retention(CLASS)
-@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD})
+@Target({PARAMETER,LOCAL_VARIABLE,METHOD,FIELD,ANNOTATION_TYPE})
public @interface Size {
/** An exact size (or -1 if not specified) */
long value() default -1;
diff --git a/annotations/src/android/support/annotation/StringRes.java b/annotations/src/android/support/annotation/StringRes.java
index 2c1149c..cf427f6 100644
--- a/annotations/src/android/support/annotation/StringRes.java
+++ b/annotations/src/android/support/annotation/StringRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a String resource reference (e.g. {@link android.R.string#ok}).
+ * to be a String resource reference (e.g. {@code android.R.string.ok}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleRes.java b/annotations/src/android/support/annotation/StyleRes.java
index 6d931bf..ca7a187 100644
--- a/annotations/src/android/support/annotation/StyleRes.java
+++ b/annotations/src/android/support/annotation/StyleRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a style resource reference (e.g. {@link android.R.style#TextAppearance}).
+ * to be a style resource reference (e.g. {@code android.R.style.TextAppearance}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/StyleableRes.java b/annotations/src/android/support/annotation/StyleableRes.java
index d7902d1..6e4d97f 100644
--- a/annotations/src/android/support/annotation/StyleableRes.java
+++ b/annotations/src/android/support/annotation/StyleableRes.java
@@ -27,7 +27,7 @@
/**
* Denotes that an integer parameter, field or method return value is expected
- * to be a styleable resource reference (e.g. {@link android.R.styleable#TextView_text}).
+ * to be a styleable resource reference (e.g. {@code android.R.styleable.TextView_text}).
*/
@Documented
@Retention(CLASS)
diff --git a/annotations/src/android/support/annotation/UiThread.java b/annotations/src/android/support/annotation/UiThread.java
index 0f8e9d7..98d35f9 100644
--- a/annotations/src/android/support/annotation/UiThread.java
+++ b/annotations/src/android/support/annotation/UiThread.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -35,6 +36,7 @@
* public abstract void setText(@NonNull String text) { ... }
* </code></pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface UiThread {
diff --git a/annotations/src/android/support/annotation/VisibleForTesting.java b/annotations/src/android/support/annotation/VisibleForTesting.java
index bb02ab4..0c893ff 100644
--- a/annotations/src/android/support/annotation/VisibleForTesting.java
+++ b/annotations/src/android/support/annotation/VisibleForTesting.java
@@ -17,12 +17,12 @@
import java.lang.annotation.Retention;
-import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.lang.annotation.RetentionPolicy.CLASS;
/**
* Denotes that the class, method or field has its visibility relaxed, so that it is more widely
* visible than otherwise necessary to make code testable.
*/
-@Retention(SOURCE)
+@Retention(CLASS)
public @interface VisibleForTesting {
}
diff --git a/annotations/src/android/support/annotation/WorkerThread.java b/annotations/src/android/support/annotation/WorkerThread.java
index c003abc..a5aabd7 100644
--- a/annotations/src/android/support/annotation/WorkerThread.java
+++ b/annotations/src/android/support/annotation/WorkerThread.java
@@ -15,6 +15,7 @@
*/
package android.support.annotation;
+import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@@ -34,6 +35,7 @@
* protected abstract FilterResults performFiltering(CharSequence constraint);
* </code></pre>
*/
+@Documented
@Retention(CLASS)
@Target({METHOD,CONSTRUCTOR,TYPE})
public @interface WorkerThread {
diff --git a/apicheck.mk b/apicheck.mk
index bb81c8e..7f56866 100644
--- a/apicheck.mk
+++ b/apicheck.mk
@@ -44,10 +44,11 @@
LOCAL_JAVA_LIBRARIES := $(support_module_java_libraries)
LOCAL_ADDITIONAL_JAVA_DIR := \
$(call intermediates-dir-for,$(LOCAL_MODULE_CLASS),$(support_module),,COMMON)/src
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+
+LOCAL_DROIDDOC_STUB_OUT_DIR := $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/src
LOCAL_DROIDDOC_OPTIONS:= \
- -stubs $(TARGET_OUT_COMMON_INTERMEDIATES)/$(LOCAL_MODULE_CLASS)/$(LOCAL_MODULE)_intermediates/stubs/src \
-stubpackages "$(subst $(space),:,$(support_module_java_packages))" \
-api $(support_module_api_file) \
-removedApi $(support_module_removed_file) \
diff --git a/build.gradle b/build.gradle
index 7bd791a..ff93bdc 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,18 +1,22 @@
+import com.android.build.gradle.internal.coverage.JacocoReportTask
+import com.android.build.gradle.internal.tasks.DeviceProviderInstrumentTestTask
+
buildscript {
repositories {
maven { url '../../prebuilts/gradle-plugin' }
maven { url '../../prebuilts/tools/common/m2/repository' }
maven { url '../../prebuilts/tools/common/m2/internal' }
+ maven { url "../../prebuilts/maven_repo/android" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.3.1'
+ classpath 'com.android.tools.build:gradle:2.2.0-alpha1'
}
}
-ext.supportVersion = '23.1.1'
-ext.extraVersion = 25
+ext.supportVersion = '24.0.0'
+ext.extraVersion = 33
ext.supportRepoOut = ''
-ext.buildToolsVersion = '22.1.0'
+ext.buildToolsVersion = '23.0.2'
ext.buildNumber = Integer.toString(ext.extraVersion)
/*
@@ -34,7 +38,7 @@
}
ext.supportRepoOut = new File(buildDir, 'support_repo')
-ext.testApkDistOut = new File(buildDir, 'test_apks')
+ext.testApkDistOut = ext.distDir
// Main task called by the build server.
task(createArchive) << {
@@ -70,6 +74,8 @@
task(prepareRepo) << {
}
+
+import android.support.build.ApiModule
import com.google.common.io.Files
import com.google.common.base.Charsets
@@ -110,6 +116,7 @@
Archive.Arch=ANY\n\
Extra.NameDisplay=Android Support Repository\n\
Archive.Os=ANY\n\
+Pkg.Desc=Local Maven repository for Support Libraries\n\
Pkg.Revision=${project.ext.extraVersion}.0.0\n\
Extra.VendorId=android"
@@ -118,7 +125,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
@@ -130,11 +136,73 @@
return hashCode.toString()
}
+def createApiSourceSets(Project subProject, List<ApiModule> apiModules) {
+ subProject.ext._apiModules = apiModules
+ subProject.ext.allSS = []
+ if (gradle.ext.studioCompat.enableApiModules) {
+ // nothing to do, they are all modules
+ return
+ }
+ // create a jar task for the api specific internal implementations
+ def internalJar = subProject.tasks.create(name: "internalJar", type: Jar) {
+ baseName "internal_impl"
+ }
+ apiModules.each { ApiModule apiModule ->
+ apiModule.sourceSet = createApiSourceset(subProject, apiModule.folderName, apiModule.folderName,
+ apiModule.apiForSourceSet.toString(), apiModule.prev == null ? null : apiModule.prev.sourceSet)
+ subProject.ext.allSS.add(apiModule.sourceSet)
+ }
+ subProject.android.libraryVariants.all { variant ->
+ variant.javaCompile.dependsOn internalJar
+ }
+}
+
+def createApiSourceset(Project subProject, String name, String folder, String apiLevel,
+ SourceSet previousSource) {
+ def sourceSet = subProject.sourceSets.create(name)
+ sourceSet.java.srcDirs = [folder]
+
+ def configName = sourceSet.getCompileConfigurationName()
+
+ subProject.getDependencies().add(configName, getAndroidPrebuilt(apiLevel))
+ if (previousSource != null) {
+ setupDependencies(subProject, configName, previousSource)
+ }
+ subProject.ext.allSS.add(sourceSet)
+ subProject.tasks.internalJar.from sourceSet.output
+ return sourceSet
+}
+
+def setApiModuleDependencies(Project subProject, DependencyHandler handler, List extraDeps) {
+ if (gradle.ext.studioCompat.enableApiModules) {
+ subProject.android.enforceUniquePackageName=false
+ // add dependency on the latest module
+ handler.compile(project(subProject.ext._apiModules.last().moduleName))
+ } else {
+ handler.compile(files(subProject.tasks.internalJar.archivePath))
+ def firstModule = subProject.ext._apiModules[0]
+ extraDeps.each { dep ->
+ handler."${firstModule.folderName}Compile"(project(dep))
+ handler.compile(project(dep))
+ }
+
+ }
+}
+
+def setupDependencies(Project subProject, String configName, SourceSet previousSourceSet) {
+ subProject.getDependencies().add(configName, previousSourceSet.output)
+ subProject.getDependencies().add(configName, previousSourceSet.compileClasspath)
+}
+
subprojects {
// Change buildDir first so that all plugins pick up the new value.
project.buildDir = project.file("$project.parent.buildDir/../$project.name/build")
-
+ // current SDK is set in studioCompat.gradle
+ project.ext.currentSdk = gradle.ext.currentSdk
apply plugin: 'maven'
+ project.ext.createApiSourceSets = this.&createApiSourceset
+ project.ext.setApiModuleDependencies = this.&setApiModuleDependencies
+
version = rootProject.ext.supportVersion
group = 'com.android.support'
@@ -150,7 +218,23 @@
repositories {
mavenDeployer {
repository(url: uri("$rootProject.ext.supportRepoOut"))
- }
+
+ // Disable unique names for SNAPSHOTS so they can be updated in place.
+ setUniqueVersion(false)
+ doLast {
+ // Remove any invalid maven-metadata.xml files that may have been created
+ // for SNAPSHOT versions that are *not* uniquely versioned.
+ pom*.each { pom ->
+ if (pom.version.endsWith('-SNAPSHOT')) {
+ final File artifactDir = new File(rootProject.ext.supportRepoOut,
+ pom.groupId.replace('.', '/')
+ + '/' + pom.artifactId
+ + '/' + pom.version)
+ delete fileTree(dir: artifactDir, include: 'maven-metadata.xml*')
+ }
+ }
+ }
+ }
}
}
@@ -167,6 +251,46 @@
project.plugins.whenPluginAdded { plugin ->
if ("com.android.build.gradle.LibraryPlugin".equals(plugin.class.name)) {
project.android.buildToolsVersion = rootProject.buildToolsVersion
+ // enable code coverage for debug builds only if we are not running inside the IDE
+ // enabling coverage reports breaks the method parameter resolution in the IDE debugger
+ project.android.buildTypes.debug.testCoverageEnabled = !hasProperty('android.injected.invoked.from.ide')
+ }
+ }
+
+ // 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.afterEvaluate { p ->
+ // remove dependency on the test so that we still get coverage even if some tests fail
+ p.tasks.findAll { it instanceof JacocoReportTask}.each { task ->
+ def toBeRemoved = new ArrayList()
+ def dependencyList = task.taskDependencies.values
+ dependencyList.each { dep ->
+ if (dep instanceof String) {
+ def t = tasks.findByName(dep)
+ if (t instanceof DeviceProviderInstrumentTestTask) {
+ toBeRemoved.add(dep)
+ task.mustRunAfter(t)
+ }
+ }
+ }
+ toBeRemoved.each { dep ->
+ dependencyList.remove(dep)
+ }
}
}
}
diff --git a/buildSrc/apiModule.gradle b/buildSrc/apiModule.gradle
new file mode 100644
index 0000000..a67f29c
--- /dev/null
+++ b/buildSrc/apiModule.gradle
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/**
+ * Generic build.gradle file that can be used for API specific support lib implementations.
+ * This file is used only if Android Studio opens the project.
+ */
+apply plugin: 'com.android.library'
+def apiModule = gradle.ext.getApiModule(project)
+logger.info ("apiModule for ${project.projectDir} is $apiModule. "
+ + "compileSDK: ${apiModule.apiForSourceSet} minSDK: ${apiModule.api}")
+android {
+ compileSdkVersion apiModule.apiForSourceSet
+ // these api modules all use the same package name so we should not package their BuildConfig
+ // files.
+ packageBuildConfig false
+
+ sourceSets {
+ main.manifest.srcFile '../AndroidManifest.xml'
+ main.java.srcDirs = ['.']
+
+ apiModule.resourceFolders.each {
+ main.res.srcDirs += "../$it"
+ }
+ apiModule.assetFolders.each {
+ main.assets.srcDirs += "../$it"
+ }
+ apiModule.javaResourceFolders.each {
+ main.resources.srcDirs += "../$it"
+ }
+ }
+
+ lintOptions {
+ abortOnError false
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ enforceUniquePackageName = false
+}
+
+
+dependencies {
+ if (apiModule.prev != null) {
+ compile project(apiModule.prev.moduleName)
+ } else {
+ apiModule.parentModuleDependencies.each { dep ->
+ compile project(dep)
+ }
+ }
+}
diff --git a/buildSrc/src/main/java/android/support/build/ApiModule.java b/buildSrc/src/main/java/android/support/build/ApiModule.java
new file mode 100644
index 0000000..4b73f94
--- /dev/null
+++ b/buildSrc/src/main/java/android/support/build/ApiModule.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.build;
+
+import org.gradle.api.tasks.SourceSet;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * Defines an API specific support library modules.
+ * e.g. Honeycomb implementation of Support-v4.
+ *
+ * These ApiModules are converted into real modules when project is opened in AndroidStudio.
+ * When project is run from the command line, they are converted into source sets.
+ * This allows us to rely on previous compile setup to deploy projects with their dependencies while
+ * supporting development on Android Studio.
+ */
+public class ApiModule {
+ public static final int CURRENT = 99;
+ private String mFolderName;
+ private int mApi;
+ private SourceSet mSourceSet;
+ private ApiModule mPrev;
+ private String mParentModuleName;
+ private List<String> mParentModuleDependencies;
+ private List<String> mResourceFolders = new ArrayList<>();
+ private List<String> mAssetFolders = new ArrayList<>();
+ private List<String> mJavaResourceFolders = new ArrayList<>();
+
+ public ApiModule(String folderName, int api) {
+ mFolderName = folderName;
+ mApi = api;
+ }
+
+ public ApiModule resources(String... resourceFolders) {
+ Collections.addAll(mResourceFolders, resourceFolders);
+ return this;
+ }
+
+ public ApiModule assets(String... assetFolders) {
+ Collections.addAll(mAssetFolders, assetFolders);
+ return this;
+ }
+
+ public ApiModule javaResources(String... javaResourceFolders) {
+ Collections.addAll(mJavaResourceFolders, javaResourceFolders);
+ return this;
+ }
+
+ public List<String> getResourceFolders() {
+ return mResourceFolders;
+ }
+
+ public List<String> getAssetFolders() {
+ return mAssetFolders;
+ }
+
+ public List<String> getJavaResourceFolders() {
+ return mJavaResourceFolders;
+ }
+
+ public void setResourceFolders(List<String> resourceFolders) {
+ mResourceFolders = resourceFolders;
+ }
+
+ public String getParentModuleName() {
+ return mParentModuleName;
+ }
+
+ public void setParentModuleName(String parentModuleName) {
+ mParentModuleName = parentModuleName;
+ }
+
+ public String getFolderName() {
+ return mFolderName;
+ }
+
+ public int getApi() {
+ return mApi;
+ }
+
+ public Object getApiForSourceSet() {
+ return mApi == CURRENT ? "current" : mApi;
+ }
+
+ public SourceSet getSourceSet() {
+ return mSourceSet;
+ }
+
+ public void setSourceSet(SourceSet sourceSet) {
+ mSourceSet = sourceSet;
+ }
+
+ public ApiModule getPrev() {
+ return mPrev;
+ }
+
+ public void setPrev(ApiModule prev) {
+ mPrev = prev;
+ }
+
+ public String getModuleName() {
+ return ":" + mParentModuleName + "-" + mFolderName;
+ }
+
+ public List<String> getParentModuleDependencies() {
+ return mParentModuleDependencies;
+ }
+
+ public void setParentModuleDependencies(List<String> parentModuleDependencies) {
+ mParentModuleDependencies = parentModuleDependencies;
+ }
+}
diff --git a/buildSrc/studioCompat.gradle b/buildSrc/studioCompat.gradle
new file mode 100644
index 0000000..140b438
--- /dev/null
+++ b/buildSrc/studioCompat.gradle
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import android.support.build.ApiModule
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * To add platform specific code to a library:
+ * 1) add the related source set into the studioCompat map (defined below)
+ * 2) In your build gradle file, call:
+ * * createApiSourceSets(project, gradle.ext.studioCompat.modules.<PROJECT>.apiTargets)
+ * * setApiModuleDependencies(project, dependencies, gradle.ext.studioCompat.modules.<PROJECT>.dependencies)
+ */
+
+def studioCompat = [
+ enableApiModules : hasProperty('android.injected.invoked.from.ide'),
+ modules : [
+ v4 : [
+ apiTargets : [
+ new ApiModule("donut",4),
+ new ApiModule("eclair",5),
+ new ApiModule("eclair-mr1", 7),
+ new ApiModule("froyo",8),
+ new ApiModule("gingerbread",9),
+ new ApiModule("honeycomb",11),
+ new ApiModule("honeycomb_mr1",12),
+ new ApiModule("honeycomb_mr2",13),
+ new ApiModule("ics",14),
+ new ApiModule("ics-mr1",15),
+ new ApiModule("jellybean", 16),
+ new ApiModule("jellybean-mr1",17),
+ new ApiModule("jellybean-mr2",18),
+ new ApiModule("kitkat",19),
+ new ApiModule("api20",20),
+ new ApiModule("api21",21),
+ new ApiModule("api22",22),
+ new ApiModule("api23",23),
+ new ApiModule("api24", ApiModule.CURRENT)
+ ],
+ dependencies : [":support-annotations"],
+ folder : "v4",
+ moduleName : "support-v4"
+ ],
+ v13 : [
+ apiTargets : [
+ new ApiModule("ics", 14),
+ new ApiModule("ics-mr1", 15),
+ new ApiModule("api23", 23),
+ new ApiModule("api24", ApiModule.CURRENT)
+ ],
+ dependencies : [":support-v4"],
+ folder : "v13",
+ moduleName : "support-v13"
+ ],
+ mediaRouter : [
+ apiTargets : [
+ new ApiModule("jellybean", 16),
+ new ApiModule("jellybean-mr1", 17),
+ new ApiModule("jellybean-mr2", 18),
+ new ApiModule("api24", ApiModule.CURRENT)
+ ],
+ dependencies : [':support-appcompat-v7', ':support-palette-v7'],
+ folder : "v7/mediarouter",
+ moduleName : "support-mediarouter-v7"
+ ]
+ ]
+]
+
+/**
+ * Adds a link to the previous ApiModule for each module. This information is later used when
+ * setting dependencies.
+ */
+def setupDependencies(projectConfig) {
+ projectConfig.apiTargets.eachWithIndex { entry, index ->
+ entry.parentModuleName = projectConfig.moduleName
+ entry.prev = index == 0 ? null : projectConfig.apiTargets[index - 1]
+ }
+}
+gradle.ext.currentSdk = studioCompat.enableApiModules ? ApiModule.CURRENT : 'current'
+
+// the hashmap from apiModuleProjectFolder -> ApiModule
+gradle.ext.folderToApiModuleMapping = new HashMap()
+
+/**
+ * For each APIModule in the given projectConfig, creates a gradle module. These modules use the
+ * common `apiModule.gradle` build file.
+ */
+def createModules(projectConfig) {
+ Path buildFile = Paths.get(file("apiModule.gradle").toURI())
+ projectConfig.apiTargets.each { ApiModule module ->
+ logger.info "creating ${module.moduleName}"
+ module.setParentModuleDependencies(projectConfig.dependencies)
+ include "${module.moduleName}"
+ def folder = new File(rootDir, "${projectConfig.folder}/${module.folderName}")
+ project("${module.moduleName}").projectDir = folder
+ project("${module.moduleName}").buildFileName = Paths.get(folder.toURI()).relativize(buildFile)
+ gradle.ext.folderToApiModuleMapping[folder.canonicalPath] = module
+ }
+}
+
+/**
+ * returns the APIModule for the given project.
+ */
+ApiModule getApiModule(Project project) {
+ return gradle.ext.folderToApiModuleMapping[project.projectDir.canonicalPath]
+}
+
+studioCompat.modules.each { k, v ->
+ setupDependencies(v)
+}
+// create these fake modules only if Studio opens the project.
+if (studioCompat.enableApiModules) {
+ // validate we have the 99 folder, otherwise it wont work
+ def currentSdkPrebuilt = file("${rootProject.projectDir}/../../prebuilts/sdk/" +
+ "${ApiModule.CURRENT}/")
+ if (!currentSdkPrebuilt.exists()) {
+ throw new GradleScriptException(
+ "You need a symlink in prebuilts/sdk/${ApiModule.CURRENT} that points to"
+ + " prebuilts/sdk/current."
+ + "Without it, studio cannot understand current SDK.\n"
+ + "> ln -s ../../prebuilts/sdk/current "
+ + "../../prebuilts/sdk/${ApiModule.CURRENT}\n"
+ , new Exception())
+ }
+ Properties localProps = new Properties()
+ def localPropsStream = new FileInputStream(file("${rootProject.projectDir}/local.properties"))
+ try {
+ localProps.load(localPropsStream)
+ def sdkDir = localProps.get("sdk.dir")
+ if (sdkDir != null && sdkDir != "") {
+ throw new GradleScriptException("You should not have sdk.dir in local.properties because"
+ + " it overrides android.dir and prevents studio from seeing current sdk. "
+ + " Studio may add it by mistake, just remove it and it will work fine.",
+ new Exception())
+ }
+ } finally {
+ localPropsStream.close()
+ }
+ studioCompat.modules.each { k, v ->
+ createModules(v)
+ }
+}
+gradle.ext.studioCompat = studioCompat
+gradle.ext.getApiModule = this.&getApiModule
\ No newline at end of file
diff --git a/customtabs/Android.mk b/customtabs/Android.mk
index 028c59a..fb2f354 100644
--- a/customtabs/Android.mk
+++ b/customtabs/Android.mk
@@ -20,7 +20,7 @@
# in their makefiles to include the resources in their package.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-customtabs
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_AIDL_INCLUDES := $LOCAL_PATH/src
LOCAL_SRC_FILES := $(call all-java-files-under, src) \
$(call all-Iaidl-files-under, src)
diff --git a/customtabs/api/23.1.1.txt b/customtabs/api/23.1.1.txt
new file mode 100644
index 0000000..caa5ea4b
--- /dev/null
+++ b/customtabs/api/23.1.1.txt
@@ -0,0 +1,88 @@
+package android.support.customtabs {
+
+ public class CustomTabsCallback {
+ ctor public CustomTabsCallback();
+ method public void extraCallback(java.lang.String, android.os.Bundle);
+ method public void onNavigationEvent(int, android.os.Bundle);
+ field public static final int NAVIGATION_ABORTED = 4; // 0x4
+ field public static final int NAVIGATION_FAILED = 3; // 0x3
+ field public static final int NAVIGATION_FINISHED = 2; // 0x2
+ field public static final int NAVIGATION_STARTED = 1; // 0x1
+ field public static final int TAB_HIDDEN = 6; // 0x6
+ field public static final int TAB_SHOWN = 5; // 0x5
+ }
+
+ public class CustomTabsClient {
+ method public static boolean bindCustomTabsService(android.content.Context, java.lang.String, android.support.customtabs.CustomTabsServiceConnection);
+ method public android.os.Bundle extraCommand(java.lang.String, android.os.Bundle);
+ method public android.support.customtabs.CustomTabsSession newSession(android.support.customtabs.CustomTabsCallback);
+ method public boolean warmup(long);
+ }
+
+ public final class CustomTabsIntent {
+ method public void launchUrl(android.app.Activity, android.net.Uri);
+ field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+ field public static final java.lang.String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final java.lang.String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
+ field public static final java.lang.String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
+ field public static final java.lang.String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+ field public static final java.lang.String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
+ field public static final java.lang.String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
+ field public static final java.lang.String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
+ field public static final java.lang.String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+ field public static final java.lang.String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
+ field public static final java.lang.String KEY_ICON = "android.support.customtabs.customaction.ICON";
+ field public static final java.lang.String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
+ field public static final java.lang.String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
+ field public static final int NO_TITLE = 0; // 0x0
+ field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+ field public final android.content.Intent intent;
+ field public final android.os.Bundle startAnimationBundle;
+ }
+
+ public static final class CustomTabsIntent.Builder {
+ ctor public CustomTabsIntent.Builder();
+ ctor public CustomTabsIntent.Builder(android.support.customtabs.CustomTabsSession);
+ method public android.support.customtabs.CustomTabsIntent.Builder addMenuItem(java.lang.String, android.app.PendingIntent);
+ method public android.support.customtabs.CustomTabsIntent build();
+ method public android.support.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
+ method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent, boolean);
+ method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent);
+ method public android.support.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+ method public android.support.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, int, int);
+ method public android.support.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
+ method public android.support.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, int, int);
+ method public android.support.customtabs.CustomTabsIntent.Builder setToolbarColor(int);
+ }
+
+ public abstract class CustomTabsService extends android.app.Service {
+ ctor public CustomTabsService();
+ method protected boolean cleanUpSession(android.support.customtabs.CustomTabsSessionToken);
+ method protected abstract android.os.Bundle extraCommand(java.lang.String, android.os.Bundle);
+ method protected abstract boolean mayLaunchUrl(android.support.customtabs.CustomTabsSessionToken, android.net.Uri, android.os.Bundle, java.util.List<android.os.Bundle>);
+ method protected abstract boolean newSession(android.support.customtabs.CustomTabsSessionToken);
+ method public android.os.IBinder onBind(android.content.Intent);
+ method protected abstract boolean updateVisuals(android.support.customtabs.CustomTabsSessionToken, android.os.Bundle);
+ method protected abstract boolean warmup(long);
+ field public static final java.lang.String ACTION_CUSTOM_TABS_CONNECTION = "android.support.customtabs.action.CustomTabsService";
+ field public static final java.lang.String KEY_URL = "android.support.customtabs.otherurls.URL";
+ }
+
+ public abstract class CustomTabsServiceConnection implements android.content.ServiceConnection {
+ ctor public CustomTabsServiceConnection();
+ method public abstract void onCustomTabsServiceConnected(android.content.ComponentName, android.support.customtabs.CustomTabsClient);
+ method public final void onServiceConnected(android.content.ComponentName, android.os.IBinder);
+ }
+
+ public final class CustomTabsSession {
+ method public boolean mayLaunchUrl(android.net.Uri, android.os.Bundle, java.util.List<android.os.Bundle>);
+ method public boolean setActionButton(android.graphics.Bitmap, java.lang.String);
+ }
+
+ public class CustomTabsSessionToken {
+ method public android.support.customtabs.CustomTabsCallback getCallback();
+ method public static android.support.customtabs.CustomTabsSessionToken getSessionTokenFromIntent(android.content.Intent);
+ }
+
+}
+
diff --git a/customtabs/api/current.txt b/customtabs/api/current.txt
index caa5ea4b..23f0b0a 100644
--- a/customtabs/api/current.txt
+++ b/customtabs/api/current.txt
@@ -14,28 +14,41 @@
public class CustomTabsClient {
method public static boolean bindCustomTabsService(android.content.Context, java.lang.String, android.support.customtabs.CustomTabsServiceConnection);
+ method public static boolean connectAndInitialize(android.content.Context, java.lang.String);
method public android.os.Bundle extraCommand(java.lang.String, android.os.Bundle);
+ method public static java.lang.String getPackageName(android.content.Context, java.util.List<java.lang.String>);
+ method public static java.lang.String getPackageName(android.content.Context, java.util.List<java.lang.String>, boolean);
method public android.support.customtabs.CustomTabsSession newSession(android.support.customtabs.CustomTabsCallback);
method public boolean warmup(long);
}
public final class CustomTabsIntent {
+ method public static int getMaxToolbarItems();
method public void launchUrl(android.app.Activity, android.net.Uri);
field public static final java.lang.String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
field public static final java.lang.String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
+ field public static final java.lang.String EXTRA_DEFAULT_SHARE_MENU_ITEM = "android.support.customtabs.extra.SHARE_MENU_ITEM";
field public static final java.lang.String EXTRA_ENABLE_URLBAR_HIDING = "android.support.customtabs.extra.ENABLE_URLBAR_HIDING";
field public static final java.lang.String EXTRA_EXIT_ANIMATION_BUNDLE = "android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
field public static final java.lang.String EXTRA_MENU_ITEMS = "android.support.customtabs.extra.MENU_ITEMS";
+ field public static final java.lang.String EXTRA_REMOTEVIEWS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+ field public static final java.lang.String EXTRA_REMOTEVIEWS_CLICKED_ID = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+ field public static final java.lang.String EXTRA_REMOTEVIEWS_PENDINGINTENT = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+ field public static final java.lang.String EXTRA_REMOTEVIEWS_VIEW_IDS = "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+ field public static final java.lang.String EXTRA_SECONDARY_TOOLBAR_COLOR = "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
field public static final java.lang.String EXTRA_SESSION = "android.support.customtabs.extra.SESSION";
field public static final java.lang.String EXTRA_TINT_ACTION_BUTTON = "android.support.customtabs.extra.TINT_ACTION_BUTTON";
field public static final java.lang.String EXTRA_TITLE_VISIBILITY_STATE = "android.support.customtabs.extra.TITLE_VISIBILITY";
field public static final java.lang.String EXTRA_TOOLBAR_COLOR = "android.support.customtabs.extra.TOOLBAR_COLOR";
+ field public static final java.lang.String EXTRA_TOOLBAR_ITEMS = "android.support.customtabs.extra.TOOLBAR_ITEMS";
field public static final java.lang.String KEY_DESCRIPTION = "android.support.customtabs.customaction.DESCRIPTION";
field public static final java.lang.String KEY_ICON = "android.support.customtabs.customaction.ICON";
+ field public static final java.lang.String KEY_ID = "android.support.customtabs.customaction.ID";
field public static final java.lang.String KEY_MENU_ITEM_TITLE = "android.support.customtabs.customaction.MENU_ITEM_TITLE";
field public static final java.lang.String KEY_PENDING_INTENT = "android.support.customtabs.customaction.PENDING_INTENT";
field public static final int NO_TITLE = 0; // 0x0
field public static final int SHOW_PAGE_TITLE = 1; // 0x1
+ field public static final int TOOLBAR_ACTION_BUTTON_ID = 0; // 0x0
field public final android.content.Intent intent;
field public final android.os.Bundle startAnimationBundle;
}
@@ -43,13 +56,17 @@
public static final class CustomTabsIntent.Builder {
ctor public CustomTabsIntent.Builder();
ctor public CustomTabsIntent.Builder(android.support.customtabs.CustomTabsSession);
+ method public android.support.customtabs.CustomTabsIntent.Builder addDefaultShareMenuItem();
method public android.support.customtabs.CustomTabsIntent.Builder addMenuItem(java.lang.String, android.app.PendingIntent);
+ method public deprecated android.support.customtabs.CustomTabsIntent.Builder addToolbarItem(int, android.graphics.Bitmap, java.lang.String, android.app.PendingIntent) throws java.lang.IllegalStateException;
method public android.support.customtabs.CustomTabsIntent build();
method public android.support.customtabs.CustomTabsIntent.Builder enableUrlBarHiding();
method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent, boolean);
method public android.support.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, java.lang.String, android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
method public android.support.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, int, int);
+ method public android.support.customtabs.CustomTabsIntent.Builder setSecondaryToolbarColor(int);
+ method public android.support.customtabs.CustomTabsIntent.Builder setSecondaryToolbarViews(android.widget.RemoteViews, int[], android.app.PendingIntent);
method public android.support.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
method public android.support.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, int, int);
method public android.support.customtabs.CustomTabsIntent.Builder setToolbarColor(int);
@@ -77,6 +94,7 @@
public final class CustomTabsSession {
method public boolean mayLaunchUrl(android.net.Uri, android.os.Bundle, java.util.List<android.os.Bundle>);
method public boolean setActionButton(android.graphics.Bitmap, java.lang.String);
+ method public boolean setToolbarItem(int, android.graphics.Bitmap, java.lang.String);
}
public class CustomTabsSessionToken {
diff --git a/customtabs/build.gradle b/customtabs/build.gradle
index 317dd9d..0e0e530 100644
--- a/customtabs/build.gradle
+++ b/customtabs/build.gradle
@@ -1,14 +1,26 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'customtabs'
dependencies {
compile project(':support-v4')
compile project(':support-annotations')
+
+ 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'
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
+
+ defaultConfig {
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -27,3 +39,36 @@
targetCompatibility JavaVersion.VERSION_1_7
}
}
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
diff --git a/customtabs/src/android/support/customtabs/CustomTabsClient.java b/customtabs/src/android/support/customtabs/CustomTabsClient.java
index 7cc5c76..f743c7d 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsClient.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsClient.java
@@ -20,11 +20,15 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
import android.net.Uri;
import android.os.Bundle;
import android.os.RemoteException;
+import android.support.annotation.Nullable;
import android.text.TextUtils;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -62,7 +66,96 @@
}
/**
+ * Returns the preferred package to use for Custom Tabs, preferring the default VIEW handler.
+ *
+ * @see {@link #getPackageName(Context, List<String>, boolean)}.
+ */
+ public static String getPackageName(Context context, @Nullable List<String> packages) {
+ return getPackageName(context, packages, false);
+ }
+
+ /**
+ * Returns the preferred package to use for Custom Tabs.
+ *
+ * The preferred package name is the default VIEW intent handler as long as it supports Custom
+ * Tabs. To modify this preferred behavior, set <code>ignoreDefault</code> to true and give a
+ * non empty list of package names in <code>packages</code>.
+ *
+ * @param context {@link Context} to use for querying the packages.
+ * @param packages Ordered list of packages to test for Custom Tabs support, in
+ * decreasing order of priority.
+ * @param ignoreDefault If set, the default VIEW handler won't get priority over other browsers.
+ * @return The preferred package name for handling Custom Tabs, or <code>null</code>.
+ */
+ public static String getPackageName(
+ Context context, @Nullable List<String> packages, boolean ignoreDefault) {
+ PackageManager pm = context.getPackageManager();
+
+ List<String> packageNames = packages == null ? new ArrayList<String>() : packages;
+ Intent activityIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("http://"));
+
+ if (!ignoreDefault) {
+ ResolveInfo defaultViewHandlerInfo = pm.resolveActivity(activityIntent, 0);
+ if (defaultViewHandlerInfo != null) {
+ String packageName = defaultViewHandlerInfo.activityInfo.packageName;
+ packageNames = new ArrayList<String>(packageNames.size() + 1);
+ packageNames.add(packageName);
+ if (packages != null) packageNames.addAll(packages);
+ }
+ }
+
+ Intent serviceIntent = new Intent(CustomTabsService.ACTION_CUSTOM_TABS_CONNECTION);
+ for (String packageName : packageNames) {
+ serviceIntent.setPackage(packageName);
+ if (pm.resolveService(serviceIntent, 0) != null) return packageName;
+ }
+ return null;
+ }
+
+ /**
+ * Connects to the Custom Tabs warmup service, and initializes the browser.
+ *
+ * This convenience method connects to the service, and immediately warms up the Custom Tabs
+ * implementation. Since service connection is asynchronous, the return code is not the return
+ * code of warmup.
+ * This call is optional, and clients are encouraged to connect to the service, call
+ * <code>warmup()</code> and create a session. In this case, calling this method is not
+ * necessary.
+ *
+ * @param context {@link Context} to use to connect to the remote service.
+ * @param packageName Package name of the target implamentation.
+ * @return Whether the binding was successful.
+ */
+ public static boolean connectAndInitialize(Context context, String packageName) {
+ if (packageName == null) return false;
+ final Context applicationContext = context.getApplicationContext();
+ CustomTabsServiceConnection connection = new CustomTabsServiceConnection() {
+ @Override
+ public final void onCustomTabsServiceConnected(
+ ComponentName name, CustomTabsClient client) {
+ client.warmup(0);
+ // Unbinding immediately makes the target process "Empty", provided that it is
+ // not used by anyone else, and doesn't contain any Activity. This makes it
+ // likely to get killed, but is preferable to keeping the connection around.
+ applicationContext.unbindService(this);
+ }
+
+ @Override
+ public final void onServiceDisconnected(ComponentName componentName) { }
+ };
+ try {
+ return bindCustomTabsService(applicationContext, packageName, connection);
+ } catch (SecurityException e) {
+ return false;
+ }
+ }
+
+ /**
* Warm up the browser process.
+ *
+ * Allows the browser application to pre-initialize itself in the background. Significantly
+ * speeds up URL opening in the browser. This is asynchronous and can be called several times.
+ *
* @param flags Reserved for future use.
* @return Whether the warmup was successful.
*/
diff --git a/customtabs/src/android/support/customtabs/CustomTabsIntent.java b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
index ab3685f..cd26670 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsIntent.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsIntent.java
@@ -17,7 +17,6 @@
package android.support.customtabs;
import android.app.Activity;
-import android.app.ActivityOptions;
import android.app.PendingIntent;
import android.content.Context;
import android.content.Intent;
@@ -29,7 +28,11 @@
import android.support.annotation.ColorInt;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.app.ActivityCompat;
+import android.support.v4.app.ActivityOptionsCompat;
import android.support.v4.app.BundleCompat;
+import android.view.View;
+import android.widget.RemoteViews;
import java.util.ArrayList;
@@ -94,6 +97,21 @@
"android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
/**
+ * List<Bundle> used for adding items to the top and bottom toolbars. The client should
+ * provide an ID, a description, an icon {@link Bitmap} for each item. They may also provide a
+ * {@link PendingIntent} if the item is a button.
+ */
+ public static final String EXTRA_TOOLBAR_ITEMS =
+ "android.support.customtabs.extra.TOOLBAR_ITEMS";
+
+ /**
+ * Extra that changes the background color for the secondary toolbar. The value should be an
+ * int that specifies a {@link Color}, not a resource id.
+ */
+ public static final String EXTRA_SECONDARY_TOOLBAR_COLOR =
+ "android.support.customtabs.extra.SECONDARY_TOOLBAR_COLOR";
+
+ /**
* Key that specifies the {@link Bitmap} to be used as the image source for the action button.
* The icon should't be more than 24dp in height (No padding needed. The button itself will be
* 48dp in height) and have a width/height ratio of less than 2.
@@ -134,7 +152,7 @@
"android.support.customtabs.customaction.MENU_ITEM_TITLE";
/**
- * Bundle constructed out of {@link ActivityOptions} that will be running when the
+ * Bundle constructed out of {@link ActivityOptionsCompat} that will be running when the
* {@link Activity} that holds the custom tab gets finished. A similar ActivityOptions
* for creation should be constructed and given to the startActivity() call that
* launches the custom tab.
@@ -143,6 +161,70 @@
"android.support.customtabs.extra.EXIT_ANIMATION_BUNDLE";
/**
+ * Boolean extra that specifies whether a default share button will be shown in the menu.
+ */
+ public static final String EXTRA_DEFAULT_SHARE_MENU_ITEM =
+ "android.support.customtabs.extra.SHARE_MENU_ITEM";
+
+ /**
+ * Extra that specifies the {@link RemoteViews} showing on the secondary toolbar. If this extra
+ * is set, the other secondary toolbar configurations will be overriden. The height of the
+ * {@link RemoteViews} should not exceed 56dp.
+ * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
+ */
+ public static final String EXTRA_REMOTEVIEWS =
+ "android.support.customtabs.extra.EXTRA_REMOTEVIEWS";
+
+ /**
+ * Extra that specifies an array of {@link View} ids. When these {@link View}s are clicked, a
+ * {@link PendingIntent} will be sent, carrying the current url of the custom tab as data.
+ * <p>
+ * Note that Custom Tabs will override the default onClick behavior of the listed {@link View}s.
+ * If you do not care about the current url, you can safely ignore this extra and use
+ * {@link RemoteViews#setOnClickPendingIntent(int, PendingIntent)} instead.
+ * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
+ */
+ public static final String EXTRA_REMOTEVIEWS_VIEW_IDS =
+ "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_VIEW_IDS";
+
+ /**
+ * Extra that specifies the {@link PendingIntent} to be sent when the user clicks on the
+ * {@link View}s that is listed by {@link #EXTRA_REMOTEVIEWS_VIEW_IDS}.
+ * <p>
+ * Note when this {@link PendingIntent} is triggered, it will have the current url as data
+ * field, also the id of the clicked {@link View}, specified by
+ * {@link #EXTRA_REMOTEVIEWS_CLICKED_ID}.
+ * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
+ */
+ public static final String EXTRA_REMOTEVIEWS_PENDINGINTENT =
+ "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_PENDINGINTENT";
+
+ /**
+ * Extra that specifies which {@link View} has been clicked. This extra will be put to the
+ * {@link PendingIntent} sent from Custom Tabs when a view in the {@link RemoteViews} is clicked
+ * @see CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
+ */
+ public static final String EXTRA_REMOTEVIEWS_CLICKED_ID =
+ "android.support.customtabs.extra.EXTRA_REMOTEVIEWS_CLICKED_ID";
+
+
+ /**
+ * Key that specifies the unique ID for an action button. To make a button to show on the
+ * toolbar, use {@link #TOOLBAR_ACTION_BUTTON_ID} as its ID.
+ */
+ public static final String KEY_ID = "android.support.customtabs.customaction.ID";
+
+ /**
+ * The ID allocated to the custom action button that is shown on the toolbar.
+ */
+ public static final int TOOLBAR_ACTION_BUTTON_ID = 0;
+
+ /**
+ * The maximum allowed number of toolbar items.
+ */
+ private static final int MAX_TOOLBAR_ITEMS = 5;
+
+ /**
* An {@link Intent} used to start the Custom Tabs Activity.
*/
@NonNull public final Intent intent;
@@ -159,11 +241,7 @@
*/
public void launchUrl(Activity context, Uri url) {
intent.setData(url);
- if (startAnimationBundle != null){
- context.startActivity(intent, startAnimationBundle);
- } else {
- context.startActivity(intent);
- }
+ ActivityCompat.startActivity(context, intent, startAnimationBundle);
}
private CustomTabsIntent(Intent intent, Bundle startAnimationBundle) {
@@ -178,6 +256,7 @@
private final Intent mIntent = new Intent(Intent.ACTION_VIEW);
private ArrayList<Bundle> mMenuItems = null;
private Bundle mStartAnimationBundle = null;
+ private ArrayList<Bundle> mActionButtons = null;
/**
* Creates a {@link CustomTabsIntent.Builder} object associated with no
@@ -259,16 +338,31 @@
}
/**
- * Set the action button.
+ * Adds a default share item to the menu.
+ */
+ public Builder addDefaultShareMenuItem() {
+ mIntent.putExtra(EXTRA_DEFAULT_SHARE_MENU_ITEM, true);
+ return this;
+ }
+
+ /**
+ * Sets the action button that is displayed in the Toolbar.
+ * <p>
+ * This is equivalent to calling
+ * {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)}
+ * with {@link #TOOLBAR_ACTION_BUTTON_ID} as id.
*
* @param icon The icon.
* @param description The description for the button. To be used for accessibility.
* @param pendingIntent pending intent delivered when the button is clicked.
* @param shouldTint Whether the action button should be tinted.
+ *
+ * @see CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)
*/
public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
@NonNull PendingIntent pendingIntent, boolean shouldTint) {
Bundle bundle = new Bundle();
+ bundle.putInt(KEY_ID, TOOLBAR_ACTION_BUTTON_ID);
bundle.putParcelable(KEY_ICON, icon);
bundle.putString(KEY_DESCRIPTION, description);
bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
@@ -278,7 +372,9 @@
}
/**
- * See {@link CustomTabsIntent.Builder#setActionButton(
+ * Sets the action button that is displayed in the Toolbar with default tinting behavior.
+ *
+ * @see {@link CustomTabsIntent.Builder#setActionButton(
* Bitmap, String, PendingIntent, boolean)}
*/
public Builder setActionButton(@NonNull Bitmap icon, @NonNull String description,
@@ -287,7 +383,76 @@
}
/**
- * Sets the start animations,
+ * Adds an action button to the custom tab. Multiple buttons can be added via this method.
+ * If the given id equals {@link #TOOLBAR_ACTION_BUTTON_ID}, the button will be placed on
+ * the toolbar; if the bitmap is too wide, it will be put to the bottom bar instead. If
+ * the id is not {@link #TOOLBAR_ACTION_BUTTON_ID}, it will be directly put on secondary
+ * toolbar. The maximum number of allowed toolbar items in a single intent is
+ * {@link CustomTabsIntent#getMaxToolbarItems()}. Throws an
+ * {@link IllegalStateException} when that number is exceeded per intent.
+ *
+ * @param id The unique id of the action button. This should be non-negative.
+ * @param icon The icon.
+ * @param description The description for the button. To be used for accessibility.
+ * @param pendingIntent The pending intent delivered when the button is clicked.
+ *
+ * @see CustomTabsIntent#getMaxToolbarItems()
+ * @deprecated Use
+ * CustomTabsIntent.Builder#setSecondaryToolbarViews(RemoteViews, int[], PendingIntent).
+ */
+ @Deprecated
+ public Builder addToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description,
+ PendingIntent pendingIntent) throws IllegalStateException {
+ if (mActionButtons == null) {
+ mActionButtons = new ArrayList<>();
+ }
+ if (mActionButtons.size() >= MAX_TOOLBAR_ITEMS) {
+ throw new IllegalStateException(
+ "Exceeded maximum toolbar item count of " + MAX_TOOLBAR_ITEMS);
+ }
+ Bundle bundle = new Bundle();
+ bundle.putInt(KEY_ID, id);
+ bundle.putParcelable(KEY_ICON, icon);
+ bundle.putString(KEY_DESCRIPTION, description);
+ bundle.putParcelable(KEY_PENDING_INTENT, pendingIntent);
+ mActionButtons.add(bundle);
+ return this;
+ }
+
+ /**
+ * Sets the color of the secondary toolbar.
+ * @param color The color for the secondary toolbar.
+ */
+ public Builder setSecondaryToolbarColor(@ColorInt int color) {
+ mIntent.putExtra(EXTRA_SECONDARY_TOOLBAR_COLOR, color);
+ return this;
+ }
+
+ /**
+ * Sets the remote views displayed in the secondary toolbar in a custom tab.
+ *
+ * @param remoteViews The {@link RemoteViews} that will be shown on the secondary toolbar.
+ * @param clickableIDs The IDs of clickable views. The onClick event of these views will be
+ * handled by custom tabs.
+ * @param pendingIntent The {@link PendingIntent} that will be sent when the user clicks on
+ * one of the {@link View}s in clickableIDs. When the
+ * {@link PendingIntent} is sent, it will have the current URL as its
+ * intent data.
+ * @see CustomTabsIntent#EXTRA_REMOTEVIEWS
+ * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_VIEW_IDS
+ * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_PENDINGINTENT
+ * @see CustomTabsIntent#EXTRA_REMOTEVIEWS_CLICKED_ID
+ */
+ public Builder setSecondaryToolbarViews(@NonNull RemoteViews remoteViews,
+ @Nullable int[] clickableIDs, @Nullable PendingIntent pendingIntent) {
+ mIntent.putExtra(EXTRA_REMOTEVIEWS, remoteViews);
+ mIntent.putExtra(EXTRA_REMOTEVIEWS_VIEW_IDS, clickableIDs);
+ mIntent.putExtra(EXTRA_REMOTEVIEWS_PENDINGINTENT, pendingIntent);
+ return this;
+ }
+
+ /**
+ * Sets the start animations.
*
* @param context Application context.
* @param enterResId Resource ID of the "enter" animation for the browser.
@@ -295,13 +460,13 @@
*/
public Builder setStartAnimations(
@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
- mStartAnimationBundle =
- ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
+ mStartAnimationBundle = ActivityOptionsCompat.makeCustomAnimation(
+ context, enterResId, exitResId).toBundle();
return this;
}
/**
- * Sets the exit animations,
+ * Sets the exit animations.
*
* @param context Application context.
* @param enterResId Resource ID of the "enter" animation for the application.
@@ -309,8 +474,8 @@
*/
public Builder setExitAnimations(
@NonNull Context context, @AnimRes int enterResId, @AnimRes int exitResId) {
- Bundle bundle =
- ActivityOptions.makeCustomAnimation(context, enterResId, exitResId).toBundle();
+ Bundle bundle = ActivityOptionsCompat.makeCustomAnimation(
+ context, enterResId, exitResId).toBundle();
mIntent.putExtra(EXTRA_EXIT_ANIMATION_BUNDLE, bundle);
return this;
}
@@ -323,7 +488,19 @@
if (mMenuItems != null) {
mIntent.putParcelableArrayListExtra(CustomTabsIntent.EXTRA_MENU_ITEMS, mMenuItems);
}
+ if (mActionButtons != null) {
+ mIntent.putParcelableArrayListExtra(EXTRA_TOOLBAR_ITEMS, mActionButtons);
+ }
return new CustomTabsIntent(mIntent, mStartAnimationBundle);
}
}
+
+ /**
+ * @return The maximum number of allowed toolbar items for
+ * {@link CustomTabsIntent.Builder#addToolbarItem(int, Bitmap, String, PendingIntent)} and
+ * {@link CustomTabsIntent#EXTRA_TOOLBAR_ITEMS}.
+ */
+ public static int getMaxToolbarItems() {
+ return MAX_TOOLBAR_ITEMS;
+ }
}
diff --git a/customtabs/src/android/support/customtabs/CustomTabsSession.java b/customtabs/src/android/support/customtabs/CustomTabsSession.java
index a51cf0e..db6c2b7 100644
--- a/customtabs/src/android/support/customtabs/CustomTabsSession.java
+++ b/customtabs/src/android/support/customtabs/CustomTabsSession.java
@@ -68,14 +68,29 @@
}
/**
- * Update the visuals for the button on a custom tab. Will only succeed if the given
- * session is the active one in browser.
+ * This sets the action button on the toolbar with ID
+ * {@link CustomTabsIntent#TOOLBAR_ACTION_BUTTON_ID}.
+ *
* @param icon The new icon of the action button.
* @param description Content description of the action button.
- * @return Whether the update succeeded.
+ *
+ * @see {@link CustomTabsSession#setToolbarItem(int, Bitmap, String)}
*/
public boolean setActionButton(@NonNull Bitmap icon, @NonNull String description) {
+ return setToolbarItem(CustomTabsIntent.TOOLBAR_ACTION_BUTTON_ID, icon, description);
+ }
+
+ /**
+ * Updates the visuals for toolbar items. Will only succeed if a custom tab created using this
+ * session is in the foreground in browser and the given id is valid.
+ * @param id The id for the item to update.
+ * @param icon The new icon of the toolbar item.
+ * @param description Content description of the toolbar item.
+ * @return Whether the update succeeded.
+ */
+ public boolean setToolbarItem(int id, @NonNull Bitmap icon, @NonNull String description) {
Bundle bundle = new Bundle();
+ bundle.putInt(CustomTabsIntent.KEY_ID, id);
bundle.putParcelable(CustomTabsIntent.KEY_ICON, icon);
bundle.putString(CustomTabsIntent.KEY_DESCRIPTION, description);
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/AndroidManifest.xml b/customtabs/tests/src/AndroidManifest.xml
new file mode 100644
index 0000000..d87a83f
--- /dev/null
+++ b/customtabs/tests/src/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.customtabs.test">
+ <uses-sdk
+ android:minSdkVersion="15"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
+ <application/>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.customtabs.test"/>
+</manifest>
diff --git a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
index 9440282..2431245 100644
--- a/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
+++ b/customtabs/tests/src/android/support/customtabs/CustomTabsIntentTest.java
@@ -18,13 +18,23 @@
import android.content.Intent;
import android.graphics.Color;
-import android.test.AndroidTestCase;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.*;
/**
* Tests for CustomTabsIntent.
*/
-public class CustomTabsIntentTest extends AndroidTestCase {
-
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CustomTabsIntentTest {
+ @Test
public void testBareboneCustomTabIntent() {
CustomTabsIntent customTabsIntent = new CustomTabsIntent.Builder().build();
Intent intent = customTabsIntent.intent;
@@ -33,10 +43,13 @@
assertEquals(Intent.ACTION_VIEW, intent.getAction());
assertTrue(intent.hasExtra(CustomTabsIntent.EXTRA_SESSION));
- assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+ if (Build.VERSION.SDK_INT >= 18) {
+ assertNull(intent.getExtras().getBinder(CustomTabsIntent.EXTRA_SESSION));
+ }
assertNull(intent.getComponent());
}
+ @Test
public void testToolbarColor() {
int color = Color.RED;
Intent intent = new CustomTabsIntent.Builder().setToolbarColor(color).build().intent;
@@ -44,9 +57,10 @@
assertEquals(color, intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
}
+ @Test
public void testToolbarColorIsNotAResource() {
- int colorId = android.R.color.background_dark;
- int color = getContext().getResources().getColor(colorId);
+ @ColorRes int colorId = android.R.color.background_dark;
+ int color = InstrumentationRegistry.getContext().getResources().getColor(colorId);
Intent intent = new CustomTabsIntent.Builder().setToolbarColor(colorId).build().intent;
assertFalse("The color should not be a resource ID",
color == intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_COLOR, 0));
diff --git a/design/Android.mk b/design/Android.mk
index f7836e0..c9ba6ec 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -14,21 +14,23 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
+# Android libraries referenced by this module's resources.
+resource_libs := \
+ android-support-v7-appcompat \
+ android-support-v7-recyclerview
+
+# Build the resources using the latest applicable SDK version.
# We do this here because the final static library must be compiled with an older
# SDK version than the resources. The resources library and the R class that it
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-design-res
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
- frameworks/support/v7/appcompat/res \
- frameworks/support/v7/recyclerview/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.v7.appcompat \
- --extra-packages android.support.v7.recyclerview
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs)
+LOCAL_AAPT_FLAGS := --no-version-vectors
LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -41,7 +43,8 @@
LOCAL_MODULE := android-support-design-base
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under, base)
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -56,7 +59,8 @@
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-base
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -71,7 +75,8 @@
LOCAL_SDK_VERSION := 11
LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-eclair-mr1
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -86,7 +91,8 @@
LOCAL_SDK_VERSION := 12
LOCAL_SRC_FILES := $(call all-java-files-under, honeycomb-mr1)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -101,7 +107,8 @@
LOCAL_SDK_VERSION := 14
LOCAL_SRC_FILES := $(call all-java-files-under, ics)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-honeycomb-mr1
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -116,7 +123,8 @@
LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, lollipop)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-ics
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
+LOCAL_JAVA_LIBRARIES := \
+ android-support-design-res \
android-support-v4 \
android-support-v7-appcompat \
android-support-v7-recyclerview
@@ -126,19 +134,26 @@
support_module_src_files += $(LOCAL_SRC_FILES)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-design \
+# android-support-v7-appcompat \
+# android-support-v7-recyclerview \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-design
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-design-lollipop
-LOCAL_JAVA_LIBRARIES := android-support-design-res \
- android-support-v4 \
- android-support-v7-appcompat \
- android-support-v7-recyclerview
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-design-res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs) android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/design/api/23.1.1.txt b/design/api/23.1.1.txt
new file mode 100644
index 0000000..94df705
--- /dev/null
+++ b/design/api/23.1.1.txt
@@ -0,0 +1,423 @@
+package android.support.design.widget {
+
+ public class AppBarLayout extends android.widget.LinearLayout {
+ ctor public AppBarLayout(android.content.Context);
+ ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
+ method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
+ method public float getTargetElevation();
+ method public final int getTotalScrollRange();
+ method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
+ method public void setExpanded(boolean);
+ method public void setExpanded(boolean, boolean);
+ method public void setTargetElevation(float);
+ }
+
+ public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
+ ctor public AppBarLayout.Behavior();
+ ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
+ method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
+ method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
+ method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
+ method public void onRestoreInstanceState(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.os.Parcelable);
+ method public android.os.Parcelable onSaveInstanceState(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout);
+ method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View);
+ method public void setDragCallback(android.support.design.widget.AppBarLayout.Behavior.DragCallback);
+ }
+
+ public static abstract class AppBarLayout.Behavior.DragCallback {
+ ctor public AppBarLayout.Behavior.DragCallback();
+ method public abstract boolean canDrag(android.support.design.widget.AppBarLayout);
+ }
+
+ protected static class AppBarLayout.Behavior.SavedState extends android.view.View.BaseSavedState {
+ ctor public AppBarLayout.Behavior.SavedState(android.os.Parcel, java.lang.ClassLoader);
+ ctor public AppBarLayout.Behavior.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.design.widget.AppBarLayout.Behavior.SavedState> CREATOR;
+ }
+
+ public static class AppBarLayout.LayoutParams extends android.widget.LinearLayout.LayoutParams {
+ ctor public AppBarLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public AppBarLayout.LayoutParams(int, int);
+ ctor public AppBarLayout.LayoutParams(int, int, float);
+ ctor public AppBarLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public AppBarLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public AppBarLayout.LayoutParams(android.widget.LinearLayout.LayoutParams);
+ ctor public AppBarLayout.LayoutParams(android.support.design.widget.AppBarLayout.LayoutParams);
+ method public int getScrollFlags();
+ method public android.view.animation.Interpolator getScrollInterpolator();
+ method public void setScrollFlags(int);
+ method public void setScrollInterpolator(android.view.animation.Interpolator);
+ field public static final int SCROLL_FLAG_ENTER_ALWAYS = 4; // 0x4
+ field public static final int SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED = 8; // 0x8
+ field public static final int SCROLL_FLAG_EXIT_UNTIL_COLLAPSED = 2; // 0x2
+ field public static final int SCROLL_FLAG_SCROLL = 1; // 0x1
+ field public static final int SCROLL_FLAG_SNAP = 16; // 0x10
+ }
+
+ public static abstract interface AppBarLayout.OnOffsetChangedListener {
+ method public abstract void onOffsetChanged(android.support.design.widget.AppBarLayout, int);
+ }
+
+ public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
+ ctor public AppBarLayout.ScrollingViewBehavior();
+ ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+ method public int getOverlayTop();
+ method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
+ method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
+ method public void setOverlayTop(int);
+ }
+
+ public class CollapsingToolbarLayout extends android.widget.FrameLayout {
+ ctor public CollapsingToolbarLayout(android.content.Context);
+ ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
+ ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet, int);
+ method public int getCollapsedTitleGravity();
+ method public android.graphics.Typeface getCollapsedTitleTypeface();
+ method public android.graphics.drawable.Drawable getContentScrim();
+ method public int getExpandedTitleGravity();
+ method public android.graphics.Typeface getExpandedTitleTypeface();
+ method public android.graphics.drawable.Drawable getStatusBarScrim();
+ method public java.lang.CharSequence getTitle();
+ method public boolean isTitleEnabled();
+ method public void setCollapsedTitleGravity(int);
+ method public void setCollapsedTitleTextAppearance(int);
+ method public void setCollapsedTitleTextColor(int);
+ method public void setCollapsedTitleTypeface(android.graphics.Typeface);
+ method public void setContentScrim(android.graphics.drawable.Drawable);
+ method public void setContentScrimColor(int);
+ method public void setContentScrimResource(int);
+ method public void setExpandedTitleColor(int);
+ method public void setExpandedTitleGravity(int);
+ method public void setExpandedTitleTextAppearance(int);
+ method public void setExpandedTitleTypeface(android.graphics.Typeface);
+ method public void setScrimsShown(boolean);
+ method public void setScrimsShown(boolean, boolean);
+ method public void setStatusBarScrim(android.graphics.drawable.Drawable);
+ method public void setStatusBarScrimColor(int);
+ method public void setStatusBarScrimResource(int);
+ method public void setTitle(java.lang.CharSequence);
+ method public void setTitleEnabled(boolean);
+ }
+
+ public static class CollapsingToolbarLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams {
+ ctor public CollapsingToolbarLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public CollapsingToolbarLayout.LayoutParams(int, int);
+ ctor public CollapsingToolbarLayout.LayoutParams(int, int, int);
+ ctor public CollapsingToolbarLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public CollapsingToolbarLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public CollapsingToolbarLayout.LayoutParams(android.widget.FrameLayout.LayoutParams);
+ method public int getCollapseMode();
+ method public float getParallaxMultiplier();
+ method public void setCollapseMode(int);
+ method public void setParallaxMultiplier(float);
+ field public static final int COLLAPSE_MODE_OFF = 0; // 0x0
+ field public static final int COLLAPSE_MODE_PARALLAX = 2; // 0x2
+ field public static final int COLLAPSE_MODE_PIN = 1; // 0x1
+ }
+
+ public class CoordinatorLayout extends android.view.ViewGroup {
+ ctor public CoordinatorLayout(android.content.Context);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet);
+ ctor public CoordinatorLayout(android.content.Context, android.util.AttributeSet, int);
+ method public void dispatchDependentViewsChanged(android.view.View);
+ method public boolean doViewsOverlap(android.view.View, android.view.View);
+ method public java.util.List<android.view.View> getDependencies(android.view.View);
+ method public android.graphics.drawable.Drawable getStatusBarBackground();
+ method public boolean isPointInChildBounds(android.view.View, int, int);
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void onDraw(android.graphics.Canvas);
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void onLayoutChild(android.view.View, int);
+ method public void onMeasureChild(android.view.View, int, int, int, int);
+ method public void setStatusBarBackground(android.graphics.drawable.Drawable);
+ method public void setStatusBarBackgroundColor(int);
+ method public void setStatusBarBackgroundResource(int);
+ }
+
+ public static abstract class CoordinatorLayout.Behavior {
+ ctor public CoordinatorLayout.Behavior();
+ ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
+ method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
+ method public final int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
+ method public final float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
+ method public static java.lang.Object getTag(android.view.View);
+ method public boolean isDirty(android.support.design.widget.CoordinatorLayout, V);
+ method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+ method public android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.support.design.widget.CoordinatorLayout, V, android.support.v4.view.WindowInsetsCompat);
+ method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+ method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+ method public boolean onInterceptTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, V, int);
+ method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, V, int, int, int, int);
+ method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float, boolean);
+ method public boolean onNestedPreFling(android.support.design.widget.CoordinatorLayout, V, android.view.View, float, float);
+ method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int[]);
+ method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, int, int, int, int);
+ method public void onNestedScrollAccepted(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+ method public void onRestoreInstanceState(android.support.design.widget.CoordinatorLayout, V, android.os.Parcelable);
+ method public android.os.Parcelable onSaveInstanceState(android.support.design.widget.CoordinatorLayout, V);
+ method public boolean onStartNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.support.design.widget.CoordinatorLayout, V, android.view.View);
+ method public boolean onTouchEvent(android.support.design.widget.CoordinatorLayout, V, android.view.MotionEvent);
+ method public static void setTag(android.view.View, java.lang.Object);
+ }
+
+ public static abstract class CoordinatorLayout.DefaultBehavior implements java.lang.annotation.Annotation {
+ }
+
+ public static class CoordinatorLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public CoordinatorLayout.LayoutParams(int, int);
+ ctor public CoordinatorLayout.LayoutParams(android.support.design.widget.CoordinatorLayout.LayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public CoordinatorLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ method public int getAnchorId();
+ method public android.support.design.widget.CoordinatorLayout.Behavior getBehavior();
+ method public void setAnchorId(int);
+ method public void setBehavior(android.support.design.widget.CoordinatorLayout.Behavior);
+ field public int anchorGravity;
+ field public int gravity;
+ field public int keyline;
+ }
+
+ protected static class CoordinatorLayout.SavedState extends android.view.View.BaseSavedState {
+ ctor public CoordinatorLayout.SavedState(android.os.Parcel, java.lang.ClassLoader);
+ ctor public CoordinatorLayout.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
+ }
+
+ public class FloatingActionButton extends android.widget.ImageButton {
+ ctor public FloatingActionButton(android.content.Context);
+ ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
+ ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
+ method public void hide();
+ method public void hide(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
+ method public void setRippleColor(int);
+ method public void show();
+ method public void show(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
+ }
+
+ public static class FloatingActionButton.Behavior extends android.support.design.widget.CoordinatorLayout.Behavior {
+ ctor public FloatingActionButton.Behavior();
+ method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+ method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+ method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
+ }
+
+ public static abstract class FloatingActionButton.OnVisibilityChangedListener {
+ ctor public FloatingActionButton.OnVisibilityChangedListener();
+ method public void onHidden(android.support.design.widget.FloatingActionButton);
+ method public void onShown(android.support.design.widget.FloatingActionButton);
+ }
+
+ abstract class HeaderBehavior extends android.support.design.widget.ViewOffsetBehavior {
+ ctor public HeaderBehavior();
+ ctor public HeaderBehavior(android.content.Context, android.util.AttributeSet);
+ }
+
+ abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
+ ctor public HeaderScrollingViewBehavior();
+ ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+ method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
+ }
+
+ public class NavigationView extends android.widget.FrameLayout {
+ ctor public NavigationView(android.content.Context);
+ ctor public NavigationView(android.content.Context, android.util.AttributeSet);
+ ctor public NavigationView(android.content.Context, android.util.AttributeSet, int);
+ method public void addHeaderView(android.view.View);
+ method public int getHeaderCount();
+ method public android.view.View getHeaderView(int);
+ method public android.graphics.drawable.Drawable getItemBackground();
+ method public android.content.res.ColorStateList getItemIconTintList();
+ method public android.content.res.ColorStateList getItemTextColor();
+ method public android.view.Menu getMenu();
+ method public android.view.View inflateHeaderView(int);
+ method public void inflateMenu(int);
+ method public void removeHeaderView(android.view.View);
+ method public void setCheckedItem(int);
+ method public void setItemBackground(android.graphics.drawable.Drawable);
+ method public void setItemBackgroundResource(int);
+ method public void setItemIconTintList(android.content.res.ColorStateList);
+ method public void setItemTextAppearance(int);
+ method public void setItemTextColor(android.content.res.ColorStateList);
+ method public void setNavigationItemSelectedListener(android.support.design.widget.NavigationView.OnNavigationItemSelectedListener);
+ }
+
+ public static abstract interface NavigationView.OnNavigationItemSelectedListener {
+ method public abstract boolean onNavigationItemSelected(android.view.MenuItem);
+ }
+
+ public static class NavigationView.SavedState extends android.view.View.BaseSavedState {
+ ctor public NavigationView.SavedState(android.os.Parcel, java.lang.ClassLoader);
+ ctor public NavigationView.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.design.widget.NavigationView.SavedState> CREATOR;
+ field public android.os.Bundle menuState;
+ }
+
+ public final class Snackbar {
+ method public void dismiss();
+ method public int getDuration();
+ method public android.view.View getView();
+ method public boolean isShown();
+ method public boolean isShownOrQueued();
+ method public static android.support.design.widget.Snackbar make(android.view.View, java.lang.CharSequence, int);
+ method public static android.support.design.widget.Snackbar make(android.view.View, int, int);
+ method public android.support.design.widget.Snackbar setAction(int, android.view.View.OnClickListener);
+ method public android.support.design.widget.Snackbar setAction(java.lang.CharSequence, android.view.View.OnClickListener);
+ method public android.support.design.widget.Snackbar setActionTextColor(android.content.res.ColorStateList);
+ method public android.support.design.widget.Snackbar setActionTextColor(int);
+ method public android.support.design.widget.Snackbar setCallback(android.support.design.widget.Snackbar.Callback);
+ method public android.support.design.widget.Snackbar setDuration(int);
+ method public android.support.design.widget.Snackbar setText(java.lang.CharSequence);
+ method public android.support.design.widget.Snackbar setText(int);
+ method public void show();
+ field public static final int LENGTH_INDEFINITE = -2; // 0xfffffffe
+ field public static final int LENGTH_LONG = 0; // 0x0
+ field public static final int LENGTH_SHORT = -1; // 0xffffffff
+ }
+
+ public static abstract class Snackbar.Callback {
+ ctor public Snackbar.Callback();
+ method public void onDismissed(android.support.design.widget.Snackbar, int);
+ method public void onShown(android.support.design.widget.Snackbar);
+ field public static final int DISMISS_EVENT_ACTION = 1; // 0x1
+ field public static final int DISMISS_EVENT_CONSECUTIVE = 4; // 0x4
+ field public static final int DISMISS_EVENT_MANUAL = 3; // 0x3
+ field public static final int DISMISS_EVENT_SWIPE = 0; // 0x0
+ field public static final int DISMISS_EVENT_TIMEOUT = 2; // 0x2
+ }
+
+ public class SwipeDismissBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
+ ctor public SwipeDismissBehavior();
+ method public boolean canSwipeDismissView(android.view.View);
+ method public int getDragState();
+ method public void setDragDismissDistance(float);
+ method public void setEndAlphaSwipeDistance(float);
+ method public void setListener(android.support.design.widget.SwipeDismissBehavior.OnDismissListener);
+ method public void setSensitivity(float);
+ method public void setStartAlphaSwipeDistance(float);
+ method public void setSwipeDirection(int);
+ field public static final int STATE_DRAGGING = 1; // 0x1
+ field public static final int STATE_IDLE = 0; // 0x0
+ field public static final int STATE_SETTLING = 2; // 0x2
+ field public static final int SWIPE_DIRECTION_ANY = 2; // 0x2
+ field public static final int SWIPE_DIRECTION_END_TO_START = 1; // 0x1
+ field public static final int SWIPE_DIRECTION_START_TO_END = 0; // 0x0
+ }
+
+ public static abstract interface SwipeDismissBehavior.OnDismissListener {
+ method public abstract void onDismiss(android.view.View);
+ method public abstract void onDragStateChanged(int);
+ }
+
+ public class TabLayout extends android.widget.HorizontalScrollView {
+ ctor public TabLayout(android.content.Context);
+ ctor public TabLayout(android.content.Context, android.util.AttributeSet);
+ ctor public TabLayout(android.content.Context, android.util.AttributeSet, int);
+ method public void addTab(android.support.design.widget.TabLayout.Tab);
+ method public void addTab(android.support.design.widget.TabLayout.Tab, int);
+ method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
+ method public void addTab(android.support.design.widget.TabLayout.Tab, int, boolean);
+ method public int getSelectedTabPosition();
+ method public android.support.design.widget.TabLayout.Tab getTabAt(int);
+ method public int getTabCount();
+ method public int getTabGravity();
+ method public int getTabMode();
+ method public android.content.res.ColorStateList getTabTextColors();
+ method public android.support.design.widget.TabLayout.Tab newTab();
+ method public void removeAllTabs();
+ method public void removeTab(android.support.design.widget.TabLayout.Tab);
+ method public void removeTabAt(int);
+ method public void setOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
+ method public void setScrollPosition(int, float, boolean);
+ method public void setSelectedTabIndicatorColor(int);
+ method public void setSelectedTabIndicatorHeight(int);
+ method public void setTabGravity(int);
+ method public void setTabMode(int);
+ method public void setTabTextColors(android.content.res.ColorStateList);
+ method public void setTabTextColors(int, int);
+ method public void setTabsFromPagerAdapter(android.support.v4.view.PagerAdapter);
+ method public void setupWithViewPager(android.support.v4.view.ViewPager);
+ field public static final int GRAVITY_CENTER = 1; // 0x1
+ field public static final int GRAVITY_FILL = 0; // 0x0
+ field public static final int MODE_FIXED = 1; // 0x1
+ field public static final int MODE_SCROLLABLE = 0; // 0x0
+ }
+
+ public static abstract interface TabLayout.OnTabSelectedListener {
+ method public abstract void onTabReselected(android.support.design.widget.TabLayout.Tab);
+ method public abstract void onTabSelected(android.support.design.widget.TabLayout.Tab);
+ method public abstract void onTabUnselected(android.support.design.widget.TabLayout.Tab);
+ }
+
+ public static final class TabLayout.Tab {
+ method public java.lang.CharSequence getContentDescription();
+ method public android.view.View getCustomView();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public int getPosition();
+ method public java.lang.Object getTag();
+ method public java.lang.CharSequence getText();
+ method public boolean isSelected();
+ method public void select();
+ method public android.support.design.widget.TabLayout.Tab setContentDescription(int);
+ method public android.support.design.widget.TabLayout.Tab setContentDescription(java.lang.CharSequence);
+ method public android.support.design.widget.TabLayout.Tab setCustomView(android.view.View);
+ method public android.support.design.widget.TabLayout.Tab setCustomView(int);
+ method public android.support.design.widget.TabLayout.Tab setIcon(android.graphics.drawable.Drawable);
+ method public android.support.design.widget.TabLayout.Tab setIcon(int);
+ method public android.support.design.widget.TabLayout.Tab setTag(java.lang.Object);
+ method public android.support.design.widget.TabLayout.Tab setText(java.lang.CharSequence);
+ method public android.support.design.widget.TabLayout.Tab setText(int);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static class TabLayout.TabLayoutOnPageChangeListener implements android.support.v4.view.ViewPager.OnPageChangeListener {
+ ctor public TabLayout.TabLayoutOnPageChangeListener(android.support.design.widget.TabLayout);
+ method public void onPageScrollStateChanged(int);
+ method public void onPageScrolled(int, float, int);
+ method public void onPageSelected(int);
+ }
+
+ public static class TabLayout.ViewPagerOnTabSelectedListener implements android.support.design.widget.TabLayout.OnTabSelectedListener {
+ ctor public TabLayout.ViewPagerOnTabSelectedListener(android.support.v4.view.ViewPager);
+ method public void onTabReselected(android.support.design.widget.TabLayout.Tab);
+ method public void onTabSelected(android.support.design.widget.TabLayout.Tab);
+ method public void onTabUnselected(android.support.design.widget.TabLayout.Tab);
+ }
+
+ public class TextInputLayout extends android.widget.LinearLayout {
+ ctor public TextInputLayout(android.content.Context);
+ ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
+ ctor public TextInputLayout(android.content.Context, android.util.AttributeSet, int);
+ method public int getCounterMaxLength();
+ method public android.widget.EditText getEditText();
+ method public java.lang.CharSequence getError();
+ method public java.lang.CharSequence getHint();
+ method public android.graphics.Typeface getTypeface();
+ method public boolean isErrorEnabled();
+ method public boolean isHintAnimationEnabled();
+ method public void setCounterEnabled(boolean);
+ method public void setCounterMaxLength(int);
+ method public void setError(java.lang.CharSequence);
+ method public void setErrorEnabled(boolean);
+ method public void setHint(java.lang.CharSequence);
+ method public void setHintAnimationEnabled(boolean);
+ method public void setHintTextAppearance(int);
+ method public void setTypeface(android.graphics.Typeface);
+ }
+
+ class ViewOffsetBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
+ ctor public ViewOffsetBehavior();
+ ctor public ViewOffsetBehavior(android.content.Context, android.util.AttributeSet);
+ method public int getLeftAndRightOffset();
+ method public int getTopAndBottomOffset();
+ method public boolean setLeftAndRightOffset(int);
+ method public boolean setTopAndBottomOffset(int);
+ }
+
+}
+
diff --git a/design/api/current.txt b/design/api/current.txt
index 7d85b1b..aefeea2 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -4,18 +4,19 @@
ctor public AppBarLayout(android.content.Context);
ctor public AppBarLayout(android.content.Context, android.util.AttributeSet);
method public void addOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
- method public float getTargetElevation();
+ method public deprecated float getTargetElevation();
method public final int getTotalScrollRange();
method public void removeOnOffsetChangedListener(android.support.design.widget.AppBarLayout.OnOffsetChangedListener);
method public void setExpanded(boolean);
method public void setExpanded(boolean, boolean);
- method public void setTargetElevation(float);
+ method public deprecated void setTargetElevation(float);
}
public static class AppBarLayout.Behavior extends android.support.design.widget.HeaderBehavior {
ctor public AppBarLayout.Behavior();
ctor public AppBarLayout.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int);
+ method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, int, int, int, int);
method public boolean onNestedFling(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, float, float, boolean);
method public void onNestedPreScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int[]);
method public void onNestedScroll(android.support.design.widget.CoordinatorLayout, android.support.design.widget.AppBarLayout, android.view.View, int, int, int, int);
@@ -31,7 +32,7 @@
method public abstract boolean canDrag(android.support.design.widget.AppBarLayout);
}
- protected static class AppBarLayout.Behavior.SavedState extends android.view.View.BaseSavedState {
+ protected static class AppBarLayout.Behavior.SavedState extends android.support.v4.view.AbsSavedState {
ctor public AppBarLayout.Behavior.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public AppBarLayout.Behavior.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.design.widget.AppBarLayout.Behavior.SavedState> CREATOR;
@@ -63,11 +64,8 @@
public static class AppBarLayout.ScrollingViewBehavior extends android.support.design.widget.HeaderScrollingViewBehavior {
ctor public AppBarLayout.ScrollingViewBehavior();
ctor public AppBarLayout.ScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
- method public int getOverlayTop();
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.view.View, android.view.View);
- method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
- method public void setOverlayTop(int);
}
public class BottomSheetBehavior extends android.support.design.widget.CoordinatorLayout.Behavior {
@@ -75,21 +73,44 @@
ctor public BottomSheetBehavior(android.content.Context, android.util.AttributeSet);
method public static android.support.design.widget.BottomSheetBehavior<V> from(V);
method public final int getPeekHeight();
+ method public boolean getSkipCollapsed();
method public final int getState();
+ method public boolean isHideable();
+ method public void setBottomSheetCallback(android.support.design.widget.BottomSheetBehavior.BottomSheetCallback);
+ method public void setHideable(boolean);
method public final void setPeekHeight(int);
+ method public void setSkipCollapsed(boolean);
method public final void setState(int);
field public static final int STATE_COLLAPSED = 4; // 0x4
field public static final int STATE_DRAGGING = 1; // 0x1
field public static final int STATE_EXPANDED = 3; // 0x3
+ field public static final int STATE_HIDDEN = 5; // 0x5
field public static final int STATE_SETTLING = 2; // 0x2
}
- protected static class BottomSheetBehavior.SavedState extends android.view.View.BaseSavedState {
+ public static abstract class BottomSheetBehavior.BottomSheetCallback {
+ ctor public BottomSheetBehavior.BottomSheetCallback();
+ method public abstract void onSlide(android.view.View, float);
+ method public abstract void onStateChanged(android.view.View, int);
+ }
+
+ protected static class BottomSheetBehavior.SavedState extends android.support.v4.view.AbsSavedState {
ctor public BottomSheetBehavior.SavedState(android.os.Parcel);
+ ctor public BottomSheetBehavior.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public BottomSheetBehavior.SavedState(android.os.Parcelable, int);
field public static final android.os.Parcelable.Creator<android.support.design.widget.BottomSheetBehavior.SavedState> CREATOR;
}
+ public class BottomSheetDialog extends android.support.v7.app.AppCompatDialog {
+ ctor public BottomSheetDialog(android.content.Context);
+ ctor public BottomSheetDialog(android.content.Context, int);
+ ctor protected BottomSheetDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
+ }
+
+ public class BottomSheetDialogFragment extends android.support.v7.app.AppCompatDialogFragment {
+ ctor public BottomSheetDialogFragment();
+ }
+
public class CollapsingToolbarLayout extends android.widget.FrameLayout {
ctor public CollapsingToolbarLayout(android.content.Context);
ctor public CollapsingToolbarLayout(android.content.Context, android.util.AttributeSet);
@@ -103,6 +124,8 @@
method public int getExpandedTitleMarginStart();
method public int getExpandedTitleMarginTop();
method public android.graphics.Typeface getExpandedTitleTypeface();
+ method public long getScrimAnimationDuration();
+ method public int getScrimVisibleHeightTrigger();
method public android.graphics.drawable.Drawable getStatusBarScrim();
method public java.lang.CharSequence getTitle();
method public boolean isTitleEnabled();
@@ -122,6 +145,8 @@
method public void setExpandedTitleMarginTop(int);
method public void setExpandedTitleTextAppearance(int);
method public void setExpandedTitleTypeface(android.graphics.Typeface);
+ method public void setScrimAnimationDuration(long);
+ method public void setScrimVisibleHeightTrigger(int);
method public void setScrimsShown(boolean);
method public void setScrimsShown(boolean, boolean);
method public void setStatusBarScrim(android.graphics.drawable.Drawable);
@@ -171,8 +196,8 @@
ctor public CoordinatorLayout.Behavior();
ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
- method public final int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
- method public final float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
+ method public int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
+ method public float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
method public static java.lang.Object getTag(android.view.View);
method public boolean isDirty(android.support.design.widget.CoordinatorLayout, V);
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
@@ -212,7 +237,7 @@
field public int keyline;
}
- protected static class CoordinatorLayout.SavedState extends android.view.View.BaseSavedState {
+ protected static class CoordinatorLayout.SavedState extends android.support.v4.view.AbsSavedState {
ctor public CoordinatorLayout.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public CoordinatorLayout.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.design.widget.CoordinatorLayout.SavedState> CREATOR;
@@ -222,23 +247,30 @@
ctor public FloatingActionButton(android.content.Context);
ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet);
ctor public FloatingActionButton(android.content.Context, android.util.AttributeSet, int);
+ method public float getCompatElevation();
method public android.graphics.drawable.Drawable getContentBackground();
method public boolean getContentRect(android.graphics.Rect);
- method public float getFloatingActionButtonElevation();
+ method public int getSize();
method public boolean getUseCompatPadding();
method public void hide();
method public void hide(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
- method public void setFloatingActionButtonElevation(float);
+ method public void setCompatElevation(float);
method public void setRippleColor(int);
+ method public void setSize(int);
method public void setUseCompatPadding(boolean);
method public void show();
method public void show(android.support.design.widget.FloatingActionButton.OnVisibilityChangedListener);
+ field public static final int SIZE_AUTO = -1; // 0xffffffff
+ field public static final int SIZE_MINI = 1; // 0x1
+ field public static final int SIZE_NORMAL = 0; // 0x0
}
public static class FloatingActionButton.Behavior extends android.support.design.widget.CoordinatorLayout.Behavior {
ctor public FloatingActionButton.Behavior();
+ ctor public FloatingActionButton.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
method public boolean onDependentViewChanged(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
+ method public void onDependentViewRemoved(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, android.view.View);
method public boolean onLayoutChild(android.support.design.widget.CoordinatorLayout, android.support.design.widget.FloatingActionButton, int);
}
@@ -256,7 +288,10 @@
abstract class HeaderScrollingViewBehavior extends android.support.design.widget.ViewOffsetBehavior {
ctor public HeaderScrollingViewBehavior();
ctor public HeaderScrollingViewBehavior(android.content.Context, android.util.AttributeSet);
+ method public final int getOverlayTop();
+ method protected void layoutChild(android.support.design.widget.CoordinatorLayout, android.view.View, int);
method public boolean onMeasureChild(android.support.design.widget.CoordinatorLayout, android.view.View, int, int, int, int);
+ method public final void setOverlayTop(int);
}
public class NavigationView extends android.widget.FrameLayout {
@@ -286,7 +321,7 @@
method public abstract boolean onNavigationItemSelected(android.view.MenuItem);
}
- public static class NavigationView.SavedState extends android.view.View.BaseSavedState {
+ public static class NavigationView.SavedState extends android.support.v4.view.AbsSavedState {
ctor public NavigationView.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public NavigationView.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.design.widget.NavigationView.SavedState> CREATOR;
@@ -349,10 +384,16 @@
method public abstract void onDragStateChanged(int);
}
+ public final class TabItem extends android.view.View {
+ ctor public TabItem(android.content.Context);
+ ctor public TabItem(android.content.Context, android.util.AttributeSet);
+ }
+
public class TabLayout extends android.widget.HorizontalScrollView {
ctor public TabLayout(android.content.Context);
ctor public TabLayout(android.content.Context, android.util.AttributeSet);
ctor public TabLayout(android.content.Context, android.util.AttributeSet, int);
+ method public void addOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
method public void addTab(android.support.design.widget.TabLayout.Tab);
method public void addTab(android.support.design.widget.TabLayout.Tab, int);
method public void addTab(android.support.design.widget.TabLayout.Tab, boolean);
@@ -365,9 +406,10 @@
method public android.content.res.ColorStateList getTabTextColors();
method public android.support.design.widget.TabLayout.Tab newTab();
method public void removeAllTabs();
+ method public void removeOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
method public void removeTab(android.support.design.widget.TabLayout.Tab);
method public void removeTabAt(int);
- method public void setOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
+ method public deprecated void setOnTabSelectedListener(android.support.design.widget.TabLayout.OnTabSelectedListener);
method public void setScrollPosition(int, float, boolean);
method public void setSelectedTabIndicatorColor(int);
method public void setSelectedTabIndicatorHeight(int);
@@ -377,6 +419,7 @@
method public void setTabTextColors(int, int);
method public deprecated void setTabsFromPagerAdapter(android.support.v4.view.PagerAdapter);
method public void setupWithViewPager(android.support.v4.view.ViewPager);
+ method public void setupWithViewPager(android.support.v4.view.ViewPager, boolean);
field public static final int GRAVITY_CENTER = 1; // 0x1
field public static final int GRAVITY_FILL = 0; // 0x0
field public static final int MODE_FIXED = 1; // 0x1
@@ -424,6 +467,12 @@
method public void onTabUnselected(android.support.design.widget.TabLayout.Tab);
}
+ public class TextInputEditText extends android.support.v7.widget.AppCompatEditText {
+ ctor public TextInputEditText(android.content.Context);
+ ctor public TextInputEditText(android.content.Context, android.util.AttributeSet);
+ ctor public TextInputEditText(android.content.Context, android.util.AttributeSet, int);
+ }
+
public class TextInputLayout extends android.widget.LinearLayout {
ctor public TextInputLayout(android.content.Context);
ctor public TextInputLayout(android.content.Context, android.util.AttributeSet);
@@ -437,6 +486,7 @@
method public boolean isErrorEnabled();
method public boolean isHintAnimationEnabled();
method public boolean isHintEnabled();
+ method public android.os.Parcelable onSaveInstanceState();
method public void setCounterEnabled(boolean);
method public void setCounterMaxLength(int);
method public void setError(java.lang.CharSequence);
@@ -453,6 +503,7 @@
ctor public ViewOffsetBehavior(android.content.Context, android.util.AttributeSet);
method public int getLeftAndRightOffset();
method public int getTopAndBottomOffset();
+ method protected void layoutChild(android.support.design.widget.CoordinatorLayout, V, int);
method public boolean setLeftAndRightOffset(int);
method public boolean setTopAndBottomOffset(int);
}
diff --git a/design/base/android/support/design/widget/ValueAnimatorCompat.java b/design/base/android/support/design/widget/ValueAnimatorCompat.java
index 20bebb6..c1bcd0f 100644
--- a/design/base/android/support/design/widget/ValueAnimatorCompat.java
+++ b/design/base/android/support/design/widget/ValueAnimatorCompat.java
@@ -100,7 +100,7 @@
abstract int getAnimatedIntValue();
abstract void setFloatValues(float from, float to);
abstract float getAnimatedFloatValue();
- abstract void setDuration(int duration);
+ abstract void setDuration(long duration);
abstract void cancel();
abstract float getAnimatedFraction();
abstract void end();
@@ -177,7 +177,7 @@
return mImpl.getAnimatedFloatValue();
}
- public void setDuration(int duration) {
+ public void setDuration(long duration) {
mImpl.setDuration(duration);
}
diff --git a/design/build.gradle b/design/build.gradle
index 401ec8e..7148e50 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'design'
@@ -6,10 +6,28 @@
compile project(':support-v4')
compile project(':support-appcompat-v7')
compile project(':support-recyclerview-v7')
+
+ 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'
+ }
+ androidTestCompile 'org.mockito:mockito-core:1.9.5'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
+ testCompile 'junit:junit:4.12'
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
+
+ defaultConfig {
+ minSdkVersion 7
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ // This disables the builds tools automatic vector -> PNG generation
+ generatedDensities = []
+ }
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -18,11 +36,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 +55,10 @@
buildTypes.all {
consumerProguardFiles 'proguard-rules.pro'
}
+
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
}
android.libraryVariants.all { variant ->
diff --git a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
index 92f9603..54e99e0 100644
--- a/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/FloatingActionButtonEclairMr1.java
@@ -76,7 +76,6 @@
// to inset for any border here as LayerDrawable will nest the padding for us
mRippleDrawable = DrawableCompat.wrap(touchFeedbackShape);
DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
- DrawableCompat.setTintMode(mRippleDrawable, PorterDuff.Mode.MULTIPLY);
final Drawable[] layers;
if (borderWidth > 0) {
@@ -101,7 +100,9 @@
@Override
void setBackgroundTintList(ColorStateList tint) {
- DrawableCompat.setTintList(mShapeDrawable, tint);
+ if (mShapeDrawable != null) {
+ DrawableCompat.setTintList(mShapeDrawable, tint);
+ }
if (mBorderDrawable != null) {
mBorderDrawable.setBorderTint(tint);
}
@@ -109,12 +110,16 @@
@Override
void setBackgroundTintMode(PorterDuff.Mode tintMode) {
- DrawableCompat.setTintMode(mShapeDrawable, tintMode);
+ if (mShapeDrawable != null) {
+ DrawableCompat.setTintMode(mShapeDrawable, tintMode);
+ }
}
@Override
void setRippleColor(int rippleColor) {
- DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
+ if (mRippleDrawable != null) {
+ DrawableCompat.setTintList(mRippleDrawable, createColorStateList(rippleColor));
+ }
}
@Override
diff --git a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
index 1c708f5..55d816a 100644
--- a/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
+++ b/design/eclair-mr1/android/support/design/widget/ValueAnimatorCompatImplEclairMr1.java
@@ -38,7 +38,7 @@
private final int[] mIntValues = new int[2];
private final float[] mFloatValues = new float[2];
- private int mDuration = DEFAULT_DURATION;
+ private long mDuration = DEFAULT_DURATION;
private Interpolator mInterpolator;
private AnimatorListenerProxy mListener;
private AnimatorUpdateListenerProxy mUpdateListener;
@@ -59,6 +59,9 @@
mStartTime = SystemClock.uptimeMillis();
mIsRunning = true;
+ // Reset the animated fraction
+ mAnimatedFraction = 0f;
+
if (mListener != null) {
mListener.onAnimationStart();
}
@@ -109,7 +112,7 @@
}
@Override
- public void setDuration(int duration) {
+ public void setDuration(long duration) {
mDuration = duration;
}
@@ -120,6 +123,7 @@
if (mListener != null) {
mListener.onAnimationCancel();
+ mListener.onAnimationEnd();
}
}
@@ -156,7 +160,7 @@
if (mIsRunning) {
// Update the animated fraction
final long elapsed = SystemClock.uptimeMillis() - mStartTime;
- final float linearFraction = elapsed / (float) mDuration;
+ final float linearFraction = MathUtils.constrain(elapsed / (float) mDuration, 0f, 1f);
mAnimatedFraction = mInterpolator != null
? mInterpolator.getInterpolation(linearFraction)
: linearFraction;
diff --git a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
index 5ee272b..a377220 100644
--- a/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
+++ b/design/honeycomb-mr1/android/support/design/widget/ValueAnimatorCompatImplHoneycombMr1.java
@@ -95,7 +95,7 @@
}
@Override
- public void setDuration(int duration) {
+ public void setDuration(long duration) {
mValueAnimator.setDuration(duration);
}
diff --git a/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
index f080592..cb9fc88 100644
--- a/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
+++ b/design/lollipop/android/support/design/widget/ViewUtilsLollipop.java
@@ -16,13 +16,65 @@
package android.support.design.widget;
+import android.animation.AnimatorInflater;
+import android.animation.ObjectAnimator;
+import android.animation.StateListAnimator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.design.R;
+import android.util.AttributeSet;
import android.view.View;
import android.view.ViewOutlineProvider;
class ViewUtilsLollipop {
+ private static final int[] STATE_LIST_ANIM_ATTRS = new int[] {android.R.attr.stateListAnimator};
+
static void setBoundsViewOutlineProvider(View view) {
view.setOutlineProvider(ViewOutlineProvider.BOUNDS);
}
+ static void setStateListAnimatorFromAttrs(View view, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ final Context context = view.getContext();
+ final TypedArray a = context.obtainStyledAttributes(attrs, STATE_LIST_ANIM_ATTRS,
+ defStyleAttr, defStyleRes);
+ try {
+ if (a.hasValue(0)) {
+ StateListAnimator sla = AnimatorInflater.loadStateListAnimator(context,
+ a.getResourceId(0, 0));
+ view.setStateListAnimator(sla);
+ }
+ } finally {
+ a.recycle();
+ }
+ }
+
+ /**
+ * Creates and sets a {@link StateListAnimator} with a custom elevation value
+ */
+ static void setDefaultAppBarLayoutStateListAnimator(final View view,
+ final float targetElevation) {
+ final StateListAnimator sla = new StateListAnimator();
+
+ // Enabled, collapsible and collapsed == elevated
+ sla.addState(new int[]{android.R.attr.enabled, R.attr.state_collapsible,
+ R.attr.state_collapsed},
+ ObjectAnimator.ofFloat(view, "elevation", targetElevation));
+
+ // Enabled and collapsible, but not collapsed != elevated
+ sla.addState(new int[]{android.R.attr.enabled, R.attr.state_collapsible,
+ -R.attr.state_collapsed},
+ ObjectAnimator.ofFloat(view, "elevation", 0f));
+
+ // Enabled but not collapsible == elevated
+ sla.addState(new int[]{android.R.attr.enabled, -R.attr.state_collapsible},
+ ObjectAnimator.ofFloat(view, "elevation", targetElevation));
+
+ // Default, none elevated state
+ sla.addState(new int[0], ObjectAnimator.ofFloat(view, "elevation", 0));
+
+ view.setStateListAnimator(sla);
+ }
+
}
diff --git a/design/res-public/values/public_attrs.xml b/design/res-public/values/public_attrs.xml
index 8c8eb0d..b443778 100644
--- a/design/res-public/values/public_attrs.xml
+++ b/design/res-public/values/public_attrs.xml
@@ -18,17 +18,22 @@
<resources>
<public type="attr" name="backgroundTint"/>
<public type="attr" name="backgroundTintMode"/>
+ <public type="attr" name="behavior_hideable"/>
+ <public type="attr" name="behavior_overlapTop"/>
+ <public type="attr" name="behavior_peekHeight"/>
<public type="attr" name="borderWidth"/>
+ <public type="attr" name="bottomSheetDialogTheme"/>
+ <public type="attr" name="bottomSheetStyle"/>
<public type="attr" name="collapsedTitleGravity"/>
<public type="attr" name="collapsedTitleTextAppearance"/>
<public type="attr" name="contentScrim"/>
+ <public type="attr" name="counterEnabled"/>
+ <public type="attr" name="counterMaxLength"/>
+ <public type="attr" name="counterOverflowTextAppearance"/>
+ <public type="attr" name="counterTextAppearance"/>
<public type="attr" name="elevation"/>
<public type="attr" name="errorEnabled"/>
<public type="attr" name="errorTextAppearance"/>
- <public type="attr" name="counterEnabled"/>
- <public type="attr" name="counterMaxLength"/>
- <public type="attr" name="counterTextAppearance"/>
- <public type="attr" name="counterOverflowTextAppearance"/>
<public type="attr" name="expanded"/>
<public type="attr" name="expandedTitleGravity"/>
<public type="attr" name="expandedTitleMargin"/>
@@ -40,12 +45,21 @@
<public type="attr" name="fabSize"/>
<public type="attr" name="headerLayout"/>
<public type="attr" name="hintAnimationEnabled"/>
+ <public type="attr" name="hintEnabled"/>
<public type="attr" name="hintTextAppearance"/>
<public type="attr" name="itemBackground"/>
<public type="attr" name="itemIconTint"/>
<public type="attr" name="itemTextAppearance"/>
<public type="attr" name="itemTextColor"/>
<public type="attr" name="keylines"/>
+ <public type="attr" name="layout_anchor"/>
+ <public type="attr" name="layout_anchorGravity"/>
+ <public type="attr" name="layout_behavior"/>
+ <public type="attr" name="layout_collapseMode"/>
+ <public type="attr" name="layout_collapseParallaxMultiplier"/>
+ <public type="attr" name="layout_keyline"/>
+ <public type="attr" name="layout_scrollFlags"/>
+ <public type="attr" name="layout_scrollInterpolator"/>
<public type="attr" name="menu"/>
<public type="attr" name="pressedTranslationZ"/>
<public type="attr" name="rippleColor"/>
@@ -67,8 +81,9 @@
<public type="attr" name="tabSelectedTextColor"/>
<public type="attr" name="tabTextAppearance"/>
<public type="attr" name="tabTextColor"/>
+ <public type="attr" name="textColorError"/>
<public type="attr" name="title"/>
<public type="attr" name="titleEnabled"/>
<public type="attr" name="toolbarId"/>
- <public type="attr" name="behavior_peekHeight"/>
+ <public type="attr" name="useCompatPadding"/>
</resources>
diff --git a/design/res-public/values/public_strings.xml b/design/res-public/values/public_strings.xml
index d215d5c..29f4155 100644
--- a/design/res-public/values/public_strings.xml
+++ b/design/res-public/values/public_strings.xml
@@ -17,4 +17,5 @@
<!-- Definitions of styles to be exposed as public -->
<resources>
<public type="string" name="appbar_scrolling_view_behavior"/>
+ <public type="string" name="bottom_sheet_behavior"/>
</resources>
diff --git a/design/res-public/values/public_styles.xml b/design/res-public/values/public_styles.xml
index 4ca2050a..a7c0af6 100644
--- a/design/res-public/values/public_styles.xml
+++ b/design/res-public/values/public_styles.xml
@@ -17,10 +17,20 @@
<!-- Definitions of styles to be exposed as public -->
<resources>
<public type="style" name="TextAppearance.Design.CollapsingToolbar.Expanded"/>
+ <public type="style" name="TextAppearance.Design.Counter"/>
+ <public type="style" name="TextAppearance.Design.Counter.Overflow"/>
<public type="style" name="TextAppearance.Design.Error"/>
<public type="style" name="TextAppearance.Design.Hint"/>
+ <public type="style" name="TextAppearance.Design.Snackbar.Message"/>
<public type="style" name="TextAppearance.Design.Tab"/>
+ <public type="style" name="Theme.Design"/>
+ <public type="style" name="Theme.Design.BottomSheetDialog"/>
+ <public type="style" name="Theme.Design.Light"/>
+ <public type="style" name="Theme.Design.Light.BottomSheetDialog"/>
+ <public type="style" name="Theme.Design.Light.NoActionBar"/>
+ <public type="style" name="Theme.Design.NoActionBar"/>
<public type="style" name="Widget.Design.AppBarLayout"/>
+ <public type="style" name="Widget.Design.BottomSheet.Modal"/>
<public type="style" name="Widget.Design.CollapsingToolbar"/>
<public type="style" name="Widget.Design.CoordinatorLayout"/>
<public type="style" name="Widget.Design.FloatingActionButton"/>
diff --git a/design/res/anim-v21/design_appbar_state_list_animator.xml b/design/res/anim-v21/design_appbar_state_list_animator.xml
new file mode 100644
index 0000000..c7bba14
--- /dev/null
+++ b/design/res/anim-v21/design_appbar_state_list_animator.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+
+ <item android:state_enabled="true" app:state_collapsible="true" app:state_collapsed="true">
+ <objectAnimator android:propertyName="elevation"
+ android:valueTo="@dimen/design_appbar_elevation"
+ android:valueType="floatType"/>
+ </item>
+
+ <item android:state_enabled="true" app:state_collapsible="true" app:state_collapsed="false">
+ <objectAnimator android:propertyName="elevation"
+ android:valueTo="0dp"
+ android:valueType="floatType"/>
+ </item>
+
+ <item android:state_enabled="true" app:state_collapsible="false">
+ <objectAnimator android:propertyName="elevation"
+ android:valueTo="@dimen/design_appbar_elevation"
+ android:valueType="floatType"/>
+ </item>
+
+ <item>
+ <objectAnimator android:propertyName="elevation"
+ android:valueTo="0"
+ android:valueType="floatType"/>
+ </item>
+
+</selector>
\ No newline at end of file
diff --git a/design/res/anim-v21/design_bottom_sheet_slide_in.xml b/design/res/anim-v21/design_bottom_sheet_slide_in.xml
new file mode 100644
index 0000000..b5960a3
--- /dev/null
+++ b/design/res/anim-v21/design_bottom_sheet_slide_in.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/bottom_sheet_slide_duration"
+ android:interpolator="@android:interpolator/fast_out_linear_in">
+
+ <translate
+ android:fromYDelta="20%p"
+ android:toYDelta="0"/>
+
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"/>
+
+</set>
diff --git a/design/res/anim-v21/design_bottom_sheet_slide_out.xml b/design/res/anim-v21/design_bottom_sheet_slide_out.xml
new file mode 100644
index 0000000..d680abe
--- /dev/null
+++ b/design/res/anim-v21/design_bottom_sheet_slide_out.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/bottom_sheet_slide_duration"
+ android:interpolator="@android:interpolator/fast_out_slow_in">
+
+ <translate
+ android:fromYDelta="0"
+ android:toYDelta="20%p"/>
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"/>
+
+</set>
diff --git a/design/res/anim/design_bottom_sheet_slide_in.xml b/design/res/anim/design_bottom_sheet_slide_in.xml
new file mode 100644
index 0000000..7cbae08
--- /dev/null
+++ b/design/res/anim/design_bottom_sheet_slide_in.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/bottom_sheet_slide_duration"
+ android:interpolator="@android:anim/accelerate_decelerate_interpolator">
+
+ <translate
+ android:fromYDelta="20%p"
+ android:toYDelta="0"/>
+
+ <alpha
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"/>
+
+</set>
diff --git a/design/res/anim/design_bottom_sheet_slide_out.xml b/design/res/anim/design_bottom_sheet_slide_out.xml
new file mode 100644
index 0000000..2e30963
--- /dev/null
+++ b/design/res/anim/design_bottom_sheet_slide_out.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="@integer/bottom_sheet_slide_duration"
+ android:interpolator="@android:anim/accelerate_interpolator">
+
+ <translate
+ android:fromYDelta="0"
+ android:toYDelta="20%p"/>
+
+ <alpha
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"/>
+
+</set>
diff --git a/design/res/layout/design_bottom_sheet_dialog.xml b/design/res/layout/design_bottom_sheet_dialog.xml
new file mode 100644
index 0000000..27bb806
--- /dev/null
+++ b/design/res/layout/design_bottom_sheet_dialog.xml
@@ -0,0 +1,38 @@
+<?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.
+-->
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <View
+ android:id="@+id/touch_outside"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:soundEffectsEnabled="false"/>
+
+ <FrameLayout
+ android:id="@+id/design_bottom_sheet"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal|top"
+ android:clickable="true"
+ app:layout_behavior="@string/bottom_sheet_behavior"
+ style="?attr/bottomSheetStyle"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/res/layout/design_layout_snackbar_include.xml b/design/res/layout/design_layout_snackbar_include.xml
index 0e0cde4..1aab24b 100644
--- a/design/res/layout/design_layout_snackbar_include.xml
+++ b/design/res/layout/design_layout_snackbar_include.xml
@@ -29,7 +29,8 @@
android:textAppearance="@style/TextAppearance.Design.Snackbar.Message"
android:maxLines="@integer/design_snackbar_text_max_lines"
android:layout_gravity="center_vertical|left|start"
- android:ellipsize="end"/>
+ android:ellipsize="end"
+ android:textAlignment="viewStart"/>
<Button
android:id="@+id/snackbar_action"
diff --git a/design/res/layout/design_navigation_menu.xml b/design/res/layout/design_navigation_menu.xml
index 4d85081..b6a0ad5 100644
--- a/design/res/layout/design_navigation_menu.xml
+++ b/design/res/layout/design_navigation_menu.xml
@@ -19,7 +19,6 @@
android:id="@+id/design_navigation_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:paddingTop="@dimen/design_navigation_padding_top_default"
android:paddingBottom="@dimen/design_navigation_padding_bottom"
android:clipToPadding="false"
android:scrollbars="vertical"/>
diff --git a/design/res/values-sw600dp/dimens.xml b/design/res/values-sw600dp/dimens.xml
index 8dada20..9d61f00 100644
--- a/design/res/values-sw600dp/dimens.xml
+++ b/design/res/values-sw600dp/dimens.xml
@@ -26,4 +26,7 @@
<dimen name="design_snackbar_background_corner_radius">2dp</dimen>
<dimen name="design_snackbar_action_inline_max_width">0dp</dimen>
+ <!-- 5 * standard increment (64dp on tablets) -->
+ <dimen name="design_navigation_max_width">320dp</dimen>
+
</resources>
\ No newline at end of file
diff --git a/design/res/values-v21/dimens.xml b/design/res/values-v21/dimens.xml
deleted file mode 100644
index 447aeb2..0000000
--- a/design/res/values-v21/dimens.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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>
- <dimen name="design_navigation_padding_top_default">24dp</dimen>
-</resources>
diff --git a/design/res/values-v21/styles.xml b/design/res/values-v21/styles.xml
new file mode 100644
index 0000000..76bde78
--- /dev/null
+++ b/design/res/values-v21/styles.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+-->
+<resources>
+
+ <style name="Widget.Design.AppBarLayout" parent="Base.Widget.Design.AppBarLayout">
+ <item name="android:stateListAnimator">@anim/design_appbar_state_list_animator</item>
+ </style>
+
+</resources>
+
diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml
index 29ca0af..73f46d0 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -25,7 +25,11 @@
<attr name="rippleColor" format="color"/>
<!-- Size for the FAB. -->
<attr name="fabSize">
+ <!-- A size which will change based on the window size. -->
+ <enum name="auto" value="-1"/>
+ <!-- The normal sized button. -->
<enum name="normal" value="0"/>
+ <!-- The mini sized button. -->
<enum name="mini" value="1"/>
</attr>
<!-- Elevation value for the FAB -->
@@ -64,39 +68,55 @@
</declare-styleable>
<declare-styleable name="TabLayout">
+ <!-- Color of the indicator used to show the currently selected tab. -->
<attr name="tabIndicatorColor" format="color"/>
+ <!-- Height of the indicator used to show the currently selected tab. -->
<attr name="tabIndicatorHeight" format="dimension"/>
+ <!-- Position in the Y axis from the starting edge that tabs should be positioned from. -->
<attr name="tabContentStart" format="dimension"/>
-
+ <!-- Reference to a background to be applied to tabs. -->
<attr name="tabBackground" format="reference"/>
-
+ <!-- The behavior mode for the Tabs in this layout -->
<attr name="tabMode">
<enum name="scrollable" value="0"/>
<enum name="fixed" value="1"/>
</attr>
-
- <!-- Standard gravity constant that a child supplies to its parent.
- Defines how the child view should be positioned, on both the X and Y axes,
- within its enclosing layout. -->
+ <!-- Gravity constant for tabs. -->
<attr name="tabGravity">
<enum name="fill" value="0"/>
<enum name="center" value="1"/>
</attr>
-
+ <!-- The minimum width for tabs. -->
<attr name="tabMinWidth" format="dimension"/>
+ <!-- The maximum width for tabs. -->
<attr name="tabMaxWidth" format="dimension"/>
-
+ <!-- A reference to a TextAppearance style to be applied to tabs. -->
<attr name="tabTextAppearance" format="reference"/>
+ <!-- The default text color to be applied to tabs. -->
<attr name="tabTextColor" format="color"/>
+ <!-- The text color to be applied to the currently selected tab. -->
<attr name="tabSelectedTextColor" format="color"/>
-
+ <!-- The preferred padding along the start edge of tabs. -->
<attr name="tabPaddingStart" format="dimension"/>
+ <!-- The preferred padding along the top edge of tabs. -->
<attr name="tabPaddingTop" format="dimension"/>
+ <!-- The preferred padding along the end edge of tabs. -->
<attr name="tabPaddingEnd" format="dimension"/>
+ <!-- The preferred padding along the bottom edge of tabs. -->
<attr name="tabPaddingBottom" format="dimension"/>
+ <!-- The preferred padding along all edges of tabs. -->
<attr name="tabPadding" format="dimension"/>
</declare-styleable>
+ <declare-styleable name="TabItem">
+ <!-- Text to display in the tab. -->
+ <attr name="android:text" />
+ <!-- Icon to display in the tab. -->
+ <attr name="android:icon" />
+ <!-- A reference to a layout resource to be displayed in the tab. -->
+ <attr name="android:layout" />
+ </declare-styleable>
+
<declare-styleable name="CoordinatorLayout">
<!-- A reference to an array of integers representing the
locations of horizontal keylines in dp from the starting edge.
@@ -108,7 +128,7 @@
<attr name="statusBarBackground" format="reference"/>
</declare-styleable>
- <declare-styleable name="CoordinatorLayout_LayoutParams">
+ <declare-styleable name="CoordinatorLayout_Layout">
<attr name="android:layout_gravity"/>
<!-- The class name of a Behavior class defining special runtime behavior
for this child view. -->
@@ -190,6 +210,7 @@
</declare-styleable>
<declare-styleable name="AppBarLayout">
+ <!-- Deprecated. Elevation is now controlled via a state list animator. -->
<attr name="elevation" />
<attr name="android:background" />
<!-- The initial expanded state for the AppBarLayout. This only takes effect when this
@@ -197,7 +218,16 @@
<attr name="expanded" format="boolean" />
</declare-styleable>
- <declare-styleable name="AppBarLayout_LayoutParams">
+ <declare-styleable name="AppBarLayoutStates">
+ <!-- State value for {@link android.support.design.widget.AppBarLayout} set when the view
+ has been collapsed. -->
+ <attr name="state_collapsed" format="boolean" />
+ <!-- State value for {@link android.support.design.widget.AppBarLayout} set when the view
+ has children which are capable of being collapsed. -->
+ <attr name="state_collapsible" format="boolean" />
+ </declare-styleable>
+
+ <declare-styleable name="AppBarLayout_Layout">
<attr name="layout_scrollFlags">
<!-- The view will be scroll in direct relation to scroll events. This flag needs to be
set for any of the other flags to take effect. If any sibling views
@@ -229,7 +259,7 @@
<attr name="layout_scrollInterpolator" format="reference" />
</declare-styleable>
- <declare-styleable name="ScrollingViewBehavior_Params">
+ <declare-styleable name="ScrollingViewBehavior_Layout">
<!-- The amount that the scrolling view should overlap the bottom of any AppBarLayout -->
<attr name="behavior_overlapTop" format="dimension" />
</declare-styleable>
@@ -264,8 +294,14 @@
Lollipop with the correct setup. -->
<attr name="statusBarScrim" format="color" />
<!-- The id of the primary Toolbar child that you wish to use for the purpose of collapsing.
- If you do not set this then the first Toolbar child found will be used. -->
+ This Toolbar descendant view does not need to be a direct child of the layout.
+ If you do not set this, the first direct Toolbar child found will be used. -->
<attr name="toolbarId" format="reference"/>
+ <!-- Specifies the amount of visible height in pixels used to define when to trigger a
+ scrim visibility change. -->
+ <attr name="scrimVisibleHeightTrigger" format="dimension"/>
+ <!-- Specifies the duration used for scrim visibility animations. -->
+ <attr name="scrimAnimationDuration" format="integer"/>
<!-- Specifies how the title should be positioned when collapsed. -->
<attr name="collapsedTitleGravity">
@@ -321,7 +357,7 @@
<attr name="title"/>
</declare-styleable>
- <declare-styleable name="CollapsingAppBarLayout_LayoutParams">
+ <declare-styleable name="CollapsingToolbarLayout_Layout">
<attr name="layout_collapseMode">
<!-- The view will act as normal with no collapsing behavior. -->
<enum name="none" value="0"/>
@@ -337,9 +373,23 @@
<attr name="layout_collapseParallaxMultiplier" format="float"/>
</declare-styleable>
- <declare-styleable name="BottomSheetBehavior_Params">
+ <declare-styleable name="BottomSheetBehavior_Layout">
<!-- The height of the bottom sheet when it is collapsed. -->
<attr name="behavior_peekHeight" format="dimension"/>
+ <!-- Whether this bottom sheet can be hidden by dragging it further downwards -->
+ <attr name="behavior_hideable" format="boolean"/>
+ <!-- Skip the collapsed state once expanded; no effect unless it is hideable -->
+ <attr name="behavior_skipCollapsed" format="boolean"/>
+ </declare-styleable>
+
+ <declare-styleable name="DesignTheme">
+ <!-- Theme to use for modal bottom sheet dialogs spawned from this theme. -->
+ <attr name="bottomSheetDialogTheme" format="reference" />
+ <!-- Style to use for modal bottom sheets in this theme. -->
+ <attr name="bottomSheetStyle" format="reference" />
+
+ <!-- Text color used to indicate an error has occurred. -->
+ <attr name="textColorError" format="color" />
</declare-styleable>
</resources>
diff --git a/design/res/values/colors.xml b/design/res/values/colors.xml
index a958156..ebf6412 100644
--- a/design/res/values/colors.xml
+++ b/design/res/values/colors.xml
@@ -33,7 +33,8 @@
<!-- Shadow color for the furthest pixels of a shadow -->
<color name="design_fab_shadow_end_color">@android:color/transparent</color>
- <color name="design_textinput_error_color">#FFDD2C00</color>
+ <color name="design_textinput_error_color_light">#FFD50000</color>
+ <color name="design_textinput_error_color_dark">#FFFF6E6E</color>
<color name="design_snackbar_background_color">#323232</color>
diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml
index a0a536d..604ac52 100644
--- a/design/res/values/dimens.xml
+++ b/design/res/values/dimens.xml
@@ -23,12 +23,12 @@
<dimen name="design_fab_size_mini">40dp</dimen>
<dimen name="design_fab_border_width">0.5dp</dimen>
- <dimen name="design_navigation_max_width">320dp</dimen>
+ <!-- 5 * standard increment (56dp) -->
+ <dimen name="design_navigation_max_width">280dp</dimen>
<dimen name="design_navigation_elevation">16dp</dimen>
<dimen name="design_navigation_icon_padding">32dp</dimen>
<dimen name="design_navigation_icon_size">24dp</dimen>
<dimen name="design_navigation_separator_vertical_padding">8dp</dimen>
- <dimen name="design_navigation_padding_top_default">0dp</dimen>
<dimen name="design_navigation_padding_bottom">8dp</dimen>
<dimen name="design_tab_scrollable_min_width">72dp</dimen>
@@ -55,4 +55,7 @@
<dimen name="design_appbar_elevation">4dp</dimen>
+ <dimen name="design_bottom_sheet_modal_elevation">16dp</dimen>
+ <dimen name="design_bottom_sheet_modal_peek_height">256dp</dimen>
+
</resources>
diff --git a/design/res/values/integers.xml b/design/res/values/integers.xml
new file mode 100644
index 0000000..de20303
--- /dev/null
+++ b/design/res/values/integers.xml
@@ -0,0 +1,19 @@
+<?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>
+ <integer name="bottom_sheet_slide_duration">150</integer>
+</resources>
diff --git a/design/res/values/strings.xml b/design/res/values/strings.xml
index 416d289..35ee706 100644
--- a/design/res/values/strings.xml
+++ b/design/res/values/strings.xml
@@ -17,6 +17,8 @@
<resources>
<!-- The class name to the ScrollingChildBehavior required for AppBarLayout -->
<string name="appbar_scrolling_view_behavior" translatable="false">android.support.design.widget.AppBarLayout$ScrollingViewBehavior</string>
+ <!-- The class name to the BottomSheetBehavior -->
+ <string name="bottom_sheet_behavior" translatable="false">android.support.design.widget.BottomSheetBehavior</string>
<!-- The text pattern for the character counter -->
<string name="character_counter_pattern" translatable="false">%1$d / %2$d</string>
</resources>
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index 24a9aa9..b25646c 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -19,7 +19,7 @@
<style name="Widget.Design.FloatingActionButton" parent="android:Widget">
<item name="android:background">@drawable/design_fab_background</item>
<item name="backgroundTint">?attr/colorAccent</item>
- <item name="fabSize">normal</item>
+ <item name="fabSize">auto</item>
<item name="elevation">@dimen/design_fab_elevation</item>
<item name="pressedTranslationZ">@dimen/design_fab_translation_z_pressed</item>
<item name="rippleColor">?attr/colorControlHighlight</item>
@@ -71,13 +71,13 @@
</style>
<style name="TextAppearance.Design.Error" parent="TextAppearance.AppCompat.Caption">
- <item name="android:textColor">@color/design_textinput_error_color</item>
+ <item name="android:textColor">?attr/textColorError</item>
</style>
<style name="TextAppearance.Design.Counter" parent="TextAppearance.AppCompat.Caption"/>
<style name="TextAppearance.Design.Counter.Overflow" parent="TextAppearance.AppCompat.Caption">
- <item name="android:textColor">@color/design_textinput_error_color</item>
+ <item name="android:textColor">?attr/textColorError</item>
</style>
<style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance">
@@ -101,11 +101,13 @@
<item name="statusBarScrim">?attr/colorPrimaryDark</item>
</style>
- <style name="Widget.Design.AppBarLayout" parent="android:Widget">
- <item name="elevation">@dimen/design_appbar_elevation</item>
+ <style name="Base.Widget.Design.AppBarLayout" parent="android:Widget">
<item name="android:background">?attr/colorPrimary</item>
</style>
+ <style name="Widget.Design.AppBarLayout" parent="Base.Widget.Design.AppBarLayout">
+ </style>
+
<style name="Widget.Design.CoordinatorLayout" parent="android:Widget">
<item name="statusBarBackground">?attr/colorPrimaryDark</item>
</style>
@@ -114,5 +116,18 @@
<item name="android:textColor">?android:attr/textColorPrimary</item>
</style>
+ <style name="Animation.Design.BottomSheetDialog" parent="Animation.AppCompat.Dialog">
+ <item name="android:windowEnterAnimation">@anim/design_bottom_sheet_slide_in</item>
+ <item name="android:windowExitAnimation">@anim/design_bottom_sheet_slide_out</item>
+ </style>
+
+ <style name="Widget.Design.BottomSheet.Modal" parent="android:Widget">
+ <item name="android:background">?android:attr/colorBackground</item>
+ <item name="android:elevation">@dimen/design_bottom_sheet_modal_elevation</item>
+ <item name="behavior_peekHeight">@dimen/design_bottom_sheet_modal_peek_height</item>
+ <item name="behavior_hideable">true</item>
+ <item name="behavior_skipCollapsed">false</item>
+ </style>
+
</resources>
diff --git a/design/res/values/themes.xml b/design/res/values/themes.xml
new file mode 100644
index 0000000..5768692
--- /dev/null
+++ b/design/res/values/themes.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.
+-->
+<resources>
+
+ <style name="Theme.Design.BottomSheetDialog" parent="Theme.AppCompat.Dialog">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowAnimationStyle">@style/Animation.Design.BottomSheetDialog</item>
+ <item name="bottomSheetStyle">@style/Widget.Design.BottomSheet.Modal</item>
+ </style>
+
+ <style name="Theme.Design.Light.BottomSheetDialog" parent="Theme.AppCompat.Light.Dialog">
+ <item name="android:windowBackground">@android:color/transparent</item>
+ <item name="android:windowAnimationStyle">@style/Animation.Design.BottomSheetDialog</item>
+ <item name="bottomSheetStyle">@style/Widget.Design.BottomSheet.Modal</item>
+ </style>
+
+ <style name="Theme.Design" parent="Theme.AppCompat">
+ <item name="textColorError">@color/design_textinput_error_color_dark</item>
+ </style>
+
+ <style name="Theme.Design.Light" parent="Theme.AppCompat.Light">
+ <item name="textColorError">@color/design_textinput_error_color_light</item>
+ </style>
+
+ <style name="Theme.Design.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+ <style name="Theme.Design.Light.NoActionBar">
+ <item name="windowActionBar">false</item>
+ <item name="windowNoTitle">true</item>
+ </style>
+
+</resources>
diff --git a/design/src/android/support/design/internal/NavigationMenuItemView.java b/design/src/android/support/design/internal/NavigationMenuItemView.java
index 0423345..1e2c928 100644
--- a/design/src/android/support/design/internal/NavigationMenuItemView.java
+++ b/design/src/android/support/design/internal/NavigationMenuItemView.java
@@ -108,7 +108,8 @@
private StateListDrawable createDefaultBackground() {
TypedValue value = new TypedValue();
- if (getContext().getTheme().resolveAttribute(R.attr.colorControlHighlight, value, true)) {
+ if (getContext().getTheme().resolveAttribute(
+ android.support.v7.appcompat.R.attr.colorControlHighlight, value, true)) {
StateListDrawable drawable = new StateListDrawable();
drawable.addState(CHECKED_STATE_SET, new ColorDrawable(value.data));
drawable.addState(EMPTY_STATE_SET, new ColorDrawable(Color.TRANSPARENT));
diff --git a/design/src/android/support/design/internal/NavigationMenuPresenter.java b/design/src/android/support/design/internal/NavigationMenuPresenter.java
index 959c0e7..609795f 100644
--- a/design/src/android/support/design/internal/NavigationMenuPresenter.java
+++ b/design/src/android/support/design/internal/NavigationMenuPresenter.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
+import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
@@ -85,8 +86,6 @@
mLayoutInflater = LayoutInflater.from(context);
mMenu = menu;
Resources res = context.getResources();
- mPaddingTopDefault = res.getDimensionPixelOffset(
- R.dimen.design_navigation_padding_top_default);
mPaddingSeparator = res.getDimensionPixelOffset(
R.dimen.design_navigation_separator_vertical_padding);
}
@@ -239,12 +238,14 @@
updateMenuView(false);
}
+ @Nullable
public Drawable getItemBackground() {
return mItemBackground;
}
- public void setItemBackground(Drawable itemBackground) {
+ public void setItemBackground(@Nullable Drawable itemBackground) {
mItemBackground = itemBackground;
+ updateMenuView(false);
}
public void setUpdateSuspended(boolean updateSuspended) {
@@ -253,6 +254,15 @@
}
}
+ public void setPaddingTopDefault(int paddingTopDefault) {
+ if (mPaddingTopDefault != paddingTopDefault) {
+ mPaddingTopDefault = paddingTopDefault;
+ if (mHeaderLayout.getChildCount() == 0) {
+ mMenuView.setPadding(0, mPaddingTopDefault, 0, mMenuView.getPaddingBottom());
+ }
+ }
+ }
+
private abstract static class ViewHolder extends RecyclerView.ViewHolder {
public ViewHolder(View itemView) {
@@ -508,7 +518,7 @@
MenuItem item = textItem.getMenuItem();
if (item.getIcon() == null) {
if (mTransparentIcon == null) {
- mTransparentIcon = new ColorDrawable(android.R.color.transparent);
+ mTransparentIcon = new ColorDrawable(Color.TRANSPARENT);
}
item.setIcon(mTransparentIcon);
}
diff --git a/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java b/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java
index 246d5b3..252c6b4 100644
--- a/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java
+++ b/design/src/android/support/design/internal/ScrimInsetsFrameLayout.java
@@ -70,6 +70,7 @@
insets.getSystemWindowInsetTop(),
insets.getSystemWindowInsetRight(),
insets.getSystemWindowInsetBottom());
+ onInsetsChanged(mInsets);
setWillNotDraw(mInsets.isEmpty() || mInsetForeground == null);
ViewCompat.postInvalidateOnAnimation(ScrimInsetsFrameLayout.this);
return insets.consumeSystemWindowInsets();
@@ -127,4 +128,7 @@
}
}
+ protected void onInsetsChanged(Rect insets) {
+ }
+
}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index eb7a962..841018f 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -18,14 +18,17 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
import android.support.design.R;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
@@ -124,15 +127,18 @@
private int mDownPreScrollRange = INVALID_SCROLL_RANGE;
private int mDownScrollRange = INVALID_SCROLL_RANGE;
- boolean mHaveChildWithInterpolator;
-
- private float mTargetElevation;
+ private boolean mHaveChildWithInterpolator;
private int mPendingAction = PENDING_ACTION_NONE;
private WindowInsetsCompat mLastInsets;
- private final List<OnOffsetChangedListener> mListeners;
+ private List<OnOffsetChangedListener> mListeners;
+
+ private boolean mCollapsible;
+ private boolean mCollapsed;
+
+ private final int[] mTmpStatesArray = new int[2];
public AppBarLayout(Context context) {
this(context, null);
@@ -144,29 +150,35 @@
ThemeUtils.checkAppCompatTheme(context);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
+ if (Build.VERSION.SDK_INT >= 21) {
+ // Use the bounds view outline provider so that we cast a shadow, even without a
+ // background
+ ViewUtilsLollipop.setBoundsViewOutlineProvider(this);
+
+ // If we're running on API 21+, we should reset any state list animator from our
+ // default style
+ ViewUtilsLollipop.setStateListAnimatorFromAttrs(this, attrs, 0,
+ R.style.Widget_Design_AppBarLayout);
+ }
+
+ final TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.AppBarLayout,
0, R.style.Widget_Design_AppBarLayout);
- mTargetElevation = a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0);
setBackgroundDrawable(a.getDrawable(R.styleable.AppBarLayout_android_background));
if (a.hasValue(R.styleable.AppBarLayout_expanded)) {
setExpanded(a.getBoolean(R.styleable.AppBarLayout_expanded, false));
}
+ if (Build.VERSION.SDK_INT >= 21 && a.hasValue(R.styleable.AppBarLayout_elevation)) {
+ ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(
+ this, a.getDimensionPixelSize(R.styleable.AppBarLayout_elevation, 0));
+ }
a.recycle();
- // Use the bounds view outline provider so that we cast a shadow, even without a background
- ViewUtils.setBoundsViewOutlineProvider(this);
-
- mListeners = new ArrayList<>();
-
- ViewCompat.setElevation(this, mTargetElevation);
-
ViewCompat.setOnApplyWindowInsetsListener(this,
new android.support.v4.view.OnApplyWindowInsetsListener() {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
- setWindowInsets(insets);
- return insets.consumeSystemWindowInsets();
+ return onWindowInsetChanged(insets);
}
});
}
@@ -179,6 +191,9 @@
* @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
*/
public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<>();
+ }
if (listener != null && !mListeners.contains(listener)) {
mListeners.add(listener);
}
@@ -190,7 +205,7 @@
* @param listener the listener to remove.
*/
public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
- if (listener != null) {
+ if (mListeners != null && listener != null) {
mListeners.remove(listener);
}
}
@@ -217,6 +232,19 @@
break;
}
}
+
+ updateCollapsible();
+ }
+
+ private void updateCollapsible() {
+ boolean haveCollapsibleChild = false;
+ for (int i = 0, z = getChildCount(); i < z; i++) {
+ if (((LayoutParams) getChildAt(i).getLayoutParams()).isCollapsible()) {
+ haveCollapsibleChild = true;
+ break;
+ }
+ }
+ setCollapsibleState(haveCollapsibleChild);
}
private void invalidateScrollRanges() {
@@ -382,7 +410,7 @@
break;
}
}
- return mDownPreScrollRange = Math.max(0, range - getTopInset());
+ return mDownPreScrollRange = Math.max(0, range);
}
/**
@@ -423,8 +451,21 @@
return mDownScrollRange = Math.max(0, range);
}
+ private void dispatchOffsetUpdates(int offset) {
+ // Iterate backwards through the list so that most recently added listeners
+ // get the first chance to decide
+ if (mListeners != null) {
+ for (int i = 0, z = mListeners.size(); i < z; i++) {
+ final OnOffsetChangedListener listener = mListeners.get(i);
+ if (listener != null) {
+ listener.onOffsetChanged(this, offset);
+ }
+ }
+ }
+ }
+
final int getMinimumHeightForVisibleOverlappingContent() {
- final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+ final int topInset = getTopInset();
final int minHeight = ViewCompat.getMinimumHeight(this);
if (minHeight != 0) {
// If this layout has a min height, use it (doubled)
@@ -433,33 +474,66 @@
// Otherwise, we'll use twice the min height of our last child
final int childCount = getChildCount();
- return childCount >= 1
- ? (ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) * 2) + topInset
- : 0;
+ final int lastChildMinHeight = childCount >= 1
+ ? ViewCompat.getMinimumHeight(getChildAt(childCount - 1)) : 0;
+ if (lastChildMinHeight != 0) {
+ return (lastChildMinHeight * 2) + topInset;
+ }
+
+ // If we reach here then we don't have a min height explicitly set. Instead we'll take a
+ // guess at 1/3 of our height being visible
+ return getHeight() / 3;
+ }
+
+ @Override
+ protected int[] onCreateDrawableState(int extraSpace) {
+ final int[] extraStates = mTmpStatesArray;
+ final int[] states = super.onCreateDrawableState(extraSpace + extraStates.length);
+
+ extraStates[0] = mCollapsible ? R.attr.state_collapsible : -R.attr.state_collapsible;
+ extraStates[1] = mCollapsible && mCollapsed
+ ? R.attr.state_collapsed : -R.attr.state_collapsed;
+
+ return mergeDrawableStates(states, extraStates);
+ }
+
+ private void setCollapsibleState(boolean collapsible) {
+ if (mCollapsible != collapsible) {
+ mCollapsible = collapsible;
+ refreshDrawableState();
+ }
+ }
+
+ private void setCollapsedState(boolean collapsed) {
+ if (mCollapsed != collapsed) {
+ mCollapsed = collapsed;
+ refreshDrawableState();
+ }
}
/**
- * Set the elevation value to use when this {@link AppBarLayout} should be elevated
- * above content.
- * <p>
- * This method does not do anything itself. A typical use for this method is called from within
- * an {@link OnOffsetChangedListener} when the offset has changed in such a way to require an
- * elevation change.
+ * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
+ * controlled via a {@link android.animation.StateListAnimator}. If a target
+ * elevation is set, either by this method or the {@code app:elevation} attibute,
+ * a new state list animator is created which uses the given {@code elevation} value.
*
- * @param elevation the elevation value to use.
- *
- * @see ViewCompat#setElevation(View, float)
+ * @attr ref android.support.design.R.styleable#AppBarLayout_elevation
*/
+ @Deprecated
public void setTargetElevation(float elevation) {
- mTargetElevation = elevation;
+ if (Build.VERSION.SDK_INT >= 21) {
+ ViewUtilsLollipop.setDefaultAppBarLayoutStateListAnimator(this, elevation);
+ }
}
/**
- * Returns the elevation value to use when this {@link AppBarLayout} should be elevated
- * above content.
+ * @deprecated target elevation is now deprecated. AppBarLayout's elevation is now
+ * controlled via a {@link android.animation.StateListAnimator}. This method now
+ * always returns 0.
*/
+ @Deprecated
public float getTargetElevation() {
- return mTargetElevation;
+ return 0;
}
private int getPendingAction() {
@@ -470,23 +544,26 @@
mPendingAction = PENDING_ACTION_NONE;
}
- private int getTopInset() {
+ @VisibleForTesting
+ final int getTopInset() {
return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
}
- private void setWindowInsets(WindowInsetsCompat insets) {
- // Invalidate the total scroll range...
- mTotalScrollRange = INVALID_SCROLL_RANGE;
- mLastInsets = insets;
+ private WindowInsetsCompat onWindowInsetChanged(final WindowInsetsCompat insets) {
+ WindowInsetsCompat newInsets = null;
- // Now dispatch them to our children
- for (int i = 0, z = getChildCount(); i < z; i++) {
- final View child = getChildAt(i);
- insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
- if (insets.isConsumed()) {
- break;
- }
+ if (ViewCompat.getFitsSystemWindows(this)) {
+ // If we're set to fit system windows, keep the insets
+ newInsets = insets;
}
+
+ // If our insets have changed, keep them and invalidate the scroll ranges...
+ if (newInsets != mLastInsets) {
+ mLastInsets = newInsets;
+ invalidateScrollRanges();
+ }
+
+ return insets;
}
public static class LayoutParams extends LinearLayout.LayoutParams {
@@ -549,17 +626,19 @@
*/
static final int FLAG_QUICK_RETURN = SCROLL_FLAG_SCROLL | SCROLL_FLAG_ENTER_ALWAYS;
static final int FLAG_SNAP = SCROLL_FLAG_SCROLL | SCROLL_FLAG_SNAP;
+ static final int COLLAPSIBLE_FLAGS = SCROLL_FLAG_EXIT_UNTIL_COLLAPSED
+ | SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED;
int mScrollFlags = SCROLL_FLAG_SCROLL;
Interpolator mScrollInterpolator;
public LayoutParams(Context c, AttributeSet attrs) {
super(c, attrs);
- TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_LayoutParams);
- mScrollFlags = a.getInt(R.styleable.AppBarLayout_LayoutParams_layout_scrollFlags, 0);
- if (a.hasValue(R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator)) {
+ TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.AppBarLayout_Layout);
+ mScrollFlags = a.getInt(R.styleable.AppBarLayout_Layout_layout_scrollFlags, 0);
+ if (a.hasValue(R.styleable.AppBarLayout_Layout_layout_scrollInterpolator)) {
int resId = a.getResourceId(
- R.styleable.AppBarLayout_LayoutParams_layout_scrollInterpolator, 0);
+ R.styleable.AppBarLayout_Layout_layout_scrollInterpolator, 0);
mScrollInterpolator = android.view.animation.AnimationUtils.loadInterpolator(
c, resId);
}
@@ -601,7 +680,7 @@
*
* @see #getScrollFlags()
*
- * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags
+ * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags
*/
public void setScrollFlags(@ScrollFlags int flags) {
mScrollFlags = flags;
@@ -612,7 +691,7 @@
*
* @see #setScrollFlags(int)
*
- * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollFlags
+ * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollFlags
*/
@ScrollFlags
public int getScrollFlags() {
@@ -625,7 +704,7 @@
*
* @param interpolator the interpolator to use, or null to use normal 1-to-1 scrolling.
*
- * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator
+ * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator
* @see #getScrollInterpolator()
*/
public void setScrollInterpolator(Interpolator interpolator) {
@@ -636,12 +715,20 @@
* Returns the {@link Interpolator} being used for scrolling the view associated with this
* {@link LayoutParams}. Null indicates 'normal' 1-to-1 scrolling.
*
- * @attr ref android.support.design.R.styleable#AppBarLayout_LayoutParams_layout_scrollInterpolator
+ * @attr ref android.support.design.R.styleable#AppBarLayout_Layout_layout_scrollInterpolator
* @see #setScrollInterpolator(Interpolator)
*/
public Interpolator getScrollInterpolator() {
return mScrollInterpolator;
}
+
+ /**
+ * Returns true if the scroll flags are compatible for 'collapsing'
+ */
+ private boolean isCollapsible() {
+ return (mScrollFlags & SCROLL_FLAG_SCROLL) == SCROLL_FLAG_SCROLL
+ && (mScrollFlags & COLLAPSIBLE_FLAGS) != 0;
+ }
}
/**
@@ -649,7 +736,7 @@
* scroll handling with offsetting.
*/
public static class Behavior extends HeaderBehavior<AppBarLayout> {
- private static final int ANIMATE_OFFSET_DIPS_PER_SECOND = 300;
+ private static final int MAX_OFFSET_ANIMATION_DURATION = 600; // ms
private static final int INVALID_POSITION = -1;
/**
@@ -673,7 +760,7 @@
private boolean mSkipNestedPreScroll;
private boolean mWasNestedFlung;
- private ValueAnimatorCompat mAnimator;
+ private ValueAnimatorCompat mOffsetAnimator;
private int mOffsetToChildIndexOnLayout = INVALID_POSITION;
private boolean mOffsetToChildIndexOnLayoutIsMinHeight;
@@ -697,9 +784,9 @@
&& child.hasScrollableChildren()
&& parent.getHeight() - directTargetChild.getHeight() <= child.getHeight();
- if (started && mAnimator != null) {
+ if (started && mOffsetAnimator != null) {
// Cancel any offset animation
- mAnimator.cancel();
+ mOffsetAnimator.cancel();
}
// A new nested scroll has started so clear out the previous ref
@@ -778,7 +865,7 @@
if (getTopBottomOffsetForScrollingSibling() < targetScroll) {
// If we're currently not expanded more than the target scroll, we'll
// animate a fling
- animateOffsetTo(coordinatorLayout, child, targetScroll);
+ animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
flung = true;
}
} else {
@@ -787,7 +874,7 @@
if (getTopBottomOffsetForScrollingSibling() > targetScroll) {
// If we're currently not expanded less than the target scroll, we'll
// animate a fling
- animateOffsetTo(coordinatorLayout, child, targetScroll);
+ animateOffsetTo(coordinatorLayout, child, targetScroll, velocityY);
flung = true;
}
}
@@ -807,19 +894,35 @@
}
private void animateOffsetTo(final CoordinatorLayout coordinatorLayout,
- final AppBarLayout child, final int offset) {
+ final AppBarLayout child, final int offset, float velocity) {
+ final int distance = Math.abs(getTopBottomOffsetForScrollingSibling() - offset);
+
+ final int duration;
+ velocity = Math.abs(velocity);
+ if (velocity > 0) {
+ duration = 3 * Math.round(1000 * (distance / velocity));
+ } else {
+ final float distanceRatio = (float) distance / child.getHeight();
+ duration = (int) ((distanceRatio + 1) * 150);
+ }
+
+ animateOffsetWithDuration(coordinatorLayout, child, offset, duration);
+ }
+
+ private void animateOffsetWithDuration(final CoordinatorLayout coordinatorLayout,
+ final AppBarLayout child, final int offset, final int duration) {
final int currentOffset = getTopBottomOffsetForScrollingSibling();
if (currentOffset == offset) {
- if (mAnimator != null && mAnimator.isRunning()) {
- mAnimator.cancel();
+ if (mOffsetAnimator != null && mOffsetAnimator.isRunning()) {
+ mOffsetAnimator.cancel();
}
return;
}
- if (mAnimator == null) {
- mAnimator = ViewUtils.createAnimator();
- mAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
- mAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
+ if (mOffsetAnimator == null) {
+ mOffsetAnimator = ViewUtils.createAnimator();
+ mOffsetAnimator.setInterpolator(AnimationUtils.DECELERATE_INTERPOLATOR);
+ mOffsetAnimator.setUpdateListener(new ValueAnimatorCompat.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimatorCompat animator) {
setHeaderTopBottomOffset(coordinatorLayout, child,
@@ -827,52 +930,91 @@
}
});
} else {
- mAnimator.cancel();
+ mOffsetAnimator.cancel();
}
- // Set the duration based on the amount of dips we're travelling in
- final float distanceDp = Math.abs(currentOffset - offset) /
- coordinatorLayout.getResources().getDisplayMetrics().density;
- mAnimator.setDuration(Math.round(distanceDp * 1000 / ANIMATE_OFFSET_DIPS_PER_SECOND));
-
- mAnimator.setIntValues(currentOffset, offset);
- mAnimator.start();
+ mOffsetAnimator.setDuration(Math.min(duration, MAX_OFFSET_ANIMATION_DURATION));
+ mOffsetAnimator.setIntValues(currentOffset, offset);
+ mOffsetAnimator.start();
}
- private View getChildOnOffset(AppBarLayout abl, final int offset) {
+ private int getChildIndexOnOffset(AppBarLayout abl, final int offset) {
for (int i = 0, count = abl.getChildCount(); i < count; i++) {
View child = abl.getChildAt(i);
if (child.getTop() <= -offset && child.getBottom() >= -offset) {
- return child;
+ return i;
}
}
- return null;
+ return -1;
}
private void snapToChildIfNeeded(CoordinatorLayout coordinatorLayout, AppBarLayout abl) {
final int offset = getTopBottomOffsetForScrollingSibling();
- final View offsetChild = getChildOnOffset(abl, offset);
- if (offsetChild != null) {
+ final int offsetChildIndex = getChildIndexOnOffset(abl, offset);
+ if (offsetChildIndex >= 0) {
+ final View offsetChild = abl.getChildAt(offsetChildIndex);
final LayoutParams lp = (LayoutParams) offsetChild.getLayoutParams();
- if ((lp.getScrollFlags() & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) {
- // We're set the snap, so animate the offset to the nearest edge
- int childTop = -offsetChild.getTop();
- int childBottom = -offsetChild.getBottom();
+ final int flags = lp.getScrollFlags();
- // If the view is set only exit until it is collapsed, we'll abide by that
- if ((lp.getScrollFlags() & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)
- == LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) {
- childBottom += ViewCompat.getMinimumHeight(offsetChild);
+ if ((flags & LayoutParams.FLAG_SNAP) == LayoutParams.FLAG_SNAP) {
+ // We're set the snap, so animate the offset to the nearest edge
+ int snapTop = -offsetChild.getTop();
+ int snapBottom = -offsetChild.getBottom();
+
+ if (offsetChildIndex == abl.getChildCount() - 1) {
+ // If this is the last child, we need to take the top inset into account
+ snapBottom += abl.getTopInset();
}
- final int newOffset = offset < (childBottom + childTop) / 2
- ? childBottom : childTop;
+ if (checkFlag(flags, LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED)) {
+ // If the view is set only exit until it is collapsed, we'll abide by that
+ snapBottom += ViewCompat.getMinimumHeight(offsetChild);
+ } else if (checkFlag(flags, LayoutParams.FLAG_QUICK_RETURN
+ | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS)) {
+ // If it's set to always enter collapsed, it actually has two states. We
+ // select the state and then snap within the state
+ final int seam = snapBottom + ViewCompat.getMinimumHeight(offsetChild);
+ if (offset < seam) {
+ snapTop = seam;
+ } else {
+ snapBottom = seam;
+ }
+ }
+
+ final int newOffset = offset < (snapBottom + snapTop) / 2
+ ? snapBottom
+ : snapTop;
animateOffsetTo(coordinatorLayout, abl,
- MathUtils.constrain(newOffset, -abl.getTotalScrollRange(), 0));
+ MathUtils.constrain(newOffset, -abl.getTotalScrollRange(), 0), 0);
}
}
}
+ private static boolean checkFlag(final int flags, final int check) {
+ return (flags & check) == check;
+ }
+
+ @Override
+ public boolean onMeasureChild(CoordinatorLayout parent, AppBarLayout child,
+ int parentWidthMeasureSpec, int widthUsed, int parentHeightMeasureSpec,
+ int heightUsed) {
+ final CoordinatorLayout.LayoutParams lp =
+ (CoordinatorLayout.LayoutParams) child.getLayoutParams();
+ if (lp.height == CoordinatorLayout.LayoutParams.WRAP_CONTENT) {
+ // If the view is set to wrap on it's height, CoordinatorLayout by default will
+ // cap the view at the CoL's height. Since the AppBarLayout can scroll, this isn't
+ // what we actually want, so we measure it ourselves with an unspecified spec to
+ // allow the child to be larger than it's parent
+ parent.onMeasureChild(child, parentWidthMeasureSpec, widthUsed,
+ MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED), heightUsed);
+ return true;
+ }
+
+ // Let the parent handle it as normal
+ return super.onMeasureChild(parent, child, parentWidthMeasureSpec, widthUsed,
+ parentHeightMeasureSpec, heightUsed);
+ }
+
@Override
public boolean onLayoutChild(CoordinatorLayout parent, AppBarLayout abl,
int layoutDirection) {
@@ -884,13 +1026,13 @@
if ((pendingAction & PENDING_ACTION_COLLAPSED) != 0) {
final int offset = -abl.getUpNestedPreScrollRange();
if (animate) {
- animateOffsetTo(parent, abl, offset);
+ animateOffsetTo(parent, abl, offset, 0);
} else {
setHeaderTopBottomOffset(parent, abl, offset);
}
} else if ((pendingAction & PENDING_ACTION_EXPANDED) != 0) {
if (animate) {
- animateOffsetTo(parent, abl, 0);
+ animateOffsetTo(parent, abl, 0, 0);
} else {
setHeaderTopBottomOffset(parent, abl, 0);
}
@@ -915,8 +1057,8 @@
setTopAndBottomOffset(
MathUtils.constrain(getTopAndBottomOffset(), -abl.getTotalScrollRange(), 0));
- // Make sure we update the elevation
- dispatchOffsetUpdates(abl);
+ // Make sure we dispatch the offset update
+ abl.dispatchOffsetUpdates(getTopAndBottomOffset());
return handled;
}
@@ -962,8 +1104,7 @@
final int curOffset = getTopBottomOffsetForScrollingSibling();
int consumed = 0;
- if (minOffset != 0 && curOffset >= minOffset
- && curOffset <= maxOffset) {
+ if (minOffset != 0 && curOffset >= minOffset && curOffset <= maxOffset) {
// If we have some scrolling range, and we're currently within the min and max
// offsets, calculate a new offset
newOffset = MathUtils.constrain(newOffset, minOffset, maxOffset);
@@ -973,7 +1114,7 @@
? interpolateOffset(appBarLayout, newOffset)
: newOffset;
- boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
+ final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
// Update how much dy we have consumed
consumed = curOffset - newOffset;
@@ -989,26 +1130,20 @@
}
// Dispatch the updates to any listeners
- dispatchOffsetUpdates(appBarLayout);
+ appBarLayout.dispatchOffsetUpdates(getTopAndBottomOffset());
+
+ // Update the AppBarLayout's drawable state (for any elevation changes)
+ updateAppBarLayoutDrawableState(appBarLayout, newOffset,
+ newOffset < curOffset ? -1 : 1);
}
+ } else {
+ // Reset the offset delta
+ mOffsetDelta = 0;
}
return consumed;
}
- private void dispatchOffsetUpdates(AppBarLayout layout) {
- final List<OnOffsetChangedListener> listeners = layout.mListeners;
-
- // Iterate backwards through the list so that most recently added listeners
- // get the first chance to decide
- for (int i = 0, z = listeners.size(); i < z; i++) {
- final OnOffsetChangedListener listener = listeners.get(i);
- if (listener != null) {
- listener.onOffsetChanged(layout, getTopAndBottomOffset());
- }
- }
- }
-
private int interpolateOffset(AppBarLayout layout, final int offset) {
final int absOffset = Math.abs(offset);
@@ -1056,6 +1191,44 @@
return offset;
}
+ private void updateAppBarLayoutDrawableState(final AppBarLayout layout,
+ final int offset, final int direction) {
+ final View child = getAppBarChildOnOffset(layout, offset);
+ if (child != null) {
+ final AppBarLayout.LayoutParams childLp = (LayoutParams) child.getLayoutParams();
+ final int flags = childLp.getScrollFlags();
+ boolean collapsed = false;
+
+ if ((flags & LayoutParams.SCROLL_FLAG_SCROLL) != 0) {
+ final int minHeight = ViewCompat.getMinimumHeight(child);
+
+ if (direction > 0 && (flags & (LayoutParams.SCROLL_FLAG_ENTER_ALWAYS
+ | LayoutParams.SCROLL_FLAG_ENTER_ALWAYS_COLLAPSED)) != 0) {
+ // We're set to enter always collapsed so we are only collapsed when
+ // being scrolled down, and in a collapsed offset
+ collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
+ } else if ((flags & LayoutParams.SCROLL_FLAG_EXIT_UNTIL_COLLAPSED) != 0) {
+ // We're set to exit until collapsed, so any offset which results in
+ // the minimum height (or less) being shown is collapsed
+ collapsed = -offset >= child.getBottom() - minHeight - layout.getTopInset();
+ }
+ }
+
+ layout.setCollapsedState(collapsed);
+ }
+ }
+
+ private static View getAppBarChildOnOffset(final AppBarLayout layout, final int offset) {
+ final int absOffset = Math.abs(offset);
+ for (int i = 0, z = layout.getChildCount(); i < z; i++) {
+ final View child = layout.getChildAt(i);
+ if (absOffset >= child.getTop() && absOffset <= child.getBottom()) {
+ return child;
+ }
+ }
+ return null;
+ }
+
@Override
int getTopBottomOffsetForScrollingSibling() {
return getTopAndBottomOffset() + mOffsetDelta;
@@ -1100,13 +1273,13 @@
}
}
- protected static class SavedState extends BaseSavedState {
+ protected static class SavedState extends AbsSavedState {
int firstVisibleChildIndex;
float firstVisibileChildPercentageShown;
boolean firstVisibileChildAtMinimumHeight;
public SavedState(Parcel source, ClassLoader loader) {
- super(source);
+ super(source, loader);
firstVisibleChildIndex = source.readInt();
firstVisibileChildPercentageShown = source.readFloat();
firstVisibileChildAtMinimumHeight = source.readByte() != 0;
@@ -1144,17 +1317,16 @@
* nested scrolling to automatically scroll any {@link AppBarLayout} siblings.
*/
public static class ScrollingViewBehavior extends HeaderScrollingViewBehavior {
- private int mOverlayTop;
public ScrollingViewBehavior() {}
public ScrollingViewBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
- TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.ScrollingViewBehavior_Params);
- mOverlayTop = a.getDimensionPixelSize(
- R.styleable.ScrollingViewBehavior_Params_behavior_overlapTop, 0);
+ final TypedArray a = context.obtainStyledAttributes(attrs,
+ R.styleable.ScrollingViewBehavior_Layout);
+ setOverlayTop(a.getDimensionPixelSize(
+ R.styleable.ScrollingViewBehavior_Layout_behavior_overlapTop, 0));
a.recycle();
}
@@ -1165,47 +1337,35 @@
}
@Override
- public boolean onLayoutChild(CoordinatorLayout parent, View child, int layoutDirection) {
- // First lay out the child as normal
- super.onLayoutChild(parent, child, layoutDirection);
-
- // Now offset us correctly to be in the correct position. This is important for things
- // like activity transitions which rely on accurate positioning after the first layout.
- final List<View> dependencies = parent.getDependencies(child);
- for (int i = 0, z = dependencies.size(); i < z; i++) {
- if (updateOffset(parent, child, dependencies.get(i))) {
- // If we updated the offset, break out of the loop now
- break;
- }
- }
- return true;
- }
-
- @Override
public boolean onDependentViewChanged(CoordinatorLayout parent, View child,
View dependency) {
- updateOffset(parent, child, dependency);
+ offsetChildAsNeeded(parent, child, dependency);
return false;
}
- private boolean updateOffset(CoordinatorLayout parent, View child, View dependency) {
+ private void offsetChildAsNeeded(CoordinatorLayout parent, View child, View dependency) {
final CoordinatorLayout.Behavior behavior =
((CoordinatorLayout.LayoutParams) dependency.getLayoutParams()).getBehavior();
if (behavior instanceof Behavior) {
- // Offset the child so that it is below the app-bar (with any overlap)
- final int offset = ((Behavior) behavior).getTopBottomOffsetForScrollingSibling();
- setTopAndBottomOffset(dependency.getHeight() + offset
- - getOverlapForOffset(dependency, offset));
- return true;
+ // Offset the child, pinning it to the bottom the header-dependency, maintaining
+ // any vertical gap, and overlap
+ final Behavior ablBehavior = (Behavior) behavior;
+ final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling();
+ ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
+ + ablBehavior.mOffsetDelta
+ + getVerticalLayoutGap()
+ - getOverlapPixelsForOffset(dependency));
}
- return false;
}
- private int getOverlapForOffset(final View dependency, final int offset) {
- if (mOverlayTop != 0 && dependency instanceof AppBarLayout) {
- final AppBarLayout abl = (AppBarLayout) dependency;
+
+ @Override
+ float getOverlapRatioForOffset(final View header) {
+ if (header instanceof AppBarLayout) {
+ final AppBarLayout abl = (AppBarLayout) header;
final int totalScrollRange = abl.getTotalScrollRange();
final int preScrollDown = abl.getDownNestedPreScrollRange();
+ final int offset = getAppBarLayoutOffset(abl);
if (preScrollDown != 0 && (totalScrollRange + offset) <= preScrollDown) {
// If we're in a pre-scroll down. Don't use the offset at all.
@@ -1214,29 +1374,20 @@
final int availScrollRange = totalScrollRange - preScrollDown;
if (availScrollRange != 0) {
// Else we'll use a interpolated ratio of the overlap, depending on offset
- final float percScrolled = offset / (float) availScrollRange;
- return MathUtils.constrain(
- Math.round((1f + percScrolled) * mOverlayTop), 0, mOverlayTop);
+ return 1f + (offset / (float) availScrollRange);
}
}
}
- return mOverlayTop;
+ return 0f;
}
- /**
- * Set the distance that this view should overlap any {@link AppBarLayout}.
- *
- * @param overlayTop the distance in px
- */
- public void setOverlayTop(int overlayTop) {
- mOverlayTop = overlayTop;
- }
-
- /**
- * Returns the distance that this view should overlap any {@link AppBarLayout}.
- */
- public int getOverlayTop() {
- return mOverlayTop;
+ private static int getAppBarLayoutOffset(AppBarLayout abl) {
+ final CoordinatorLayout.Behavior behavior =
+ ((CoordinatorLayout.LayoutParams) abl.getLayoutParams()).getBehavior();
+ if (behavior instanceof Behavior) {
+ return ((Behavior) behavior).getTopBottomOffsetForScrollingSibling();
+ }
+ return 0;
}
@Override
diff --git a/design/src/android/support/design/widget/BottomSheetBehavior.java b/design/src/android/support/design/widget/BottomSheetBehavior.java
index db5e037..b9a7a0f 100644
--- a/design/src/android/support/design/widget/BottomSheetBehavior.java
+++ b/design/src/android/support/design/widget/BottomSheetBehavior.java
@@ -21,13 +21,21 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.design.R;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.NestedScrollingChild;
+import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.widget.ViewDragHelper;
import android.util.AttributeSet;
import android.view.MotionEvent;
+import android.view.VelocityTracker;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewGroup;
import java.lang.annotation.Retention;
@@ -42,6 +50,31 @@
public class BottomSheetBehavior<V extends View> extends CoordinatorLayout.Behavior<V> {
/**
+ * Callback for monitoring events about bottom sheets.
+ */
+ public abstract static class BottomSheetCallback {
+
+ /**
+ * Called when the bottom sheet changes its state.
+ *
+ * @param bottomSheet The bottom sheet view.
+ * @param newState The new state. This will be one of {@link #STATE_DRAGGING},
+ * {@link #STATE_SETTLING}, {@link #STATE_EXPANDED},
+ * {@link #STATE_COLLAPSED}, or {@link #STATE_HIDDEN}.
+ */
+ public abstract void onStateChanged(@NonNull View bottomSheet, @State int newState);
+
+ /**
+ * Called when the bottom sheet is being dragged.
+ *
+ * @param bottomSheet The bottom sheet view.
+ * @param slideOffset The new offset of this bottom sheet within its range, from 0 to 1
+ * when it is moving upward, and from 0 to -1 when it moving downward.
+ */
+ public abstract void onSlide(@NonNull View bottomSheet, float slideOffset);
+ }
+
+ /**
* The bottom sheet is dragging.
*/
public static final int STATE_DRAGGING = 1;
@@ -61,17 +94,32 @@
*/
public static final int STATE_COLLAPSED = 4;
+ /**
+ * The bottom sheet is hidden.
+ */
+ public static final int STATE_HIDDEN = 5;
+
/** @hide */
- @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING})
+ @IntDef({STATE_EXPANDED, STATE_COLLAPSED, STATE_DRAGGING, STATE_SETTLING, STATE_HIDDEN})
@Retention(RetentionPolicy.SOURCE)
public @interface State {}
+ private static final float HIDE_THRESHOLD = 0.5f;
+
+ private static final float HIDE_FRICTION = 0.1f;
+
+ private float mMaximumVelocity;
+
private int mPeekHeight;
private int mMinOffset;
private int mMaxOffset;
+ private boolean mHideable;
+
+ private boolean mSkipCollapsed;
+
@State
private int mState = STATE_COLLAPSED;
@@ -81,10 +129,24 @@
private int mLastNestedScrollDy;
+ private boolean mNestedScrolled;
+
private int mParentHeight;
private WeakReference<V> mViewRef;
+ private WeakReference<View> mNestedScrollingChildRef;
+
+ private BottomSheetCallback mCallback;
+
+ private VelocityTracker mVelocityTracker;
+
+ private int mActivePointerId;
+
+ private int mInitialY;
+
+ private boolean mTouchingScrollingChild;
+
/**
* Default constructor for instantiating BottomSheetBehaviors.
*/
@@ -100,10 +162,15 @@
public BottomSheetBehavior(Context context, AttributeSet attrs) {
super(context, attrs);
TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.BottomSheetBehavior_Params);
+ R.styleable.BottomSheetBehavior_Layout);
setPeekHeight(a.getDimensionPixelSize(
- R.styleable.BottomSheetBehavior_Params_behavior_peekHeight, 0));
+ R.styleable.BottomSheetBehavior_Layout_behavior_peekHeight, 0));
+ setHideable(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_hideable, false));
+ setSkipCollapsed(a.getBoolean(R.styleable.BottomSheetBehavior_Layout_behavior_skipCollapsed,
+ false));
a.recycle();
+ ViewConfiguration configuration = ViewConfiguration.get(context);
+ mMaximumVelocity = configuration.getScaledMaximumFlingVelocity();
}
@Override
@@ -115,40 +182,62 @@
public void onRestoreInstanceState(CoordinatorLayout parent, V child, Parcelable state) {
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(parent, child, ss.getSuperState());
- mState = ss.state;
// Intermediate states are restored as collapsed state
- if (mState == STATE_DRAGGING || mState == STATE_SETTLING) {
+ if (ss.state == STATE_DRAGGING || ss.state == STATE_SETTLING) {
mState = STATE_COLLAPSED;
+ } else {
+ mState = ss.state;
}
}
@Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
+ if (ViewCompat.getFitsSystemWindows(parent) && !ViewCompat.getFitsSystemWindows(child)) {
+ ViewCompat.setFitsSystemWindows(child, true);
+ }
+ int savedTop = child.getTop();
// First let the parent lay it out
parent.onLayoutChild(child, layoutDirection);
// Offset the bottom sheet
mParentHeight = parent.getHeight();
mMinOffset = Math.max(0, mParentHeight - child.getHeight());
- mMaxOffset = mParentHeight - mPeekHeight;
+ mMaxOffset = Math.max(mParentHeight - mPeekHeight, mMinOffset);
if (mState == STATE_EXPANDED) {
ViewCompat.offsetTopAndBottom(child, mMinOffset);
- } else {
+ } else if (mHideable && mState == STATE_HIDDEN) {
+ ViewCompat.offsetTopAndBottom(child, mParentHeight);
+ } else if (mState == STATE_COLLAPSED) {
ViewCompat.offsetTopAndBottom(child, mMaxOffset);
- mState = STATE_COLLAPSED;
+ } else if (mState == STATE_DRAGGING || mState == STATE_SETTLING) {
+ ViewCompat.offsetTopAndBottom(child, savedTop - child.getTop());
}
if (mViewDragHelper == null) {
mViewDragHelper = ViewDragHelper.create(parent, mDragCallback);
}
mViewRef = new WeakReference<>(child);
+ mNestedScrollingChildRef = new WeakReference<>(findScrollingChild(child));
return true;
}
@Override
public boolean onInterceptTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+ if (!child.isShown()) {
+ return false;
+ }
int action = MotionEventCompat.getActionMasked(event);
+ // Record the velocity
+ if (action == MotionEvent.ACTION_DOWN) {
+ reset();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
switch (action) {
case MotionEvent.ACTION_UP:
case MotionEvent.ACTION_CANCEL:
+ mTouchingScrollingChild = false;
+ mActivePointerId = MotionEvent.INVALID_POINTER_ID;
// Reset the ignore flag
if (mIgnoreEvents) {
mIgnoreEvents = false;
@@ -156,60 +245,110 @@
}
break;
case MotionEvent.ACTION_DOWN:
- mIgnoreEvents = !parent.isPointInChildBounds(child,
- (int) event.getX(), (int) event.getY());
+ int initialX = (int) event.getX();
+ mInitialY = (int) event.getY();
+ View scroll = mNestedScrollingChildRef.get();
+ if (scroll != null && parent.isPointInChildBounds(scroll, initialX, mInitialY)) {
+ mActivePointerId = event.getPointerId(event.getActionIndex());
+ mTouchingScrollingChild = true;
+ }
+ mIgnoreEvents = mActivePointerId == MotionEvent.INVALID_POINTER_ID &&
+ !parent.isPointInChildBounds(child, initialX, mInitialY);
break;
}
- return !mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event);
+ if (!mIgnoreEvents && mViewDragHelper.shouldInterceptTouchEvent(event)) {
+ return true;
+ }
+ // We have to handle cases that the ViewDragHelper does not capture the bottom sheet because
+ // it is not the top most view of its parent. This is not necessary when the touch event is
+ // happening over the scrolling content as nested scrolling logic handles that case.
+ View scroll = mNestedScrollingChildRef.get();
+ return action == MotionEvent.ACTION_MOVE && scroll != null &&
+ !mIgnoreEvents && mState != STATE_DRAGGING &&
+ !parent.isPointInChildBounds(scroll, (int) event.getX(), (int) event.getY()) &&
+ Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop();
}
@Override
public boolean onTouchEvent(CoordinatorLayout parent, V child, MotionEvent event) {
+ if (!child.isShown()) {
+ return false;
+ }
+ int action = MotionEventCompat.getActionMasked(event);
+ if (mState == STATE_DRAGGING && action == MotionEvent.ACTION_DOWN) {
+ return true;
+ }
mViewDragHelper.processTouchEvent(event);
- return true;
+ // Record the velocity
+ if (action == MotionEvent.ACTION_DOWN) {
+ reset();
+ }
+ if (mVelocityTracker == null) {
+ mVelocityTracker = VelocityTracker.obtain();
+ }
+ mVelocityTracker.addMovement(event);
+ // The ViewDragHelper tries to capture only the top-most View. We have to explicitly tell it
+ // to capture the bottom sheet in case it is not captured and the touch slop is passed.
+ if (action == MotionEvent.ACTION_MOVE && !mIgnoreEvents) {
+ if (Math.abs(mInitialY - event.getY()) > mViewDragHelper.getTouchSlop()) {
+ mViewDragHelper.captureChildView(child, event.getPointerId(event.getActionIndex()));
+ }
+ }
+ return !mIgnoreEvents;
}
@Override
public boolean onStartNestedScroll(CoordinatorLayout coordinatorLayout, V child,
View directTargetChild, View target, int nestedScrollAxes) {
mLastNestedScrollDy = 0;
+ mNestedScrolled = false;
return (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@Override
public void onNestedPreScroll(CoordinatorLayout coordinatorLayout, V child, View target, int dx,
int dy, int[] consumed) {
+ View scrollingChild = mNestedScrollingChildRef.get();
+ if (target != scrollingChild) {
+ return;
+ }
int currentTop = child.getTop();
int newTop = currentTop - dy;
- if (dy > 0) { // Scrolling up
+ if (dy > 0) { // Upward
if (newTop < mMinOffset) {
consumed[1] = currentTop - mMinOffset;
- child.offsetTopAndBottom(-consumed[1]);
+ ViewCompat.offsetTopAndBottom(child, -consumed[1]);
setStateInternal(STATE_EXPANDED);
} else {
consumed[1] = dy;
- child.offsetTopAndBottom(-dy);
+ ViewCompat.offsetTopAndBottom(child, -dy);
setStateInternal(STATE_DRAGGING);
}
- } else if (dy < 0) { // Scrolling down
+ } else if (dy < 0) { // Downward
if (!ViewCompat.canScrollVertically(target, -1)) {
- if (newTop > mMaxOffset) {
- consumed[1] = currentTop - mMaxOffset;
- child.offsetTopAndBottom(-consumed[1]);
- setStateInternal(STATE_COLLAPSED);
- } else {
+ if (newTop <= mMaxOffset || mHideable) {
consumed[1] = dy;
- child.offsetTopAndBottom(-dy);
+ ViewCompat.offsetTopAndBottom(child, -dy);
setStateInternal(STATE_DRAGGING);
+ } else {
+ consumed[1] = currentTop - mMaxOffset;
+ ViewCompat.offsetTopAndBottom(child, -consumed[1]);
+ setStateInternal(STATE_COLLAPSED);
}
}
}
+ dispatchOnSlide(child.getTop());
mLastNestedScrollDy = dy;
+ mNestedScrolled = true;
}
@Override
public void onStopNestedScroll(CoordinatorLayout coordinatorLayout, V child, View target) {
- if (mLastNestedScrollDy == 0 || child.getTop() == mMinOffset) {
+ if (child.getTop() == mMinOffset) {
+ setStateInternal(STATE_EXPANDED);
+ return;
+ }
+ if (target != mNestedScrollingChildRef.get() || !mNestedScrolled) {
return;
}
int top;
@@ -217,21 +356,45 @@
if (mLastNestedScrollDy > 0) {
top = mMinOffset;
targetState = STATE_EXPANDED;
+ } else if (mHideable && shouldHide(child, getYVelocity())) {
+ top = mParentHeight;
+ targetState = STATE_HIDDEN;
+ } else if (mLastNestedScrollDy == 0) {
+ int currentTop = child.getTop();
+ if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
+ top = mMinOffset;
+ targetState = STATE_EXPANDED;
+ } else {
+ top = mMaxOffset;
+ targetState = STATE_COLLAPSED;
+ }
} else {
top = mMaxOffset;
targetState = STATE_COLLAPSED;
}
- setStateInternal(STATE_SETTLING);
if (mViewDragHelper.smoothSlideViewTo(child, child.getLeft(), top)) {
+ setStateInternal(STATE_SETTLING);
ViewCompat.postOnAnimation(child, new SettleRunnable(child, targetState));
+ } else {
+ setStateInternal(targetState);
}
+ mNestedScrolled = false;
+ }
+
+ @Override
+ public boolean onNestedPreFling(CoordinatorLayout coordinatorLayout, V child, View target,
+ float velocityX, float velocityY) {
+ return target == mNestedScrollingChildRef.get() &&
+ (mState != STATE_EXPANDED ||
+ super.onNestedPreFling(coordinatorLayout, child, target,
+ velocityX, velocityY));
}
/**
* Sets the height of the bottom sheet when it is collapsed.
*
* @param peekHeight The height of the collapsed bottom sheet in pixels.
- * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight
*/
public final void setPeekHeight(int peekHeight) {
mPeekHeight = Math.max(0, peekHeight);
@@ -242,20 +405,80 @@
* Gets the height of the bottom sheet when it is collapsed.
*
* @return The height of the collapsed bottom sheet.
- * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Params_behavior_peekHeight
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_peekHeight
*/
public final int getPeekHeight() {
return mPeekHeight;
}
/**
+ * Sets whether this bottom sheet can hide when it is swiped down.
+ *
+ * @param hideable {@code true} to make this bottom sheet hideable.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_hideable
+ */
+ public void setHideable(boolean hideable) {
+ mHideable = hideable;
+ }
+
+ /**
+ * Gets whether this bottom sheet can hide when it is swiped down.
+ *
+ * @return {@code true} if this bottom sheet can hide.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_hideable
+ */
+ public boolean isHideable() {
+ return mHideable;
+ }
+
+ /**
+ * Sets whether this bottom sheet should skip the collapsed state when it is being hidden
+ * after it is expanded once. Setting this to true has no effect unless the sheet is hideable.
+ *
+ * @param skipCollapsed True if the bottom sheet should skip the collapsed state.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed
+ */
+ public void setSkipCollapsed(boolean skipCollapsed) {
+ mSkipCollapsed = skipCollapsed;
+ }
+
+ /**
+ * Sets whether this bottom sheet should skip the collapsed state when it is being hidden
+ * after it is expanded once.
+ *
+ * @return Whether the bottom sheet should skip the collapsed state.
+ * @attr ref android.support.design.R.styleable#BottomSheetBehavior_Layout_behavior_skipCollapsed
+ */
+ public boolean getSkipCollapsed() {
+ return mSkipCollapsed;
+ }
+
+ /**
+ * Sets a callback to be notified of bottom sheet events.
+ *
+ * @param callback The callback to notify when bottom sheet events occur.
+ */
+ public void setBottomSheetCallback(BottomSheetCallback callback) {
+ mCallback = callback;
+ }
+
+ /**
* Sets the state of the bottom sheet. The bottom sheet will transition to that state with
* animation.
*
- * @param state Either {@link #STATE_COLLAPSED} or {@link #STATE_EXPANDED}.
+ * @param state One of {@link #STATE_COLLAPSED}, {@link #STATE_EXPANDED}, or
+ * {@link #STATE_HIDDEN}.
*/
public final void setState(@State int state) {
+ if (state == mState) {
+ return;
+ }
if (mViewRef == null) {
+ // The view is not laid out yet; modify mState and let onLayoutChild handle it later
+ if (state == STATE_COLLAPSED || state == STATE_EXPANDED ||
+ (mHideable && state == STATE_HIDDEN)) {
+ mState = state;
+ }
return;
}
V child = mViewRef.get();
@@ -267,6 +490,8 @@
top = mMaxOffset;
} else if (state == STATE_EXPANDED) {
top = mMinOffset;
+ } else if (mHideable && state == STATE_HIDDEN) {
+ top = mParentHeight;
} else {
throw new IllegalArgumentException("Illegal state argument: " + state);
}
@@ -292,14 +517,76 @@
return;
}
mState = state;
- // TODO: Invoke listeners.
+ View bottomSheet = mViewRef.get();
+ if (bottomSheet != null && mCallback != null) {
+ mCallback.onStateChanged(bottomSheet, state);
+ }
+ }
+
+ private void reset() {
+ mActivePointerId = ViewDragHelper.INVALID_POINTER;
+ if (mVelocityTracker != null) {
+ mVelocityTracker.recycle();
+ mVelocityTracker = null;
+ }
+ }
+
+ private boolean shouldHide(View child, float yvel) {
+ if (mSkipCollapsed) {
+ return true;
+ }
+ if (child.getTop() < mMaxOffset) {
+ // It should not hide, but collapse.
+ return false;
+ }
+ final float newTop = child.getTop() + yvel * HIDE_FRICTION;
+ return Math.abs(newTop - mMaxOffset) / (float) mPeekHeight > HIDE_THRESHOLD;
+ }
+
+ private View findScrollingChild(View view) {
+ if (view instanceof NestedScrollingChild) {
+ return view;
+ }
+ if (view instanceof ViewGroup) {
+ ViewGroup group = (ViewGroup) view;
+ for (int i = 0, count = group.getChildCount(); i < count; i++) {
+ View scrollingChild = findScrollingChild(group.getChildAt(i));
+ if (scrollingChild != null) {
+ return scrollingChild;
+ }
+ }
+ }
+ return null;
+ }
+
+ private float getYVelocity() {
+ mVelocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ return VelocityTrackerCompat.getYVelocity(mVelocityTracker, mActivePointerId);
}
private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
@Override
public boolean tryCaptureView(View child, int pointerId) {
- return true;
+ if (mState == STATE_DRAGGING) {
+ return false;
+ }
+ if (mTouchingScrollingChild) {
+ return false;
+ }
+ if (mState == STATE_EXPANDED && mActivePointerId == pointerId) {
+ View scroll = mNestedScrollingChildRef.get();
+ if (scroll != null && ViewCompat.canScrollVertically(scroll, -1)) {
+ // Let the content scroll up
+ return false;
+ }
+ }
+ return mViewRef != null && mViewRef.get() == child;
+ }
+
+ @Override
+ public void onViewPositionChanged(View changedView, int left, int top, int dx, int dy) {
+ dispatchOnSlide(top);
}
@Override
@@ -313,31 +600,66 @@
public void onViewReleased(View releasedChild, float xvel, float yvel) {
int top;
@State int targetState;
- if (yvel < 0) {
+ if (yvel < 0) { // Moving up
top = mMinOffset;
targetState = STATE_EXPANDED;
+ } else if (mHideable && shouldHide(releasedChild, yvel)) {
+ top = mParentHeight;
+ targetState = STATE_HIDDEN;
+ } else if (yvel == 0.f) {
+ int currentTop = releasedChild.getTop();
+ if (Math.abs(currentTop - mMinOffset) < Math.abs(currentTop - mMaxOffset)) {
+ top = mMinOffset;
+ targetState = STATE_EXPANDED;
+ } else {
+ top = mMaxOffset;
+ targetState = STATE_COLLAPSED;
+ }
} else {
top = mMaxOffset;
targetState = STATE_COLLAPSED;
}
- setStateInternal(STATE_SETTLING);
if (mViewDragHelper.settleCapturedViewAt(releasedChild.getLeft(), top)) {
+ setStateInternal(STATE_SETTLING);
ViewCompat.postOnAnimation(releasedChild,
new SettleRunnable(releasedChild, targetState));
+ } else {
+ setStateInternal(targetState);
}
}
@Override
public int clampViewPositionVertical(View child, int top, int dy) {
- return MathUtils.constrain(top, mMinOffset, mMaxOffset);
+ return MathUtils.constrain(top, mMinOffset, mHideable ? mParentHeight : mMaxOffset);
}
@Override
public int clampViewPositionHorizontal(View child, int left, int dx) {
return child.getLeft();
}
+
+ @Override
+ public int getViewVerticalDragRange(View child) {
+ if (mHideable) {
+ return mParentHeight - mMinOffset;
+ } else {
+ return mMaxOffset - mMinOffset;
+ }
+ }
};
+ private void dispatchOnSlide(int top) {
+ View bottomSheet = mViewRef.get();
+ if (bottomSheet != null && mCallback != null) {
+ if (top > mMaxOffset) {
+ mCallback.onSlide(bottomSheet, (float) (mMaxOffset - top) / mPeekHeight);
+ } else {
+ mCallback.onSlide(bottomSheet,
+ (float) (mMaxOffset - top) / ((mMaxOffset - mMinOffset)));
+ }
+ }
+ }
+
private class SettleRunnable implements Runnable {
private final View mView;
@@ -360,13 +682,16 @@
}
}
- protected static class SavedState extends View.BaseSavedState {
-
+ protected static class SavedState extends AbsSavedState {
@State
final int state;
public SavedState(Parcel source) {
- super(source);
+ this(source, null);
+ }
+
+ public SavedState(Parcel source, ClassLoader loader) {
+ super(source, loader);
//noinspection ResourceType
state = source.readInt();
}
@@ -382,21 +707,21 @@
out.writeInt(state);
}
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
@Override
- public SavedState createFromParcel(Parcel source) {
- return new SavedState(source);
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
}
@Override
public SavedState[] newArray(int size) {
return new SavedState[size];
}
- };
+ });
}
- /*
+ /**
* A utility function to get the {@link BottomSheetBehavior} associated with the {@code view}.
*
* @param view The {@link View} with {@link BottomSheetBehavior}.
diff --git a/design/src/android/support/design/widget/BottomSheetDialog.java b/design/src/android/support/design/widget/BottomSheetDialog.java
new file mode 100644
index 0000000..55b5cbe
--- /dev/null
+++ b/design/src/android/support/design/widget/BottomSheetDialog.java
@@ -0,0 +1,148 @@
+/*
+ * 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.content.Context;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
+import android.support.design.R;
+import android.support.v7.app.AppCompatDialog;
+import android.util.TypedValue;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.FrameLayout;
+
+/**
+ * Base class for {@link android.app.Dialog}s styled as a bottom sheet.
+ */
+public class BottomSheetDialog extends AppCompatDialog {
+
+ public BottomSheetDialog(@NonNull Context context) {
+ this(context, 0);
+ }
+
+ public BottomSheetDialog(@NonNull Context context, @StyleRes int theme) {
+ super(context, getThemeResId(context, theme));
+ // We hide the title bar for any style configuration. Otherwise, there will be a gap
+ // above the bottom sheet when it is expanded.
+ supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
+ }
+
+ protected BottomSheetDialog(@NonNull Context context, boolean cancelable,
+ OnCancelListener cancelListener) {
+ super(context, cancelable, cancelListener);
+ supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
+ }
+
+ @Override
+ public void setContentView(@LayoutRes int layoutResId) {
+ super.setContentView(wrapInBottomSheet(layoutResId, null, null));
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().setLayout(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT);
+ }
+
+ @Override
+ public void setContentView(View view) {
+ super.setContentView(wrapInBottomSheet(0, view, null));
+ }
+
+ @Override
+ public void setContentView(View view, ViewGroup.LayoutParams params) {
+ super.setContentView(wrapInBottomSheet(0, view, params));
+ }
+
+ private View wrapInBottomSheet(int layoutResId, View view, ViewGroup.LayoutParams params) {
+ final CoordinatorLayout coordinator = (CoordinatorLayout) View.inflate(getContext(),
+ R.layout.design_bottom_sheet_dialog, null);
+ if (layoutResId != 0 && view == null) {
+ view = getLayoutInflater().inflate(layoutResId, coordinator, false);
+ }
+ FrameLayout bottomSheet = (FrameLayout) coordinator.findViewById(R.id.design_bottom_sheet);
+ BottomSheetBehavior.from(bottomSheet).setBottomSheetCallback(mBottomSheetCallback);
+ if (params == null) {
+ bottomSheet.addView(view);
+ } else {
+ bottomSheet.addView(view, params);
+ }
+ // We treat the CoordinatorLayout as outside the dialog though it is technically inside
+ if (shouldWindowCloseOnTouchOutside()) {
+ coordinator.findViewById(R.id.touch_outside).setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (isShowing()) {
+ cancel();
+ }
+ }
+ });
+ }
+ return coordinator;
+ }
+
+ private boolean shouldWindowCloseOnTouchOutside() {
+ if (Build.VERSION.SDK_INT < 11) {
+ return true;
+ }
+ TypedValue value = new TypedValue();
+ //noinspection SimplifiableIfStatement
+ if (getContext().getTheme()
+ .resolveAttribute(android.R.attr.windowCloseOnTouchOutside, value, true)) {
+ return value.data != 0;
+ }
+ return false;
+ }
+
+ private static int getThemeResId(Context context, int themeId) {
+ if (themeId == 0) {
+ // If the provided theme is 0, then retrieve the dialogTheme from our theme
+ TypedValue outValue = new TypedValue();
+ if (context.getTheme().resolveAttribute(
+ R.attr.bottomSheetDialogTheme, outValue, true)) {
+ themeId = outValue.resourceId;
+ } else {
+ // bottomSheetDialogTheme is not provided; we default to our light theme
+ themeId = R.style.Theme_Design_Light_BottomSheetDialog;
+ }
+ }
+ return themeId;
+ }
+
+ private BottomSheetBehavior.BottomSheetCallback mBottomSheetCallback
+ = new BottomSheetBehavior.BottomSheetCallback() {
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet,
+ @BottomSheetBehavior.State int newState) {
+ if (newState == BottomSheetBehavior.STATE_HIDDEN) {
+ dismiss();
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ }
+ };
+
+}
diff --git a/design/src/android/support/design/widget/BottomSheetDialogFragment.java b/design/src/android/support/design/widget/BottomSheetDialogFragment.java
new file mode 100644
index 0000000..8842988
--- /dev/null
+++ b/design/src/android/support/design/widget/BottomSheetDialogFragment.java
@@ -0,0 +1,35 @@
+/*
+ * 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.Dialog;
+import android.os.Bundle;
+import android.support.v4.app.DialogFragment;
+import android.support.v7.app.AppCompatDialogFragment;
+
+/**
+ * Modal bottom sheet. This is a version of {@link DialogFragment} that shows a bottom sheet
+ * using {@link BottomSheetDialog} instead of a floating dialog.
+ */
+public class BottomSheetDialogFragment extends AppCompatDialogFragment {
+
+ @Override
+ public Dialog onCreateDialog(Bundle savedInstanceState) {
+ return new BottomSheetDialog(getContext(), getTheme());
+ }
+
+}
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
index 9a38c7d..4a95a13 100644
--- a/design/src/android/support/design/widget/CollapsingTextHelper.java
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -105,8 +105,7 @@
public CollapsingTextHelper(View view) {
mView = view;
- mTextPaint = new TextPaint();
- mTextPaint.setAntiAlias(true);
+ mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG | Paint.SUBPIXEL_TEXT_FLAG);
mCollapsedBounds = new Rect();
mExpandedBounds = new Rect();
@@ -195,19 +194,26 @@
}
void setCollapsedTextAppearance(int resId) {
- TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
- if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+ TypedArray a = mView.getContext().obtainStyledAttributes(resId,
+ android.support.v7.appcompat.R.styleable.TextAppearance);
+ if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
mCollapsedTextColor = a.getColor(
- R.styleable.TextAppearance_android_textColor, mCollapsedTextColor);
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+ mCollapsedTextColor);
}
- if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+ if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
mCollapsedTextSize = a.getDimensionPixelSize(
- R.styleable.TextAppearance_android_textSize, (int) mCollapsedTextSize);
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+ (int) mCollapsedTextSize);
}
- mCollapsedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
- mCollapsedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
- mCollapsedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
- mCollapsedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
+ mCollapsedShadowColor = a.getInt(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
+ mCollapsedShadowDx = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
+ mCollapsedShadowDy = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
+ mCollapsedShadowRadius = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
if (Build.VERSION.SDK_INT >= 16) {
@@ -218,19 +224,26 @@
}
void setExpandedTextAppearance(int resId) {
- TypedArray a = mView.getContext().obtainStyledAttributes(resId, R.styleable.TextAppearance);
- if (a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+ TypedArray a = mView.getContext().obtainStyledAttributes(resId,
+ android.support.v7.appcompat.R.styleable.TextAppearance);
+ if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor)) {
mExpandedTextColor = a.getColor(
- R.styleable.TextAppearance_android_textColor, mExpandedTextColor);
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor,
+ mExpandedTextColor);
}
- if (a.hasValue(R.styleable.TextAppearance_android_textSize)) {
+ if (a.hasValue(android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize)) {
mExpandedTextSize = a.getDimensionPixelSize(
- R.styleable.TextAppearance_android_textSize, (int) mExpandedTextSize);
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize,
+ (int) mExpandedTextSize);
}
- mExpandedShadowColor = a.getInt(R.styleable.TextAppearance_android_shadowColor, 0);
- mExpandedShadowDx = a.getFloat(R.styleable.TextAppearance_android_shadowDx, 0);
- mExpandedShadowDy = a.getFloat(R.styleable.TextAppearance_android_shadowDy, 0);
- mExpandedShadowRadius = a.getFloat(R.styleable.TextAppearance_android_shadowRadius, 0);
+ mExpandedShadowColor = a.getInt(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowColor, 0);
+ mExpandedShadowDx = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDx, 0);
+ mExpandedShadowDy = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowDy, 0);
+ mExpandedShadowRadius = a.getFloat(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_shadowRadius, 0);
a.recycle();
if (Build.VERSION.SDK_INT >= 16) {
@@ -363,7 +376,7 @@
mCollapsedDrawY = mCollapsedBounds.centerY() + textOffset;
break;
}
- switch (collapsedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ switch (collapsedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
mCollapsedDrawX = mCollapsedBounds.centerX() - (width / 2);
break;
@@ -395,7 +408,7 @@
mExpandedDrawY = mExpandedBounds.centerY() + textOffset;
break;
}
- switch (expandedAbsGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ switch (expandedAbsGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK) {
case Gravity.CENTER_HORIZONTAL:
mExpandedDrawX = mExpandedBounds.centerX() - (width / 2);
break;
@@ -436,10 +449,6 @@
final float ascent;
final float descent;
-
- // Update the TextPaint to the current text size
- mTextPaint.setTextSize(mCurrentTextSize);
-
if (drawTexture) {
ascent = mTextureAscent * mScale;
descent = mTextureDescent * mScale;
@@ -536,6 +545,8 @@
if (mTextToDraw == null || updateDrawText) {
mTextPaint.setTextSize(mCurrentTextSize);
mTextPaint.setTypeface(mCurrentTypeface);
+ // Use linear text scaling if we're scaling the canvas
+ mTextPaint.setLinearText(mScale != 1f);
// If we don't currently have text to draw, or the text size has changed, ellipsize...
final CharSequence title = TextUtils.ellipsize(mText, mTextPaint,
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 62fef07..2181a1c 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -23,15 +23,16 @@
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
@@ -52,31 +53,36 @@
* It is designed to be used as a direct child of a {@link AppBarLayout}.
* CollapsingToolbarLayout contains the following features:
*
- * <h3>Collapsing title</h3>
+ * <h4>Collapsing title</h4>
* A title which is larger when the layout is fully visible but collapses and becomes smaller as
* the layout is scrolled off screen. You can set the title to display via
* {@link #setTitle(CharSequence)}. The title appearance can be tweaked via the
* {@code collapsedTextAppearance} and {@code expandedTextAppearance} attributes.
*
- * <h3>Content scrim</h3>
+ * <h4>Content scrim</h4>
* A full-bleed scrim which is show or hidden when the scroll position has hit a certain threshold.
* You can change this via {@link #setContentScrim(Drawable)}.
*
- * <h3>Status bar scrim</h3>
+ * <h4>Status bar scrim</h4>
* A scrim which is show or hidden behind the status bar when the scroll position has hit a certain
* threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works
- * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows.
+ * on {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system
+ * windows.
*
- * <h3>Parallax scrolling children</h3>
+ * <h4>Parallax scrolling children</h4>
* Child views can opt to be scrolled within this layout in a parallax fashion.
* See {@link LayoutParams#COLLAPSE_MODE_PARALLAX} and
* {@link LayoutParams#setParallaxMultiplier(float)}.
*
- * <h3>Pinned position children</h3>
+ * <h4>Pinned position children</h4>
* Child views can opt to be pinned in space globally. This is useful when implementing a
* collapsing as it allows the {@link Toolbar} to be fixed in place even though this layout is
* moving. See {@link LayoutParams#COLLAPSE_MODE_PIN}.
*
+ * <p><strong>Do not manually add views to the Toolbar at run time</strong>.
+ * We will add a 'dummy view' to the Toolbar which allows us to work out the available space
+ * for the title. This can interfere with any views which you add.</p>
+ *
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_collapsedTitleTextAppearance
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_expandedTitleTextAppearance
* @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_contentScrim
@@ -89,11 +95,12 @@
*/
public class CollapsingToolbarLayout extends FrameLayout {
- private static final int SCRIM_ANIMATION_DURATION = 600;
+ private static final int DEFAULT_SCRIM_ANIMATION_DURATION = 600;
private boolean mRefreshToolbar = true;
private int mToolbarId;
private Toolbar mToolbar;
+ private View mToolbarDirectChild;
private View mDummyView;
private int mExpandedMarginStart;
@@ -111,6 +118,8 @@
private int mScrimAlpha;
private boolean mScrimsAreShown;
private ValueAnimatorCompat mScrimAnimator;
+ private long mScrimAnimationDuration;
+ private int mScrimVisibleHeightTrigger = -1;
private AppBarLayout.OnOffsetChangedListener mOnOffsetChangedListener;
@@ -173,7 +182,7 @@
mCollapsingTextHelper.setExpandedTextAppearance(
R.style.TextAppearance_Design_CollapsingToolbar_Expanded);
mCollapsingTextHelper.setCollapsedTextAppearance(
- R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
+ android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Widget_ActionBar_Title);
// Now overlay any custom text appearances
if (a.hasValue(R.styleable.CollapsingToolbarLayout_expandedTitleTextAppearance)) {
@@ -187,6 +196,13 @@
R.styleable.CollapsingToolbarLayout_collapsedTitleTextAppearance, 0));
}
+ mScrimVisibleHeightTrigger = a.getInt(
+ R.styleable.CollapsingToolbarLayout_scrimVisibleHeightTrigger, -1);
+
+ mScrimAnimationDuration = a.getInt(
+ R.styleable.CollapsingToolbarLayout_scrimAnimationDuration,
+ DEFAULT_SCRIM_ANIMATION_DURATION);
+
setContentScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_contentScrim));
setStatusBarScrim(a.getDrawable(R.styleable.CollapsingToolbarLayout_statusBarScrim));
@@ -201,9 +217,7 @@
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
- mLastInsets = insets;
- requestLayout();
- return insets.consumeSystemWindowInsets();
+ return setWindowInsets(insets);
}
});
}
@@ -220,6 +234,9 @@
}
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
}
+
+ // We're attached, so lets request an inset dispatch
+ ViewCompat.requestApplyInsets(this);
}
@Override
@@ -233,6 +250,14 @@
super.onDetachedFromWindow();
}
+ private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
+ if (mLastInsets != insets) {
+ mLastInsets = insets;
+ requestLayout();
+ }
+ return insets.consumeSystemWindowInsets();
+ }
+
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -245,7 +270,7 @@
mContentScrim.draw(canvas);
}
- // Let the collapsing text helper draw it's text
+ // Let the collapsing text helper draw its text
if (mCollapsingTitleEnabled && mDrawCollapsingTitle) {
mCollapsingTextHelper.draw(canvas);
}
@@ -290,40 +315,50 @@
return;
}
- Toolbar fallback = null, selected = null;
+ // First clear out the current Toolbar
+ mToolbar = null;
+ mToolbarDirectChild = null;
- for (int i = 0, count = getChildCount(); i < count; i++) {
- final View child = getChildAt(i);
- if (child instanceof Toolbar) {
- if (mToolbarId != -1) {
- // There's a toolbar id set so try and find it...
- if (mToolbarId == child.getId()) {
- // We found the primary Toolbar, use it
- selected = (Toolbar) child;
- break;
- }
- if (fallback == null) {
- // We'll record the first Toolbar as our fallback
- fallback = (Toolbar) child;
- }
- } else {
- // We don't have a id to check for so just use the first we come across
- selected = (Toolbar) child;
- break;
- }
+ if (mToolbarId != -1) {
+ // If we have an ID set, try and find it and it's direct parent to us
+ mToolbar = (Toolbar) findViewById(mToolbarId);
+ if (mToolbar != null) {
+ mToolbarDirectChild = findDirectChild(mToolbar);
}
}
- if (selected == null) {
- // If we didn't find a primary Toolbar, use the fallback
- selected = fallback;
+ if (mToolbar == null) {
+ // If we don't have an ID, or couldn't find a Toolbar with the correct ID, try and find
+ // one from our direct children
+ Toolbar toolbar = null;
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ final View child = getChildAt(i);
+ if (child instanceof Toolbar) {
+ toolbar = (Toolbar) child;
+ break;
+ }
+ }
+ mToolbar = toolbar;
}
- mToolbar = selected;
updateDummyView();
mRefreshToolbar = false;
}
+ /**
+ * Returns the direct child of this layout, which itself is the ancestor of the
+ * given view.
+ */
+ private View findDirectChild(final View descendant) {
+ View directChild = descendant;
+ for (ViewParent p = descendant.getParent(); p != this && p != null; p = p.getParent()) {
+ if (p instanceof View) {
+ directChild = (View) p;
+ }
+ }
+ return directChild;
+ }
+
private void updateDummyView() {
if (!mCollapsingTitleEnabled && mDummyView != null) {
// If we have a dummy view and we have our title disabled, remove it from its parent
@@ -357,15 +392,30 @@
if (mCollapsingTitleEnabled && mDummyView != null) {
// We only draw the title if the dummy view is being displayed (Toolbar removes
// views if there is no space)
- mDrawCollapsingTitle = mDummyView.isShown();
+ mDrawCollapsingTitle = ViewCompat.isAttachedToWindow(mDummyView)
+ && mDummyView.getVisibility() == VISIBLE;
if (mDrawCollapsingTitle) {
- ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
- mCollapsingTextHelper.setCollapsedBounds(mTmpRect.left, bottom - mTmpRect.height(),
- mTmpRect.right, bottom);
-
final boolean isRtl = ViewCompat.getLayoutDirection(this)
== ViewCompat.LAYOUT_DIRECTION_RTL;
+
+ // Update the collapsed bounds
+ int bottomOffset = 0;
+ if (mToolbarDirectChild != null && mToolbarDirectChild != this) {
+ final LayoutParams lp = (LayoutParams) mToolbarDirectChild.getLayoutParams();
+ bottomOffset = lp.bottomMargin;
+ }
+ ViewGroupUtils.getDescendantRect(this, mDummyView, mTmpRect);
+ mCollapsingTextHelper.setCollapsedBounds(
+ mTmpRect.left + (isRtl
+ ? mToolbar.getTitleMarginEnd()
+ : mToolbar.getTitleMarginStart()),
+ bottom + mToolbar.getTitleMarginTop() - mTmpRect.height() - bottomOffset,
+ mTmpRect.right + (isRtl
+ ? mToolbar.getTitleMarginStart()
+ : mToolbar.getTitleMarginEnd()),
+ bottom - bottomOffset - mToolbar.getTitleMarginBottom());
+
// Update the expanded bounds
mCollapsingTextHelper.setExpandedBounds(
isRtl ? mExpandedMarginEnd : mExpandedMarginStart,
@@ -386,7 +436,7 @@
if (child.getTop() < insetTop) {
// If the child isn't set to fit system windows but is drawing within the inset
// offset it down
- child.offsetTopAndBottom(insetTop);
+ ViewCompat.offsetTopAndBottom(child, insetTop);
}
}
@@ -399,10 +449,23 @@
// If we do not currently have a title, try and grab it from the Toolbar
mCollapsingTextHelper.setText(mToolbar.getTitle());
}
- setMinimumHeight(mToolbar.getHeight());
+ if (mToolbarDirectChild == null || mToolbarDirectChild == this) {
+ setMinimumHeight(getHeightWithMargins(mToolbar));
+ } else {
+ setMinimumHeight(getHeightWithMargins(mToolbarDirectChild));
+ }
}
}
+ private static int getHeightWithMargins(@NonNull final View view) {
+ final ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof MarginLayoutParams) {
+ final MarginLayoutParams mlp = (MarginLayoutParams) lp;
+ return view.getHeight() + mlp.topMargin + mlp.bottomMargin;
+ }
+ return view.getHeight();
+ }
+
private static ViewOffsetHelper getViewOffsetHelper(View view) {
ViewOffsetHelper offsetHelper = (ViewOffsetHelper) view.getTag(R.id.view_offset_helper);
if (offsetHelper == null) {
@@ -503,7 +566,7 @@
ensureToolbar();
if (mScrimAnimator == null) {
mScrimAnimator = ViewUtils.createAnimator();
- mScrimAnimator.setDuration(SCRIM_ANIMATION_DURATION);
+ mScrimAnimator.setDuration(mScrimAnimationDuration);
mScrimAnimator.setInterpolator(
targetAlpha > mScrimAlpha
? AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR
@@ -547,13 +610,11 @@
if (mContentScrim != null) {
mContentScrim.setCallback(null);
}
- if (drawable != null) {
- mContentScrim = drawable.mutate();
- drawable.setBounds(0, 0, getWidth(), getHeight());
- drawable.setCallback(this);
- drawable.setAlpha(mScrimAlpha);
- } else {
- mContentScrim = null;
+ mContentScrim = drawable != null ? drawable.mutate() : null;
+ if (mContentScrim != null) {
+ mContentScrim.setBounds(0, 0, getWidth(), getHeight());
+ mContentScrim.setCallback(this);
+ mContentScrim.setAlpha(mScrimAlpha);
}
ViewCompat.postInvalidateOnAnimation(this);
}
@@ -590,6 +651,7 @@
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #setContentScrim(Drawable)
*/
+ @Nullable
public Drawable getContentScrim() {
return mContentScrim;
}
@@ -610,14 +672,60 @@
if (mStatusBarScrim != null) {
mStatusBarScrim.setCallback(null);
}
-
- mStatusBarScrim = drawable;
- drawable.setCallback(this);
- drawable.mutate().setAlpha(mScrimAlpha);
+ mStatusBarScrim = drawable != null ? drawable.mutate() : null;
+ if (mStatusBarScrim != null) {
+ if (mStatusBarScrim.isStateful()) {
+ mStatusBarScrim.setState(getDrawableState());
+ }
+ DrawableCompat.setLayoutDirection(mStatusBarScrim,
+ ViewCompat.getLayoutDirection(this));
+ mStatusBarScrim.setVisible(getVisibility() == VISIBLE, false);
+ mStatusBarScrim.setCallback(this);
+ mStatusBarScrim.setAlpha(mScrimAlpha);
+ }
ViewCompat.postInvalidateOnAnimation(this);
}
}
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final int[] state = getDrawableState();
+ boolean changed = false;
+
+ Drawable d = mStatusBarScrim;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+ d = mContentScrim;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+
+ if (changed) {
+ invalidate();
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mContentScrim || who == mStatusBarScrim;
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ final boolean visible = visibility == VISIBLE;
+ if (mStatusBarScrim != null && mStatusBarScrim.isVisible() != visible) {
+ mStatusBarScrim.setVisible(visible, false);
+ }
+ if (mContentScrim != null && mContentScrim.isVisible() != visible) {
+ mContentScrim.setVisible(visible, false);
+ }
+ }
+
/**
* Set the color to use for the status bar scrim.
*
@@ -650,6 +758,7 @@
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #setStatusBarScrim(Drawable)
*/
+ @Nullable
public Drawable getStatusBarScrim() {
return mStatusBarScrim;
}
@@ -874,10 +983,66 @@
}
/**
- * The additional offset used to define when to trigger the scrim visibility change.
+ * Set the amount of visible height in pixels used to define when to trigger a scrim
+ * visibility change.
+ *
+ * <p>If the visible height of this view is less than the given value, the scrims will be
+ * made visible, otherwise they are hidden.</p>
+ *
+ * @param height value in pixels used to define when to trigger a scrim visibility change
+ *
+ * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimVisibleHeightTrigger
*/
- final int getScrimTriggerOffset() {
- return 2 * ViewCompat.getMinimumHeight(this);
+ public void setScrimVisibleHeightTrigger(@IntRange(from = 0) final int height) {
+ if (mScrimVisibleHeightTrigger != height) {
+ mScrimVisibleHeightTrigger = height;
+ // Update the scrim visibilty
+ updateScrimVisibility();
+ }
+ }
+
+ /**
+ * Returns the amount of visible height in pixels used to define when to trigger a scrim
+ * visibility change.
+ *
+ * @see #setScrimTriggerOffset(int)
+ */
+ public int getScrimVisibleHeightTrigger() {
+ if (mScrimVisibleHeightTrigger >= 0) {
+ // If we have one explictly set, return it
+ return mScrimVisibleHeightTrigger;
+ }
+
+ // Otherwise we'll use the default computed value
+ final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+
+ final int minHeight = ViewCompat.getMinimumHeight(this);
+ if (minHeight > 0) {
+ // If we have a minHeight set, lets use 2 * minHeight (capped at our height)
+ return Math.min((minHeight * 2) + insetTop, getHeight());
+ }
+
+ // If we reach here then we don't have a min height set. Instead we'll take a
+ // guess at 1/3 of our height being visible
+ return getHeight() / 3;
+ }
+
+ /**
+ * Set the duration used for scrim visibility animations.
+ *
+ * @param duration the duration to use in milliseconds
+ *
+ * @attr ref android.support.design.R.styleable#CollapsingToolbarLayout_scrimAnimationDuration
+ */
+ public void setScrimAnimationDuration(@IntRange(from = 0) final long duration) {
+ mScrimAnimationDuration = duration;
+ }
+
+ /**
+ * Returns the duration in milliseconds used for scrim visibility animations.
+ */
+ public long getScrimAnimationDuration() {
+ return mScrimAnimationDuration;
}
@Override
@@ -937,12 +1102,12 @@
super(c, attrs);
TypedArray a = c.obtainStyledAttributes(attrs,
- R.styleable.CollapsingAppBarLayout_LayoutParams);
+ R.styleable.CollapsingToolbarLayout_Layout);
mCollapseMode = a.getInt(
- R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseMode,
+ R.styleable.CollapsingToolbarLayout_Layout_layout_collapseMode,
COLLAPSE_MODE_OFF);
setParallaxMultiplier(a.getFloat(
- R.styleable.CollapsingAppBarLayout_LayoutParams_layout_collapseParallaxMultiplier,
+ R.styleable.CollapsingToolbarLayout_Layout_layout_collapseParallaxMultiplier,
DEFAULT_PARALLAX_MULTIPLIER));
a.recycle();
}
@@ -1012,13 +1177,21 @@
}
}
+ /**
+ * Show or hide the scrims if needed
+ */
+ final void updateScrimVisibility() {
+ if (mContentScrim != null || mStatusBarScrim != null) {
+ setScrimsShown(getHeight() + mCurrentOffset < getScrimVisibleHeightTrigger());
+ }
+ }
+
private class OffsetUpdateListener implements AppBarLayout.OnOffsetChangedListener {
@Override
public void onOffsetChanged(AppBarLayout layout, int verticalOffset) {
mCurrentOffset = verticalOffset;
final int insetTop = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
- final int scrollRange = layout.getTotalScrollRange();
for (int i = 0, z = getChildCount(); i < z; i++) {
final View child = getChildAt(i);
@@ -1039,9 +1212,7 @@
}
// Show or hide the scrims if needed
- if (mContentScrim != null || mStatusBarScrim != null) {
- setScrimsShown(getHeight() + verticalOffset < getScrimTriggerOffset() + insetTop);
- }
+ updateScrimVisibility();
if (mStatusBarScrim != null && insetTop > 0) {
ViewCompat.postInvalidateOnAnimation(CollapsingToolbarLayout.this);
@@ -1052,15 +1223,6 @@
CollapsingToolbarLayout.this) - insetTop;
mCollapsingTextHelper.setExpansionFraction(
Math.abs(verticalOffset) / (float) expandRange);
-
- if (Math.abs(verticalOffset) == scrollRange) {
- // If we have some pinned children, and we're offset to only show those views,
- // we want to be elevate
- ViewCompat.setElevation(layout, layout.getTargetElevation());
- } else {
- // Otherwise, we're inline with the content
- ViewCompat.setElevation(layout, 0f);
- }
}
}
}
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index 3d16ac9..b86a977 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -29,10 +29,15 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingParent;
@@ -144,6 +149,8 @@
private final int[] mTempIntPair = new int[2];
private Paint mScrimPaint;
+ private boolean mDisallowInterceptReset;
+
private boolean mIsAttachedToWindow;
private int[] mKeylines;
@@ -242,9 +249,23 @@
*
* @param bg Background drawable to draw behind the status bar
*/
- public void setStatusBarBackground(Drawable bg) {
- mStatusBarBackground = bg;
- invalidate();
+ public void setStatusBarBackground(@Nullable final Drawable bg) {
+ if (mStatusBarBackground != bg) {
+ if (mStatusBarBackground != null) {
+ mStatusBarBackground.setCallback(null);
+ }
+ mStatusBarBackground = bg != null ? bg.mutate() : null;
+ if (mStatusBarBackground != null) {
+ if (mStatusBarBackground.isStateful()) {
+ mStatusBarBackground.setState(getDrawableState());
+ }
+ DrawableCompat.setLayoutDirection(mStatusBarBackground,
+ ViewCompat.getLayoutDirection(this));
+ mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false);
+ mStatusBarBackground.setCallback(this);
+ }
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
}
/**
@@ -252,17 +273,50 @@
*
* @return The status bar background drawable, or null if none set
*/
+ @Nullable
public Drawable getStatusBarBackground() {
return mStatusBarBackground;
}
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final int[] state = getDrawableState();
+ boolean changed = false;
+
+ Drawable d = mStatusBarBackground;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+
+ if (changed) {
+ invalidate();
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mStatusBarBackground;
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ final boolean visible = visibility == VISIBLE;
+ if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) {
+ mStatusBarBackground.setVisible(visible, false);
+ }
+ }
+
/**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
* @param resId Resource id of a background drawable to draw behind the status bar
*/
- public void setStatusBarBackgroundResource(int resId) {
+ public void setStatusBarBackgroundResource(@DrawableRes int resId) {
setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
}
@@ -273,18 +327,25 @@
* @param color Color to use as a background drawable to draw behind the status bar
* in 0xAARRGGBB format.
*/
- public void setStatusBarBackgroundColor(int color) {
+ public void setStatusBarBackgroundColor(@ColorInt int color) {
setStatusBarBackground(new ColorDrawable(color));
}
- private void setWindowInsets(WindowInsetsCompat insets) {
+ private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
if (mLastInsets != insets) {
mLastInsets = insets;
mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
- dispatchChildApplyWindowInsets(insets);
+
+ // Now dispatch to the Behaviors
+ insets = dispatchApplyWindowInsetsToBehaviors(insets);
requestLayout();
}
+ return insets;
+ }
+
+ final WindowInsetsCompat getLastWindowInsets() {
+ return mLastInsets;
}
/**
@@ -312,6 +373,7 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.resetTouchBehaviorTracking();
}
+ mDisallowInterceptReset = false;
}
/**
@@ -476,8 +538,9 @@
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
- if (disallowIntercept) {
+ if (disallowIntercept && !mDisallowInterceptReset) {
resetTouchBehaviors();
+ mDisallowInterceptReset = true;
}
}
@@ -694,9 +757,9 @@
setMeasuredDimension(width, height);
}
- private void dispatchChildApplyWindowInsets(WindowInsetsCompat insets) {
+ private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
if (insets.isConsumed()) {
- return;
+ return insets;
}
for (int i = 0, z = getChildCount(); i < z; i++) {
@@ -713,14 +776,10 @@
break;
}
}
-
- // Now let the view try and consume them
- insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
- if (insets.isConsumed()) {
- break;
- }
}
}
+
+ return insets;
}
/**
@@ -1698,7 +1757,7 @@
* {@link Color#BLACK}.
* @see #getScrimOpacity(CoordinatorLayout, android.view.View)
*/
- public final int getScrimColor(CoordinatorLayout parent, V child) {
+ public int getScrimColor(CoordinatorLayout parent, V child) {
return Color.BLACK;
}
@@ -1715,7 +1774,7 @@
* @param child the child view above the scrim
* @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
*/
- public final float getScrimOpacity(CoordinatorLayout parent, V child) {
+ public float getScrimOpacity(CoordinatorLayout parent, V child) {
return 0.f;
}
@@ -1855,7 +1914,7 @@
* in place of the default child layout behavior. The Behavior's implementation can
* delegate to the standard CoordinatorLayout measurement behavior by calling
* {@link CoordinatorLayout#onLayoutChild(android.view.View, int)
- * parent.onMeasureChild}.</p>
+ * parent.onLayoutChild}.</p>
*
* <p>If a Behavior implements
* {@link #onDependentViewChanged(CoordinatorLayout, android.view.View, android.view.View)}
@@ -2218,25 +2277,25 @@
super(context, attrs);
final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.CoordinatorLayout_LayoutParams);
+ R.styleable.CoordinatorLayout_Layout);
this.gravity = a.getInteger(
- R.styleable.CoordinatorLayout_LayoutParams_android_layout_gravity,
+ R.styleable.CoordinatorLayout_Layout_android_layout_gravity,
Gravity.NO_GRAVITY);
- mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_LayoutParams_layout_anchor,
+ mAnchorId = a.getResourceId(R.styleable.CoordinatorLayout_Layout_layout_anchor,
View.NO_ID);
this.anchorGravity = a.getInteger(
- R.styleable.CoordinatorLayout_LayoutParams_layout_anchorGravity,
+ R.styleable.CoordinatorLayout_Layout_layout_anchorGravity,
Gravity.NO_GRAVITY);
- this.keyline = a.getInteger(R.styleable.CoordinatorLayout_LayoutParams_layout_keyline,
+ this.keyline = a.getInteger(R.styleable.CoordinatorLayout_Layout_layout_keyline,
-1);
mBehaviorResolved = a.hasValue(
- R.styleable.CoordinatorLayout_LayoutParams_layout_behavior);
+ R.styleable.CoordinatorLayout_Layout_layout_behavior);
if (mBehaviorResolved) {
mBehavior = parseBehavior(context, attrs, a.getString(
- R.styleable.CoordinatorLayout_LayoutParams_layout_behavior));
+ R.styleable.CoordinatorLayout_Layout_layout_behavior));
}
a.recycle();
@@ -2528,15 +2587,15 @@
}
}
- final class ApplyInsetsListener implements android.support.v4.view.OnApplyWindowInsetsListener {
+ private class ApplyInsetsListener
+ implements android.support.v4.view.OnApplyWindowInsetsListener {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
- setWindowInsets(insets);
- return insets.consumeSystemWindowInsets();
+ return setWindowInsets(insets);
}
}
- final class HierarchyChangeListener implements OnHierarchyChangeListener {
+ private class HierarchyChangeListener implements OnHierarchyChangeListener {
@Override
public void onChildViewAdded(View parent, View child) {
if (mOnHierarchyChangeListener != null) {
@@ -2556,6 +2615,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
@@ -2599,11 +2663,11 @@
return ss;
}
- protected static class SavedState extends BaseSavedState {
+ protected static class SavedState extends AbsSavedState {
SparseArray<Parcelable> behaviorStates;
public SavedState(Parcel source, ClassLoader loader) {
- super(source);
+ super(source, loader);
final int size = source.readInt();
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index c39b852..d3b8c19 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -19,23 +19,31 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.R;
import android.support.design.widget.FloatingActionButtonImpl.InternalVisibilityChangedListener;
+import android.support.v4.content.res.ConfigurationHelper;
import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.AppCompatDrawableManager;
+import android.support.v7.widget.AppCompatImageHelper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.widget.ImageView;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -52,8 +60,6 @@
* <p>The background color of this view defaults to the your theme's {@code colorAccent}. If you
* wish to change this at runtime then you can do so via
* {@link #setBackgroundTintList(ColorStateList)}.</p>
- *
- * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
*/
@CoordinatorLayout.DefaultBehavior(FloatingActionButton.Behavior.class)
public class FloatingActionButton extends VisibilityAwareImageButton {
@@ -82,8 +88,39 @@
}
// These values must match those in the attrs declaration
- private static final int SIZE_MINI = 1;
- private static final int SIZE_NORMAL = 0;
+
+ /**
+ * The mini sized button. Will always been smaller than {@link #SIZE_NORMAL}.
+ *
+ * @see #setSize(int)
+ */
+ public static final int SIZE_MINI = 1;
+
+ /**
+ * The normal sized button. Will always been larger than {@link #SIZE_MINI}.
+ *
+ * @see #setSize(int)
+ */
+ public static final int SIZE_NORMAL = 0;
+
+ /**
+ * Size which will change based on the window size. For small sized windows
+ * (largest screen dimension < 470dp) this will select a small sized button, and for
+ * larger sized windows it will select a larger size.
+ *
+ * @see #setSize(int)
+ */
+ public static final int SIZE_AUTO = -1;
+
+ /**
+ * The switch point for the largest screen edge where SIZE_AUTO switches from mini to normal.
+ */
+ private static final int AUTO_MINI_LARGEST_SCREEN_WIDTH = 470;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({SIZE_MINI, SIZE_NORMAL, SIZE_AUTO})
+ public @interface Size {}
private ColorStateList mBackgroundTint;
private PorterDuff.Mode mBackgroundTintMode;
@@ -92,12 +129,15 @@
private int mRippleColor;
private int mSize;
private int mImagePadding;
- private Rect mTouchArea;
+ private int mMaxImageSize;
private boolean mCompatPadding;
- private final Rect mShadowPadding;
+ private final Rect mShadowPadding = new Rect();
+ private final Rect mTouchArea = new Rect();
- private final FloatingActionButtonImpl mImpl;
+ private AppCompatImageHelper mImageHelper;
+
+ private FloatingActionButtonImpl mImpl;
public FloatingActionButton(Context context) {
this(context, null);
@@ -112,9 +152,6 @@
ThemeUtils.checkAppCompatTheme(context);
- mShadowPadding = new Rect();
- mTouchArea = new Rect();
-
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.FloatingActionButton, defStyleAttr,
R.style.Widget_Design_FloatingActionButton);
@@ -122,7 +159,7 @@
mBackgroundTintMode = parseTintMode(a.getInt(
R.styleable.FloatingActionButton_backgroundTintMode, -1), null);
mRippleColor = a.getColor(R.styleable.FloatingActionButton_rippleColor, 0);
- mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_NORMAL);
+ mSize = a.getInt(R.styleable.FloatingActionButton_fabSize, SIZE_AUTO);
mBorderWidth = a.getDimensionPixelSize(R.styleable.FloatingActionButton_borderWidth, 0);
final float elevation = a.getDimension(R.styleable.FloatingActionButton_elevation, 0f);
final float pressedTranslationZ = a.getDimension(
@@ -130,53 +167,24 @@
mCompatPadding = a.getBoolean(R.styleable.FloatingActionButton_useCompatPadding, false);
a.recycle();
- final ShadowViewDelegate delegate = new ShadowViewDelegate() {
- @Override
- public float getRadius() {
- return getSizeDimension() / 2f;
- }
+ mImageHelper = new AppCompatImageHelper(this, AppCompatDrawableManager.get());
+ mImageHelper.loadFromAttributes(attrs, defStyleAttr);
- @Override
- public void setShadowPadding(int left, int top, int right, int bottom) {
- mShadowPadding.set(left, top, right, bottom);
- setPadding(left + mImagePadding, top + mImagePadding,
- right + mImagePadding, bottom + mImagePadding);
- }
+ mMaxImageSize = (int) getResources().getDimension(R.dimen.design_fab_image_size);
- @Override
- public void setBackgroundDrawable(Drawable background) {
- FloatingActionButton.super.setBackgroundDrawable(background);
- }
-
- @Override
- public boolean isCompatPaddingEnabled() {
- return mCompatPadding;
- }
- };
-
- final int sdk = Build.VERSION.SDK_INT;
- if (sdk >= 21) {
- mImpl = new FloatingActionButtonLollipop(this, delegate);
- } else if (sdk >= 14) {
- mImpl = new FloatingActionButtonIcs(this, delegate);
- } else {
- mImpl = new FloatingActionButtonEclairMr1(this, delegate);
- }
-
- final int maxImageSize = (int) getResources().getDimension(R.dimen.design_fab_image_size);
- mImagePadding = (getSizeDimension() - maxImageSize) / 2;
-
- mImpl.setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode,
+ getImpl().setBackgroundDrawable(mBackgroundTint, mBackgroundTintMode,
mRippleColor, mBorderWidth);
- mImpl.setElevation(elevation);
- mImpl.setPressedTranslationZ(pressedTranslationZ);
- mImpl.updatePadding();
+ getImpl().setElevation(elevation);
+ getImpl().setPressedTranslationZ(pressedTranslationZ);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
final int preferredSize = getSizeDimension();
+ mImagePadding = (preferredSize - mMaxImageSize) / 2;
+ getImpl().updatePadding();
+
final int w = resolveAdjustedSize(preferredSize, widthMeasureSpec);
final int h = resolveAdjustedSize(preferredSize, heightMeasureSpec);
@@ -202,7 +210,7 @@
public void setRippleColor(@ColorInt int color) {
if (mRippleColor != color) {
mRippleColor = color;
- mImpl.setRippleColor(color);
+ getImpl().setRippleColor(color);
}
}
@@ -227,7 +235,7 @@
public void setBackgroundTintList(@Nullable ColorStateList tint) {
if (mBackgroundTint != tint) {
mBackgroundTint = tint;
- mImpl.setBackgroundTintList(tint);
+ getImpl().setBackgroundTintList(tint);
}
}
@@ -256,7 +264,7 @@
public void setBackgroundTintMode(@Nullable PorterDuff.Mode tintMode) {
if (mBackgroundTintMode != tintMode) {
mBackgroundTintMode = tintMode;
- mImpl.setBackgroundTintMode(tintMode);
+ getImpl().setBackgroundTintMode(tintMode);
}
}
@@ -275,6 +283,12 @@
Log.i(LOG_TAG, "Setting a custom background is not supported.");
}
+ @Override
+ public void setImageResource(@DrawableRes int resId) {
+ // Intercept this call and instead retrieve the Drawable via the image helper
+ mImageHelper.setImageResource(resId);
+ }
+
/**
* Shows the button.
* <p>This method will animate the button show if the view has already been laid out.</p>
@@ -294,7 +308,7 @@
}
private void show(OnVisibilityChangedListener listener, boolean fromUser) {
- mImpl.show(wrapOnVisibilityChangedListener(listener), fromUser);
+ getImpl().show(wrapOnVisibilityChangedListener(listener), fromUser);
}
/**
@@ -316,7 +330,7 @@
}
private void hide(@Nullable OnVisibilityChangedListener listener, boolean fromUser) {
- mImpl.hide(wrapOnVisibilityChangedListener(listener), fromUser);
+ getImpl().hide(wrapOnVisibilityChangedListener(listener), fromUser);
}
/**
@@ -332,7 +346,7 @@
public void setUseCompatPadding(boolean useCompatPadding) {
if (mCompatPadding != useCompatPadding) {
mCompatPadding = useCompatPadding;
- mImpl.onCompatShadowChanged();
+ getImpl().onCompatShadowChanged();
}
}
@@ -349,6 +363,35 @@
return mCompatPadding;
}
+ /**
+ * Sets the size of the button.
+ *
+ * <p>The options relate to the options available on the material design specification.
+ * {@link #SIZE_NORMAL} is larger than {@link #SIZE_MINI}. {@link #SIZE_AUTO} will choose
+ * an appropriate size based on the screen size.</p>
+ *
+ * @param size one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO}
+ *
+ * @attr ref android.support.design.R.styleable#FloatingActionButton_fabSize
+ */
+ public void setSize(@Size int size) {
+ if (size != mSize) {
+ mSize = size;
+ requestLayout();
+ }
+ }
+
+ /**
+ * Returns the chosen size for this button.
+ *
+ * @return one of {@link #SIZE_NORMAL}, {@link #SIZE_MINI} or {@link #SIZE_AUTO}
+ * @see #setSize(int)
+ */
+ @Size
+ public int getSize() {
+ return mSize;
+ }
+
@Nullable
private InternalVisibilityChangedListener wrapOnVisibilityChangedListener(
@Nullable final OnVisibilityChangedListener listener) {
@@ -369,39 +412,51 @@
};
}
- final int getSizeDimension() {
- switch (mSize) {
+ private int getSizeDimension() {
+ return getSizeDimension(mSize);
+ }
+
+ private int getSizeDimension(@Size final int size) {
+ final Resources res = getResources();
+ switch (size) {
+ case SIZE_AUTO:
+ // If we're set to auto, grab the size from resources and refresh
+ final int width = ConfigurationHelper.getScreenWidthDp(res);
+ final int height = ConfigurationHelper.getScreenHeightDp(res);
+ return Math.max(width, height) < AUTO_MINI_LARGEST_SCREEN_WIDTH
+ ? getSizeDimension(SIZE_MINI)
+ : getSizeDimension(SIZE_NORMAL);
case SIZE_MINI:
- return getResources().getDimensionPixelSize(R.dimen.design_fab_size_mini);
+ return res.getDimensionPixelSize(R.dimen.design_fab_size_mini);
case SIZE_NORMAL:
default:
- return getResources().getDimensionPixelSize(R.dimen.design_fab_size_normal);
+ return res.getDimensionPixelSize(R.dimen.design_fab_size_normal);
}
}
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
- mImpl.onAttachedToWindow();
+ getImpl().onAttachedToWindow();
}
@Override
protected void onDetachedFromWindow() {
super.onDetachedFromWindow();
- mImpl.onDetachedFromWindow();
+ getImpl().onDetachedFromWindow();
}
@Override
protected void drawableStateChanged() {
super.drawableStateChanged();
- mImpl.onDrawableStateChanged(getDrawableState());
+ getImpl().onDrawableStateChanged(getDrawableState());
}
@TargetApi(Build.VERSION_CODES.HONEYCOMB)
@Override
public void jumpDrawablesToCurrentState() {
super.jumpDrawablesToCurrentState();
- mImpl.jumpDrawableToCurrentState();
+ getImpl().jumpDrawableToCurrentState();
}
/**
@@ -428,7 +483,7 @@
*/
@NonNull
public Drawable getContentBackground() {
- return mImpl.getContentBackground();
+ return getImpl().getContentBackground();
}
private static int resolveAdjustedSize(int desiredSize, int measureSpec) {
@@ -482,7 +537,7 @@
}
/**
- * Behavior designed for use with {@link FloatingActionButton} instances. It's main function
+ * Behavior designed for use with {@link FloatingActionButton} instances. Its main function
* is to move {@link FloatingActionButton} views so that any displayed {@link Snackbar}s do
* not cover them.
*/
@@ -495,6 +550,14 @@
private float mFabTranslationY;
private Rect mTmpRect;
+ public Behavior() {
+ super();
+ }
+
+ public Behavior(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
@Override
public boolean layoutDependsOn(CoordinatorLayout parent,
FloatingActionButton child, View dependency) {
@@ -506,7 +569,7 @@
public boolean onDependentViewChanged(CoordinatorLayout parent, FloatingActionButton child,
View dependency) {
if (dependency instanceof Snackbar.SnackbarLayout) {
- updateFabTranslationForSnackbar(parent, child, dependency);
+ updateFabTranslationForSnackbar(parent, child, true);
} else if (dependency instanceof AppBarLayout) {
// If we're depending on an AppBarLayout we will show/hide it automatically
// if the FAB is anchored to the AppBarLayout
@@ -515,6 +578,14 @@
return false;
}
+ @Override
+ public void onDependentViewRemoved(CoordinatorLayout parent, FloatingActionButton child,
+ View dependency) {
+ if (dependency instanceof Snackbar.SnackbarLayout) {
+ updateFabTranslationForSnackbar(parent, child, true);
+ }
+ }
+
private boolean updateFabVisibility(CoordinatorLayout parent,
AppBarLayout appBarLayout, FloatingActionButton child) {
final CoordinatorLayout.LayoutParams lp =
@@ -526,7 +597,7 @@
}
if (child.getUserSetVisibility() != VISIBLE) {
- // The view isn't set to be visible so skip changing it's visibility
+ // The view isn't set to be visible so skip changing its visibility
return false;
}
@@ -549,11 +620,7 @@
}
private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
- final FloatingActionButton fab, View snackbar) {
- if (fab.getVisibility() != View.VISIBLE) {
- return;
- }
-
+ final FloatingActionButton fab, boolean animationAllowed) {
final float targetTransY = getFabTranslationYForSnackbar(parent, fab);
if (mFabTranslationY == targetTransY) {
// We're already at (or currently animating to) the target value, return...
@@ -567,8 +634,9 @@
mFabTranslationYAnimator.cancel();
}
- if (Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
- // If the FAB will be travelling by more than 2/3 of it's height, let's animate
+ if (animationAllowed && fab.isShown()
+ && Math.abs(currentTransY - targetTransY) > (fab.getHeight() * 0.667f)) {
+ // If the FAB will be travelling by more than 2/3 of its height, let's animate
// it instead
if (mFabTranslationYAnimator == null) {
mFabTranslationYAnimator = ViewUtils.createAnimator();
@@ -611,7 +679,7 @@
@Override
public boolean onLayoutChild(CoordinatorLayout parent, FloatingActionButton child,
int layoutDirection) {
- // First, lets make sure that the visibility of the FAB is consistent
+ // First, let's make sure that the visibility of the FAB is consistent
final List<View> dependencies = parent.getDependencies(child);
for (int i = 0, count = dependencies.size(); i < count; i++) {
final View dependency = dependencies.get(i);
@@ -624,6 +692,8 @@
parent.onLayoutChild(child, layoutDirection);
// Now offset it if needed
offsetIfNeeded(parent, child);
+ // Make sure we translate the FAB for any displayed Snackbars (without an animation)
+ updateFabTranslationForSnackbar(parent, child, false);
return true;
}
@@ -667,10 +737,10 @@
*
* @return the backward compatible elevation in pixels.
* @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
- * @see #setFloatingActionButtonElevation(float)
+ * @see #setCompatElevation(float)
*/
- public float getFloatingActionButtonElevation() {
- return mImpl.getElevation();
+ public float getCompatElevation() {
+ return getImpl().getElevation();
}
/**
@@ -678,10 +748,52 @@
*
* @param elevation The backward compatible elevation in pixels.
* @attr ref android.support.design.R.styleable#FloatingActionButton_elevation
- * @see #getFloatingActionButtonElevation()
+ * @see #getCompatElevation()
* @see #setUseCompatPadding(boolean)
*/
- public void setFloatingActionButtonElevation(float elevation) {
- mImpl.setElevation(elevation);
+ public void setCompatElevation(float elevation) {
+ getImpl().setElevation(elevation);
+ }
+
+ private FloatingActionButtonImpl getImpl() {
+ if (mImpl == null) {
+ mImpl = createImpl();
+ }
+ return mImpl;
+ }
+
+ private FloatingActionButtonImpl createImpl() {
+ final int sdk = Build.VERSION.SDK_INT;
+ if (sdk >= 21) {
+ return new FloatingActionButtonLollipop(this, new ShadowDelegateImpl());
+ } else if (sdk >= 14) {
+ return new FloatingActionButtonIcs(this, new ShadowDelegateImpl());
+ } else {
+ return new FloatingActionButtonEclairMr1(this, new ShadowDelegateImpl());
+ }
+ }
+
+ private class ShadowDelegateImpl implements ShadowViewDelegate {
+ @Override
+ public float getRadius() {
+ return getSizeDimension() / 2f;
+ }
+
+ @Override
+ public void setShadowPadding(int left, int top, int right, int bottom) {
+ mShadowPadding.set(left, top, right, bottom);
+ setPadding(left + mImagePadding, top + mImagePadding,
+ right + mImagePadding, bottom + mImagePadding);
+ }
+
+ @Override
+ public void setBackgroundDrawable(Drawable background) {
+ FloatingActionButton.super.setBackgroundDrawable(background);
+ }
+
+ @Override
+ public boolean isCompatPaddingEnabled() {
+ return mCompatPadding;
+ }
}
}
diff --git a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
index ac24ca2..4d989869 100644
--- a/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
+++ b/design/src/android/support/design/widget/HeaderScrollingViewBehavior.java
@@ -17,9 +17,13 @@
package android.support.design.widget;
import android.content.Context;
+import android.graphics.Rect;
import android.support.design.widget.CoordinatorLayout.Behavior;
+import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
+import android.support.v4.view.WindowInsetsCompat;
import android.util.AttributeSet;
+import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -31,6 +35,12 @@
*/
abstract class HeaderScrollingViewBehavior extends ViewOffsetBehavior<View> {
+ private final Rect mTempRect1 = new Rect();
+ private final Rect mTempRect2 = new Rect();
+
+ private int mVerticalLayoutGap = 0;
+ private int mOverlayTop;
+
public HeaderScrollingViewBehavior() {}
public HeaderScrollingViewBehavior(Context context, AttributeSet attrs) {
@@ -48,17 +58,19 @@
// with the maximum visible height
final List<View> dependencies = parent.getDependencies(child);
- if (dependencies.isEmpty()) {
- // If we don't have any dependencies, return false
- return false;
- }
-
final View header = findFirstDependency(dependencies);
- if (header != null && ViewCompat.isLaidOut(header)) {
- if (ViewCompat.getFitsSystemWindows(header)) {
+ if (header != null) {
+ if (ViewCompat.getFitsSystemWindows(header)
+ && !ViewCompat.getFitsSystemWindows(child)) {
// If the header is fitting system windows then we need to also,
- // otherwise we'll get CoL's compatible layout functionality
+ // otherwise we'll get CoL's compatible measuring
ViewCompat.setFitsSystemWindows(child, true);
+
+ if (ViewCompat.getFitsSystemWindows(child)) {
+ // If the set succeeded, trigger a new layout and return true
+ child.requestLayout();
+ return true;
+ }
}
int availableHeight = View.MeasureSpec.getSize(parentHeightMeasureSpec);
@@ -74,7 +86,7 @@
? View.MeasureSpec.EXACTLY
: View.MeasureSpec.AT_MOST);
- // Now measure the scrolling menu with the correct height
+ // Now measure the scrolling view with the correct height
parent.onMeasureChild(child, parentWidthMeasureSpec,
widthUsed, heightMeasureSpec, heightUsed);
@@ -84,9 +96,89 @@
return false;
}
+ @Override
+ protected void layoutChild(final CoordinatorLayout parent, final View child,
+ final int layoutDirection) {
+ final List<View> dependencies = parent.getDependencies(child);
+ final View header = findFirstDependency(dependencies);
+
+ if (header != null) {
+ final CoordinatorLayout.LayoutParams lp =
+ (CoordinatorLayout.LayoutParams) child.getLayoutParams();
+ final Rect available = mTempRect1;
+ available.set(parent.getPaddingLeft() + lp.leftMargin,
+ header.getBottom() + lp.topMargin,
+ parent.getWidth() - parent.getPaddingRight() - lp.rightMargin,
+ parent.getHeight() + header.getBottom()
+ - parent.getPaddingBottom() - lp.bottomMargin);
+
+ final WindowInsetsCompat parentInsets = parent.getLastWindowInsets();
+ if (parentInsets != null && ViewCompat.getFitsSystemWindows(parent)
+ && !ViewCompat.getFitsSystemWindows(child)) {
+ // If we're set to handle insets but this child isn't, then it has been measured as
+ // if there are no insets. We need to lay it out to match horizontally.
+ // Top and bottom and already handled in the logic above
+ available.left += parentInsets.getSystemWindowInsetLeft();
+ available.right -= parentInsets.getSystemWindowInsetRight();
+ }
+
+ final Rect out = mTempRect2;
+ GravityCompat.apply(resolveGravity(lp.gravity), child.getMeasuredWidth(),
+ child.getMeasuredHeight(), available, out, layoutDirection);
+
+ final int overlap = getOverlapPixelsForOffset(header);
+
+ child.layout(out.left, out.top - overlap, out.right, out.bottom - overlap);
+ mVerticalLayoutGap = out.top - header.getBottom();
+ } else {
+ // If we don't have a dependency, let super handle it
+ super.layoutChild(parent, child, layoutDirection);
+ mVerticalLayoutGap = 0;
+ }
+ }
+
+ float getOverlapRatioForOffset(final View header) {
+ return 1f;
+ }
+
+ final int getOverlapPixelsForOffset(final View header) {
+ return mOverlayTop == 0
+ ? 0
+ : MathUtils.constrain(Math.round(getOverlapRatioForOffset(header) * mOverlayTop),
+ 0, mOverlayTop);
+
+ }
+
+ private static int resolveGravity(int gravity) {
+ return gravity == Gravity.NO_GRAVITY ? GravityCompat.START | Gravity.TOP : gravity;
+ }
+
abstract View findFirstDependency(List<View> views);
int getScrollRange(View v) {
return v.getMeasuredHeight();
}
+
+ /**
+ * The gap between the top of the scrolling view and the bottom of the header layout in pixels.
+ */
+ final int getVerticalLayoutGap() {
+ return mVerticalLayoutGap;
+ }
+
+ /**
+ * Set the distance that this view should overlap any {@link AppBarLayout}.
+ *
+ * @param overlayTop the distance in px
+ */
+ public final void setOverlayTop(int overlayTop) {
+ mOverlayTop = overlayTop;
+ }
+
+ /**
+ * Returns the distance that this view should overlap any {@link AppBarLayout}.
+ */
+ public final int getOverlayTop() {
+ return mOverlayTop;
+ }
}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 57882db..6918632 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcel;
@@ -36,6 +37,7 @@
import android.support.v4.content.ContextCompat;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.ViewCompat;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
@@ -186,6 +188,10 @@
@Override
protected void onRestoreInstanceState(Parcelable savedState) {
+ if (!(savedState instanceof SavedState)) {
+ super.onRestoreInstanceState(savedState);
+ return;
+ }
SavedState state = (SavedState) savedState;
super.onRestoreInstanceState(state.getSuperState());
mMenu.restorePresenterStates(state.menuState);
@@ -218,6 +224,13 @@
super.onMeasure(widthSpec, heightSpec);
}
+ /**
+ * @hide
+ */
+ @Override
+ protected void onInsetsChanged(Rect insets) {
+ mPresenter.setPaddingTopDefault(insets.top);
+ }
/**
* Inflate a menu resource into this navigation view.
@@ -289,7 +302,7 @@
}
/**
- * Returns the tint which is applied to our item's icons.
+ * Returns the tint which is applied to our menu items' icons.
*
* @see #setItemIconTintList(ColorStateList)
*
@@ -301,7 +314,7 @@
}
/**
- * Set the tint which is applied to our item's icons.
+ * Set the tint which is applied to our menu items' icons.
*
* @param tint the tint to apply.
*
@@ -312,7 +325,7 @@
}
/**
- * Returns the tint which is applied to our item's icons.
+ * Returns the tint which is applied to our menu items' icons.
*
* @see #setItemTextColor(ColorStateList)
*
@@ -324,7 +337,7 @@
}
/**
- * Set the text color which is text to our items.
+ * Set the text color to be used on our menu items.
*
* @see #getItemTextColor()
*
@@ -335,18 +348,19 @@
}
/**
- * Returns the background drawable for the menu items.
+ * Returns the background drawable for our menu items.
*
* @see #setItemBackgroundResource(int)
*
* @attr ref R.styleable#NavigationView_itemBackground
*/
+ @Nullable
public Drawable getItemBackground() {
return mPresenter.getItemBackground();
}
/**
- * Set the background of the menu items to the given resource.
+ * Set the background of our menu items to the given resource.
*
* @param resId The identifier of the resource.
*
@@ -357,12 +371,12 @@
}
/**
- * Set the background of the menu items to a given resource. The resource should refer to
- * a Drawable object or 0 to use the background background.
+ * Set the background of our menu items to a given resource. The resource should refer to
+ * a Drawable object or null to use the default background set on this navigation menu.
*
* @attr ref R.styleable#NavigationView_itemBackground
*/
- public void setItemBackground(Drawable itemBackground) {
+ public void setItemBackground(@Nullable Drawable itemBackground) {
mPresenter.setItemBackground(itemBackground);
}
@@ -400,7 +414,8 @@
return null;
}
ColorStateList baseColor = getResources().getColorStateList(value.resourceId);
- if (!getContext().getTheme().resolveAttribute(R.attr.colorPrimary, value, true)) {
+ if (!getContext().getTheme().resolveAttribute(
+ android.support.v7.appcompat.R.attr.colorPrimary, value, true)) {
return null;
}
int colorPrimary = value.data;
@@ -435,11 +450,11 @@
* User interface state that is stored by NavigationView for implementing
* onSaveInstanceState().
*/
- public static class SavedState extends BaseSavedState {
+ public static class SavedState extends AbsSavedState {
public Bundle menuState;
public SavedState(Parcel in, ClassLoader loader) {
- super(in);
+ super(in, loader);
menuState = in.readBundle(loader);
}
diff --git a/design/src/android/support/design/widget/Snackbar.java b/design/src/android/support/design/widget/Snackbar.java
index a328d98..657e9ced 100644
--- a/design/src/android/support/design/widget/Snackbar.java
+++ b/design/src/android/support/design/widget/Snackbar.java
@@ -25,6 +25,7 @@
import android.os.Message;
import android.support.annotation.ColorInt;
import android.support.annotation.IntDef;
+import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
import android.support.annotation.StringRes;
import android.support.design.R;
@@ -37,6 +38,7 @@
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
+import android.view.accessibility.AccessibilityManager;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.widget.Button;
@@ -120,6 +122,7 @@
* @hide
*/
@IntDef({LENGTH_INDEFINITE, LENGTH_SHORT, LENGTH_LONG})
+ @IntRange(from = 1)
@Retention(RetentionPolicy.SOURCE)
public @interface Duration {}
@@ -145,8 +148,8 @@
*/
public static final int LENGTH_LONG = 0;
- private static final int ANIMATION_DURATION = 250;
- private static final int ANIMATION_FADE_DURATION = 180;
+ static final int ANIMATION_DURATION = 250;
+ static final int ANIMATION_FADE_DURATION = 180;
private static final Handler sHandler;
private static final int MSG_SHOW = 0;
@@ -175,6 +178,8 @@
private int mDuration;
private Callback mCallback;
+ private final AccessibilityManager mAccessibilityManager;
+
private Snackbar(ViewGroup parent) {
mTargetParent = parent;
mContext = parent.getContext();
@@ -184,6 +189,9 @@
LayoutInflater inflater = LayoutInflater.from(mContext);
mView = (SnackbarLayout) inflater.inflate(
R.layout.design_layout_snackbar, mTargetParent, false);
+
+ mAccessibilityManager = (AccessibilityManager)
+ mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
}
/**
@@ -444,6 +452,7 @@
behavior.setListener(new SwipeDismissBehavior.OnDismissListener() {
@Override
public void onDismiss(View view) {
+ view.setVisibility(View.GONE);
dispatchDismiss(Callback.DISMISS_EVENT_SWIPE);
}
@@ -490,15 +499,27 @@
});
if (ViewCompat.isLaidOut(mView)) {
- // If the view is already laid out, animate it now
- animateViewIn();
+ if (shouldAnimate()) {
+ // If animations are enabled, animate it in
+ animateViewIn();
+ } else {
+ // Else if anims are disabled just call back now
+ onViewShown();
+ }
} else {
- // Otherwise, add one of our layout change listeners and animate it in when laid out
+ // Otherwise, add one of our layout change listeners and show it in when laid out
mView.setOnLayoutChangeListener(new SnackbarLayout.OnLayoutChangeListener() {
@Override
public void onLayoutChange(View view, int left, int top, int right, int bottom) {
- animateViewIn();
mView.setOnLayoutChangeListener(null);
+
+ if (shouldAnimate()) {
+ // If animations are enabled, animate it in
+ animateViewIn();
+ } else {
+ // Else if anims are disabled just call back now
+ onViewShown();
+ }
}
});
}
@@ -520,10 +541,7 @@
@Override
public void onAnimationEnd(View view) {
- if (mCallback != null) {
- mCallback.onShown(Snackbar.this);
- }
- SnackbarManager.getInstance().onShown(mManagerCallback);
+ onViewShown();
}
}).start();
} else {
@@ -534,10 +552,7 @@
anim.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationEnd(Animation animation) {
- if (mCallback != null) {
- mCallback.onShown(Snackbar.this);
- }
- SnackbarManager.getInstance().onShown(mManagerCallback);
+ onViewShown();
}
@Override
@@ -568,7 +583,8 @@
}
}).start();
} else {
- Animation anim = AnimationUtils.loadAnimation(mView.getContext(), R.anim.design_snackbar_out);
+ Animation anim = AnimationUtils.loadAnimation(mView.getContext(),
+ R.anim.design_snackbar_out);
anim.setInterpolator(FAST_OUT_SLOW_IN_INTERPOLATOR);
anim.setDuration(ANIMATION_DURATION);
anim.setAnimationListener(new Animation.AnimationListener() {
@@ -587,11 +603,19 @@
}
}
- final void hideView(int event) {
- if (mView.getVisibility() != View.VISIBLE || isBeingDragged()) {
- onViewHidden(event);
- } else {
+ final void hideView(@Callback.DismissEvent final int event) {
+ if (shouldAnimate() && mView.getVisibility() == View.VISIBLE) {
animateViewOut(event);
+ } else {
+ // If anims are disabled or the view isn't visible, just call back now
+ onViewHidden(event);
+ }
+ }
+
+ private void onViewShown() {
+ SnackbarManager.getInstance().onShown(mManagerCallback);
+ if (mCallback != null) {
+ mCallback.onShown(this);
}
}
@@ -610,21 +634,10 @@
}
/**
- * @return if the view is being being dragged or settled by {@link SwipeDismissBehavior}.
+ * Returns true if we should animate the Snackbar view in/out.
*/
- private boolean isBeingDragged() {
- final ViewGroup.LayoutParams lp = mView.getLayoutParams();
-
- if (lp instanceof CoordinatorLayout.LayoutParams) {
- final CoordinatorLayout.LayoutParams cllp = (CoordinatorLayout.LayoutParams) lp;
- final CoordinatorLayout.Behavior behavior = cllp.getBehavior();
-
- if (behavior instanceof SwipeDismissBehavior) {
- return ((SwipeDismissBehavior) behavior).getDragState()
- != SwipeDismissBehavior.STATE_IDLE;
- }
- }
- return false;
+ private boolean shouldAnimate() {
+ return !mAccessibilityManager.isEnabled();
}
/**
@@ -674,6 +687,8 @@
ViewCompat.setAccessibilityLiveRegion(this,
ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
+ ViewCompat.setImportantForAccessibility(this,
+ ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
@Override
@@ -752,7 +767,7 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
super.onLayout(changed, l, t, r, b);
- if (changed && mOnLayoutChangeListener != null) {
+ if (mOnLayoutChangeListener != null) {
mOnLayoutChangeListener.onLayoutChange(this, l, t, r, b);
}
}
diff --git a/design/src/android/support/design/widget/SnackbarManager.java b/design/src/android/support/design/widget/SnackbarManager.java
index be2f024..e0c3922 100644
--- a/design/src/android/support/design/widget/SnackbarManager.java
+++ b/design/src/android/support/design/widget/SnackbarManager.java
@@ -197,6 +197,8 @@
private boolean cancelSnackbarLocked(SnackbarRecord record, int event) {
final Callback callback = record.callback.get();
if (callback != null) {
+ // Make sure we remove any timeouts for the SnackbarRecord
+ mHandler.removeCallbacksAndMessages(record);
callback.dismiss(event);
return true;
}
diff --git a/design/src/android/support/design/widget/SwipeDismissBehavior.java b/design/src/android/support/design/widget/SwipeDismissBehavior.java
index e059fe5..bfc98ea 100644
--- a/design/src/android/support/design/widget/SwipeDismissBehavior.java
+++ b/design/src/android/support/design/widget/SwipeDismissBehavior.java
@@ -24,6 +24,7 @@
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
+import android.view.ViewParent;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -210,16 +211,28 @@
}
private final ViewDragHelper.Callback mDragCallback = new ViewDragHelper.Callback() {
+ private static final int INVALID_POINTER_ID = -1;
+
private int mOriginalCapturedViewLeft;
+ private int mActivePointerId = INVALID_POINTER_ID;
@Override
public boolean tryCaptureView(View child, int pointerId) {
- return canSwipeDismissView(child);
+ // Only capture if we don't already have an active pointer id
+ return mActivePointerId == INVALID_POINTER_ID && canSwipeDismissView(child);
}
@Override
public void onViewCaptured(View capturedChild, int activePointerId) {
+ mActivePointerId = activePointerId;
mOriginalCapturedViewLeft = capturedChild.getLeft();
+
+ // The view has been captured, and thus a drag is about to start so stop any parents
+ // intercepting
+ final ViewParent parent = capturedChild.getParent();
+ if (parent != null) {
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
}
@Override
@@ -231,6 +244,9 @@
@Override
public void onViewReleased(View child, float xvel, float yvel) {
+ // Reset the active pointer ID
+ mActivePointerId = INVALID_POINTER_ID;
+
final int childWidth = child.getWidth();
int targetLeft;
boolean dismiss = false;
diff --git a/design/src/android/support/design/widget/TabItem.java b/design/src/android/support/design/widget/TabItem.java
new file mode 100644
index 0000000..09b01db
--- /dev/null
+++ b/design/src/android/support/design/widget/TabItem.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.design.R;
+import android.support.v7.widget.TintTypedArray;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * TabItem is a special 'view' which allows you to declare tab items for a {@link TabLayout}
+ * within a layout. This view is not actually added to TabLayout, it is just a dummy which allows
+ * setting of a tab items's text, icon and custom layout. See TabLayout for more information on how
+ * to use it.
+ *
+ * @attr ref android.support.design.R.styleable#TabItem_android_icon
+ * @attr ref android.support.design.R.styleable#TabItem_android_text
+ * @attr ref android.support.design.R.styleable#TabItem_android_layout
+ *
+ * @see TabLayout
+ */
+public final class TabItem extends View {
+ final CharSequence mText;
+ final Drawable mIcon;
+ final int mCustomLayout;
+
+ public TabItem(Context context) {
+ this(context, null);
+ }
+
+ public TabItem(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+ R.styleable.TabItem);
+ mText = a.getText(R.styleable.TabItem_android_text);
+ mIcon = a.getDrawable(R.styleable.TabItem_android_icon);
+ mCustomLayout = a.getResourceId(R.styleable.TabItem_android_layout, 0);
+ a.recycle();
+ }
+}
\ No newline at end of file
diff --git a/design/src/android/support/design/widget/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index fe1a105..d53bf6ba 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -24,6 +24,7 @@
import android.database.DataSetObserver;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.annotation.ColorInt;
@@ -85,13 +86,61 @@
* </pre>
* You should set a listener via {@link #setOnTabSelectedListener(OnTabSelectedListener)} to be
* notified when any tab's selection state has been changed.
+ *
+ * <p>You can also add items to TabLayout in your layout through the use of {@link TabItem}.
+ * An example usage is like so:</p>
+ *
+ * <pre>
+ * <android.support.design.widget.TabLayout
+ * android:layout_height="wrap_content"
+ * android:layout_width="match_parent">
+ *
+ * <android.support.design.widget.TabItem
+ * android:text="@string/tab_text"/>
+ *
+ * <android.support.design.widget.TabItem
+ * android:icon="@drawable/ic_android"/>
+ *
+ * </android.support.design.widget.TabLayout>
+ * </pre>
+ *
+ * <h3>ViewPager integration</h3>
* <p>
* If you're using a {@link android.support.v4.view.ViewPager} together
- * with this layout, you can use {@link #setupWithViewPager(ViewPager)} to link the two together.
+ * with this layout, you can call {@link #setupWithViewPager(ViewPager)} to link the two together.
* This layout will be automatically populated from the {@link PagerAdapter}'s page titles.</p>
*
+ * <p>
+ * This view also supports being used as part of a ViewPager's decor, and can be added
+ * directly to the ViewPager in a layout resource file like so:</p>
+ *
+ * <pre>
+ * <android.support.v4.view.ViewPager
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent">
+ *
+ * <android.support.design.widget.TabLayout
+ * android:layout_width="match_parent"
+ * android:layout_height="wrap_content"
+ * android:layout_gravity="top" />
+ *
+ * </android.support.v4.view.ViewPager>
+ * </pre>
+ *
* @see <a href="http://www.google.com/design/spec/components/tabs.html">Tabs</a>
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabPadding
+ * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingStart
+ * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingTop
+ * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingEnd
+ * @attr ref android.support.design.R.styleable#TabLayout_tabPaddingBottom
+ * @attr ref android.support.design.R.styleable#TabLayout_tabContentStart
+ * @attr ref android.support.design.R.styleable#TabLayout_tabBackground
+ * @attr ref android.support.design.R.styleable#TabLayout_tabMinWidth
+ * @attr ref android.support.design.R.styleable#TabLayout_tabMaxWidth
+ * @attr ref android.support.design.R.styleable#TabLayout_tabTextAppearance
*/
+@ViewPager.DecorView
public class TabLayout extends HorizontalScrollView {
private static final int DEFAULT_HEIGHT_WITH_TEXT_ICON = 72; // dps
@@ -212,8 +261,9 @@
private int mTabGravity;
private int mMode;
- private OnTabSelectedListener mOnTabSelectedListener;
- private View.OnClickListener mTabClickListener;
+ private OnTabSelectedListener mSelectedListener;
+ private final ArrayList<OnTabSelectedListener> mSelectedListeners = new ArrayList<>();
+ private OnTabSelectedListener mCurrentVpSelectedListener;
private ValueAnimatorCompat mScrollAnimator;
@@ -221,6 +271,8 @@
private PagerAdapter mPagerAdapter;
private DataSetObserver mPagerAdapterObserver;
private TabLayoutOnPageChangeListener mPageChangeListener;
+ private AdapterChangeListener mAdapterChangeListener;
+ private boolean mSetupViewPagerImplicitly;
// Pool we use as a simple RecyclerBin
private final Pools.Pool<TabView> mTabViewPool = new Pools.SimplePool<>(12);
@@ -243,7 +295,8 @@
// Add the TabStrip
mTabStrip = new SlidingTabStrip(context);
- addView(mTabStrip, LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
+ super.addView(mTabStrip, 0, new HorizontalScrollView.LayoutParams(
+ LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT));
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.TabLayout,
defStyleAttr, R.style.Widget_Design_TabLayout);
@@ -268,10 +321,12 @@
// Text colors/sizes come from the text appearance first
final TypedArray ta = context.obtainStyledAttributes(mTabTextAppearance,
- R.styleable.TextAppearance);
+ android.support.v7.appcompat.R.styleable.TextAppearance);
try {
- mTabTextSize = ta.getDimensionPixelSize(R.styleable.TextAppearance_android_textSize, 0);
- mTabTextColors = ta.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ mTabTextSize = ta.getDimensionPixelSize(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textSize, 0);
+ mTabTextColors = ta.getColorStateList(
+ android.support.v7.appcompat.R.styleable.TextAppearance_android_textColor);
} finally {
ta.recycle();
}
@@ -312,6 +367,8 @@
* Sets the tab indicator's color for the currently selected tab.
*
* @param color color to use for the indicator
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorColor
*/
public void setSelectedTabIndicatorColor(@ColorInt int color) {
mTabStrip.setSelectedIndicatorColor(color);
@@ -321,6 +378,8 @@
* Sets the tab indicator's height for the currently selected tab.
*
* @param height height to use for the indicator in pixels
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabIndicatorHeight
*/
public void setSelectedTabIndicatorHeight(int height) {
mTabStrip.setSelectedIndicatorHeight(height);
@@ -426,14 +485,65 @@
}
}
+ private void addTabFromItemView(@NonNull TabItem item) {
+ final Tab tab = newTab();
+ if (item.mText != null) {
+ tab.setText(item.mText);
+ }
+ if (item.mIcon != null) {
+ tab.setIcon(item.mIcon);
+ }
+ if (item.mCustomLayout != 0) {
+ tab.setCustomView(item.mCustomLayout);
+ }
+ if (!TextUtils.isEmpty(item.getContentDescription())) {
+ tab.setContentDescription(item.getContentDescription());
+ }
+ addTab(tab);
+ }
+
/**
- * Set the {@link android.support.design.widget.TabLayout.OnTabSelectedListener} that will
- * handle switching to and from tabs.
- *
- * @param onTabSelectedListener Listener to handle tab selection events
+ * @deprecated Use {@link #addOnTabSelectedListener(OnTabSelectedListener)} and
+ * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.
*/
- public void setOnTabSelectedListener(OnTabSelectedListener onTabSelectedListener) {
- mOnTabSelectedListener = onTabSelectedListener;
+ @Deprecated
+ public void setOnTabSelectedListener(@Nullable OnTabSelectedListener listener) {
+ // The logic in this method emulates what we had before support for multiple
+ // registered listeners.
+ if (mSelectedListener != null) {
+ removeOnTabSelectedListener(mSelectedListener);
+ }
+ // Update the deprecated field so that we can remove the passed listener the next
+ // time we're called
+ mSelectedListener = listener;
+ if (listener != null) {
+ addOnTabSelectedListener(listener);
+ }
+ }
+
+ /**
+ * Add a {@link TabLayout.OnTabSelectedListener} that will be invoked when tab selection
+ * changes.
+ *
+ * <p>Components that add a listener should take care to remove it when finished via
+ * {@link #removeOnTabSelectedListener(OnTabSelectedListener)}.</p>
+ *
+ * @param listener listener to add
+ */
+ public void addOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
+ if (!mSelectedListeners.contains(listener)) {
+ mSelectedListeners.add(listener);
+ }
+ }
+
+ /**
+ * Remove the given {@link TabLayout.OnTabSelectedListener} that was previously added via
+ * {@link #addOnTabSelectedListener(OnTabSelectedListener)}.
+ *
+ * @param listener listener to remove
+ */
+ public void removeOnTabSelectedListener(@NonNull OnTabSelectedListener listener) {
+ mSelectedListeners.remove(listener);
}
/**
@@ -445,8 +555,13 @@
*/
@NonNull
public Tab newTab() {
- final Tab poolTab = sTabPool.acquire();
- return poolTab != null ? poolTab : new Tab(this);
+ Tab tab = sTabPool.acquire();
+ if (tab == null) {
+ tab = new Tab();
+ }
+ tab.mParent = this;
+ tab.mView = createTabView(tab);
+ return tab;
}
/**
@@ -546,6 +661,8 @@
* </ul>
*
* @param mode one of {@link #MODE_FIXED} or {@link #MODE_SCROLLABLE}.
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabMode
*/
public void setTabMode(@Mode int mode) {
if (mode != mMode) {
@@ -568,6 +685,8 @@
* Set the gravity to use when laying out the tabs.
*
* @param gravity one of {@link #GRAVITY_CENTER} or {@link #GRAVITY_FILL}.
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabGravity
*/
public void setTabGravity(@TabGravity int gravity) {
if (mTabGravity != gravity) {
@@ -588,6 +707,8 @@
/**
* Sets the text colors for the different states (normal, selected) used for the tabs.
+ *
+ * @see #getTabTextColors()
*/
public void setTabTextColors(@Nullable ColorStateList textColor) {
if (mTabTextColors != textColor) {
@@ -606,6 +727,9 @@
/**
* Sets the text colors for the different states (normal, selected) used for the tabs.
+ *
+ * @attr ref android.support.design.R.styleable#TabLayout_tabTextColor
+ * @attr ref android.support.design.R.styleable#TabLayout_tabSelectedTextColor
*/
public void setTabTextColors(int normalColor, int selectedColor) {
setTabTextColors(createColorStateList(normalColor, selectedColor));
@@ -614,31 +738,56 @@
/**
* The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
*
- * <p>This method will link the given ViewPager and this TabLayout together so that any
- * changes in one are automatically reflected in the other. This includes adapter changes,
- * scroll state changes, and clicks. The tabs displayed in this layout will be populated
+ * <p>This is the same as calling {@link #setupWithViewPager(ViewPager, boolean)} with
+ * auto-refresh enabled.</p>
+ *
+ * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
+ */
+ public void setupWithViewPager(@Nullable ViewPager viewPager) {
+ setupWithViewPager(viewPager, true);
+ }
+
+ /**
+ * The one-stop shop for setting up this {@link TabLayout} with a {@link ViewPager}.
+ *
+ * <p>This method will link the given ViewPager and this TabLayout together so that
+ * changes in one are automatically reflected in the other. This includes scroll state changes
+ * and clicks. The tabs displayed in this layout will be populated
* from the ViewPager adapter's page titles.</p>
*
- * <p>After this method is called, you will not need this method again unless you want
- * to change the linked ViewPager.</p>
+ * <p>If {@code autoRefresh} is {@code true}, any changes in the {@link PagerAdapter} will
+ * trigger this layout to re-populate itself from the adapter's titles.</p>
*
* <p>If the given ViewPager is non-null, it needs to already have a
* {@link PagerAdapter} set.</p>
*
- * @param viewPager The ViewPager to link, or {@code null} to clear any previous link.
+ * @param viewPager the ViewPager to link to, or {@code null} to clear any previous link
+ * @param autoRefresh whether this layout should refresh its contents if the given ViewPager's
+ * content changes
*/
- public void setupWithViewPager(@Nullable final ViewPager viewPager) {
- if (mViewPager != null && mPageChangeListener != null) {
+ public void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh) {
+ setupWithViewPager(viewPager, autoRefresh, false);
+ }
+
+ private void setupWithViewPager(@Nullable final ViewPager viewPager, boolean autoRefresh,
+ boolean implicitSetup) {
+ if (mViewPager != null) {
// If we've already been setup with a ViewPager, remove us from it
- mViewPager.removeOnPageChangeListener(mPageChangeListener);
+ if (mPageChangeListener != null) {
+ mViewPager.removeOnPageChangeListener(mPageChangeListener);
+ }
+ if (mAdapterChangeListener != null) {
+ mViewPager.removeOnAdapterChangeListener(mAdapterChangeListener);
+ }
+ }
+
+ if (mCurrentVpSelectedListener != null) {
+ // If we already have a tab selected listener for the ViewPager, remove it
+ removeOnTabSelectedListener(mCurrentVpSelectedListener);
+ mCurrentVpSelectedListener = null;
}
if (viewPager != null) {
- final PagerAdapter adapter = viewPager.getAdapter();
- if (adapter == null) {
- throw new IllegalArgumentException("ViewPager does not have a PagerAdapter set");
- }
-
mViewPager = viewPager;
// Add our custom OnPageChangeListener to the ViewPager
@@ -649,17 +798,33 @@
viewPager.addOnPageChangeListener(mPageChangeListener);
// Now we'll add a tab selected listener to set ViewPager's current item
- setOnTabSelectedListener(new ViewPagerOnTabSelectedListener(viewPager));
+ mCurrentVpSelectedListener = new ViewPagerOnTabSelectedListener(viewPager);
+ addOnTabSelectedListener(mCurrentVpSelectedListener);
- // Now we'll populate ourselves from the pager adapter
- setPagerAdapter(adapter, true);
+ final PagerAdapter adapter = viewPager.getAdapter();
+ if (adapter != null) {
+ // Now we'll populate ourselves from the pager adapter, adding an observer if
+ // autoRefresh is enabled
+ setPagerAdapter(adapter, autoRefresh);
+ }
+
+ // Add a listener so that we're notified of any adapter changes
+ if (mAdapterChangeListener == null) {
+ mAdapterChangeListener = new AdapterChangeListener();
+ }
+ mAdapterChangeListener.setAutoRefresh(autoRefresh);
+ viewPager.addOnAdapterChangeListener(mAdapterChangeListener);
+
+ // Now update the scroll position to match the ViewPager's current item
+ setScrollPosition(viewPager.getCurrentItem(), 0f, true);
} else {
// We've been given a null ViewPager so we need to clear out the internal state,
// listeners and observers
mViewPager = null;
- setOnTabSelectedListener(null);
- setPagerAdapter(null, true);
+ setPagerAdapter(null, false);
}
+
+ mSetupViewPagerImplicitly = implicitSetup;
}
/**
@@ -672,6 +837,44 @@
setPagerAdapter(adapter, false);
}
+ @Override
+ public boolean shouldDelayChildPressedState() {
+ // Only delay the pressed state if the tabs can scroll
+ return getTabScrollRange() > 0;
+ }
+
+ @Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
+ if (mViewPager == null) {
+ // If we don't have a ViewPager already, check if our parent is a ViewPager to
+ // setup with it automatically
+ final ViewParent vp = getParent();
+ if (vp instanceof ViewPager) {
+ // If we have a ViewPager parent and we've been added as part of its decor, let's
+ // assume that we should automatically setup to display any titles
+ setupWithViewPager((ViewPager) vp, true, true);
+ }
+ }
+ }
+
+ @Override
+ protected void onDetachedFromWindow() {
+ super.onDetachedFromWindow();
+
+ if (mSetupViewPagerImplicitly) {
+ // If we've been setup with a ViewPager implicitly, let's clear out any listeners, etc
+ setupWithViewPager(null);
+ mSetupViewPagerImplicitly = false;
+ }
+ }
+
+ private int getTabScrollRange() {
+ return Math.max(0, mTabStrip.getWidth() - getWidth() - getPaddingLeft()
+ - getPaddingRight());
+ }
+
private void setPagerAdapter(@Nullable final PagerAdapter adapter, final boolean addObserver) {
if (mPagerAdapter != null && mPagerAdapterObserver != null) {
// If we already have a PagerAdapter, unregister our observer
@@ -708,14 +911,12 @@
selectTab(getTabAt(curItem));
}
}
- } else {
- removeAllTabs();
}
}
private void updateAllTabs() {
- for (int i = 0, z = mTabStrip.getChildCount(); i < z; i++) {
- updateTab(i);
+ for (int i = 0, z = mTabs.size(); i < z; i++) {
+ mTabs.get(i).updateView();
}
}
@@ -727,17 +928,6 @@
tabView.setTab(tab);
tabView.setFocusable(true);
tabView.setMinimumWidth(getTabMinWidth());
-
- if (mTabClickListener == null) {
- mTabClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- TabView tabView = (TabView) view;
- tabView.getTab().select();
- }
- };
- }
- tabView.setOnClickListener(mTabClickListener);
return tabView;
}
@@ -751,19 +941,8 @@
}
}
- private void updateTab(int position) {
- final TabView view = getTabView(position);
- if (view != null) {
- view.update();
- }
- }
-
- private TabView getTabView(int position) {
- return (TabView) mTabStrip.getChildAt(position);
- }
-
private void addTabView(Tab tab, boolean setSelected) {
- final TabView tabView = createTabView(tab);
+ final TabView tabView = tab.mView;
mTabStrip.addView(tabView, createLayoutParamsForTabs());
if (setSelected) {
tabView.setSelected(true);
@@ -771,13 +950,41 @@
}
private void addTabView(Tab tab, int position, boolean setSelected) {
- final TabView tabView = createTabView(tab);
+ final TabView tabView = tab.mView;
mTabStrip.addView(tabView, position, createLayoutParamsForTabs());
if (setSelected) {
tabView.setSelected(true);
}
}
+ @Override
+ public void addView(View child) {
+ addViewInternal(child);
+ }
+
+ @Override
+ public void addView(View child, int index) {
+ addViewInternal(child);
+ }
+
+ @Override
+ public void addView(View child, ViewGroup.LayoutParams params) {
+ addViewInternal(child);
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ addViewInternal(child);
+ }
+
+ private void addViewInternal(final View child) {
+ if (child instanceof TabItem) {
+ addTabFromItemView((TabItem) child);
+ } else {
+ throw new IllegalArgumentException("Only TabItem instances can be added to TabLayout");
+ }
+ }
+
private LinearLayout.LayoutParams createLayoutParamsForTabs() {
final LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT);
@@ -917,12 +1124,12 @@
selectTab(tab, true);
}
- void selectTab(Tab tab, boolean updateIndicator) {
- if (mSelectedTab == tab) {
- if (mSelectedTab != null) {
- if (mOnTabSelectedListener != null) {
- mOnTabSelectedListener.onTabReselected(mSelectedTab);
- }
+ void selectTab(final Tab tab, boolean updateIndicator) {
+ final Tab currentTab = mSelectedTab;
+
+ if (currentTab == tab) {
+ if (currentTab != null) {
+ dispatchTabReselected(tab);
animateToTab(tab.getPosition());
}
} else {
@@ -931,7 +1138,7 @@
if (newPosition != Tab.INVALID_POSITION) {
setSelectedTabView(newPosition);
}
- if ((mSelectedTab == null || mSelectedTab.getPosition() == Tab.INVALID_POSITION)
+ if ((currentTab == null || currentTab.getPosition() == Tab.INVALID_POSITION)
&& newPosition != Tab.INVALID_POSITION) {
// If we don't currently have a tab, just draw the indicator
setScrollPosition(newPosition, 0f, true);
@@ -939,13 +1146,27 @@
animateToTab(newPosition);
}
}
- if (mSelectedTab != null && mOnTabSelectedListener != null) {
- mOnTabSelectedListener.onTabUnselected(mSelectedTab);
- }
+ dispatchTabUnselected(currentTab);
mSelectedTab = tab;
- if (mSelectedTab != null && mOnTabSelectedListener != null) {
- mOnTabSelectedListener.onTabSelected(mSelectedTab);
- }
+ dispatchTabSelected(tab);
+ }
+ }
+
+ private void dispatchTabSelected(@NonNull final Tab tab) {
+ for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+ mSelectedListeners.get(i).onTabSelected(tab);
+ }
+ }
+
+ private void dispatchTabUnselected(@NonNull final Tab tab) {
+ for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+ mSelectedListeners.get(i).onTabUnselected(tab);
+ }
+ }
+
+ private void dispatchTabReselected(@NonNull final Tab tab) {
+ for (int i = mSelectedListeners.size() - 1; i >= 0; i--) {
+ mSelectedListeners.get(i).onTabReselected(tab);
}
}
@@ -1016,10 +1237,11 @@
private int mPosition = INVALID_POSITION;
private View mCustomView;
- private final TabLayout mParent;
+ private TabLayout mParent;
+ private TabView mView;
- Tab(TabLayout parent) {
- mParent = parent;
+ private Tab() {
+ // Private constructor
}
/**
@@ -1070,9 +1292,11 @@
@NonNull
public Tab setCustomView(@Nullable View view) {
mCustomView = view;
- if (mPosition >= 0) {
- mParent.updateTab(mPosition);
- }
+ updateView();
+
+ final boolean isSelected = (mParent.getSelectedTabPosition() == getPosition());
+ mCustomView.setSelected(isSelected);
+
return this;
}
@@ -1091,9 +1315,8 @@
*/
@NonNull
public Tab setCustomView(@LayoutRes int resId) {
- final TabView tabView = mParent.getTabView(mPosition);
- final LayoutInflater inflater = LayoutInflater.from(tabView.getContext());
- return setCustomView(inflater.inflate(resId, tabView, false));
+ final LayoutInflater inflater = LayoutInflater.from(mView.getContext());
+ return setCustomView(inflater.inflate(resId, mView, false));
}
/**
@@ -1139,9 +1362,7 @@
@NonNull
public Tab setIcon(@Nullable Drawable icon) {
mIcon = icon;
- if (mPosition >= 0) {
- mParent.updateTab(mPosition);
- }
+ updateView();
return this;
}
@@ -1153,6 +1374,9 @@
*/
@NonNull
public Tab setIcon(@DrawableRes int resId) {
+ if (mParent == null) {
+ throw new IllegalArgumentException("Tab not attached to a TabLayout");
+ }
return setIcon(AppCompatDrawableManager.get().getDrawable(mParent.getContext(), resId));
}
@@ -1166,9 +1390,7 @@
@NonNull
public Tab setText(@Nullable CharSequence text) {
mText = text;
- if (mPosition >= 0) {
- mParent.updateTab(mPosition);
- }
+ updateView();
return this;
}
@@ -1181,6 +1403,9 @@
*/
@NonNull
public Tab setText(@StringRes int resId) {
+ if (mParent == null) {
+ throw new IllegalArgumentException("Tab not attached to a TabLayout");
+ }
return setText(mParent.getResources().getText(resId));
}
@@ -1188,6 +1413,9 @@
* Select this tab. Only valid if the tab has been added to the action bar.
*/
public void select() {
+ if (mParent == null) {
+ throw new IllegalArgumentException("Tab not attached to a TabLayout");
+ }
mParent.selectTab(this);
}
@@ -1195,6 +1423,9 @@
* Returns true if this tab is currently selected.
*/
public boolean isSelected() {
+ if (mParent == null) {
+ throw new IllegalArgumentException("Tab not attached to a TabLayout");
+ }
return mParent.getSelectedTabPosition() == mPosition;
}
@@ -1209,6 +1440,9 @@
*/
@NonNull
public Tab setContentDescription(@StringRes int resId) {
+ if (mParent == null) {
+ throw new IllegalArgumentException("Tab not attached to a TabLayout");
+ }
return setContentDescription(mParent.getResources().getText(resId));
}
@@ -1224,9 +1458,7 @@
@NonNull
public Tab setContentDescription(@Nullable CharSequence contentDesc) {
mContentDesc = contentDesc;
- if (mPosition >= 0) {
- mParent.updateTab(mPosition);
- }
+ updateView();
return this;
}
@@ -1242,7 +1474,15 @@
return mContentDesc;
}
+ private void updateView() {
+ if (mView != null) {
+ mView.update();
+ }
+ }
+
private void reset() {
+ mParent = null;
+ mView = null;
mTag = null;
mIcon = null;
mText = null;
@@ -1273,6 +1513,19 @@
mTabPaddingEnd, mTabPaddingBottom);
setGravity(Gravity.CENTER);
setOrientation(VERTICAL);
+ setClickable(true);
+ }
+
+ @Override
+ public boolean performClick() {
+ final boolean value = super.performClick();
+
+ if (mTab != null) {
+ mTab.select();
+ return true;
+ } else {
+ return value;
+ }
}
@Override
@@ -1320,7 +1573,7 @@
|| specWidthSize > maxWidth)) {
// If we have a max width and a given spec which is either unspecified or
// larger than the max width, update the width spec using the same mode
- widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, specWidthMode);
+ widthMeasureSpec = MeasureSpec.makeMeasureSpec(mTabMaxWidth, MeasureSpec.AT_MOST);
} else {
// Else, use the original width spec
widthMeasureSpec = origWidthMeasureSpec;
@@ -1355,10 +1608,11 @@
// If we're in fixed mode, going up in text size and currently have 1 line
// then it's very easy to get into an infinite recursion.
// To combat that we check to see if the change in text size
- // will cause a line count change. If so, abort the size change.
+ // will cause a line count change. If so, abort the size change and stick
+ // to the smaller size.
final Layout layout = mTextView.getLayout();
- if (layout == null
- || approximateLineWidth(layout, 0, textSize) > layout.getWidth()) {
+ if (layout == null || approximateLineWidth(layout, 0, textSize)
+ > getMeasuredWidth() - getPaddingLeft() - getPaddingRight()) {
updateTextView = false;
}
}
@@ -1500,21 +1754,32 @@
}
@Override
- public boolean onLongClick(View v) {
+ public boolean onLongClick(final View v) {
final int[] screenPos = new int[2];
+ final Rect displayFrame = new Rect();
getLocationOnScreen(screenPos);
+ getWindowVisibleDisplayFrame(displayFrame);
final Context context = getContext();
final int width = getWidth();
final int height = getHeight();
- final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+ final int midy = screenPos[1] + height / 2;
+ int referenceX = screenPos[0] + width / 2;
+ if (ViewCompat.getLayoutDirection(v) == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ final int screenWidth = context.getResources().getDisplayMetrics().widthPixels;
+ referenceX = screenWidth - referenceX; // mirror
+ }
Toast cheatSheet = Toast.makeText(context, mTab.getContentDescription(),
Toast.LENGTH_SHORT);
- // Show under the tab
- cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL,
- (screenPos[0] + width / 2) - screenWidth / 2, height);
-
+ if (midy < displayFrame.height()) {
+ // Show below the tab view
+ cheatSheet.setGravity(Gravity.TOP | GravityCompat.END, referenceX,
+ screenPos[1] + height - displayFrame.top);
+ } else {
+ // Show along the bottom center
+ cheatSheet.setGravity(Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL, 0, height);
+ }
cheatSheet.show();
return true;
}
@@ -1702,6 +1967,12 @@
== ViewCompat.LAYOUT_DIRECTION_RTL;
final View targetView = getChildAt(position);
+ if (targetView == null) {
+ // If we don't have a view, just update the position now and return
+ updateIndicatorPosition();
+ return;
+ }
+
final int targetLeft = targetView.getLeft();
final int targetRight = targetView.getRight();
final int startLeft;
@@ -1806,6 +2077,15 @@
return mMode == MODE_SCROLLABLE ? mScrollableTabMinWidth : 0;
}
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ // We don't care about the layout params of any views added to us, since we don't actually
+ // add them. The only view we add is the SlidingTabStrip, which is done manually.
+ // We return the default layout params so that we don't blow up if we're given a TabItem
+ // without android:layout_* values.
+ return generateDefaultLayoutParams();
+ }
+
private int getTabMaxWidth() {
return mTabMaxWidth;
}
@@ -1830,14 +2110,14 @@
}
@Override
- public void onPageScrollStateChanged(int state) {
+ public void onPageScrollStateChanged(final int state) {
mPreviousScrollState = mScrollState;
mScrollState = state;
}
@Override
- public void onPageScrolled(int position, float positionOffset,
- int positionOffsetPixels) {
+ public void onPageScrolled(final int position, final float positionOffset,
+ final int positionOffsetPixels) {
final TabLayout tabLayout = mTabLayoutRef.get();
if (tabLayout != null) {
// Only update the text selection if we're not settling, or we are settling after
@@ -1854,9 +2134,10 @@
}
@Override
- public void onPageSelected(int position) {
+ public void onPageSelected(final int position) {
final TabLayout tabLayout = mTabLayoutRef.get();
- if (tabLayout != null && tabLayout.getSelectedTabPosition() != position) {
+ if (tabLayout != null && tabLayout.getSelectedTabPosition() != position
+ && position < tabLayout.getTabCount()) {
// Select the tab, only updating the indicator if we're not being dragged/settled
// (since onPageScrolled will handle that).
final boolean updateIndicator = mScrollState == SCROLL_STATE_IDLE
@@ -1910,4 +2191,19 @@
}
}
+ private class AdapterChangeListener implements ViewPager.OnAdapterChangeListener {
+ private boolean mAutoRefresh;
+
+ @Override
+ public void onAdapterChanged(@NonNull ViewPager viewPager,
+ @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter) {
+ if (mViewPager == viewPager) {
+ setPagerAdapter(newAdapter, mAutoRefresh);
+ }
+ }
+
+ void setAutoRefresh(boolean autoRefresh) {
+ mAutoRefresh = autoRefresh;
+ }
+ }
}
diff --git a/design/src/android/support/design/widget/TextInputEditText.java b/design/src/android/support/design/widget/TextInputEditText.java
new file mode 100644
index 0000000..a141439
--- /dev/null
+++ b/design/src/android/support/design/widget/TextInputEditText.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.Context;
+import android.support.v7.widget.AppCompatEditText;
+import android.util.AttributeSet;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputConnection;
+
+/**
+ * A special sub-class of {@link android.widget.EditText} designed for use as a child of
+ * {@link TextInputLayout}.
+ *
+ * <p>Using this class allows us to display a hint in the IME when in 'extract' mode.</p>
+ */
+public class TextInputEditText extends AppCompatEditText {
+
+ public TextInputEditText(Context context) {
+ super(context);
+ }
+
+ public TextInputEditText(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TextInputEditText(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ public InputConnection onCreateInputConnection(EditorInfo outAttrs) {
+ final InputConnection ic = super.onCreateInputConnection(outAttrs);
+ if (ic != null && outAttrs.hintText == null) {
+ // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the
+ // EditorInfo. This allows us to display a hint in 'extract mode'.
+ final ViewParent parent = getParent();
+ if (parent instanceof TextInputLayout) {
+ outAttrs.hintText = ((TextInputLayout) parent).getHint();
+ }
+ }
+ return ic;
+ }
+}
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index 32abe85..2bb959c 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -20,16 +20,24 @@
import android.content.res.ColorStateList;
import android.content.res.TypedArray;
import android.graphics.Canvas;
-import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.DrawableContainer;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StyleRes;
import android.support.design.R;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableWrapper;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
@@ -41,7 +49,7 @@
import android.text.TextUtils;
import android.text.TextWatcher;
import android.util.AttributeSet;
-import android.util.TypedValue;
+import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -55,14 +63,34 @@
* Layout which wraps an {@link android.widget.EditText} (or descendant) to show a floating label
* when the hint is hidden due to the user inputting text.
*
- * Also supports showing an error via {@link #setErrorEnabled(boolean)} and
- * {@link #setError(CharSequence)}.
+ * <p>Also supports showing an error via {@link #setErrorEnabled(boolean)} and
+ * {@link #setError(CharSequence)}, and a character counter via
+ * {@link #setCounterEnabled(boolean)}.</p>
+ *
+ * The {@link TextInputEditText} class is provided to be used as a child of this layout. Using
+ * TextInputEditText allows TextInputLayout greater control over the visual aspects of any
+ * text input. An example usage is as so:
+ *
+ * <pre>
+ * <android.support.design.widget.TextInputLayout
+ * android:layout_width="match_parent"
+ * android:layout_height="wrap_content">
+ *
+ * <android.support.design.widget.TextInputEditText
+ * android:layout_width="match_parent"
+ * android:layout_height="wrap_content"
+ * android:hint="@string/form_username"/>
+ *
+ * </android.support.design.widget.TextInputLayout>
+ * </pre>
*/
public class TextInputLayout extends LinearLayout {
private static final int ANIMATION_DURATION = 200;
private static final int INVALID_MAX_LENGTH = -1;
+ private static final String LOG_TAG = "TextInputLayout";
+
private EditText mEditText;
private boolean mHintEnabled;
@@ -77,6 +105,7 @@
private TextView mErrorView;
private int mErrorTextAppearance;
private boolean mErrorShown;
+ private CharSequence mError;
private boolean mCounterEnabled;
private TextView mCounterView;
@@ -196,12 +225,22 @@
if (mEditText != null) {
throw new IllegalArgumentException("We already have an EditText, can only have one");
}
+
+ if (!(editText instanceof TextInputEditText)) {
+ Log.i(LOG_TAG, "EditText added is not a TextInputEditText. Please switch to using that"
+ + " class instead.");
+ }
+
mEditText = editText;
// Use the EditText's typeface, and it's text size for our expanded text
mCollapsingTextHelper.setTypefaces(mEditText.getTypeface());
mCollapsingTextHelper.setExpandedTextSize(mEditText.getTextSize());
- mCollapsingTextHelper.setExpandedTextGravity(mEditText.getGravity());
+
+ final int editTextGravity = mEditText.getGravity();
+ mCollapsingTextHelper.setCollapsedTextGravity(
+ Gravity.TOP | (editTextGravity & GravityCompat.RELATIVE_HORIZONTAL_GRAVITY_MASK));
+ mCollapsingTextHelper.setExpandedTextGravity(editTextGravity);
// Add a TextWatcher so that we know when the text input has changed
mEditText.addTextChangedListener(new TextWatcher() {
@@ -274,8 +313,6 @@
if (mCounterOverflowed && mCounterView != null) {
mCollapsingTextHelper.setCollapsedTextColor(mCounterView.getCurrentTextColor());
- } else if (isErrorShowing && mErrorView != null) {
- mCollapsingTextHelper.setCollapsedTextColor(mErrorView.getCurrentTextColor());
} else if (isFocused && mFocusedTextColor != null) {
mCollapsingTextHelper.setCollapsedTextColor(mFocusedTextColor.getDefaultColor());
} else if (mDefaultTextColor != null) {
@@ -455,7 +492,16 @@
if (enabled) {
mErrorView = new TextView(getContext());
- mErrorView.setTextAppearance(getContext(), mErrorTextAppearance);
+ try {
+ mErrorView.setTextAppearance(getContext(), mErrorTextAppearance);
+ } catch (Exception e) {
+ // Probably caused by our theme not extending from Theme.Design*. Instead
+ // we manually set something appropriate
+ mErrorView.setTextAppearance(getContext(),
+ android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
+ mErrorView.setTextColor(ContextCompat.getColor(
+ getContext(), R.color.design_textinput_error_color_light));
+ }
mErrorView.setVisibility(INVISIBLE);
ViewCompat.setAccessibilityLiveRegion(mErrorView,
ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
@@ -492,7 +538,9 @@
*
* @see #getError()
*/
- public void setError(@Nullable CharSequence error) {
+ public void setError(@Nullable final CharSequence error) {
+ mError = error;
+
if (!mErrorEnabled) {
if (TextUtils.isEmpty(error)) {
// If error isn't enabled, and the error is empty, just return
@@ -502,15 +550,17 @@
setErrorEnabled(true);
}
- if (!TextUtils.isEmpty(error)) {
- boolean animate;
- if (TextUtils.equals(error, mErrorView.getText())) {
- // We've been given the same error message, so only animate if needed
- animate = !mErrorView.isShown() || ViewCompat.getAlpha(mErrorView) < 1f;
- } else {
- animate = true;
- mErrorView.setText(error);
- }
+ // Only animate if we've been laid out already and we have a different error
+ final boolean animate = ViewCompat.isLaidOut(this)
+ && !TextUtils.equals(mErrorView.getText(), error);
+ mErrorShown = !TextUtils.isEmpty(error);
+
+ // Cancel any on-going animation
+ ViewCompat.animate(mErrorView).cancel();
+
+ if (mErrorShown) {
+ mErrorView.setText(error);
+ mErrorView.setVisibility(VISIBLE);
if (animate) {
if (ViewCompat.getAlpha(mErrorView) == 1f) {
@@ -526,34 +576,34 @@
public void onAnimationStart(View view) {
view.setVisibility(VISIBLE);
}
- })
- .start();
+ }).start();
+ } else {
+ // Set alpha to 1f, just in case
+ ViewCompat.setAlpha(mErrorView, 1f);
}
-
- // Set the EditText's background tint to the error color
- mErrorShown = true;
- updateEditTextBackground();
- updateLabelState(true);
} else {
if (mErrorView.getVisibility() == VISIBLE) {
- ViewCompat.animate(mErrorView)
- .alpha(0f)
- .setDuration(ANIMATION_DURATION)
- .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
- .setListener(new ViewPropertyAnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(View view) {
- view.setVisibility(INVISIBLE);
-
- updateLabelState(true);
- }
- }).start();
-
- // Restore the 'original' tint, using colorControlNormal and colorControlActivated
- mErrorShown = false;
- updateEditTextBackground();
+ if (animate) {
+ ViewCompat.animate(mErrorView)
+ .alpha(0f)
+ .setDuration(ANIMATION_DURATION)
+ .setInterpolator(AnimationUtils.FAST_OUT_LINEAR_IN_INTERPOLATOR)
+ .setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(View view) {
+ mErrorView.setText(error);
+ view.setVisibility(INVISIBLE);
+ }
+ }).start();
+ } else {
+ mErrorView.setText(error);
+ mErrorView.setVisibility(INVISIBLE);
+ }
}
}
+
+ updateEditTextBackground();
+ updateLabelState(true);
}
/**
@@ -566,9 +616,16 @@
if (enabled) {
mCounterView = new TextView(getContext());
mCounterView.setMaxLines(1);
- mCounterView.setTextAppearance(getContext(), mCounterTextAppearance);
- ViewCompat.setAccessibilityLiveRegion(mCounterView,
- ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE);
+ try {
+ mCounterView.setTextAppearance(getContext(), mCounterTextAppearance);
+ } catch (Exception e) {
+ // Probably caused by our theme not extending from Theme.Design*. Instead
+ // we manually set something appropriate
+ mCounterView.setTextAppearance(getContext(),
+ android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
+ mCounterView.setTextColor(ContextCompat.getColor(
+ getContext(), R.color.design_textinput_error_color_light));
+ }
addIndicator(mCounterView, -1);
if (mEditText == null) {
updateCounter(0);
@@ -646,11 +703,15 @@
private void updateEditTextBackground() {
ensureBackgroundDrawableStateWorkaround();
- final Drawable editTextBackground = mEditText.getBackground();
+ Drawable editTextBackground = mEditText.getBackground();
if (editTextBackground == null) {
return;
}
+ if (android.support.v7.widget.DrawableUtils.canSafelyMutateDrawable(editTextBackground)) {
+ editTextBackground = editTextBackground.mutate();
+ }
+
if (mErrorShown && mErrorView != null) {
// Set a color filter of the error color
editTextBackground.setColorFilter(
@@ -664,12 +725,42 @@
} else {
// Else reset the color filter and refresh the drawable state so that the
// normal tint is used
- editTextBackground.clearColorFilter();
+ clearColorFilter(editTextBackground);
mEditText.refreshDrawableState();
}
}
+ private static void clearColorFilter(@NonNull Drawable drawable) {
+ drawable.clearColorFilter();
+
+ if (Build.VERSION.SDK_INT == 21 || Build.VERSION.SDK_INT == 22) {
+ // API 21 + 22 have an issue where clearing a color filter on a DrawableContainer
+ // will not propagate to all of its children. To workaround this we unwrap the drawable
+ // to find any DrawableContainers, and then unwrap those to clear the filter on its
+ // children manually
+ if (drawable instanceof InsetDrawable) {
+ clearColorFilter(((InsetDrawable) drawable).getDrawable());
+ } else if (drawable instanceof DrawableWrapper) {
+ clearColorFilter(((DrawableWrapper) drawable).getWrappedDrawable());
+ } else if (drawable instanceof DrawableContainer) {
+ final DrawableContainer container = (DrawableContainer) drawable;
+ final DrawableContainer.DrawableContainerState state =
+ (DrawableContainer.DrawableContainerState) container.getConstantState();
+ if (state != null) {
+ for (int i = 0, count = state.getChildCount(); i < count; i++) {
+ clearColorFilter(state.getChild(i));
+ }
+ }
+ }
+ }
+ }
+
private void ensureBackgroundDrawableStateWorkaround() {
+ final int sdk = Build.VERSION.SDK_INT;
+ if (sdk != 21 && sdk != 22) {
+ // The workaround is only required on API 21-22
+ return;
+ }
final Drawable bg = mEditText.getBackground();
if (bg == null) {
return;
@@ -701,6 +792,68 @@
}
}
+ static class SavedState extends AbsSavedState {
+ CharSequence error;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source, ClassLoader loader) {
+ super(source, loader);
+ error = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(source);
+
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ TextUtils.writeToParcel(error, dest, flags);
+ }
+
+ @Override
+ public String toString() {
+ return "TextInputLayout.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " error=" + error + "}";
+ }
+
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ if (mErrorShown) {
+ ss.error = getError();
+ }
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ setError(ss.error);
+ requestLayout();
+ }
+
/**
* Returns the error message that was set to be displayed with
* {@link #setError(CharSequence)}, or <code>null</code> if no error was set
@@ -710,10 +863,7 @@
*/
@Nullable
public CharSequence getError() {
- if (mErrorEnabled && mErrorView != null && mErrorView.getVisibility() == VISIBLE) {
- return mErrorView.getText();
- }
- return null;
+ return mErrorEnabled ? mError : null;
}
/**
@@ -818,15 +968,6 @@
mAnimator.start();
}
- private int getThemeAttrColor(int attr) {
- TypedValue tv = new TypedValue();
- if (getContext().getTheme().resolveAttribute(attr, tv, true)) {
- return tv.data;
- } else {
- return Color.MAGENTA;
- }
- }
-
private class TextInputAccessibilityDelegate extends AccessibilityDelegateCompat {
@Override
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
diff --git a/design/src/android/support/design/widget/ThemeUtils.java b/design/src/android/support/design/widget/ThemeUtils.java
index 327a44d..ffdc3f4 100644
--- a/design/src/android/support/design/widget/ThemeUtils.java
+++ b/design/src/android/support/design/widget/ThemeUtils.java
@@ -22,7 +22,9 @@
class ThemeUtils {
- private static final int[] APPCOMPAT_CHECK_ATTRS = { R.attr.colorPrimary };
+ private static final int[] APPCOMPAT_CHECK_ATTRS = {
+ android.support.v7.appcompat.R.attr.colorPrimary
+ };
static void checkAppCompatTheme(Context context) {
TypedArray a = context.obtainStyledAttributes(APPCOMPAT_CHECK_ATTRS);
diff --git a/design/src/android/support/design/widget/ViewGroupUtils.java b/design/src/android/support/design/widget/ViewGroupUtils.java
index 47cc080..a5d2f8a 100644
--- a/design/src/android/support/design/widget/ViewGroupUtils.java
+++ b/design/src/android/support/design/widget/ViewGroupUtils.java
@@ -31,6 +31,10 @@
@Override
public void offsetDescendantRect(ViewGroup parent, View child, Rect rect) {
parent.offsetDescendantRectToMyCoords(child, rect);
+ // View#offsetDescendantRectToMyCoords includes scroll offsets of the last child.
+ // We need to reverse it here so that we get the rect of the view itself rather
+ // than its content.
+ rect.offset(child.getScrollX(), child.getScrollY());
}
}
diff --git a/design/src/android/support/design/widget/ViewOffsetBehavior.java b/design/src/android/support/design/widget/ViewOffsetBehavior.java
index 3ffc744..541de69 100644
--- a/design/src/android/support/design/widget/ViewOffsetBehavior.java
+++ b/design/src/android/support/design/widget/ViewOffsetBehavior.java
@@ -38,8 +38,8 @@
@Override
public boolean onLayoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
- // First let the parent lay it out
- parent.onLayoutChild(child, layoutDirection);
+ // First let lay the child out
+ layoutChild(parent, child, layoutDirection);
if (mViewOffsetHelper == null) {
mViewOffsetHelper = new ViewOffsetHelper(child);
@@ -58,6 +58,11 @@
return true;
}
+ protected void layoutChild(CoordinatorLayout parent, V child, int layoutDirection) {
+ // Let the parent lay it out by default
+ parent.onLayoutChild(child, layoutDirection);
+ }
+
public boolean setTopAndBottomOffset(int offset) {
if (mViewOffsetHelper != null) {
return mViewOffsetHelper.setTopAndBottomOffset(offset);
diff --git a/design/src/android/support/design/widget/ViewOffsetHelper.java b/design/src/android/support/design/widget/ViewOffsetHelper.java
index 1254f17..fc16d28 100644
--- a/design/src/android/support/design/widget/ViewOffsetHelper.java
+++ b/design/src/android/support/design/widget/ViewOffsetHelper.java
@@ -16,10 +16,8 @@
package android.support.design.widget;
-import android.os.Build;
import android.support.v4.view.ViewCompat;
import android.view.View;
-import android.view.ViewParent;
/**
* Utility helper for moving a {@link android.view.View} around using
@@ -54,21 +52,6 @@
private void updateOffsets() {
ViewCompat.offsetTopAndBottom(mView, mOffsetTop - (mView.getTop() - mLayoutTop));
ViewCompat.offsetLeftAndRight(mView, mOffsetLeft - (mView.getLeft() - mLayoutLeft));
-
- // Manually invalidate the view and parent to make sure we get drawn pre-M
- if (Build.VERSION.SDK_INT < 23) {
- tickleInvalidationFlag(mView);
- final ViewParent vp = mView.getParent();
- if (vp instanceof View) {
- tickleInvalidationFlag((View) vp);
- }
- }
- }
-
- private static void tickleInvalidationFlag(View view) {
- final float y = ViewCompat.getTranslationY(view);
- ViewCompat.setTranslationY(view, y + 1);
- ViewCompat.setTranslationY(view, y);
}
/**
diff --git a/design/src/android/support/design/widget/ViewUtils.java b/design/src/android/support/design/widget/ViewUtils.java
index 29a4522..c27716c 100644
--- a/design/src/android/support/design/widget/ViewUtils.java
+++ b/design/src/android/support/design/widget/ViewUtils.java
@@ -17,7 +17,6 @@
package android.support.design.widget;
import android.os.Build;
-import android.view.View;
class ViewUtils {
@@ -31,39 +30,6 @@
}
};
- private interface ViewUtilsImpl {
- void setBoundsViewOutlineProvider(View view);
- }
-
- private static class ViewUtilsImplBase implements ViewUtilsImpl {
- @Override
- public void setBoundsViewOutlineProvider(View view) {
- // no-op
- }
- }
-
- private static class ViewUtilsImplLollipop implements ViewUtilsImpl {
- @Override
- public void setBoundsViewOutlineProvider(View view) {
- ViewUtilsLollipop.setBoundsViewOutlineProvider(view);
- }
- }
-
- private static final ViewUtilsImpl IMPL;
-
- static {
- final int version = Build.VERSION.SDK_INT;
- if (version >= 21) {
- IMPL = new ViewUtilsImplLollipop();
- } else {
- IMPL = new ViewUtilsImplBase();
- }
- }
-
- static void setBoundsViewOutlineProvider(View view) {
- IMPL.setBoundsViewOutlineProvider(view);
- }
-
static ValueAnimatorCompat createAnimator() {
return DEFAULT_ANIMATOR_CREATOR.createAnimator();
}
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
new file mode 100755
index 0000000..aee8877
--- /dev/null
+++ b/design/tests/AndroidManifest.xml
@@ -0,0 +1,89 @@
+<?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"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling"/>
+
+ <application
+ android:supportsRtl="true"
+ 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"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.SnackbarActivity"/>
+
+ <activity
+ android:theme="@style/Theme.TranslucentStatus"
+ android:name="android.support.design.widget.DynamicCoordinatorLayoutActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.TabLayoutPoolingActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.SnackbarBucketTestsActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.BottomSheetBehaviorActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.BottomSheetBehaviorWithInsetsActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.BottomSheetDialogActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.CoordinatorLayoutActivity"/>
+
+ <activity
+ android:name="android.support.design.widget.FloatingActionButtonActivity"
+ android:theme="@style/Theme.AppCompat.Light.NoActionBar"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.NavigationViewActivity"/>
+
+ <activity
+ android:theme="@style/Theme.AppCompat.NoActionBar"
+ android:name="android.support.design.widget.TextInputLayoutActivity"/>
+
+ <activity
+ android:name="android.support.v7.app.AppCompatActivity"/>
+
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.design.test"/>
+
+</manifest>
diff --git a/design/tests/NO_DOCS b/design/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/design/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/design/tests/res/color/color_state_list_lilac.xml b/design/tests/res/color/color_state_list_lilac.xml
new file mode 100644
index 0000000..f0b2791
--- /dev/null
+++ b/design/tests/res/color/color_state_list_lilac.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/lilac_disabled" />
+ <item android:color="@color/lilac_default"/>
+</selector>
+
diff --git a/design/tests/res/color/color_state_list_red_translucent.xml b/design/tests/res/color/color_state_list_red_translucent.xml
new file mode 100644
index 0000000..fdf8b2b
--- /dev/null
+++ b/design/tests/res/color/color_state_list_red_translucent.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="@color/red_translucent"/>
+</selector>
+
diff --git a/design/tests/res/color/color_state_list_sand.xml b/design/tests/res/color/color_state_list_sand.xml
new file mode 100644
index 0000000..eb472c2
--- /dev/null
+++ b/design/tests/res/color/color_state_list_sand.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/sand_disabled" />
+ <item android:state_checked="true" android:color="@color/sand_checked" />
+ <item android:color="@color/sand_default" />
+</selector>
+
diff --git a/design/tests/res/drawable-mdpi/test_drawable_blue.png b/design/tests/res/drawable-mdpi/test_drawable_blue.png
new file mode 100644
index 0000000..6350c14
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_blue.png
Binary files differ
diff --git a/design/tests/res/drawable-mdpi/test_drawable_green.png b/design/tests/res/drawable-mdpi/test_drawable_green.png
new file mode 100644
index 0000000..e3afddbd
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_green.png
Binary files differ
diff --git a/design/tests/res/drawable-mdpi/test_drawable_red.png b/design/tests/res/drawable-mdpi/test_drawable_red.png
new file mode 100644
index 0000000..d95db88
--- /dev/null
+++ b/design/tests/res/drawable-mdpi/test_drawable_red.png
Binary files differ
diff --git a/design/tests/res/drawable-nodpi/photo.jpg b/design/tests/res/drawable-nodpi/photo.jpg
new file mode 100644
index 0000000..d5a2ef0
--- /dev/null
+++ b/design/tests/res/drawable-nodpi/photo.jpg
Binary files differ
diff --git a/design/tests/res/drawable-xxhdpi/ic_add.png b/design/tests/res/drawable-xxhdpi/ic_add.png
new file mode 100644
index 0000000..a84106b
--- /dev/null
+++ b/design/tests/res/drawable-xxhdpi/ic_add.png
Binary files differ
diff --git a/design/tests/res/drawable/test_background_blue.xml b/design/tests/res/drawable/test_background_blue.xml
new file mode 100644
index 0000000..fe4bca2
--- /dev/null
+++ b/design/tests/res/drawable/test_background_blue.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_background_green.xml b/design/tests/res/drawable/test_background_green.xml
new file mode 100644
index 0000000..b90d9bc
--- /dev/null
+++ b/design/tests/res/drawable/test_background_green.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/design/tests/res/drawable/test_drawable_state_list.xml b/design/tests/res/drawable/test_drawable_state_list.xml
new file mode 100644
index 0000000..f125913
--- /dev/null
+++ b/design/tests/res/drawable/test_drawable_state_list.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true" android:drawable="@drawable/test_background_blue" />
+ <item android:drawable="@drawable/test_background_green" />
+</selector>
+
diff --git a/design/tests/res/drawable/vector_icon.xml b/design/tests/res/drawable/vector_icon.xml
new file mode 100644
index 0000000..19e97ee
--- /dev/null
+++ b/design/tests/res/drawable/vector_icon.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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/design/tests/res/layout/action_layout.xml b/design/tests/res/layout/action_layout.xml
new file mode 100644
index 0000000..8c4dc05
--- /dev/null
+++ b/design/tests/res/layout/action_layout.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.v7.widget.SwitchCompat
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/toggle"
+ android:orientation="vertical"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:gravity="center_vertical"/>
diff --git a/design/tests/res/layout/activity_coordinator_layout.xml b/design/tests/res/layout/activity_coordinator_layout.xml
new file mode 100644
index 0000000..2aa36f5
--- /dev/null
+++ b/design/tests/res/layout/activity_coordinator_layout.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
+</FrameLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml
new file mode 100644
index 0000000..151e213
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_bottom.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_anchor="@+id/app_bar"
+ app:layout_anchorGravity="bottom|center_horizontal"
+ android:src="@drawable/ic_add"
+ android:layout_marginBottom="@dimen/fab_margin"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml
new file mode 100644
index 0000000..bb139d6
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_left.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_anchor="@+id/app_bar"
+ app:layout_anchorGravity="bottom|left"
+ android:src="@drawable/ic_add"
+ android:layout_marginLeft="@dimen/fab_margin"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml
new file mode 100644
index 0000000..b67dbd6
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_right.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_anchor="@+id/app_bar"
+ app:layout_anchorGravity="bottom|right"
+ android:src="@drawable/ic_add"
+ android:layout_marginRight="@dimen/fab_margin"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml b/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml
new file mode 100644
index 0000000..ce20235
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_anchored_fab_margin_top.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_anchor="@+id/app_bar"
+ app:layout_anchorGravity="bottom|center_horizontal"
+ android:src="@drawable/ic_add"
+ android:layout_marginTop="@dimen/fab_margin"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml
new file mode 100644
index 0000000..6c8b4b0
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml
new file mode 100644
index 0000000..9c5797b
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_pin_with_fab.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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">
+
+ <include layout="@layout/design_content_appbar_toolbar_collapse_pin" />
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ app:layout_anchor="@+id/app_bar"
+ app:layout_anchorGravity="bottom|right|end"
+ android:src="@drawable/ic_add"
+ android:layout_marginRight="16dp"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml
new file mode 100644
index 0000000..7101892
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_scroll.xml
@@ -0,0 +1,51 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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:fitsSystemWindows="true">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/appbar_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ android:fitsSystemWindows="true">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/collapsing_app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll|enterAlways|enterAlwaysCollapsed"
+ android:fitsSystemWindows="true">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_collapseMode="pin"/>
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview" />
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml b/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml
new file mode 100644
index 0000000..f55f470
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_collapse_with_image.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/appbar_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/collapsing_app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+ <ImageView
+ android:id="@+id/app_bar_image"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_collapseMode="parallax"
+ android:src="@drawable/photo"
+ android:scaleType="centerCrop"/>
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ app:layout_collapseMode="pin"/>
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml
new file mode 100644
index 0000000..b886561
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_fitsystemwindows_parent.xml
@@ -0,0 +1,42 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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:fitsSystemWindows="true">
+
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_scrollFlags="scroll|enterAlways"/>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml
new file mode 100644
index 0000000..c13dd7a
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_pinned.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_scrollFlags="scroll|enterAlways"/>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:tabMode="scrollable"/>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml
new file mode 100644
index 0000000..ffcfd6d
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_scrollFlags="scroll|enterAlways"/>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_scrollFlags="scroll|enterAlways"
+ app:tabMode="scrollable"/>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml
new file mode 100644
index 0000000..27272f2
--- /dev/null
+++ b/design/tests/res/layout/design_appbar_toolbar_scroll_tabs_scroll_snap.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ 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.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_height="wrap_content"
+ android:layout_width="match_parent"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_scrollFlags="scroll|enterAlways|snap"/>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:layout_scrollFlags="scroll|enterAlways|snap"
+ app:tabMode="scrollable"/>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview"/>
+
+</android.support.design.widget.CoordinatorLayout>
+
diff --git a/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml b/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml
new file mode 100644
index 0000000..1a8102f
--- /dev/null
+++ b/design/tests/res/layout/design_content_appbar_toolbar_collapse_pin.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<merge
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <android.support.design.widget.AppBarLayout
+ android:id="@+id/app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/appbar_height"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar">
+
+ <android.support.design.widget.CollapsingToolbarLayout
+ android:id="@+id/collapsing_app_bar"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_scrollFlags="scroll|exitUntilCollapsed">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_height="?attr/actionBarSize"
+ android:layout_width="match_parent"
+ app:layout_collapseMode="pin"/>
+
+ </android.support.design.widget.CollapsingToolbarLayout>
+
+ </android.support.design.widget.AppBarLayout>
+
+ <include layout="@layout/include_appbar_scrollview" />
+</merge>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_fab.xml b/design/tests/res/layout/design_fab.xml
new file mode 100644
index 0000000..cecd5a4
--- /dev/null
+++ b/design/tests/res/layout/design_fab.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.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <!-- Dummy view to steal the focus -->
+ <ImageButton
+ android:id="@+id/dummy_focus_vew"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"/>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab_standard"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_margin="16dp"
+ android:src="@drawable/ic_add"/>
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab_tint"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_margin="16dp"
+ android:src="@drawable/ic_add"
+ app:backgroundTint="#FF00FF"/>
+
+</LinearLayout>
diff --git a/design/tests/res/layout/design_navigation_view.xml b/design/tests/res/layout/design_navigation_view.xml
new file mode 100644
index 0000000..1789843
--- /dev/null
+++ b/design/tests/res/layout/design_navigation_view.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.v4.widget.DrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- android:layout_gravity="start" tells DrawerLayout to treat
+ this as a sliding drawer on the starting side, which is
+ left for left-to-right locales. The navigation view extends
+ the full height of the container. A
+ solid background is used for contrast with the content view.
+ android:fitsSystemWindows="true" tells the system to have
+ DrawerLayout span the full height of the screen, including the
+ system status bar on Lollipop+ versions of the plaform. -->
+ <android.support.design.widget.NavigationView
+ android:id="@+id/start_drawer"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true"
+ app:menu="@menu/navigation_view_content"
+ app:itemIconTint="@color/emerald_translucent"
+ app:itemTextColor="@color/emerald_text"
+ app:itemBackground="@color/sand_default"
+ app:itemTextAppearance="@style/TextMediumStyle" />
+
+</android.support.v4.widget.DrawerLayout>
+
diff --git a/design/tests/res/layout/design_navigation_view_header1.xml b/design/tests/res/layout/design_navigation_view_header1.xml
new file mode 100644
index 0000000..2fd6f20
--- /dev/null
+++ b/design/tests/res/layout/design_navigation_view_header1.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header1"
+ android:layout_width="match_parent"
+ android:layout_height="120dip"
+ android:background="@color/test_red" />
+
diff --git a/design/tests/res/layout/design_navigation_view_header2.xml b/design/tests/res/layout/design_navigation_view_header2.xml
new file mode 100644
index 0000000..77ad32a
--- /dev/null
+++ b/design/tests/res/layout/design_navigation_view_header2.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header2"
+ android:layout_width="match_parent"
+ android:layout_height="100dip"
+ android:background="@color/test_blue" />
+
diff --git a/design/tests/res/layout/design_navigation_view_header3.xml b/design/tests/res/layout/design_navigation_view_header3.xml
new file mode 100644
index 0000000..b1fd676
--- /dev/null
+++ b/design/tests/res/layout/design_navigation_view_header3.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/header3"
+ android:layout_width="match_parent"
+ android:layout_height="80dip"
+ android:background="@color/test_green" />
+
diff --git a/design/tests/res/layout/design_snackbar_behavior_annotation.xml b/design/tests/res/layout/design_snackbar_behavior_annotation.xml
new file mode 100644
index 0000000..9529770
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_annotation.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined as an annotation on custom class. -->
+<android.support.design.widget.CoordinatorLayout
+ 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.support.design.custom.CustomTextView
+ android:id="@+id/text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|center_horizontal"
+ android:text="text1"
+ android:textSize="18dip"
+ android:textStyle="bold"
+ android:padding="12dip"
+ app:textAllCaps="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml b/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml
new file mode 100644
index 0000000..88928bb
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_layout_attr.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined as a child attribute in XML. -->
+<android.support.design.widget.CoordinatorLayout
+ 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.support.v7.widget.AppCompatTextView
+ android:id="@+id/text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:text="text1"
+ android:textSize="18dip"
+ android:textStyle="bold"
+ android:padding="12dip"
+ app:textAllCaps="true"
+ app:layout_behavior="android.support.design.custom.TestFloatingBehavior" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_behavior_runtime.xml b/design/tests/res/layout/design_snackbar_behavior_runtime.xml
new file mode 100644
index 0000000..85415ea
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_behavior_runtime.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Layout for testing CoordinatorLayout.Behavior defined via a runtime API call. -->
+<android.support.design.widget.CoordinatorLayout
+ 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.support.v7.widget.AppCompatTextView
+ android:id="@+id/text"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|start"
+ android:text="text1"
+ android:textSize="18dip"
+ android:textStyle="bold"
+ android:padding="12dip"
+ app:textAllCaps="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_snackbar_with_fab.xml b/design/tests/res/layout/design_snackbar_with_fab.xml
new file mode 100644
index 0000000..a06dc1b
--- /dev/null
+++ b/design/tests/res/layout/design_snackbar_with_fab.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.design.widget.FloatingActionButton
+ android:id="@+id/fab"
+ android:layout_height="wrap_content"
+ android:layout_width="wrap_content"
+ android:layout_gravity="bottom|end"
+ android:layout_margin="16dp"
+ android:src="@drawable/ic_add"
+ android:clickable="true" />
+
+</android.support.design.widget.CoordinatorLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_tab_item_custom.xml b/design/tests/res/layout/design_tab_item_custom.xml
new file mode 100644
index 0000000..f6ba30b
--- /dev/null
+++ b/design/tests/res/layout/design_tab_item_custom.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/my_custom_tab"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
\ No newline at end of file
diff --git a/design/tests/res/layout/design_tabs.xml b/design/tests/res/layout/design_tabs.xml
new file mode 100644
index 0000000..fbb24bd
--- /dev/null
+++ b/design/tests/res/layout/design_tabs.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
diff --git a/design/tests/res/layout/design_tabs_items.xml b/design/tests/res/layout/design_tabs_items.xml
new file mode 100644
index 0000000..e67f3b6
--- /dev/null
+++ b/design/tests/res/layout/design_tabs_items.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <android.support.design.widget.TabItem
+ android:text="@string/tab_layout_text"/>
+
+ <android.support.design.widget.TabItem
+ android:icon="@android:drawable/star_on"/>
+
+ <android.support.design.widget.TabItem
+ android:layout="@layout/design_tab_item_custom"/>
+
+</android.support.design.widget.TabLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_tabs_twice.xml b/design/tests/res/layout/design_tabs_twice.xml
new file mode 100644
index 0000000..1421c10
--- /dev/null
+++ b/design/tests/res/layout/design_tabs_twice.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs_1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <android.support.design.widget.TabItem
+ android:text="@string/tab_layout_text"/>
+
+ <android.support.design.widget.TabItem
+ android:icon="@android:drawable/star_on"/>
+
+ <android.support.design.widget.TabItem
+ android:layout="@layout/design_tab_item_custom"/>
+
+ </android.support.design.widget.TabLayout>
+
+ <android.support.design.widget.TabLayout
+ android:id="@+id/tabs_2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content" />
+
+</LinearLayout>
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..877c690
--- /dev/null
+++ b/design/tests/res/layout/design_tabs_viewpager.xml
@@ -0,0 +1,43 @@
+<?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:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize"
+ android:background="?attr/colorPrimary"
+ android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
+ app:contentInsetStart="72dp"
+ app:popupTheme="@style/ThemeOverlay.AppCompat.Light"/>
+
+ <include layout="@layout/tab_layout_unbound" />
+
+ <android.support.v4.view.ViewPager
+ android:id="@+id/tabs_viewpager"
+ android:layout_width="match_parent"
+ android:layout_height="0px"
+ android:layout_weight="1"/>
+
+</LinearLayout>
diff --git a/design/tests/res/layout/design_tabs_with_non_tabitems.xml b/design/tests/res/layout/design_tabs_with_non_tabitems.xml
new file mode 100644
index 0000000..cb96715
--- /dev/null
+++ b/design/tests/res/layout/design_tabs_with_non_tabitems.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <TextView
+ android:text="@string/tab_layout_text"/>
+
+ <TextView
+ android:src="@android:drawable/star_on"/>
+
+ <include
+ layout="@layout/design_tab_item_custom"/>
+
+</android.support.design.widget.TabLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/design_text_input.xml b/design/tests/res/layout/design_text_input.xml
new file mode 100644
index 0000000..1312b50
--- /dev/null
+++ b/design/tests/res/layout/design_text_input.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<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:padding="16dp">
+
+ <android.support.design.widget.TextInputLayout
+ android:id="@+id/textinput"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ app:errorEnabled="true">
+
+ <android.support.design.widget.TextInputEditText
+ android:id="@+id/textinput_edittext"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:hint="@string/textinput_hint"/>
+
+ </android.support.design.widget.TextInputLayout>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/design/tests/res/layout/dynamic_coordinator_layout.xml b/design/tests/res/layout/dynamic_coordinator_layout.xml
new file mode 100644
index 0000000..a2253c78
--- /dev/null
+++ b/design/tests/res/layout/dynamic_coordinator_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- ViewStub that will be inflated to a CoordinatorLayout at test runtime. -->
+<ViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/coordinator_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:inflatedId="@+id/coordinator_layout"
+ android:fitsSystemWindows="true" />
diff --git a/design/tests/res/layout/frame_layout.xml b/design/tests/res/layout/frame_layout.xml
new file mode 100644
index 0000000..45b3474
--- /dev/null
+++ b/design/tests/res/layout/frame_layout.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/design/tests/res/layout/include_appbar_scrollview.xml b/design/tests/res/layout/include_appbar_scrollview.xml
new file mode 100644
index 0000000..529a38b
--- /dev/null
+++ b/design/tests/res/layout/include_appbar_scrollview.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.v4.widget.NestedScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/scrolling_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_behavior="@string/appbar_scrolling_view_behavior">
+
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="1000dp"
+ android:orientation="vertical">
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:text="@string/scroll_top" />
+
+ <TextView
+ android:id="@+id/textview_dialogue"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:padding="16dp"/>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:gravity="center_horizontal"
+ android:text="@string/scroll_bottom" />
+
+ </LinearLayout>
+
+</android.support.v4.widget.NestedScrollView>
\ No newline at end of file
diff --git a/design/tests/res/layout/tab_layout_bound_max.xml b/design/tests/res/layout/tab_layout_bound_max.xml
new file mode 100644
index 0000000..90a0943
--- /dev/null
+++ b/design/tests/res/layout/tab_layout_bound_max.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ 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"
+ app:tabMaxWidth="@dimen/tab_width_limit_medium" />
diff --git a/design/tests/res/layout/tab_layout_bound_min.xml b/design/tests/res/layout/tab_layout_bound_min.xml
new file mode 100644
index 0000000..c8c8554
--- /dev/null
+++ b/design/tests/res/layout/tab_layout_bound_min.xml
@@ -0,0 +1,28 @@
+<?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.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ 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"
+ app:tabMinWidth="@dimen/tab_width_limit_medium" />
diff --git a/design/tests/res/layout/tab_layout_bound_minmax.xml b/design/tests/res/layout/tab_layout_bound_minmax.xml
new file mode 100644
index 0000000..5f76659
--- /dev/null
+++ b/design/tests/res/layout/tab_layout_bound_minmax.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ 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"
+ app:tabMinWidth="@dimen/tab_width_limit_small"
+ app:tabMaxWidth="@dimen/tab_width_limit_large" />
diff --git a/design/tests/res/layout/tab_layout_unbound.xml b/design/tests/res/layout/tab_layout_unbound.xml
new file mode 100644
index 0000000..4a49916
--- /dev/null
+++ b/design/tests/res/layout/tab_layout_unbound.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.
+-->
+
+<android.support.design.widget.TabLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ 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"/>
diff --git a/design/tests/res/layout/test_design_bottom_sheet_behavior.xml b/design/tests/res/layout/test_design_bottom_sheet_behavior.xml
new file mode 100644
index 0000000..570a7f8
--- /dev/null
+++ b/design/tests/res/layout/test_design_bottom_sheet_behavior.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator"
+ 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">
+
+ <LinearLayout
+ android:id="@+id/bottom_sheet"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="#00f"
+ app:layout_behavior="@string/bottom_sheet_behavior"
+ app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
+ app:behavior_hideable="true">
+
+ </LinearLayout>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/test_design_bottom_sheet_behavior_with_insets.xml b/design/tests/res/layout/test_design_bottom_sheet_behavior_with_insets.xml
new file mode 100644
index 0000000..442654c
--- /dev/null
+++ b/design/tests/res/layout/test_design_bottom_sheet_behavior_with_insets.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.design.widget.CoordinatorLayout
+ android:id="@+id/coordinator"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:fitsSystemWindows="true"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <LinearLayout
+ android:id="@+id/bottom_sheet"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:background="#00f"
+ app:layout_behavior="@string/bottom_sheet_behavior"
+ app:behavior_peekHeight="@dimen/bottom_sheet_peek_height"
+ app:behavior_hideable="true">
+
+ <View
+ android:id="@+id/bottom_sheet_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="#f00"/>
+
+ </LinearLayout>
+
+</android.support.design.widget.CoordinatorLayout>
diff --git a/design/tests/res/layout/test_design_snackbar.xml b/design/tests/res/layout/test_design_snackbar.xml
new file mode 100644
index 0000000..b7bab1e
--- /dev/null
+++ b/design/tests/res/layout/test_design_snackbar.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.design.widget.CoordinatorLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/col"/>
\ No newline at end of file
diff --git a/design/tests/res/menu/navigation_view_content.xml b/design/tests/res/menu/navigation_view_content.xml
new file mode 100644
index 0000000..758334f
--- /dev/null
+++ b/design/tests/res/menu/navigation_view_content.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+ 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.
+-->
+<menu
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <group android:checkableBehavior="single">
+ <item android:id="@+id/destination_home"
+ android:title="@string/navigate_home"
+ android:icon="@drawable/test_drawable_red" />
+ <item android:id="@+id/destination_profile"
+ android:title="@string/navigate_profile"
+ android:icon="@drawable/test_drawable_green" />
+ <item android:id="@+id/destination_people"
+ android:title="@string/navigate_people"
+ android:icon="@drawable/test_drawable_blue"
+ app:actionLayout="@layout/action_layout" />
+ <item android:id="@+id/destination_settings"
+ android:title="@string/navigate_settings" />
+ </group>
+</menu>
diff --git a/design/tests/res/values/colors.xml b/design/tests/res/values/colors.xml
new file mode 100644
index 0000000..ea8da2a
--- /dev/null
+++ b/design/tests/res/values/colors.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <color name="emerald_translucent">#8020A060</color>
+ <color name="emerald_text">#30B050</color>
+ <color name="red_translucent">#90FF2040</color>
+
+ <color name="lilac_default">#F080F0</color>
+ <color name="lilac_disabled">#F0A0FF</color>
+ <color name="sand_default">#F0B000</color>
+ <color name="sand_disabled">#FFC080</color>
+ <color name="sand_checked">#FFD0A0</color>
+
+ <color name="test_red">#FF6030</color>
+ <color name="test_green">#50E080</color>
+ <color name="test_blue">#3050CF</color>
+</resources>
diff --git a/design/tests/res/values/dimens.xml b/design/tests/res/values/dimens.xml
new file mode 100644
index 0000000..134463c
--- /dev/null
+++ b/design/tests/res/values/dimens.xml
@@ -0,0 +1,35 @@
+<?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>
+ <dimen name="tab_width_limit_small">80dip</dimen>
+ <dimen name="tab_width_limit_medium">100dip</dimen>
+ <dimen name="tab_width_limit_large">120dip</dimen>
+ <dimen name="bottom_sheet_peek_height">256dp</dimen>
+
+ <dimen name="text_small_size">16sp</dimen>
+ <dimen name="text_medium_size">20sp</dimen>
+
+ <dimen name="drawable_small_size">12dip</dimen>
+ <dimen name="drawable_medium_size">16dip</dimen>
+ <dimen name="drawable_large_size">20dip</dimen>
+
+ <dimen name="appbar_height">128dip</dimen>
+ <dimen name="fab_margin">32dip</dimen>
+
+ <dimen name="fab_mini_height">40dp</dimen>
+ <dimen name="fab_normal_height">56dip</dimen>
+
+</resources>
\ No newline at end of file
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/res/values/strings.xml b/design/tests/res/values/strings.xml
new file mode 100644
index 0000000..a431e2b
--- /dev/null
+++ b/design/tests/res/values/strings.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <string name="tab_layout_text">Tab text!</string>
+
+ <string name="navigate_home">Home</string>
+ <string name="navigate_profile">Profile</string>
+ <string name="navigate_people">People</string>
+ <string name="navigate_settings">Settings</string>
+
+ <string name="snackbar_text">This is a test message</string>
+ <string name="snackbar_action">Undo</string>
+
+ <string name="text1">Text</string>
+
+ <string name="scroll_top">Top</string>
+ <string name="scroll_bottom">Bottom</string>
+ <string name="design_appbar_collapsing_toolbar_pin">AppBar/Collapsing Toolbar (pinned)</string>
+ <string name="design_appbar_collapsing_toolbar_scroll">AppBar/Collapsing Toolbar (scroll off)</string>
+ <string name="design_appbar_collapsing_toolbar_pin_fab">AppBar/Collapsing Toolbar (pinned with FAB)</string>
+ <string name="design_appbar_toolbar_scroll_tabs_scroll">AppBar/Toolbar Scroll + Tabs Scroll</string>
+ <string name="design_appbar_toolbar_scroll_tabs_scroll_snap">AppBar/Toolbar Scroll + Tabs Scroll + Snap</string>
+ <string name="design_appbar_toolbar_scroll_tabs_pin">AppBar/Toolbar Scroll + Tabs Pin</string>
+ <string name="design_appbar_collapsing_toolbar_with_image">AppBar/Collapsing Toolbar + Parallax Image</string>
+ <string name="design_appbar_anchored_fab_margin_bottom">AppBar + anchored FAB with bottom margin</string>
+ <string name="design_appbar_anchored_fab_margin_top">AppBar + anchored FAB with top margin</string>
+ <string name="design_appbar_anchored_fab_margin_left">AppBar + anchored FAB with left margin</string>
+ <string name="design_appbar_anchored_fab_margin_right">AppBar + anchored FAB with right margin</string>
+
+ <string name="textinput_hint">Hint to the user</string>
+
+</resources>
\ No newline at end of file
diff --git a/design/tests/res/values/styles.xml b/design/tests/res/values/styles.xml
new file mode 100644
index 0000000..44b58eb
--- /dev/null
+++ b/design/tests/res/values/styles.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <style name="TextSmallStyle" parent="@android:style/TextAppearance">
+ <item name="android:textSize">@dimen/text_small_size</item>
+ </style>
+
+ <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
+ <item name="android:textSize">@dimen/text_medium_size</item>
+ </style>
+
+ <style name="Theme.TranslucentStatus" parent="Theme.AppCompat.Light.NoActionBar">
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <item name="android:statusBarColor">@android:color/transparent</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/custom/CustomTextView.java b/design/tests/src/android/support/design/custom/CustomTextView.java
new file mode 100644
index 0000000..3b3025e
--- /dev/null
+++ b/design/tests/src/android/support/design/custom/CustomTextView.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.custom;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.v7.widget.AppCompatTextView;
+import android.util.AttributeSet;
+
+@CoordinatorLayout.DefaultBehavior(TestFloatingBehavior.class)
+public class CustomTextView extends AppCompatTextView {
+ public CustomTextView(Context context) {
+ super(context);
+ }
+
+ public CustomTextView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomTextView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+}
diff --git a/design/tests/src/android/support/design/custom/TestFloatingBehavior.java b/design/tests/src/android/support/design/custom/TestFloatingBehavior.java
new file mode 100644
index 0000000..a508736
--- /dev/null
+++ b/design/tests/src/android/support/design/custom/TestFloatingBehavior.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.custom;
+
+import android.content.Context;
+import android.support.design.widget.CoordinatorLayout;
+import android.support.design.widget.Snackbar;
+import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.TextView;
+
+public class TestFloatingBehavior extends CoordinatorLayout.Behavior<TextView> {
+ // Default constructor is needed to instantiate a Behavior object when it is attached
+ // to custom view class as class-level annotation
+ public TestFloatingBehavior() {
+ }
+
+ // This constructor is needed to instantiate a Behavior object when it is attached to a
+ // view via layout_behavior XML attribute
+ public TestFloatingBehavior(Context context, AttributeSet attrs) {
+ }
+
+ @Override
+ public boolean layoutDependsOn(CoordinatorLayout parent, TextView child, View dependency) {
+ return dependency instanceof Snackbar.SnackbarLayout;
+ }
+
+ @Override
+ public boolean onDependentViewChanged(CoordinatorLayout parent, TextView child,
+ View dependency) {
+ ViewCompat.setTranslationY(child, Math.min(0,
+ ViewCompat.getTranslationY(dependency) - dependency.getHeight()));
+ return true;
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/Cheeses.java b/design/tests/src/android/support/design/testutils/Cheeses.java
new file mode 100644
index 0000000..6215569
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/Cheeses.java
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+public class Cheeses {
+
+ public static final String[] sCheeseStrings = {
+ "Abbaye de Belloc", "Abbaye du Mont des Cats", "Abertam", "Abondance", "Ackawi",
+ "Acorn", "Adelost", "Affidelice au Chablis", "Afuega'l Pitu", "Airag", "Airedale",
+ "Aisy Cendre", "Allgauer Emmentaler", "Alverca", "Ambert", "American Cheese",
+ "Ami du Chambertin", "Anejo Enchilado", "Anneau du Vic-Bilh", "Anthoriro", "Appenzell",
+ "Aragon", "Ardi Gasna", "Ardrahan", "Armenian String", "Aromes au Gene de Marc",
+ "Asadero", "Asiago", "Aubisque Pyrenees", "Autun", "Avaxtskyr", "Baby Swiss",
+ "Babybel", "Baguette Laonnaise", "Bakers", "Baladi", "Balaton", "Bandal", "Banon",
+ "Barry's Bay Cheddar", "Basing", "Basket Cheese", "Bath Cheese", "Bavarian Bergkase",
+ "Baylough", "Beaufort", "Beauvoorde", "Beenleigh Blue", "Beer Cheese", "Bel Paese",
+ "Bergader", "Bergere Bleue", "Berkswell", "Beyaz Peynir", "Bierkase", "Bishop Kennedy",
+ "Blarney", "Bleu d'Auvergne", "Bleu de Gex", "Bleu de Laqueuille",
+ "Bleu de Septmoncel", "Bleu Des Causses", "Blue", "Blue Castello", "Blue Rathgore",
+ "Blue Vein (Australian)", "Blue Vein Cheeses", "Bocconcini", "Bocconcini (Australian)",
+ "Boeren Leidenkaas", "Bonchester", "Bosworth", "Bougon", "Boule Du Roves",
+ "Boulette d'Avesnes", "Boursault", "Boursin", "Bouyssou", "Bra", "Braudostur",
+ "Breakfast Cheese", "Brebis du Lavort", "Brebis du Lochois", "Brebis du Puyfaucon",
+ "Bresse Bleu", "Brick", "Brie", "Brie de Meaux", "Brie de Melun", "Brillat-Savarin",
+ "Brin", "Brin d' Amour", "Brin d'Amour", "Brinza (Burduf Brinza)",
+ "Briquette de Brebis", "Briquette du Forez", "Broccio", "Broccio Demi-Affine",
+ "Brousse du Rove", "Bruder Basil", "Brusselae Kaas (Fromage de Bruxelles)", "Bryndza",
+ "Buchette d'Anjou", "Buffalo", "Burgos", "Butte", "Butterkase", "Button (Innes)",
+ "Buxton Blue", "Cabecou", "Caboc", "Cabrales", "Cachaille", "Caciocavallo", "Caciotta",
+ "Caerphilly", "Cairnsmore", "Calenzana", "Cambazola", "Camembert de Normandie",
+ "Canadian Cheddar", "Canestrato", "Cantal", "Caprice des Dieux", "Capricorn Goat",
+ "Capriole Banon", "Carre de l'Est", "Casciotta di Urbino", "Cashel Blue", "Castellano",
+ "Castelleno", "Castelmagno", "Castelo Branco", "Castigliano", "Cathelain",
+ "Celtic Promise", "Cendre d'Olivet", "Cerney", "Chabichou", "Chabichou du Poitou",
+ "Chabis de Gatine", "Chaource", "Charolais", "Chaumes", "Cheddar",
+ "Cheddar Clothbound", "Cheshire", "Chevres", "Chevrotin des Aravis", "Chontaleno",
+ "Civray", "Coeur de Camembert au Calvados", "Coeur de Chevre", "Colby", "Cold Pack",
+ "Comte", "Coolea", "Cooleney", "Coquetdale", "Corleggy", "Cornish Pepper",
+ "Cotherstone", "Cotija", "Cottage Cheese", "Cottage Cheese (Australian)",
+ "Cougar Gold", "Coulommiers", "Coverdale", "Crayeux de Roncq", "Cream Cheese",
+ "Cream Havarti", "Crema Agria", "Crema Mexicana", "Creme Fraiche", "Crescenza",
+ "Croghan", "Crottin de Chavignol", "Crottin du Chavignol", "Crowdie", "Crowley",
+ "Cuajada", "Curd", "Cure Nantais", "Curworthy", "Cwmtawe Pecorino",
+ "Cypress Grove Chevre", "Danablu (Danish Blue)", "Danbo", "Danish Fontina",
+ "Daralagjazsky", "Dauphin", "Delice des Fiouves", "Denhany Dorset Drum", "Derby",
+ "Dessertnyj Belyj", "Devon Blue", "Devon Garland", "Dolcelatte", "Doolin",
+ "Doppelrhamstufel", "Dorset Blue Vinney", "Double Gloucester", "Double Worcester",
+ "Dreux a la Feuille", "Dry Jack", "Duddleswell", "Dunbarra", "Dunlop", "Dunsyre Blue",
+ "Duroblando", "Durrus", "Dutch Mimolette (Commissiekaas)", "Edam", "Edelpilz",
+ "Emental Grand Cru", "Emlett", "Emmental", "Epoisses de Bourgogne", "Esbareich",
+ "Esrom", "Etorki", "Evansdale Farmhouse Brie", "Evora De L'Alentejo", "Exmoor Blue",
+ "Explorateur", "Feta", "Feta (Australian)", "Figue", "Filetta", "Fin-de-Siecle",
+ "Finlandia Swiss", "Finn", "Fiore Sardo", "Fleur du Maquis", "Flor de Guia",
+ "Flower Marie", "Folded", "Folded cheese with mint", "Fondant de Brebis",
+ "Fontainebleau", "Fontal", "Fontina Val d'Aosta", "Formaggio di capra", "Fougerus",
+ "Four Herb Gouda", "Fourme d' Ambert", "Fourme de Haute Loire", "Fourme de Montbrison",
+ "Fresh Jack", "Fresh Mozzarella", "Fresh Ricotta", "Fresh Truffles", "Fribourgeois",
+ "Friesekaas", "Friesian", "Friesla", "Frinault", "Fromage a Raclette", "Fromage Corse",
+ "Fromage de Montagne de Savoie", "Fromage Frais", "Fruit Cream Cheese",
+ "Frying Cheese", "Fynbo", "Gabriel", "Galette du Paludier", "Galette Lyonnaise",
+ "Galloway Goat's Milk Gems", "Gammelost", "Gaperon a l'Ail", "Garrotxa", "Gastanberra",
+ "Geitost", "Gippsland Blue", "Gjetost", "Gloucester", "Golden Cross", "Gorgonzola",
+ "Gornyaltajski", "Gospel Green", "Gouda", "Goutu", "Gowrie", "Grabetto", "Graddost",
+ "Grafton Village Cheddar", "Grana", "Grana Padano", "Grand Vatel",
+ "Grataron d' Areches", "Gratte-Paille", "Graviera", "Greuilh", "Greve",
+ "Gris de Lille", "Gruyere", "Gubbeen", "Guerbigny", "Halloumi",
+ "Halloumy (Australian)", "Haloumi-Style Cheese", "Harbourne Blue", "Havarti",
+ "Heidi Gruyere", "Hereford Hop", "Herrgardsost", "Herriot Farmhouse", "Herve",
+ "Hipi Iti", "Hubbardston Blue Cow", "Hushallsost", "Iberico", "Idaho Goatster",
+ "Idiazabal", "Il Boschetto al Tartufo", "Ile d'Yeu", "Isle of Mull", "Jarlsberg",
+ "Jermi Tortes", "Jibneh Arabieh", "Jindi Brie", "Jubilee Blue", "Juustoleipa",
+ "Kadchgall", "Kaseri", "Kashta", "Kefalotyri", "Kenafa", "Kernhem", "Kervella Affine",
+ "Kikorangi", "King Island Cape Wickham Brie", "King River Gold", "Klosterkaese",
+ "Knockalara", "Kugelkase", "L'Aveyronnais", "L'Ecir de l'Aubrac", "La Taupiniere",
+ "La Vache Qui Rit", "Laguiole", "Lairobell", "Lajta", "Lanark Blue", "Lancashire",
+ "Langres", "Lappi", "Laruns", "Lavistown", "Le Brin", "Le Fium Orbo", "Le Lacandou",
+ "Le Roule", "Leafield", "Lebbene", "Leerdammer", "Leicester", "Leyden", "Limburger",
+ "Lincolnshire Poacher", "Lingot Saint Bousquet d'Orb", "Liptauer", "Little Rydings",
+ "Livarot", "Llanboidy", "Llanglofan Farmhouse", "Loch Arthur Farmhouse",
+ "Loddiswell Avondale", "Longhorn", "Lou Palou", "Lou Pevre", "Lyonnais", "Maasdam",
+ "Macconais", "Mahoe Aged Gouda", "Mahon", "Malvern", "Mamirolle", "Manchego",
+ "Manouri", "Manur", "Marble Cheddar", "Marbled Cheeses", "Maredsous", "Margotin",
+ "Maribo", "Maroilles", "Mascares", "Mascarpone", "Mascarpone (Australian)",
+ "Mascarpone Torta", "Matocq", "Maytag Blue", "Meira", "Menallack Farmhouse",
+ "Menonita", "Meredith Blue", "Mesost", "Metton (Cancoillotte)", "Meyer Vintage Gouda",
+ "Mihalic Peynir", "Milleens", "Mimolette", "Mine-Gabhar", "Mini Baby Bells", "Mixte",
+ "Molbo", "Monastery Cheeses", "Mondseer", "Mont D'or Lyonnais", "Montasio",
+ "Monterey Jack", "Monterey Jack Dry", "Morbier", "Morbier Cru de Montagne",
+ "Mothais a la Feuille", "Mozzarella", "Mozzarella (Australian)",
+ "Mozzarella di Bufala", "Mozzarella Fresh, in water", "Mozzarella Rolls", "Munster",
+ "Murol", "Mycella", "Myzithra", "Naboulsi", "Nantais", "Neufchatel",
+ "Neufchatel (Australian)", "Niolo", "Nokkelost", "Northumberland", "Oaxaca",
+ "Olde York", "Olivet au Foin", "Olivet Bleu", "Olivet Cendre",
+ "Orkney Extra Mature Cheddar", "Orla", "Oschtjepka", "Ossau Fermier", "Ossau-Iraty",
+ "Oszczypek", "Oxford Blue", "P'tit Berrichon", "Palet de Babligny", "Paneer", "Panela",
+ "Pannerone", "Pant ys Gawn", "Parmesan (Parmigiano)", "Parmigiano Reggiano",
+ "Pas de l'Escalette", "Passendale", "Pasteurized Processed", "Pate de Fromage",
+ "Patefine Fort", "Pave d'Affinois", "Pave d'Auge", "Pave de Chirac", "Pave du Berry",
+ "Pecorino", "Pecorino in Walnut Leaves", "Pecorino Romano", "Peekskill Pyramid",
+ "Pelardon des Cevennes", "Pelardon des Corbieres", "Penamellera", "Penbryn",
+ "Pencarreg", "Perail de Brebis", "Petit Morin", "Petit Pardou", "Petit-Suisse",
+ "Picodon de Chevre", "Picos de Europa", "Piora", "Pithtviers au Foin",
+ "Plateau de Herve", "Plymouth Cheese", "Podhalanski", "Poivre d'Ane", "Polkolbin",
+ "Pont l'Eveque", "Port Nicholson", "Port-Salut", "Postel", "Pouligny-Saint-Pierre",
+ "Pourly", "Prastost", "Pressato", "Prince-Jean", "Processed Cheddar", "Provolone",
+ "Provolone (Australian)", "Pyengana Cheddar", "Pyramide", "Quark",
+ "Quark (Australian)", "Quartirolo Lombardo", "Quatre-Vents", "Quercy Petit",
+ "Queso Blanco", "Queso Blanco con Frutas --Pina y Mango", "Queso de Murcia",
+ "Queso del Montsec", "Queso del Tietar", "Queso Fresco", "Queso Fresco (Adobera)",
+ "Queso Iberico", "Queso Jalapeno", "Queso Majorero", "Queso Media Luna",
+ "Queso Para Frier", "Queso Quesadilla", "Rabacal", "Raclette", "Ragusano", "Raschera",
+ "Reblochon", "Red Leicester", "Regal de la Dombes", "Reggianito", "Remedou",
+ "Requeson", "Richelieu", "Ricotta", "Ricotta (Australian)", "Ricotta Salata", "Ridder",
+ "Rigotte", "Rocamadour", "Rollot", "Romano", "Romans Part Dieu", "Roncal", "Roquefort",
+ "Roule", "Rouleau De Beaulieu", "Royalp Tilsit", "Rubens", "Rustinu", "Saaland Pfarr",
+ "Saanenkaese", "Saga", "Sage Derby", "Sainte Maure", "Saint-Marcellin",
+ "Saint-Nectaire", "Saint-Paulin", "Salers", "Samso", "San Simon", "Sancerre",
+ "Sap Sago", "Sardo", "Sardo Egyptian", "Sbrinz", "Scamorza", "Schabzieger", "Schloss",
+ "Selles sur Cher", "Selva", "Serat", "Seriously Strong Cheddar", "Serra da Estrela",
+ "Sharpam", "Shelburne Cheddar", "Shropshire Blue", "Siraz", "Sirene", "Smoked Gouda",
+ "Somerset Brie", "Sonoma Jack", "Sottocenare al Tartufo", "Soumaintrain",
+ "Sourire Lozerien", "Spenwood", "Sraffordshire Organic", "St. Agur Blue Cheese",
+ "Stilton", "Stinking Bishop", "String", "Sussex Slipcote", "Sveciaost", "Swaledale",
+ "Sweet Style Swiss", "Swiss", "Syrian (Armenian String)", "Tala", "Taleggio", "Tamie",
+ "Tasmania Highland Chevre Log", "Taupiniere", "Teifi", "Telemea", "Testouri",
+ "Tete de Moine", "Tetilla", "Texas Goat Cheese", "Tibet", "Tillamook Cheddar",
+ "Tilsit", "Timboon Brie", "Toma", "Tomme Brulee", "Tomme d'Abondance",
+ "Tomme de Chevre", "Tomme de Romans", "Tomme de Savoie", "Tomme des Chouans", "Tommes",
+ "Torta del Casar", "Toscanello", "Touree de L'Aubier", "Tourmalet",
+ "Trappe (Veritable)", "Trois Cornes De Vendee", "Tronchon", "Trou du Cru", "Truffe",
+ "Tupi", "Turunmaa", "Tymsboro", "Tyn Grug", "Tyning", "Ubriaco", "Ulloa",
+ "Vacherin-Fribourgeois", "Valencay", "Vasterbottenost", "Venaco", "Vendomois",
+ "Vieux Corse", "Vignotte", "Vulscombe", "Waimata Farmhouse Blue",
+ "Washed Rind Cheese (Australian)", "Waterloo", "Weichkaese", "Wellington",
+ "Wensleydale", "White Stilton", "Whitestone Farmhouse", "Wigmore", "Woodside Cabecou",
+ "Xanadu", "Xynotyro", "Yarg Cornish", "Yarra Valley Pyramid", "Yorkshire Blue",
+ "Zamorano", "Zanetti Grana Padano", "Zanetti Parmigiano Reggiano"
+ };
+
+}
diff --git a/design/tests/src/android/support/design/testutils/DrawerLayoutActions.java b/design/tests/src/android/support/design/testutils/DrawerLayoutActions.java
new file mode 100755
index 0000000..e4ea867
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/DrawerLayoutActions.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+public class DrawerLayoutActions {
+ /**
+ * Opens the drawer at the specified edge gravity.
+ */
+ public static ViewAction openDrawer(final int drawerEdgeGravity) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Opens the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.openDrawer(drawerEdgeGravity);
+
+ // Wait for a full second to let the inner ViewDragHelper complete the operation
+ uiController.loopMainThreadForAtLeast(1000);
+ }
+ };
+ }
+
+ /**
+ * Closes the drawer at the specified edge gravity.
+ */
+ public static ViewAction closeDrawer(final int drawerEdgeGravity) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Closes the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.closeDrawer(drawerEdgeGravity);
+
+ // Wait for a full second to let the inner ViewDragHelper complete the operation
+ uiController.loopMainThreadForAtLeast(1000);
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
new file mode 100644
index 0000000..bc80607
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/FloatingActionButtonActions.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.content.res.ColorStateList;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.design.widget.FloatingActionButton;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class FloatingActionButtonActions {
+
+ public static ViewAction setBackgroundTintColor(@ColorInt final int color) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(FloatingActionButton.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets FloatingActionButton background tint";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+ fab.setBackgroundTintList(ColorStateList.valueOf(color));
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ public static ViewAction setImageResource(@DrawableRes final int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(FloatingActionButton.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets FloatingActionButton image resource";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+ fab.setImageResource(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ public static ViewAction setSize(@FloatingActionButton.Size final int size) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(FloatingActionButton.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets FloatingActionButton size";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+ fab.setSize(size);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+}
diff --git a/design/tests/src/android/support/design/testutils/NavigationViewActions.java b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
new file mode 100644
index 0000000..bcc15da8
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/NavigationViewActions.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.IdRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.design.widget.NavigationView;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.LayoutInflater;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class NavigationViewActions {
+ /**
+ * Sets item text appearance on the content of the navigation view.
+ */
+ public static ViewAction setItemTextAppearance(final @StyleRes int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item text appearance";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemTextAppearance(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item text color on the content of the navigation view.
+ */
+ public static ViewAction setItemTextColor(final ColorStateList textColor) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item text color";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemTextColor(textColor);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item background on the content of the navigation view.
+ */
+ public static ViewAction setItemBackground(final @Nullable Drawable itemBackground) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item background";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemBackground(itemBackground);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item background on the content of the navigation view.
+ */
+ public static ViewAction setItemBackgroundResource(final @DrawableRes int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item background";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemBackgroundResource(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets item icon tint list on the content of the navigation view.
+ */
+ public static ViewAction setItemIconTintList(final @Nullable ColorStateList tint) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set item icon tint list";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setItemIconTintList(tint);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Add the specified view as a header to the navigation view.
+ */
+ public static ViewAction addHeaderView(final @NonNull LayoutInflater inflater,
+ final @LayoutRes int res) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Add header view";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.addHeaderView(inflater.inflate(res, null, false));
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Inflates a view from the specified layout ID and adds it as a header to the navigation view.
+ */
+ public static ViewAction inflateHeaderView(final @LayoutRes int res) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Inflate and add header view";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.inflateHeaderView(res);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Removes a previously added header view from the navigation view.
+ */
+ public static ViewAction removeHeaderView(final @Nullable View headerView) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Remove header view";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.removeHeaderView(headerView);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets checked item on the navigation view.
+ */
+ public static ViewAction setCheckedItem(final @IdRes int id) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set checked item";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.setCheckedItem(id);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets icon for the menu item of the navigation view.
+ */
+ public static ViewAction setIconForMenuItem(final @IdRes int menuItemId,
+ final Drawable iconDrawable) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set menu item icon";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ NavigationView navigationView = (NavigationView) view;
+ navigationView.getMenu().findItem(menuItemId).setIcon(iconDrawable);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/Shakespeare.java b/design/tests/src/android/support/design/testutils/Shakespeare.java
new file mode 100644
index 0000000..285a185
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/Shakespeare.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+public final class Shakespeare {
+ /**
+ * Our data, part 1.
+ */
+ public static final String[] TITLES = {
+ "Henry IV (1)",
+ "Henry V",
+ "Henry VIII",
+ "Richard II",
+ "Richard III",
+ "Merchant of Venice",
+ "Othello",
+ "King Lear"
+ };
+
+ /**
+ * Our data, part 2.
+ */
+ public static final String[] DIALOGUE = {
+ "So shaken as we are, so wan with care," +
+ "Find we a time for frighted peace to pant," +
+ "And breathe short-winded accents of new broils" +
+ "To be commenced in strands afar remote." +
+ "No more the thirsty entrance of this soil" +
+ "Shall daub her lips with her own children's blood;" +
+ "Nor more shall trenching war channel her fields," +
+ "Nor bruise her flowerets with the armed hoofs" +
+ "Of hostile paces: those opposed eyes," +
+ "Which, like the meteors of a troubled heaven," +
+ "All of one nature, of one substance bred," +
+ "Did lately meet in the intestine shock" +
+ "And furious close of civil butchery" +
+ "Shall now, in mutual well-beseeming ranks," +
+ "March all one way and be no more opposed" +
+ "Against acquaintance, kindred and allies:" +
+ "The edge of war, like an ill-sheathed knife," +
+ "No more shall cut his master. Therefore, friends," +
+ "As far as to the sepulchre of Christ," +
+ "Whose soldier now, under whose blessed cross" +
+ "We are impressed and engaged to fight," +
+ "Forthwith a power of English shall we levy;" +
+ "Whose arms were moulded in their mothers' womb" +
+ "To chase these pagans in those holy fields" +
+ "Over whose acres walk'd those blessed feet" +
+ "Which fourteen hundred years ago were nail'd" +
+ "For our advantage on the bitter cross." +
+ "But this our purpose now is twelve month old," +
+ "And bootless 'tis to tell you we will go:" +
+ "Therefore we meet not now. Then let me hear" +
+ "Of you, my gentle cousin Westmoreland," +
+ "What yesternight our council did decree" +
+ "In forwarding this dear expedience.",
+
+ "Hear him but reason in divinity," +
+ "And all-admiring with an inward wish" +
+ "You would desire the king were made a prelate:" +
+ "Hear him debate of commonwealth affairs," +
+ "You would say it hath been all in all his study:" +
+ "List his discourse of war, and you shall hear" +
+ "A fearful battle render'd you in music:" +
+ "Turn him to any cause of policy," +
+ "The Gordian knot of it he will unloose," +
+ "Familiar as his garter: that, when he speaks," +
+ "The air, a charter'd libertine, is still," +
+ "And the mute wonder lurketh in men's ears," +
+ "To steal his sweet and honey'd sentences;" +
+ "So that the art and practic part of life" +
+ "Must be the mistress to this theoric:" +
+ "Which is a wonder how his grace should glean it," +
+ "Since his addiction was to courses vain," +
+ "His companies unletter'd, rude and shallow," +
+ "His hours fill'd up with riots, banquets, sports," +
+ "And never noted in him any study," +
+ "Any retirement, any sequestration" +
+ "From open haunts and popularity.",
+
+ "I come no more to make you laugh: things now," +
+ "That bear a weighty and a serious brow," +
+ "Sad, high, and working, full of state and woe," +
+ "Such noble scenes as draw the eye to flow," +
+ "We now present. Those that can pity, here" +
+ "May, if they think it well, let fall a tear;" +
+ "The subject will deserve it. Such as give" +
+ "Their money out of hope they may believe," +
+ "May here find truth too. Those that come to see" +
+ "Only a show or two, and so agree" +
+ "The play may pass, if they be still and willing," +
+ "I'll undertake may see away their shilling" +
+ "Richly in two short hours. Only they" +
+ "That come to hear a merry bawdy play," +
+ "A noise of targets, or to see a fellow" +
+ "In a long motley coat guarded with yellow," +
+ "Will be deceived; for, gentle hearers, know," +
+ "To rank our chosen truth with such a show" +
+ "As fool and fight is, beside forfeiting" +
+ "Our own brains, and the opinion that we bring," +
+ "To make that only true we now intend," +
+ "Will leave us never an understanding friend." +
+ "Therefore, for goodness' sake, and as you are known" +
+ "The first and happiest hearers of the town," +
+ "Be sad, as we would make ye: think ye see" +
+ "The very persons of our noble story" +
+ "As they were living; think you see them great," +
+ "And follow'd with the general throng and sweat" +
+ "Of thousand friends; then in a moment, see" +
+ "How soon this mightiness meets misery:" +
+ "And, if you can be merry then, I'll say" +
+ "A man may weep upon his wedding-day.",
+
+ "First, heaven be the record to my speech!" +
+ "In the devotion of a subject's love," +
+ "Tendering the precious safety of my prince," +
+ "And free from other misbegotten hate," +
+ "Come I appellant to this princely presence." +
+ "Now, Thomas Mowbray, do I turn to thee," +
+ "And mark my greeting well; for what I speak" +
+ "My body shall make good upon this earth," +
+ "Or my divine soul answer it in heaven." +
+ "Thou art a traitor and a miscreant," +
+ "Too good to be so and too bad to live," +
+ "Since the more fair and crystal is the sky," +
+ "The uglier seem the clouds that in it fly." +
+ "Once more, the more to aggravate the note," +
+ "With a foul traitor's name stuff I thy throat;" +
+ "And wish, so please my sovereign, ere I move," +
+ "What my tongue speaks my right drawn sword may prove.",
+
+ "Now is the winter of our discontent" +
+ "Made glorious summer by this sun of York;" +
+ "And all the clouds that lour'd upon our house" +
+ "In the deep bosom of the ocean buried." +
+ "Now are our brows bound with victorious wreaths;" +
+ "Our bruised arms hung up for monuments;" +
+ "Our stern alarums changed to merry meetings," +
+ "Our dreadful marches to delightful measures." +
+ "Grim-visaged war hath smooth'd his wrinkled front;" +
+ "And now, instead of mounting barded steeds" +
+ "To fright the souls of fearful adversaries," +
+ "He capers nimbly in a lady's chamber" +
+ "To the lascivious pleasing of a lute." +
+ "But I, that am not shaped for sportive tricks," +
+ "Nor made to court an amorous looking-glass;" +
+ "I, that am rudely stamp'd, and want love's majesty" +
+ "To strut before a wanton ambling nymph;" +
+ "I, that am curtail'd of this fair proportion," +
+ "Cheated of feature by dissembling nature," +
+ "Deformed, unfinish'd, sent before my time" +
+ "Into this breathing world, scarce half made up," +
+ "And that so lamely and unfashionable" +
+ "That dogs bark at me as I halt by them;" +
+ "Why, I, in this weak piping time of peace," +
+ "Have no delight to pass away the time," +
+ "Unless to spy my shadow in the sun" +
+ "And descant on mine own deformity:" +
+ "And therefore, since I cannot prove a lover," +
+ "To entertain these fair well-spoken days," +
+ "I am determined to prove a villain" +
+ "And hate the idle pleasures of these days." +
+ "Plots have I laid, inductions dangerous," +
+ "By drunken prophecies, libels and dreams," +
+ "To set my brother Clarence and the king" +
+ "In deadly hate the one against the other:" +
+ "And if King Edward be as true and just" +
+ "As I am subtle, false and treacherous," +
+ "This day should Clarence closely be mew'd up," +
+ "About a prophecy, which says that 'G'" +
+ "Of Edward's heirs the murderer shall be." +
+ "Dive, thoughts, down to my soul: here" +
+ "Clarence comes.",
+
+ "To bait fish withal: if it will feed nothing else," +
+ "it will feed my revenge. He hath disgraced me, and" +
+ "hindered me half a million; laughed at my losses," +
+ "mocked at my gains, scorned my nation, thwarted my" +
+ "bargains, cooled my friends, heated mine" +
+ "enemies; and what's his reason? I am a Jew. Hath" +
+ "not a Jew eyes? hath not a Jew hands, organs," +
+ "dimensions, senses, affections, passions? fed with" +
+ "the same food, hurt with the same weapons, subject" +
+ "to the same diseases, healed by the same means," +
+ "warmed and cooled by the same winter and summer, as" +
+ "a Christian is? If you prick us, do we not bleed?" +
+ "if you tickle us, do we not laugh? if you poison" +
+ "us, do we not die? and if you wrong us, shall we not" +
+ "revenge? If we are like you in the rest, we will" +
+ "resemble you in that. If a Jew wrong a Christian," +
+ "what is his humility? Revenge. If a Christian" +
+ "wrong a Jew, what should his sufferance be by" +
+ "Christian example? Why, revenge. The villany you" +
+ "teach me, I will execute, and it shall go hard but I" +
+ "will better the instruction.",
+
+ "Virtue! a fig! 'tis in ourselves that we are thus" +
+ "or thus. Our bodies are our gardens, to the which" +
+ "our wills are gardeners: so that if we will plant" +
+ "nettles, or sow lettuce, set hyssop and weed up" +
+ "thyme, supply it with one gender of herbs, or" +
+ "distract it with many, either to have it sterile" +
+ "with idleness, or manured with industry, why, the" +
+ "power and corrigible authority of this lies in our" +
+ "wills. If the balance of our lives had not one" +
+ "scale of reason to poise another of sensuality, the" +
+ "blood and baseness of our natures would conduct us" +
+ "to most preposterous conclusions: but we have" +
+ "reason to cool our raging motions, our carnal" +
+ "stings, our unbitted lusts, whereof I take this that" +
+ "you call love to be a sect or scion.",
+
+ "Blow, winds, and crack your cheeks! rage! blow!" +
+ "You cataracts and hurricanoes, spout" +
+ "Till you have drench'd our steeples, drown'd the cocks!" +
+ "You sulphurous and thought-executing fires," +
+ "Vaunt-couriers to oak-cleaving thunderbolts," +
+ "Singe my white head! And thou, all-shaking thunder," +
+ "Smite flat the thick rotundity o' the world!" +
+ "Crack nature's moulds, an germens spill at once," +
+ "That make ingrateful man!"
+ };
+}
diff --git a/design/tests/src/android/support/design/testutils/SnackbarUtils.java b/design/tests/src/android/support/design/testutils/SnackbarUtils.java
new file mode 100644
index 0000000..67a30dd
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/SnackbarUtils.java
@@ -0,0 +1,161 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.testutils;
+
+import android.support.annotation.Nullable;
+import android.support.design.widget.Snackbar;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.design.testutils.TestUtilsActions.waitUntilIdle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+
+public class SnackbarUtils {
+ private static class SnackbarShownCallback extends Snackbar.Callback
+ implements IdlingResource {
+ private boolean mIsShown = false;
+
+ @Nullable
+ private IdlingResource.ResourceCallback mCallback;
+
+ private boolean mNeedsIdle = false;
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ mCallback = resourceCallback;
+ }
+
+ @Override
+ public String getName() {
+ return "Snackbar shown callback";
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (!mNeedsIdle) {
+ return true;
+ } else {
+ return mIsShown;
+ }
+ }
+
+ @Override
+ public void onShown(Snackbar snackbar) {
+ mIsShown = true;
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ private static class SnackbarDismissedCallback extends Snackbar.Callback
+ implements IdlingResource {
+ private boolean mIsDismissed = false;
+
+ @Nullable
+ private IdlingResource.ResourceCallback mCallback;
+
+ private boolean mNeedsIdle = false;
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ mCallback = resourceCallback;
+ }
+
+ @Override
+ public String getName() {
+ return "Snackbar dismissed callback";
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (!mNeedsIdle) {
+ return true;
+ } else {
+ return mIsDismissed;
+ }
+ }
+
+ @Override
+ public void onDismissed(Snackbar snackbar, @DismissEvent int event) {
+ mIsDismissed = true;
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ /**
+ * Helper method that shows that specified {@link Snackbar} and waits until
+ * it has been fully shown. Note that calling this method will reset the currently
+ * set {@link Snackbar.Callback}.
+ */
+ public static void showSnackbarAndWaitUntilFullyShown(Snackbar snackbar) {
+ SnackbarShownCallback snackbarCallback = new SnackbarShownCallback();
+ snackbar.setCallback(snackbarCallback);
+ try {
+ // Register our listener as idling resource so that Espresso waits until the
+ // the snackbar has been fully shown
+ Espresso.registerIdlingResources(snackbarCallback);
+ // Show the snackbar
+ snackbar.show();
+ // Mark the callback to require waiting for idle state
+ snackbarCallback.mNeedsIdle = true;
+ // Perform a dummy Espresso action that loops until the UI thread is idle. This
+ // effectively blocks us until the Snackbar has completed its sliding animation.
+ onView(isRoot()).perform(waitUntilIdle());
+ snackbarCallback.mNeedsIdle = false;
+ } finally {
+ // Unregister our idling resource
+ Espresso.unregisterIdlingResources(snackbarCallback);
+ // And remove our tracker listener from Snackbar
+ snackbar.setCallback(null);
+ }
+ }
+
+ /**
+ * Helper method that dismissed that specified {@link Snackbar} and waits until
+ * it has been fully dismissed. Note that calling this method will reset the currently
+ * set {@link Snackbar.Callback}.
+ */
+ public static void dismissSnackbarAndWaitUntilFullyDismissed(Snackbar snackbar) {
+ SnackbarDismissedCallback snackbarCallback = new SnackbarDismissedCallback();
+ snackbar.setCallback(snackbarCallback);
+ try {
+ // Register our listener as idling resource so that Espresso waits until the
+ // the snackbar has been fully dismissed
+ Espresso.registerIdlingResources(snackbarCallback);
+ // Dismiss the snackbar
+ snackbar.dismiss();
+ // Mark the callback to require waiting for idle state
+ snackbarCallback.mNeedsIdle = true;
+ // Perform a dummy Espresso action that loops until the UI thread is idle. This
+ // effectively blocks us until the Snackbar has completed its sliding animation.
+ onView(isRoot()).perform(waitUntilIdle());
+ snackbarCallback.mNeedsIdle = false;
+ } finally {
+ // Unregister our idling resource
+ Espresso.unregisterIdlingResources(snackbarCallback);
+ // And remove our tracker listener from Snackbar
+ snackbar.setCallback(null);
+ }
+ }
+}
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..149b14f
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TabLayoutActions.java
@@ -0,0 +1,146 @@
+/*
+ * 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();
+ }
+ };
+ }
+
+ /**
+ * Wires <code>TabLayout</code> to <code>ViewPager</code> content.
+ */
+ public static ViewAction setupWithViewPager(final @Nullable ViewPager viewPager,
+ final boolean autoRefresh) {
+ 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, autoRefresh);
+
+ 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 "Selects tab";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TabLayout tabLayout = (TabLayout) view;
+ tabLayout.getTabAt(tabIndex).select();
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the specified tab mode in the <code>TabLayout</code>.
+ */
+ public static ViewAction setTabMode(final int tabMode) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets tab mode";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TabLayout tabLayout = (TabLayout) view;
+ tabLayout.setTabMode(tabMode);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/TestUtils.java b/design/tests/src/android/support/design/testutils/TestUtils.java
new file mode 100644
index 0000000..b9aae7a
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TestUtils.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+
+import junit.framework.Assert;
+
+public class TestUtils {
+ /**
+ * Checks whether all the pixels in the specified drawable are of the same specified color.
+ *
+ * In case there is a color mismatch, the behavior of this method depends on the
+ * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+ * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+ * <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
+ int allowedComponentVariance, boolean throwExceptionIfFails) {
+ assertAllPixelsOfColor(failMessagePrefix, drawable, drawableWidth, drawableHeight,
+ callSetBounds, color, null, allowedComponentVariance, throwExceptionIfFails);
+ }
+
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
+ Rect checkArea, int allowedComponentVariance, boolean throwExceptionIfFails) {
+
+ // Create a bitmap
+ Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
+ // Create a canvas that wraps the bitmap
+ Canvas canvas = new Canvas(bitmap);
+ if (callSetBounds) {
+ // Configure the drawable to have bounds that match the passed size
+ drawable.setBounds(0, 0, drawableWidth, drawableHeight);
+ }
+
+ // And ask the drawable to draw itself to the canvas / bitmap
+ drawable.draw(canvas);
+
+ try {
+ int[] rowPixels = new int[drawableWidth];
+
+ final int firstRow = checkArea != null ? checkArea.top : 0;
+ final int lastRow = checkArea != null ? checkArea.bottom : drawableHeight - 1;
+ final int firstCol = checkArea != null ? checkArea.left : 0;
+ final int lastCol = checkArea != null ? checkArea.right : drawableWidth - 1;
+
+ final int expectedAlpha = Color.alpha(color);
+ final int expectedRed = Color.red(color);
+ final int expectedGreen = Color.green(color);
+ final int expectedBlue = Color.blue(color);
+
+ for (int row = firstRow; row <= lastRow; row++) {
+ bitmap.getPixels(rowPixels, 0, drawableWidth, 0, row, drawableWidth, 1);
+
+ for (int column = firstCol; column <= lastCol; column++) {
+ int sourceAlpha = Color.alpha(rowPixels[column]);
+ int sourceRed = Color.red(rowPixels[column]);
+ int sourceGreen = Color.green(rowPixels[column]);
+ int sourceBlue = Color.blue(rowPixels[column]);
+
+ int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
+ int varianceRed = Math.abs(sourceRed - expectedRed);
+ int varianceGreen = Math.abs(sourceGreen - expectedGreen);
+ int varianceBlue = Math.abs(sourceBlue - expectedBlue);
+
+ boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
+ && (varianceRed <= allowedComponentVariance)
+ && (varianceGreen <= allowedComponentVariance)
+ && (varianceBlue <= allowedComponentVariance);
+
+ if (!isColorMatch) {
+ String mismatchDescription = failMessagePrefix
+ + ": expected all drawable colors to be ["
+ + expectedAlpha + "," + expectedRed + ","
+ + expectedGreen + "," + expectedBlue
+ + "] but at position (" + row + "," + column + ") found ["
+ + sourceAlpha + "," + sourceRed + ","
+ + sourceGreen + "," + sourceBlue + "]";
+ if (throwExceptionIfFails) {
+ throw new RuntimeException(mismatchDescription);
+ } else {
+ Assert.fail(mismatchDescription);
+ }
+ }
+ }
+ }
+ } finally {
+ bitmap.recycle();
+ }
+ }
+
+ public static int getThemeAttrColor(Context context, final int attr) {
+ TypedArray a = null;
+ try {
+ a = context.obtainStyledAttributes(new int[]{attr});
+ return a.getColor(0, 0);
+ } finally {
+ if (a != null) {
+ a.recycle();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsActions.java b/design/tests/src/android/support/design/testutils/TestUtilsActions.java
new file mode 100644
index 0000000..0ad7c88
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TestUtilsActions.java
@@ -0,0 +1,214 @@
+/*
+ * 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.LayoutRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.design.widget.CollapsingToolbarLayout;
+import android.support.design.widget.TabLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewPager;
+import android.support.v7.widget.Toolbar;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+
+public class TestUtilsActions {
+ /**
+ * Replaces an existing {@link TabLayout} with a new one inflated from the specified
+ * layout resource.
+ */
+ public static ViewAction replaceTabLayout(final @LayoutRes int tabLayoutResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Replace TabLayout";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ final ViewGroup viewGroup = (ViewGroup) view;
+ final int childCount = viewGroup.getChildCount();
+ // Iterate over children and find TabLayout
+ for (int i = 0; i < childCount; i++) {
+ View child = viewGroup.getChildAt(i);
+ if (child instanceof TabLayout) {
+ // Remove the existing TabLayout
+ viewGroup.removeView(child);
+ // Create a new one
+ final LayoutInflater layoutInflater =
+ LayoutInflater.from(view.getContext());
+ final TabLayout newTabLayout = (TabLayout) layoutInflater.inflate(
+ tabLayoutResId, viewGroup, false);
+ // Make sure we're adding the new TabLayout at the same index
+ viewGroup.addView(newTabLayout, i);
+ break;
+ }
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets layout direction on the view.
+ */
+ public static ViewAction setLayoutDirection(final int layoutDirection) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set layout direction";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setLayoutDirection(view, layoutDirection);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets title on the {@link CollapsingToolbarLayout}.
+ */
+ public static ViewAction setTitle(final CharSequence title) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(CollapsingToolbarLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "set toolbar title";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ CollapsingToolbarLayout collapsingToolbarLayout =
+ (CollapsingToolbarLayout) view;
+ collapsingToolbarLayout.setTitle(title);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets text content on {@link TextView}
+ */
+ public static ViewAction setText(final @Nullable CharSequence text) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set text";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ textView.setText(text);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Adds tabs to {@link TabLayout}
+ */
+ public static ViewAction addTabs(final String... tabs) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TabLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TabLayout add tabs";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TabLayout tabLayout = (TabLayout) view;
+ for (int i = 0; i < tabs.length; i++) {
+ tabLayout.addTab(tabLayout.newTab().setText(tabs[i]));
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Dummy Espresso action that waits until the UI thread is idle. This action can be performed
+ * on the root view to wait for an ongoing animation to be completed.
+ */
+ public static ViewAction waitUntilIdle() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isRoot();
+ }
+
+ @Override
+ public String getDescription() {
+ return "wait for idle";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
new file mode 100644
index 0000000..f25b89c
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TestUtilsMatchers.java
@@ -0,0 +1,316 @@
+/*
+ * 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.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.design.widget.FloatingActionButton;
+import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.TextView;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class TestUtilsMatchers {
+ /**
+ * Returns a matcher that matches Views that are not narrower than specified width in pixels.
+ */
+ public static Matcher<View> isNotNarrowerThan(final int minWidth) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ final int viewWidth = view.getWidth();
+ if (viewWidth < minWidth) {
+ failedCheckDescription =
+ "width " + viewWidth + " is less than minimum " + minWidth;
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views that are not wider than specified width in pixels.
+ */
+ public static Matcher<View> isNotWiderThan(final int maxWidth) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ final int viewWidth = view.getWidth();
+ if (viewWidth > maxWidth) {
+ failedCheckDescription =
+ "width " + viewWidth + " is more than maximum " + maxWidth;
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches TextViews with the specified text size.
+ */
+ public static Matcher withTextSize(final float textSize) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ final float ourTextSize = view.getTextSize();
+ if (Math.abs(textSize - ourTextSize) > 1.0f) {
+ failedCheckDescription =
+ "text size " + ourTextSize + " is different than expected " + textSize;
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches TextViews with the specified text color.
+ */
+ public static Matcher withTextColor(final @ColorInt int textColor) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ final @ColorInt int ourTextColor = view.getCurrentTextColor();
+ if (ourTextColor != textColor) {
+ int ourAlpha = Color.alpha(ourTextColor);
+ int ourRed = Color.red(ourTextColor);
+ int ourGreen = Color.green(ourTextColor);
+ int ourBlue = Color.blue(ourTextColor);
+
+ int expectedAlpha = Color.alpha(textColor);
+ int expectedRed = Color.red(textColor);
+ int expectedGreen = Color.green(textColor);
+ int expectedBlue = Color.blue(textColor);
+
+ failedCheckDescription =
+ "expected color to be ["
+ + expectedAlpha + "," + expectedRed + ","
+ + expectedGreen + "," + expectedBlue
+ + "] but found ["
+ + ourAlpha + "," + ourRed + ","
+ + ourGreen + "," + ourBlue + "]";
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches TextViews whose start drawable is filled with the specified
+ * fill color.
+ */
+ public static Matcher withStartDrawableFilledWith(final @ColorInt int fillColor,
+ final int allowedComponentVariance) {
+ return new BoundedMatcher<View, TextView>(TextView.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final TextView view) {
+ final Drawable[] compoundDrawables = view.getCompoundDrawables();
+ final boolean isRtl =
+ (ViewCompat.getLayoutDirection(view) == ViewCompat.LAYOUT_DIRECTION_RTL);
+ final Drawable startDrawable = isRtl ? compoundDrawables[2] : compoundDrawables[0];
+ if (startDrawable == null) {
+ failedCheckDescription = "no start drawable";
+ return false;
+ }
+ try {
+ final Rect bounds = startDrawable.getBounds();
+ TestUtils.assertAllPixelsOfColor("",
+ startDrawable, bounds.width(), bounds.height(), true,
+ fillColor, allowedComponentVariance, true);
+ } catch (Throwable t) {
+ failedCheckDescription = t.getMessage();
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views with the specified background fill color.
+ */
+ public static Matcher withBackgroundFill(final @ColorInt int fillColor) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ Drawable background = view.getBackground();
+ try {
+ TestUtils.assertAllPixelsOfColor("",
+ background, view.getWidth(), view.getHeight(), true,
+ fillColor, 0, true);
+ } catch (Throwable t) {
+ failedCheckDescription = t.getMessage();
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches FloatingActionButtons with the specified background
+ * fill color.
+ */
+ public static Matcher withFabBackgroundFill(final @ColorInt int fillColor) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ if (!(view instanceof FloatingActionButton)) {
+ return false;
+ }
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+
+ // Since the FAB background is round, and may contain the shadow, we'll look at
+ // just the center half rect of the content area
+ final Rect area = new Rect();
+ fab.getContentRect(area);
+
+ final int rectHeightQuarter = area.height() / 4;
+ final int rectWidthQuarter = area.width() / 4;
+ area.left += rectWidthQuarter;
+ area.top += rectHeightQuarter;
+ area.right -= rectWidthQuarter;
+ area.bottom -= rectHeightQuarter;
+
+ try {
+ TestUtils.assertAllPixelsOfColor("",
+ fab.getBackground(), view.getWidth(), view.getHeight(), false,
+ fillColor, area, 0, true);
+ } catch (Throwable t) {
+ failedCheckDescription = t.getMessage();
+ return false;
+ }
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches {@link View}s based on the given parent type.
+ *
+ * @param parentMatcher the type of the parent to match on
+ */
+ public static Matcher<View> isChildOfA(final Matcher<View> parentMatcher) {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is child of a: ");
+ parentMatcher.describeTo(description);
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ final ViewParent viewParent = view.getParent();
+ if (!(viewParent instanceof View)) {
+ return false;
+ }
+ if (parentMatcher.matches(viewParent)) {
+ return true;
+ }
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches FloatingActionButtons with the specified content height
+ */
+ public static Matcher withFabContentHeight(final int size) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ if (!(view instanceof FloatingActionButton)) {
+ return false;
+ }
+
+ final FloatingActionButton fab = (FloatingActionButton) view;
+ final Rect area = new Rect();
+ fab.getContentRect(area);
+
+ return area.height() == size;
+ }
+ };
+ }
+
+}
diff --git a/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
new file mode 100755
index 0000000..3360029
--- /dev/null
+++ b/design/tests/src/android/support/design/testutils/TextInputLayoutActions.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.support.design.widget.TextInputLayout;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class TextInputLayoutActions {
+
+ public static ViewAction setErrorEnabled(final boolean enabled) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextInputLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Enables/disables the error";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextInputLayout layout = (TextInputLayout) view;
+ layout.setErrorEnabled(enabled);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ public static ViewAction setError(final CharSequence error) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextInputLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets the error";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextInputLayout layout = (TextInputLayout) view;
+ layout.setError(error);
+
+ 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..e7b4bf7
--- /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 to a specific page";
+ }
+
+ @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/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
new file mode 100644
index 0000000..0e73c41
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.os.Build;
+import android.support.annotation.CallSuper;
+import android.support.annotation.IdRes;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.Shakespeare;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.widget.Toolbar;
+import android.text.TextUtils;
+import android.view.View;
+import android.widget.TextView;
+
+import static android.support.design.testutils.TestUtilsActions.setText;
+import static android.support.design.testutils.TestUtilsActions.setTitle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.junit.Assert.assertEquals;
+
+public abstract class AppBarLayoutBaseTest extends BaseDynamicCoordinatorLayoutTest {
+
+ protected AppBarLayout mAppBar;
+
+ protected CollapsingToolbarLayout mCollapsingToolbar;
+
+ protected Toolbar mToolbar;
+
+ protected TextView mTextView;
+
+ protected float mDefaultElevationValue;
+
+ protected static void performVerticalSwipeUpGesture(@IdRes int containerId, final int swipeX,
+ final int swipeStartY, final int swipeAmountY) {
+ onView(withId(containerId)).perform(new GeneralSwipeAction(
+ Swipe.SLOW,
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[] { swipeX, swipeStartY };
+ }
+ },
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[] { swipeX, swipeStartY - swipeAmountY };
+ }
+ }, Press.FINGER));
+ }
+
+ protected static void performVerticalSwipeDownGesture(@IdRes int containerId, final int swipeX,
+ final int swipeStartY, final int swipeAmountY) {
+ onView(withId(containerId)).perform(new GeneralSwipeAction(
+ Swipe.SLOW,
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[] { swipeX, swipeStartY };
+ }
+ },
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[] { swipeX, swipeStartY + swipeAmountY };
+ }
+ }, Press.FINGER));
+ }
+
+ @CallSuper
+ protected void configureContent(final @LayoutRes int layoutResId,
+ final @StringRes int titleResId) {
+ onView(withId(R.id.coordinator_stub)).perform(inflateViewStub(layoutResId));
+
+ mAppBar = (AppBarLayout) mCoordinatorLayout.findViewById(R.id.app_bar);
+ mCollapsingToolbar =
+ (CollapsingToolbarLayout) mAppBar.findViewById(R.id.collapsing_app_bar);
+ mToolbar = (Toolbar) mAppBar.findViewById(R.id.toolbar);
+
+ final AppCompatActivity activity = mActivityTestRule.getActivity();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ activity.setSupportActionBar(mToolbar);
+ }
+ });
+
+ final CharSequence activityTitle = activity.getString(titleResId);
+ activity.setTitle(activityTitle);
+ if (mCollapsingToolbar != null) {
+ onView(withId(R.id.collapsing_app_bar)).perform(setTitle(activityTitle));
+ }
+
+ TextView dialog = (TextView) mCoordinatorLayout.findViewById(R.id.textview_dialogue);
+ if (dialog != null) {
+ onView(withId(R.id.textview_dialogue)).perform(
+ setText(TextUtils.concat(Shakespeare.DIALOGUE)));
+ }
+
+ mDefaultElevationValue = mAppBar.getResources()
+ .getDimensionPixelSize(R.dimen.design_appbar_elevation);
+ }
+
+ protected void assertAppBarElevation(float expectedValue) {
+ if (Build.VERSION.SDK_INT >= 21) {
+ assertEquals(expectedValue, ViewCompat.getElevation(mAppBar), 0.05f);
+ }
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java b/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java
new file mode 100644
index 0000000..748e079
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithAnchoredFabMarginsTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class AppBarWithAnchoredFabMarginsTest extends AppBarLayoutBaseTest {
+ private int mFabMargin;
+
+ @Before
+ public void setup() {
+ mFabMargin = mActivityTestRule.getActivity().getResources().getDimensionPixelSize(
+ R.dimen.fab_margin);
+ }
+
+ @Test
+ public void testFabBottomMargin() throws Throwable {
+ configureContent(R.layout.design_appbar_anchored_fab_margin_bottom,
+ R.string.design_appbar_anchored_fab_margin_bottom);
+
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ final CoordinatorLayout.LayoutParams fabLp =
+ (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+ assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] fabOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ fab.getLocationOnScreen(fabOnScreenXY);
+
+ // FAB is horizontally centered in the coordinate system of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() / 2,
+ fabOnScreenXY[0] + fab.getWidth() / 2, 1);
+ // Bottom margin is in the coordinate space of the parent (CoordinatorLayout) and not
+ // the anchor. Since our FAB is far enough from the bottom edge of CoordinatorLayout,
+ // we are expecting the vertical center of the FAB to be aligned with the bottom edge
+ // of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+ fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+ }
+
+ @Test
+ public void testFabTopMargin() throws Throwable {
+ configureContent(R.layout.design_appbar_anchored_fab_margin_top,
+ R.string.design_appbar_anchored_fab_margin_top);
+
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ final CoordinatorLayout.LayoutParams fabLp =
+ (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+ assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] fabOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ fab.getLocationOnScreen(fabOnScreenXY);
+
+ // FAB is horizontally centered in the coordinate system of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() / 2,
+ fabOnScreenXY[0] + fab.getWidth() / 2, 1);
+ // Top margin is in the coordinate space of the parent (CoordinatorLayout) and not
+ // the anchor. Since our FAB is far enough from the bottom edge of CoordinatorLayout,
+ // we are expecting the vertical center of the FAB to be aligned with the bottom edge
+ // of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+ fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+ }
+
+ @Test
+ public void testFabLeftMargin() throws Throwable {
+ configureContent(R.layout.design_appbar_anchored_fab_margin_left,
+ R.string.design_appbar_anchored_fab_margin_left);
+
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ final CoordinatorLayout.LayoutParams fabLp =
+ (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+ assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] fabOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ fab.getLocationOnScreen(fabOnScreenXY);
+
+ // FAB is left-aligned in the coordinate system of its anchor (app bar). In addition,
+ // its left margin "pushes" it away in the coordinate system of the parent
+ // (CoordinatorLayout)
+ assertEquals(appbarOnScreenXY[0] + mFabMargin, fabOnScreenXY[0], 1);
+ // FAB's vertical center should be aligned with the bottom edge of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+ fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+ }
+
+ @Test
+ public void testFabRightMargin() throws Throwable {
+ configureContent(R.layout.design_appbar_anchored_fab_margin_right,
+ R.string.design_appbar_anchored_fab_margin_right);
+
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ final CoordinatorLayout.LayoutParams fabLp =
+ (CoordinatorLayout.LayoutParams) fab.getLayoutParams();
+ assertEquals(mAppBar.getId(), fabLp.getAnchorId());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] fabOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ fab.getLocationOnScreen(fabOnScreenXY);
+
+ // FAB is right-aligned in the coordinate system of its anchor (app bar). In addition,
+ // its right margin "pushes" it away in the coordinate system of the parent
+ // (CoordinatorLayout)
+ assertEquals(appbarOnScreenXY[0] + mAppBar.getWidth() - mFabMargin,
+ fabOnScreenXY[0] + fab.getWidth(), 1);
+ // FAB's vertical center should be aligned with the bottom edge of its anchor (app bar).
+ assertEquals(appbarOnScreenXY[1] + mAppBar.getHeight(),
+ fabOnScreenXY[1] + fab.getHeight() / 2, 1);
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
new file mode 100644
index 0000000..fe34eaf
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.os.Build;
+import android.os.SystemClock;
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.ImageView;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+public class AppBarWithCollapsingToolbarTest extends AppBarLayoutBaseTest {
+ @Test
+ public void testPinnedToolbar() {
+ configureContent(R.layout.design_appbar_toolbar_collapse_pin,
+ R.string.design_appbar_collapsing_toolbar_pin);
+
+ CollapsingToolbarLayout.LayoutParams toolbarLp =
+ (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+ assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+ toolbarLp.getCollapseMode());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int toolbarHeight = mToolbar.getHeight();
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+ final int shortSwipeAmount = toolbarHeight;
+
+ assertAppBarElevation(0f);
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be visually snapped below the system status bar.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+
+ // Perform another swipe-up gesture
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ shortSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be visually snapped below the system status bar
+ // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a short swipe-down gesture across the horizontal center of the screen.
+ // Note that the swipe down is a bit longer than the swipe up to check that the app bar
+ // is not starting to expand too early.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom - shortSwipeAmount,
+ 3 * shortSwipeAmount / 2);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be visually snapped below the system status bar
+ // as it is in the pinned mode and we haven't fully swiped down the content below the
+ // app bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + toolbarHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
+
+ // Perform yet another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
+ }
+
+ @Test
+ public void testScrollingToolbar() {
+ configureContent(R.layout.design_appbar_toolbar_collapse_scroll,
+ R.string.design_appbar_collapsing_toolbar_scroll);
+
+ CollapsingToolbarLayout.LayoutParams toolbarLp =
+ (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+ assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+ toolbarLp.getCollapseMode());
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int topInset = mAppBar.getTopInset();
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int toolbarHeight = mToolbar.getHeight();
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+ final int shortSwipeAmount = toolbarHeight;
+
+ assertAppBarElevation(0f);
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should not be visually "present" on the screen, with its bottom
+ // edge aligned with the bottom of system status bar. If we're running on a device which
+ // supports a translucent status bar, we need to take the status bar height into account.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
+ assertAppBarElevation(0f);
+
+ // Perform another swipe-up gesture
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ shortSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight - topInset, 1);
+ assertAppBarElevation(0f);
+
+ // Perform a short swipe-down gesture across the horizontal center of the screen.
+ // Note that the swipe down is a bit longer than the swipe up to fully bring down
+ // the scrolled-away toolbar
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ 3 * shortSwipeAmount / 2);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be visually snapped below the system status bar as it
+ // in scrolling mode and we've swiped down, but not fully. Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop + toolbarHeight + topInset,
+ appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1]);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight);
+ assertAppBarElevation(0f);
+
+ // Perform yet another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
+ }
+
+ @Test
+ public void testPinnedToolbarAndAnchoredFab() throws Throwable {
+ configureContent(R.layout.design_appbar_toolbar_collapse_pin_with_fab,
+ R.string.design_appbar_collapsing_toolbar_pin_fab);
+
+ CollapsingToolbarLayout.LayoutParams toolbarLp =
+ (CollapsingToolbarLayout.LayoutParams) mToolbar.getLayoutParams();
+ assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PIN,
+ toolbarLp.getCollapseMode());
+
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ // Since we the visibility change listener path is only exposed via direct calls to
+ // FloatingActionButton.show and not the internal path that FAB's behavior is using,
+ // this test needs to be tied to the internal implementation details of running animation
+ // that scales the FAB to 0/0 scales and interpolates its alpha to 0. Since that animation
+ // starts running partway through our swipe gesture and may complete a bit later then
+ // the swipe gesture, sleep for a bit to catch the "final" state of the FAB.
+ SystemClock.sleep(200);
+
+ // At this point the FAB should be scaled to 0/0 and set at alpha 0. Since the relevant
+ // getter methods are only available on v11+, wrap the asserts with build version check.
+ if (Build.VERSION.SDK_INT >= 11) {
+ assertEquals(0.0f, fab.getScaleX(), 0.0f);
+ assertEquals(0.0f, fab.getScaleY(), 0.0f);
+ assertEquals(0.0f, fab.getAlpha(), 0.0f);
+ }
+
+ // Perform a swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ // Same as for swipe-up gesture - sleep for a bit to catch the "final" visible state of
+ // the FAB.
+ SystemClock.sleep(200);
+
+ // At this point the FAB should be scaled back to its original size and be at full opacity.
+ if (Build.VERSION.SDK_INT >= 11) {
+ assertEquals(1.0f, fab.getScaleX(), 0.0f);
+ assertEquals(1.0f, fab.getScaleY(), 0.0f);
+ assertEquals(1.0f, fab.getAlpha(), 0.0f);
+ }
+ }
+
+ @Test
+ public void testPinnedToolbarAndParallaxImage() {
+ configureContent(R.layout.design_appbar_toolbar_collapse_with_image,
+ R.string.design_appbar_collapsing_toolbar_with_image);
+
+ final ImageView parallaxImageView =
+ (ImageView) mCoordinatorLayout.findViewById(R.id.app_bar_image);
+
+ CollapsingToolbarLayout.LayoutParams parallaxImageViewLp =
+ (CollapsingToolbarLayout.LayoutParams) parallaxImageView.getLayoutParams();
+ assertEquals(CollapsingToolbarLayout.LayoutParams.COLLAPSE_MODE_PARALLAX,
+ parallaxImageViewLp.getCollapseMode());
+
+ final float parallaxMultiplier = parallaxImageViewLp.getParallaxMultiplier();
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] parallaxImageOnScreenXY = new int[2];
+ final int appbarHeight = mAppBar.getHeight();
+ final int toolbarHeight = mToolbar.getHeight();
+ final int parallaxImageHeight = parallaxImageView.getHeight();
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int originalParallaxImageTop = parallaxImageOnScreenXY[1];
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ // Test that at the beginning our image is top-aligned with the app bar
+ assertEquals(appbarOnScreenXY[1], parallaxImageOnScreenXY[1]);
+
+ // Swipe up by the toolbar's height
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ toolbarHeight);
+
+ // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+ // the amount that the top edge of the app bar (in the screen coordinates) has.
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+ assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+ parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+
+ // Swipe up by another toolbar's height
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ toolbarHeight);
+
+ // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+ // the amount that the top edge of the app bar (in the screen coordinates) has.
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+ assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+ parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+
+ // Swipe down by a different value (150% of the toolbar's height) to test parallax going the
+ // other way
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ 3 * toolbarHeight / 2);
+
+ // Test that the top edge of the image (in the screen coordinates) has "moved" by half
+ // the amount that the top edge of the app bar (in the screen coordinates) has.
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ parallaxImageView.getLocationOnScreen(parallaxImageOnScreenXY);
+ assertEquals(parallaxMultiplier * (appbarOnScreenXY[1] - originalAppbarTop),
+ parallaxImageOnScreenXY[1] - originalParallaxImageTop, 1);
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
new file mode 100644
index 0000000..0c715de
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
@@ -0,0 +1,403 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.annotation.LayoutRes;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.Cheeses;
+import android.test.suitebuilder.annotation.LargeTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import org.junit.Test;
+
+import static android.support.design.testutils.TestUtilsActions.addTabs;
+import static android.support.design.testutils.TestUtilsActions.waitUntilIdle;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isRoot;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertEquals;
+
+@MediumTest
+public class AppBarWithToolbarAndTabsTest extends AppBarLayoutBaseTest {
+ private TabLayout mTabLayout;
+
+ @Override
+ protected void configureContent(@LayoutRes int layoutResId, @StringRes int titleResId) {
+ super.configureContent(layoutResId, titleResId);
+
+ mTabLayout = (TabLayout) mAppBar.findViewById(R.id.tabs);
+ String[] tabTitles = new String[5];
+ System.arraycopy(Cheeses.sCheeseStrings, 0, tabTitles, 0, 5);
+ onView(withId(R.id.tabs)).perform(addTabs(tabTitles));
+ }
+
+ @Test
+ public void testScrollingToolbarAndScrollingTabs() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll,
+ R.string.design_appbar_toolbar_scroll_tabs_scroll);
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int toolbarHeight = mToolbar.getHeight();
+ final int tabsHeight = mTabLayout.getHeight();
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+ final int shortSwipeAmount = toolbarHeight;
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should not be visually "present" on the screen, with its bottom
+ // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-up gesture
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ shortSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be off the screen. Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a long swipe-down gesture across the horizontal center of the screen.
+ // Note that the swipe down is a bit longer than the swipe up to fully bring down
+ // the scrolled-away toolbar and tab layout
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount * 3 / 2);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be visually snapped below the system status bar as it
+ // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform yet another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+ }
+
+ @Test
+ public void testScrollingToolbarAndPinnedTabs() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_tabs_pinned,
+ R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int toolbarHeight = mToolbar.getHeight();
+ final int tabsHeight = mTabLayout.getHeight();
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+ final int shortSwipeAmount = toolbarHeight;
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the tab bar should be visually snapped below the system status bar.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-up gesture
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ shortSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the tab bar should still be visually snapped below the system status bar
+ // as it is in the pinned mode. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a short swipe-down gesture across the horizontal center of the screen.
+ // Note that the swipe down is a bit longer than the swipe up to fully bring down
+ // the scrolled-away toolbar and tab layout
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom - shortSwipeAmount,
+ 3 * shortSwipeAmount / 2);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position as it
+ // in scrolling mode and we've swiped down. Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform yet another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+ }
+
+ @LargeTest
+ @Test
+ public void testSnappingToolbarAndSnappingTabs() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_tabs_scroll_snap,
+ R.string.design_appbar_toolbar_scroll_tabs_scroll_snap);
+
+ final int[] appbarOnScreenXY = new int[2];
+ final int[] coordinatorLayoutOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ mCoordinatorLayout.getLocationOnScreen(coordinatorLayoutOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int toolbarHeight = mToolbar.getHeight();
+ final int tabsHeight = mTabLayout.getHeight();
+ final int appbarHeight = mAppBar.getHeight();
+
+ // Since AppBarLayout doesn't expose a way to track snap animations, the three possible
+ // options are
+ // a) track how vertical offsets and invalidation is propagated through the
+ // view hierarchy and wait until there are no more events of that kind
+ // b) run a dummy Espresso action that waits until the main thread is idle
+ // c) sleep for a hardcoded period of time to "wait" until the snap animation is done
+ // In this test method we go with option b)
+
+ // Perform a swipe-up gesture across the horizontal center of the screen. The amount
+ // of swipe is 25% of the toolbar height and we expect the snap behavior to "move"
+ // the app bar back to its original position.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ toolbarHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position as it
+ // in snapping mode and we haven't swiped "enough". Allow for off-by-a-pixel
+ // margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a slightly longer swipe-up gesture, this time by 75% of the toolbar height.
+ // We expect the snap behavior to move the app bar to snap the tab layout below the
+ // system status bar.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ 3 * toolbarHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should "snap" the toolbar away and align the tab layout below
+ // the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a short swipe-up gesture, this time by 25% of the tab layout height. We expect
+ // snap behavior to move the app bar back to snap the tab layout below the system status
+ // bar.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ tabsHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should "snap" back to align the tab layout below
+ // the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
+ // snap behavior to move the app bar fully away from the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ 3 * tabsHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should not be visually "present" on the screen, with its bottom
+ // edge aligned with the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a short swipe-down gesture by 25% of the tab layout height. We expect
+ // snap behavior to move the app bar back fully away from the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ tabsHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still not be visually "present" on the screen, with
+ // its bottom edge aligned with the system status bar. Allow for off-by-a-pixel margin
+ // of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a longer swipe-up gesture, this time by 75% of the tab layout height. We expect
+ // snap behavior to move the app bar to snap the tab layout below the system status
+ // bar.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ 3 * tabsHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should "snap" the toolbar away and align the tab layout below
+ // the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a short swipe-down gesture by 25% of the toolbar height. We expect
+ // snap behavior to align the tab layout below the system status bar
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ toolbarHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should still align the tab layout below
+ // the system status bar. Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop + tabsHeight, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+
+ // Perform a longer swipe-up gesture, this time by 75% of the toolbar height. We expect
+ // snap behavior to move the app bar back to its original place (fully visible).
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + toolbarHeight,
+ 3 * tabsHeight / 4);
+
+ // Wait for the snap animation to be done
+ onView(isRoot()).perform(waitUntilIdle());
+
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ // At this point the app bar should be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java
new file mode 100644
index 0000000..5fc25f6
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarTest.java
@@ -0,0 +1,129 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static org.junit.Assert.assertEquals;
+
+import android.support.design.test.R;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.junit.Test;
+
+@MediumTest
+public class AppBarWithToolbarTest extends AppBarLayoutBaseTest {
+
+ /**
+ * Tests a Toolbar with fitSystemWindows = undefined, with a fitSystemWindows = true parent
+ */
+ @Test
+ public void testScrollToolbarWithFitSystemWindowsParent() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_fitsystemwindows_parent,
+ R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+ final int[] appbarOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+
+ final int originalAppbarTop = appbarOnScreenXY[1];
+ final int originalAppbarBottom = appbarOnScreenXY[1] + mAppBar.getHeight();
+ final int centerX = appbarOnScreenXY[0] + mAppBar.getWidth() / 2;
+
+ final int appbarHeight = mAppBar.getHeight();
+ final int longSwipeAmount = 3 * appbarHeight / 2;
+
+ // Perform a swipe-up gesture across the horizontal center of the screen.
+ performVerticalSwipeUpGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom + 3 * longSwipeAmount / 2,
+ longSwipeAmount);
+
+ // At this point the tab bar should be visually snapped below the system status bar.
+ // Allow for off-by-a-pixel margin of error.
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+
+ // Perform yet another swipe-down gesture across the horizontal center of the screen.
+ performVerticalSwipeDownGesture(
+ R.id.coordinator_layout,
+ centerX,
+ originalAppbarBottom,
+ longSwipeAmount);
+
+ // At this point the app bar should still be in its original position.
+ // Allow for off-by-a-pixel margin of error.
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+ assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
+ assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ }
+
+ /**
+ * Tests a AppBarLayout + scrolling content with fitSystemWindows = undefined,
+ * with a fitSystemWindows = true parent
+ */
+ @Test
+ public void testScrollingContentPositionWithFitSystemWindowsParent() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_fitsystemwindows_parent,
+ R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+ final int[] appbarOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+
+ final View scrollingContent = mCoordinatorLayout.findViewById(R.id.scrolling_content);
+ final int[] scrollingContentOnScreenXY = new int[2];
+ scrollingContent.getLocationOnScreen(scrollingContentOnScreenXY);
+
+ // Assert that they have the same left
+ assertEquals(appbarOnScreenXY[0], scrollingContentOnScreenXY[0]);
+ // ...and the same width
+ assertEquals(mAppBar.getWidth(), scrollingContent.getWidth());
+ // ...and are vertically stacked
+ assertEquals(mAppBar.getBottom(), scrollingContent.getTop());
+ }
+
+ /**
+ * Tests a AppBarLayout + scrolling content with fitSystemWindows = undefined,
+ * with a fitSystemWindows = true parent, in RTL
+ */
+ @Test
+ public void testScrollingContentPositionWithFitSystemWindowsParentInRtl() {
+ configureContent(R.layout.design_appbar_toolbar_scroll_fitsystemwindows_parent,
+ R.string.design_appbar_toolbar_scroll_tabs_pin);
+
+ // Force RTL
+ onView(withId(R.id.app_bar)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ final int[] appbarOnScreenXY = new int[2];
+ mAppBar.getLocationOnScreen(appbarOnScreenXY);
+
+ final View scrollingContent = mCoordinatorLayout.findViewById(R.id.scrolling_content);
+ final int[] scrollingContentOnScreenXY = new int[2];
+ scrollingContent.getLocationOnScreen(scrollingContentOnScreenXY);
+
+ // Assert that they have the same left
+ assertEquals(appbarOnScreenXY[0], scrollingContentOnScreenXY[0]);
+ // ...and the same width
+ assertEquals(mAppBar.getWidth(), scrollingContent.getWidth());
+ // ...and are vertically stacked
+ assertEquals(mAppBar.getBottom(), scrollingContent.getTop());
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java
new file mode 100755
index 0000000..8c6f331
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseDynamicCoordinatorLayoutTest.java
@@ -0,0 +1,130 @@
+/*
+ * 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.support.annotation.LayoutRes;
+import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewStub;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.any;
+
+/**
+ * Base class for tests that are exercising various aspects of {@link CoordinatorLayout}.
+ */
+public abstract class BaseDynamicCoordinatorLayoutTest
+ extends BaseInstrumentationTestCase<DynamicCoordinatorLayoutActivity> {
+ protected CoordinatorLayout mCoordinatorLayout;
+
+ public BaseDynamicCoordinatorLayoutTest() {
+ super(DynamicCoordinatorLayoutActivity.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Now that the test is done, replace the activity content view with ViewStub so
+ // that it's ready to be replaced for the next test.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final DynamicCoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+ activity.setContentView(R.layout.dynamic_coordinator_layout);
+ mCoordinatorLayout = null;
+ }
+ });
+ }
+
+ /**
+ * Matches views that have parents.
+ */
+ private Matcher<View> hasParent() {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has parent");
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ return view.getParent() != null;
+ }
+ };
+ }
+
+ /**
+ * Inflates the <code>ViewStub</code> with the passed layout resource.
+ */
+ protected ViewAction inflateViewStub(final @LayoutRes int layoutResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isAssignableFrom(ViewStub.class), hasParent());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Inflates view stub";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewStub viewStub = (ViewStub) view;
+ viewStub.setLayoutResource(layoutResId);
+ viewStub.inflate();
+
+ mCoordinatorLayout = (CoordinatorLayout) mActivityTestRule.getActivity()
+ .findViewById(viewStub.getInflatedId());
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ protected ViewAction setLayoutDirection(final int layoutDir) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return any(View.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets layout direction";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setLayoutDirection(view, layoutDir);
+
+ 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..0c09e7b
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseInstrumentationTestCase.java
@@ -0,0 +1,33 @@
+/*
+ * 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.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+ @Rule
+ public final ActivityTestRule<A> mActivityTestRule;
+
+ protected BaseInstrumentationTestCase(Class<A> activityClass) {
+ mActivityTestRule = new ActivityTestRule<A>(activityClass);
+ }
+}
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..0c801cb
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BaseTestActivity.java
@@ -0,0 +1,61 @@
+/*
+ * 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);
+ overridePendingTransition(0, 0);
+
+ 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);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
+ 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/BottomSheetBehaviorActivity.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorActivity.java
new file mode 100644
index 0000000..44fd41b
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorActivity.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.Intent;
+import android.support.design.test.R;
+import android.widget.LinearLayout;
+
+
+public class BottomSheetBehaviorActivity extends BaseTestActivity {
+
+ public static String EXTRA_INITIAL_STATE = "initial_state";
+
+ CoordinatorLayout mCoordinatorLayout;
+
+ LinearLayout mBottomSheet;
+
+ BottomSheetBehavior mBehavior;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.test_design_bottom_sheet_behavior;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator);
+ mBottomSheet = (LinearLayout) findViewById(R.id.bottom_sheet);
+ mBehavior = BottomSheetBehavior.from(mBottomSheet);
+ Intent intent = getIntent();
+ if (intent != null) {
+ int initialState = intent.getIntExtra(EXTRA_INITIAL_STATE, -1);
+ if (initialState != -1) {
+ //noinspection ResourceType
+ mBehavior.setState(initialState);
+ }
+ }
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorInitialStateTest.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorInitialStateTest.java
new file mode 100644
index 0000000..050739b
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorInitialStateTest.java
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.core.Is.is;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class BottomSheetBehaviorInitialStateTest {
+
+ @Rule
+ public final ActivityTestRule<BottomSheetBehaviorActivity> mActivityTestRule
+ = new ActivityTestRule<>(BottomSheetBehaviorActivity.class, true, false);
+
+ @Test
+ public void testSetStateExpanded() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent intent = new Intent(context, BottomSheetBehaviorActivity.class);
+ intent.putExtra(BottomSheetBehaviorActivity.EXTRA_INITIAL_STATE,
+ BottomSheetBehavior.STATE_EXPANDED);
+ mActivityTestRule.launchActivity(intent);
+ BottomSheetBehaviorActivity activity = mActivityTestRule.getActivity();
+ assertThat(activity.mBehavior.getState(), is(BottomSheetBehavior.STATE_EXPANDED));
+ assertThat(activity.mBottomSheet.getTop(), is(0));
+ }
+
+
+ @Test
+ public void testSetStateHidden() {
+ Context context = InstrumentationRegistry.getInstrumentation().getTargetContext();
+ Intent intent = new Intent(context, BottomSheetBehaviorActivity.class);
+ intent.putExtra(BottomSheetBehaviorActivity.EXTRA_INITIAL_STATE,
+ BottomSheetBehavior.STATE_HIDDEN);
+ mActivityTestRule.launchActivity(intent);
+ BottomSheetBehaviorActivity activity = mActivityTestRule.getActivity();
+ assertThat(activity.mBehavior.getState(), is(BottomSheetBehavior.STATE_HIDDEN));
+ assertThat(activity.mBottomSheet.getTop(), is(activity.mCoordinatorLayout.getHeight()));
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java
new file mode 100644
index 0000000..bb3b39d
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTest.java
@@ -0,0 +1,583 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+
+import android.os.SystemClock;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewAssertion;
+import android.support.test.espresso.action.CoordinatesProvider;
+import android.support.test.espresso.action.GeneralLocation;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.MotionEvents;
+import android.support.test.espresso.action.PrecisionDescriber;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.assertion.ViewAssertions;
+import android.support.test.espresso.core.deps.guava.base.Preconditions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.NestedScrollView;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+public class BottomSheetBehaviorTest extends
+ BaseInstrumentationTestCase<BottomSheetBehaviorActivity> {
+
+ public static class Callback extends BottomSheetBehavior.BottomSheetCallback
+ implements IdlingResource {
+
+ private boolean mIsIdle;
+
+ private IdlingResource.ResourceCallback mResourceCallback;
+
+ public Callback(BottomSheetBehavior behavior) {
+ behavior.setBottomSheetCallback(this);
+ int state = behavior.getState();
+ mIsIdle = isIdleState(state);
+ }
+
+ @Override
+ public void onStateChanged(@NonNull View bottomSheet,
+ @BottomSheetBehavior.State int newState) {
+ boolean wasIdle = mIsIdle;
+ mIsIdle = isIdleState(newState);
+ if (!wasIdle && mIsIdle && mResourceCallback != null) {
+ mResourceCallback.onTransitionToIdle();
+ }
+ }
+
+ @Override
+ public void onSlide(@NonNull View bottomSheet, float slideOffset) {
+ }
+
+ @Override
+ public String getName() {
+ return Callback.class.getSimpleName();
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ return mIsIdle;
+ }
+
+ @Override
+ public void registerIdleTransitionCallback(IdlingResource.ResourceCallback callback) {
+ mResourceCallback = callback;
+ }
+
+ private boolean isIdleState(int state) {
+ return state != BottomSheetBehavior.STATE_DRAGGING &&
+ state != BottomSheetBehavior.STATE_SETTLING;
+ }
+
+ }
+
+ /**
+ * This is like {@link GeneralSwipeAction}, but it does not send ACTION_UP at the end.
+ */
+ private static class DragAction implements ViewAction {
+
+ private static final int STEPS = 10;
+ private static final int DURATION = 100;
+
+ private final CoordinatesProvider mStart;
+ private final CoordinatesProvider mEnd;
+ private final PrecisionDescriber mPrecisionDescriber;
+
+ public DragAction(CoordinatesProvider start, CoordinatesProvider end,
+ PrecisionDescriber precisionDescriber) {
+ mStart = start;
+ mEnd = end;
+ mPrecisionDescriber = precisionDescriber;
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return ViewMatchers.isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "drag";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ float[] precision = mPrecisionDescriber.describePrecision();
+ float[] start = mStart.calculateCoordinates(view);
+ float[] end = mEnd.calculateCoordinates(view);
+ float[][] steps = interpolate(start, end, STEPS);
+ int delayBetweenMovements = DURATION / steps.length;
+ // Down
+ MotionEvent downEvent = MotionEvents.sendDown(uiController, start, precision).down;
+ try {
+ for (int i = 0; i < steps.length; i++) {
+ // Wait
+ long desiredTime = downEvent.getDownTime() + (long)(delayBetweenMovements * i);
+ long timeUntilDesired = desiredTime - SystemClock.uptimeMillis();
+ if (timeUntilDesired > 10L) {
+ uiController.loopMainThreadForAtLeast(timeUntilDesired);
+ }
+ // Move
+ if (!MotionEvents.sendMovement(uiController, downEvent, steps[i])) {
+ MotionEvents.sendCancel(uiController, downEvent);
+ throw new RuntimeException("Cannot drag: failed to send a move event.");
+ }
+ BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
+ }
+ int duration = ViewConfiguration.getPressedStateDuration();
+ if (duration > 0) {
+ uiController.loopMainThreadForAtLeast((long) duration);
+ }
+ } finally {
+ downEvent.recycle();
+ }
+ }
+
+ private static float[][] interpolate(float[] start, float[] end, int steps) {
+ Preconditions.checkElementIndex(1, start.length);
+ Preconditions.checkElementIndex(1, end.length);
+ float[][] res = new float[steps][2];
+ for(int i = 1; i < steps + 1; ++i) {
+ res[i - 1][0] = start[0] + (end[0] - start[0]) * (float)i / ((float)steps + 2.0F);
+ res[i - 1][1] = start[1] + (end[1] - start[1]) * (float)i / ((float)steps + 2.0F);
+ }
+ return res;
+ }
+ }
+
+ private static class AddViewAction implements ViewAction {
+
+ private final int mLayout;
+
+ public AddViewAction(@LayoutRes int layout) {
+ mLayout = layout;
+ }
+
+ @Override
+ public Matcher<View> getConstraints() {
+ return ViewMatchers.isAssignableFrom(ViewGroup.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "add view";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ ViewGroup parent = (ViewGroup) view;
+ View child = LayoutInflater.from(view.getContext()).inflate(mLayout, parent, false);
+ parent.addView(child);
+ }
+ }
+
+ private Callback mCallback;
+
+ public BottomSheetBehaviorTest() {
+ super(BottomSheetBehaviorActivity.class);
+ }
+
+ @Test
+ @SmallTest
+ public void testInitialSetup() {
+ BottomSheetBehavior behavior = getBehavior();
+ assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ CoordinatorLayout coordinatorLayout = getCoordinatorLayout();
+ ViewGroup bottomSheet = getBottomSheet();
+ assertThat(bottomSheet.getTop(),
+ is(coordinatorLayout.getHeight() - behavior.getPeekHeight()));
+ }
+
+ @Test
+ @MediumTest
+ public void testSetStateExpandedToCollapsed() {
+ checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
+ checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
+ }
+
+ @Test
+ @MediumTest
+ public void testSetStateHiddenToCollapsed() {
+ checkSetState(BottomSheetBehavior.STATE_HIDDEN, not(ViewMatchers.isDisplayed()));
+ checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
+ }
+
+ @Test
+ @MediumTest
+ public void testSetStateCollapsedToCollapsed() {
+ checkSetState(BottomSheetBehavior.STATE_COLLAPSED, ViewMatchers.isDisplayed());
+ }
+
+ @Test
+ @MediumTest
+ public void testSwipeDownToCollapse() {
+ checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
+ Swipe.FAST,
+ // Manually calculate the starting coordinates to make sure that the touch
+ // actually falls onto the view on Gingerbread
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+ return new float[]{
+ view.getWidth() / 2,
+ location[1] + 1
+ };
+ }
+ },
+ // Manually calculate the ending coordinates to make sure that the bottom
+ // sheet is collapsed, not hidden
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ BottomSheetBehavior behavior = getBehavior();
+ return new float[]{
+ // x: center of the bottom sheet
+ view.getWidth() / 2,
+ // y: just above the peek height
+ view.getHeight() - behavior.getPeekHeight()};
+ }
+ }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
+ // Avoid a deadlock (b/26160710)
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testSwipeDownToHide() {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .perform(DesignViewActions.withCustomConstraints(ViewActions.swipeDown(),
+ ViewMatchers.isDisplayingAtLeast(5)));
+ // Avoid a deadlock (b/26160710)
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ public void testSkipCollapsed() {
+ getBehavior().setSkipCollapsed(true);
+ checkSetState(BottomSheetBehavior.STATE_EXPANDED, ViewMatchers.isDisplayed());
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .perform(DesignViewActions.withCustomConstraints(new GeneralSwipeAction(
+ Swipe.FAST,
+ // Manually calculate the starting coordinates to make sure that the touch
+ // actually falls onto the view on Gingerbread
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ int[] location = new int[2];
+ view.getLocationInWindow(location);
+ return new float[]{
+ view.getWidth() / 2,
+ location[1] + 1
+ };
+ }
+ },
+ // Manually calculate the ending coordinates to make sure that the bottom
+ // sheet is collapsed, not hidden
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ BottomSheetBehavior behavior = getBehavior();
+ return new float[]{
+ // x: center of the bottom sheet
+ view.getWidth() / 2,
+ // y: just above the peek height
+ view.getHeight() - behavior.getPeekHeight()};
+ }
+ }, Press.FINGER), ViewMatchers.isDisplayingAtLeast(5)));
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(not(ViewMatchers.isDisplayed())));
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_HIDDEN));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testSwipeUpToExpand() {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .perform(DesignViewActions.withCustomConstraints(
+ new GeneralSwipeAction(Swipe.FAST,
+ GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{view.getWidth() / 2, 0};
+ }
+ }, Press.FINGER),
+ ViewMatchers.isDisplayingAtLeast(5)));
+ // Avoid a deadlock (b/26160710)
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_EXPANDED));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testInvisible() {
+ // Make the bottomsheet invisible
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getBottomSheet().setVisibility(View.INVISIBLE);
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ }
+ });
+ // Swipe up as if to expand it
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .perform(DesignViewActions.withCustomConstraints(
+ new GeneralSwipeAction(Swipe.FAST,
+ GeneralLocation.VISIBLE_CENTER, new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{view.getWidth() / 2, 0};
+ }
+ }, Press.FINGER),
+ not(ViewMatchers.isDisplayed())));
+ // Check that the bottom sheet stays the same collapsed state
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ }
+ });
+ }
+
+ @Test
+ @MediumTest
+ public void testNestedScroll() {
+ final ViewGroup bottomSheet = getBottomSheet();
+ final BottomSheetBehavior behavior = getBehavior();
+ final NestedScrollView scroll = new NestedScrollView(mActivityTestRule.getActivity());
+ // Set up nested scrolling area
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ bottomSheet.addView(scroll, new ViewGroup.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
+ TextView view = new TextView(mActivityTestRule.getActivity());
+ StringBuilder sb = new StringBuilder();
+ for (int i = 0; i < 500; ++i) {
+ sb.append("It is fine today. ");
+ }
+ view.setText(sb);
+ view.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ // Do nothing
+ }
+ });
+ scroll.addView(view);
+ assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ // The scroll offset is 0 at first
+ assertThat(scroll.getScrollY(), is(0));
+ }
+ });
+ // Swipe from the very bottom of the bottom sheet to the top edge of the screen so that the
+ // scrolling content is also scrolled
+ Espresso.onView(ViewMatchers.withId(R.id.coordinator))
+ .perform(new GeneralSwipeAction(Swipe.FAST,
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{view.getWidth() / 2, view.getHeight() - 1};
+ }
+ },
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{view.getWidth() / 2, 1};
+ }
+ }, Press.FINGER));
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_EXPANDED));
+ // This confirms that the nested scrolling area was scrolled continuously after
+ // the bottom sheet is expanded.
+ assertThat(scroll.getScrollY(), is(not(0)));
+ }
+ });
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDragOutside() {
+ // Swipe up outside of the bottom sheet
+ Espresso.onView(ViewMatchers.withId(R.id.coordinator))
+ .perform(DesignViewActions.withCustomConstraints(
+ new GeneralSwipeAction(Swipe.FAST,
+ // Just above the bottom sheet
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{
+ view.getWidth() / 2,
+ view.getHeight() - getBehavior().getPeekHeight() - 9
+ };
+ }
+ },
+ // Top of the CoordinatorLayout
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ return new float[]{view.getWidth() / 2, 1};
+ }
+ }, Press.FINGER),
+ ViewMatchers.isDisplayed()));
+ // Avoid a deadlock (b/26160710)
+ registerIdlingResourceCallback();
+ try {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
+ // The bottom sheet should remain collapsed
+ assertThat(getBehavior().getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testLayoutWhileDragging() {
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ // Drag (and not release)
+ .perform(new DragAction(
+ GeneralLocation.VISIBLE_CENTER,
+ GeneralLocation.TOP_CENTER,
+ Press.FINGER))
+ // Check that the bottom sheet is in STATE_DRAGGING
+ .check(new ViewAssertion() {
+ @Override
+ public void check(View view, NoMatchingViewException e) {
+ assertThat(view, is(ViewMatchers.isDisplayed()));
+ BottomSheetBehavior behavior = BottomSheetBehavior.from(view);
+ assertThat(behavior.getState(), is(BottomSheetBehavior.STATE_DRAGGING));
+ }
+ })
+ // Add a new view
+ .perform(new AddViewAction(R.layout.frame_layout))
+ // Check that the newly added view is properly laid out
+ .check(new ViewAssertion() {
+ @Override
+ public void check(View view, NoMatchingViewException e) {
+ ViewGroup parent = (ViewGroup) view;
+ assertThat(parent.getChildCount(), is(1));
+ View child = parent.getChildAt(0);
+ assertThat(ViewCompat.isLaidOut(child), is(true));
+ }
+ });
+ }
+
+ private void checkSetState(final int state, Matcher<View> matcher) {
+ registerIdlingResourceCallback();
+ try {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getBehavior().setState(state);
+ }
+ });
+ Espresso.onView(ViewMatchers.withId(R.id.bottom_sheet))
+ .check(ViewAssertions.matches(matcher));
+ assertThat(getBehavior().getState(), is(state));
+ } finally {
+ unregisterIdlingResourceCallback();
+ }
+ }
+
+ private void registerIdlingResourceCallback() {
+ // TODO(yaraki): Move this to setUp() when b/26160710 is fixed
+ mCallback = new Callback(getBehavior());
+ Espresso.registerIdlingResources(mCallback);
+ }
+
+ private void unregisterIdlingResourceCallback() {
+ if (mCallback != null) {
+ Espresso.unregisterIdlingResources(mCallback);
+ mCallback = null;
+ }
+ }
+
+ private ViewGroup getBottomSheet() {
+ return mActivityTestRule.getActivity().mBottomSheet;
+ }
+
+ private BottomSheetBehavior getBehavior() {
+ return mActivityTestRule.getActivity().mBehavior;
+ }
+
+ private CoordinatorLayout getCoordinatorLayout() {
+ return mActivityTestRule.getActivity().mCoordinatorLayout;
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorTouchTest.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTouchTest.java
new file mode 100644
index 0000000..ea5872e
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorTouchTest.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewAssertion;
+import android.support.test.espresso.action.ViewActions;
+import android.support.v4.view.MotionEventCompat;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.FrameLayout;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class BottomSheetBehaviorTouchTest extends
+ BaseInstrumentationTestCase<CoordinatorLayoutActivity> {
+
+ private static final int PEEK_HEIGHT = 100;
+
+ private FrameLayout mBottomSheet;
+
+ private BottomSheetBehavior<FrameLayout> mBehavior;
+
+ private boolean mDown;
+
+ private View.OnTouchListener mOnTouchListener = new View.OnTouchListener() {
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ switch (MotionEventCompat.getActionMasked(event)) {
+ case MotionEvent.ACTION_DOWN:
+ mDown = true;
+ break;
+ }
+ return true;
+ }
+
+ };
+
+ public BottomSheetBehaviorTouchTest() {
+ super(CoordinatorLayoutActivity.class);
+ }
+
+ @Before
+ public void setUpBottomSheet() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+ activity.mContainer.setOnTouchListener(mOnTouchListener);
+ mBottomSheet = new FrameLayout(activity);
+ CoordinatorLayout.LayoutParams params = new CoordinatorLayout.LayoutParams(
+ CoordinatorLayout.LayoutParams.MATCH_PARENT,
+ CoordinatorLayout.LayoutParams.MATCH_PARENT);
+ mBehavior = new BottomSheetBehavior<>();
+ mBehavior.setPeekHeight(PEEK_HEIGHT);
+ params.setBehavior(mBehavior);
+ activity.mCoordinatorLayout.addView(mBottomSheet, params);
+ }
+ });
+ }
+
+ @Test
+ public void testSetUp() {
+ assertThat(mBottomSheet, is(notNullValue()));
+ assertThat(mBehavior, is(sameInstance(BottomSheetBehavior.from(mBottomSheet))));
+ }
+
+ @Test
+ public void testTouchCoordinatorLayout() {
+ final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity();
+ mDown = false;
+ Espresso.onView(sameInstance((View) activity.mCoordinatorLayout))
+ .perform(ViewActions.click()) // Click outside the bottom sheet
+ .check(new ViewAssertion() {
+ @Override
+ public void check(View view, NoMatchingViewException e) {
+ assertThat(e, is(nullValue()));
+ assertThat(view, is(notNullValue()));
+ // Check that the touch event fell through to the container
+ assertThat(mDown, is(true));
+ }
+ });
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsActivity.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsActivity.java
new file mode 100644
index 0000000..c1ab21d
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.view.View;
+
+
+public class BottomSheetBehaviorWithInsetsActivity extends BottomSheetBehaviorActivity {
+
+ View mBottomSheetContent;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.test_design_bottom_sheet_behavior_with_insets;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ super.onContentViewSet();
+ mBottomSheetContent = findViewById(R.id.bottom_sheet_content);
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsTest.java b/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsTest.java
new file mode 100644
index 0000000..77a0b26
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetBehaviorWithInsetsTest.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+
+public class BottomSheetBehaviorWithInsetsTest extends
+ BaseInstrumentationTestCase<BottomSheetBehaviorWithInsetsActivity> {
+
+ public BottomSheetBehaviorWithInsetsTest() {
+ super(BottomSheetBehaviorWithInsetsActivity.class);
+ }
+
+ @Test
+ @SmallTest
+ public void testFitsSystemWindows() {
+ BottomSheetBehaviorWithInsetsActivity activity = mActivityTestRule.getActivity();
+ ViewCompat.setFitsSystemWindows(activity.mCoordinatorLayout, true);
+ assertThat(activity.mBehavior.getState(), is(BottomSheetBehavior.STATE_COLLAPSED));
+ ViewGroup bottomSheet = activity.mBottomSheet;
+ assertThat(bottomSheet.getTop(),
+ is(activity.mCoordinatorLayout.getHeight() - activity.mBehavior.getPeekHeight()));
+ assertThat(activity.mBottomSheetContent.getTop(), is(0));
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/BottomSheetDialogActivity.java b/design/tests/src/android/support/design/widget/BottomSheetDialogActivity.java
new file mode 100644
index 0000000..ab273c2
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetDialogActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+public class BottomSheetDialogActivity extends BaseTestActivity {
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.frame_layout;
+ }
+
+}
+
diff --git a/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java b/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java
new file mode 100644
index 0000000..da2055d
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/BottomSheetDialogTest.java
@@ -0,0 +1,121 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import org.junit.Test;
+
+import android.content.Context;
+import android.support.design.test.R;
+import android.support.design.widget.BottomSheetBehavior;
+import android.support.design.widget.BottomSheetDialog;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.action.ViewActions;
+import android.support.test.espresso.assertion.ViewAssertions;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.support.v7.widget.AppCompatTextView;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.widget.FrameLayout;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.lessThan;
+
+public class BottomSheetDialogTest extends
+ BaseInstrumentationTestCase<BottomSheetDialogActivity> {
+
+ private BottomSheetDialog mDialog;
+
+ public BottomSheetDialogTest() {
+ super(BottomSheetDialogActivity.class);
+ }
+
+ @Test
+ @MediumTest
+ public void testBasicDialogSetup() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ Context context = mActivityTestRule.getActivity();
+ mDialog = new BottomSheetDialog(context);
+ AppCompatTextView text = new AppCompatTextView(context);
+ StringBuilder builder = new StringBuilder();
+ builder.append("It is fine today. ");
+ text.setText(builder);
+ mDialog.setContentView(text);
+ mDialog.show();
+ // Confirms that the dialog is shown
+ assertThat(mDialog.isShowing(), is(true));
+ FrameLayout bottomSheet = (FrameLayout) mDialog
+ .findViewById(R.id.design_bottom_sheet);
+ assertThat(bottomSheet, is(notNullValue()));
+ BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+ assertThat(behavior, is(notNullValue()));
+ }
+ });
+ // Click outside the bottom sheet
+ Espresso.onView(ViewMatchers.withId(R.id.touch_outside))
+ .perform(ViewActions.click());
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ // Confirm that the dialog is no longer shown
+ assertThat(mDialog.isShowing(), is(false));
+ }
+ });
+ }
+
+ @Test
+ @MediumTest
+ public void testShortDialog() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ Context context = mActivityTestRule.getActivity();
+ mDialog = new BottomSheetDialog(context);
+ AppCompatTextView text = new AppCompatTextView(context);
+ StringBuilder builder = new StringBuilder();
+ builder.append("It is fine today. ");
+ text.setText(builder);
+ mDialog.setContentView(text);
+ mDialog.show();
+ }
+ });
+ // This ensures that the views are laid out before assertions below
+ Espresso.onView(ViewMatchers.withId(R.id.design_bottom_sheet))
+ .check(ViewAssertions.matches(ViewMatchers.isDisplayed()));
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ FrameLayout bottomSheet = (FrameLayout) mDialog
+ .findViewById(R.id.design_bottom_sheet);
+ CoordinatorLayout coordinator = (CoordinatorLayout) bottomSheet.getParent();
+ BottomSheetBehavior<FrameLayout> behavior = BottomSheetBehavior.from(bottomSheet);
+ assertThat(bottomSheet, is(notNullValue()));
+ assertThat(coordinator, is(notNullValue()));
+ assertThat(behavior, is(notNullValue()));
+ // This bottom sheet is shorter than the peek height
+ assertThat(bottomSheet.getHeight(), is(lessThan(behavior.getPeekHeight())));
+ // Confirm that the bottom sheet is bottom-aligned
+ assertThat(bottomSheet.getTop(),
+ is(coordinator.getHeight() - bottomSheet.getHeight()));
+ }
+ });
+ }
+
+}
+
diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutActivity.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutActivity.java
new file mode 100644
index 0000000..7d52666
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/CoordinatorLayoutActivity.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.widget.FrameLayout;
+
+public class CoordinatorLayoutActivity extends BaseTestActivity {
+
+ FrameLayout mContainer;
+ CoordinatorLayout mCoordinatorLayout;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.activity_coordinator_layout;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ mContainer = (FrameLayout) findViewById(R.id.container);
+ mCoordinatorLayout = (CoordinatorLayout) findViewById(R.id.coordinator);
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
new file mode 100644
index 0000000..ca56ecc
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
@@ -0,0 +1,192 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.os.Build;
+import android.support.design.custom.TestFloatingBehavior;
+import android.support.design.test.R;
+import android.support.design.testutils.SnackbarUtils;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v7.widget.AppCompatTextView;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+import org.hamcrest.Matcher;
+import org.junit.After;
+import org.junit.Test;
+
+import static android.support.design.widget.DesignViewActions.setVisibility;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isEnabled;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+@MediumTest
+public class CoordinatorSnackbarWithFabTest extends BaseDynamicCoordinatorLayoutTest {
+ private static final String MESSAGE_TEXT = "Test Message";
+ private static final String ACTION_TEXT = "Action";
+
+ private Snackbar mSnackbar;
+
+ @After
+ public void teardown() {
+ // Dismiss the snackbar to get back to clean state for the next test
+ if (mSnackbar != null) {
+ SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(mSnackbar);
+ }
+ }
+
+ /**
+ * Returns the location of our snackbar on the screen.
+ */
+ private static int[] getSnackbarLocationOnScreen() {
+ final int[] location = new int[2];
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)).perform(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isEnabled();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Snackbar matcher";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ view.getLocationOnScreen(location);
+ }
+ });
+ return location;
+ }
+
+ /**
+ * Helper method that verifies that the passed view is above the snackbar in the activity
+ * window.
+ */
+ private static void verifySnackbarViewStacking(View view, int extraBottomMargin) {
+ if (Build.VERSION.SDK_INT >= 11) {
+ // Get location of snackbar in window
+ final int[] snackbarOnScreenXY = getSnackbarLocationOnScreen();
+ // Get location of passed view in window
+ final int[] viewOnScreenXY = new int[2];
+ view.getLocationOnScreen(viewOnScreenXY);
+
+ // Compute the bottom visible edge of the view
+ int viewBottom = viewOnScreenXY[1] + view.getHeight() - extraBottomMargin;
+ int snackbarTop = snackbarOnScreenXY[1];
+ // and verify that our view is above the snackbar
+ assertTrue(viewBottom <= snackbarTop);
+ }
+ }
+
+ @Test
+ public void testBuiltInSliding() {
+ onView(withId(R.id.coordinator_stub)).perform(
+ inflateViewStub(R.layout.design_snackbar_with_fab));
+
+ // Create and show a snackbar
+ mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+ // Take into account bottom padding and bottom margin to account for how drop shadow is
+ // emulated on pre-Lollipop devices
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ verifySnackbarViewStacking(fab, fab.getPaddingBottom()
+ - ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin);
+ }
+
+ @Test
+ public void testBuiltInSlidingFromHiddenFab() {
+ onView(withId(R.id.coordinator_stub)).perform(
+ inflateViewStub(R.layout.design_snackbar_with_fab));
+ onView(withId(R.id.fab)).perform(setVisibility(View.GONE));
+
+ // Create and show a snackbar
+ mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+ // Take into account bottom padding and bottom margin to account for how drop shadow is
+ // emulated on pre-Lollipop devices
+ onView(withId(R.id.fab)).perform(setVisibility(View.VISIBLE));
+ final FloatingActionButton fab =
+ (FloatingActionButton) mCoordinatorLayout.findViewById(R.id.fab);
+ verifySnackbarViewStacking(fab, fab.getPaddingBottom()
+ - ((ViewGroup.MarginLayoutParams) fab.getLayoutParams()).bottomMargin);
+ }
+
+ @Test
+ public void testBehaviorBasedSlidingFromLayoutAttribute() {
+ // Use a layout in which an AppCompatTextView child has Behavior object configured via
+ // layout_behavior XML attribute
+ onView(withId(R.id.coordinator_stub)).perform(
+ inflateViewStub(R.layout.design_snackbar_behavior_layout_attr));
+
+ // Create and show a snackbar
+ mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+ final AppCompatTextView textView =
+ (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+ verifySnackbarViewStacking(textView, 0);
+ }
+
+ @Test
+ public void testBehaviorBasedSlidingFromClassAnnotation() {
+ // Use a layout in which a custom child view has Behavior object configured via
+ // annotation on the class that extends AppCompatTextView
+ onView(withId(R.id.coordinator_stub)).perform(
+ inflateViewStub(R.layout.design_snackbar_behavior_annotation));
+
+ // Create and show a snackbar
+ mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+ final AppCompatTextView textView =
+ (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+ verifySnackbarViewStacking(textView, 0);
+ }
+
+ @Test
+ public void testBehaviorBasedSlidingFromRuntimeApiCall() {
+ // Use a layout in which an AppCompatTextView child doesn't have any configured Behavior
+ onView(withId(R.id.coordinator_stub)).perform(
+ inflateViewStub(R.layout.design_snackbar_behavior_runtime));
+
+ // and configure that Behavior at runtime by setting it on its LayoutParams
+ final AppCompatTextView textView =
+ (AppCompatTextView) mCoordinatorLayout.findViewById(R.id.text);
+ final CoordinatorLayout.LayoutParams textViewLp =
+ (CoordinatorLayout.LayoutParams) textView.getLayoutParams();
+ textViewLp.setBehavior(new TestFloatingBehavior());
+
+ // Create and show a snackbar
+ mSnackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class));
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(mSnackbar);
+
+ verifySnackbarViewStacking(textView, 0);
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/DesignViewActions.java b/design/tests/src/android/support/design/widget/DesignViewActions.java
new file mode 100644
index 0000000..4ae608a
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/DesignViewActions.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import org.hamcrest.Matcher;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.matcher.ViewMatchers;
+import android.view.View;
+
+public final class DesignViewActions {
+
+ private DesignViewActions() {
+ }
+
+ /**
+ * Overwrites the constraints of the specified {@link ViewAction}.
+ */
+ public static ViewAction withCustomConstraints(final ViewAction action,
+ final Matcher<View> constraints) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return constraints;
+ }
+
+ @Override
+ public String getDescription() {
+ return action.getDescription();
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ action.perform(uiController, view);
+ }
+ };
+ }
+
+ public static ViewAction setVisibility(final int visibility) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return ViewMatchers.isEnabled();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set view visibility";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+ view.setVisibility(visibility);
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java b/design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java
new file mode 100644
index 0000000..f26ef12
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/DynamicCoordinatorLayoutActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+/**
+ * Test activity for testing various aspects of {@link CoordinatorLayout}.
+ */
+public class DynamicCoordinatorLayoutActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.dynamic_coordinator_layout;
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonActivity.java b/design/tests/src/android/support/design/widget/FloatingActionButtonActivity.java
new file mode 100644
index 0000000..3a32827
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonActivity.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+public class FloatingActionButtonActivity extends BaseTestActivity {
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.design_fab;
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
new file mode 100644
index 0000000..de35b6a
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/FloatingActionButtonTest.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.design.testutils.FloatingActionButtonActions.setBackgroundTintColor;
+import static android.support.design.testutils.FloatingActionButtonActions.setImageResource;
+import static android.support.design.testutils.FloatingActionButtonActions.setSize;
+import static android.support.design.testutils.TestUtilsMatchers.withFabBackgroundFill;
+import static android.support.design.testutils.TestUtilsMatchers.withFabContentHeight;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import android.graphics.Color;
+import android.support.design.test.R;
+import android.support.design.testutils.TestUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class FloatingActionButtonTest
+ extends BaseInstrumentationTestCase<FloatingActionButtonActivity> {
+
+ public FloatingActionButtonTest() {
+ super(FloatingActionButtonActivity.class);
+ }
+
+ @Test
+ public void testDefaultBackgroundTint() {
+ final int colorAccent = TestUtils.getThemeAttrColor(
+ mActivityTestRule.getActivity(), R.attr.colorAccent);
+ onView(withId(R.id.fab_standard))
+ .check(matches(withFabBackgroundFill(colorAccent)));
+ }
+
+ @Test
+ public void testSetTintOnDefaultBackgroundTint() {
+ onView(withId(R.id.fab_standard))
+ .perform(setBackgroundTintColor(Color.GREEN))
+ .check(matches(withFabBackgroundFill(Color.GREEN)));
+ }
+
+ @Test
+ public void testDeclaredBackgroundTint() {
+ onView(withId(R.id.fab_tint))
+ .check(matches(withFabBackgroundFill(Color.MAGENTA)));
+ }
+
+ @Test
+ public void testSetTintOnDeclaredBackgroundTint() {
+ onView(withId(R.id.fab_tint))
+ .perform(setBackgroundTintColor(Color.GREEN))
+ .check(matches(withFabBackgroundFill(Color.GREEN)));
+ }
+
+ @Test
+ public void setVectorDrawableSrc() {
+ onView(withId(R.id.fab_standard))
+ .perform(setImageResource(R.drawable.vector_icon));
+ }
+
+ @Test
+ public void testSetMiniSize() {
+ final int miniSize = mActivityTestRule.getActivity().getResources()
+ .getDimensionPixelSize(R.dimen.fab_mini_height);
+
+ onView(withId(R.id.fab_standard))
+ .perform(setSize(FloatingActionButton.SIZE_MINI))
+ .check(matches(withFabContentHeight(miniSize)));
+ }
+
+ @Test
+ public void testSetSizeToggle() {
+ final int miniSize = mActivityTestRule.getActivity().getResources()
+ .getDimensionPixelSize(R.dimen.fab_mini_height);
+ final int normalSize = mActivityTestRule.getActivity().getResources()
+ .getDimensionPixelSize(R.dimen.fab_normal_height);
+
+ onView(withId(R.id.fab_standard))
+ .perform(setSize(FloatingActionButton.SIZE_MINI))
+ .check(matches(withFabContentHeight(miniSize)));
+
+ onView(withId(R.id.fab_standard))
+ .perform(setSize(FloatingActionButton.SIZE_NORMAL))
+ .check(matches(withFabContentHeight(normalSize)));
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewActivity.java b/design/tests/src/android/support/design/widget/NavigationViewActivity.java
new file mode 100644
index 0000000..c3e810c
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/NavigationViewActivity.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+public class NavigationViewActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.design_navigation_view;
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/NavigationViewTest.java b/design/tests/src/android/support/design/widget/NavigationViewTest.java
new file mode 100755
index 0000000..2abce52
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/NavigationViewTest.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.content.res.Resources;
+import android.graphics.drawable.GradientDrawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.IdRes;
+import android.support.design.test.R;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.SwitchCompat;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import static android.support.design.testutils.DrawerLayoutActions.closeDrawer;
+import static android.support.design.testutils.DrawerLayoutActions.openDrawer;
+import static android.support.design.testutils.NavigationViewActions.*;
+import static android.support.design.testutils.TestUtilsMatchers.*;
+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.ViewMatchers.*;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+public class NavigationViewTest
+ extends BaseInstrumentationTestCase<NavigationViewActivity> {
+ private static final int[] MENU_CONTENT_ITEM_IDS = { R.id.destination_home,
+ R.id.destination_profile, R.id.destination_people, R.id.destination_settings };
+ private Map<Integer, String> mMenuStringContent;
+
+ private DrawerLayout mDrawerLayout;
+
+ private NavigationView mNavigationView;
+
+ public NavigationViewTest() {
+ super(NavigationViewActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final NavigationViewActivity activity = mActivityTestRule.getActivity();
+ mDrawerLayout = (DrawerLayout) activity.findViewById(R.id.drawer_layout);
+ mNavigationView = (NavigationView) mDrawerLayout.findViewById(R.id.start_drawer);
+
+ // Close the drawer to reset the state for the next test
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+
+ final Resources res = activity.getResources();
+ mMenuStringContent = new HashMap<>(MENU_CONTENT_ITEM_IDS.length);
+ mMenuStringContent.put(R.id.destination_home, res.getString(R.string.navigate_home));
+ mMenuStringContent.put(R.id.destination_profile, res.getString(R.string.navigate_profile));
+ mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people));
+ mMenuStringContent.put(R.id.destination_settings,
+ res.getString(R.string.navigate_settings));
+ }
+
+ @Test
+ @SmallTest
+ public void testBasics() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // Check the contents of the Menu object
+ final Menu menu = mNavigationView.getMenu();
+ assertNotNull("Menu should not be null", menu);
+ assertEquals("Should have matching number of items", MENU_CONTENT_ITEM_IDS.length,
+ menu.size());
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ final MenuItem currItem = menu.getItem(i);
+ assertEquals("ID for Item #" + i, MENU_CONTENT_ITEM_IDS[i], currItem.getItemId());
+ }
+
+ // Check that we have the expected menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(isDisplayed()));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testTextAppearance() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final int defaultTextSize = res.getDimensionPixelSize(R.dimen.text_medium_size);
+
+ // Check the default style of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextSize(defaultTextSize)));
+ }
+
+ // Set a new text appearance on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemTextAppearance(R.style.TextSmallStyle));
+
+ // And check that all the menu items have the new style
+ final int newTextSize = res.getDimensionPixelSize(R.dimen.text_small_size);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextSize(newTextSize)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testTextColor() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int defaultTextColor = ResourcesCompat.getColor(res,
+ R.color.emerald_text, null);
+
+ // Check the default text color of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextColor(defaultTextColor)));
+ }
+
+ // Set a new text color on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemTextColor(
+ ResourcesCompat.getColorStateList(res, R.color.color_state_list_lilac, null)));
+
+ // And check that all the menu items have the new color
+ final @ColorInt int newTextColor = ResourcesCompat.getColor(res,
+ R.color.lilac_default, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ onView(allOf(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(
+ matches(withTextColor(newTextColor)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testBackground() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int defaultFillColor = ResourcesCompat.getColor(res,
+ R.color.sand_default, null);
+
+ // Check the default fill color of the menu items in our NavigationView
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ // Note that here we're tying ourselves to the implementation details of the
+ // internal structure of the NavigationView. Specifically, we're looking at the
+ // direct child of RecyclerView which is expected to have the background set
+ // on it. If the internal implementation of NavigationView changes, the second
+ // Matcher below will need to be tweaked.
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(defaultFillColor)));
+ }
+
+ // Set a new background (flat fill color) on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource(
+ R.drawable.test_background_blue));
+
+ // And check that all the menu items have the new fill
+ final @ColorInt int newFillColorBlue = ResourcesCompat.getColor(res,
+ R.color.test_blue, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorBlue)));
+ }
+
+ // Set another new background on our NavigationView
+ onView(withId(R.id.start_drawer)).perform(setItemBackground(
+ ResourcesCompat.getDrawable(res, R.drawable.test_background_green, null)));
+
+ // And check that all the menu items have the new fill
+ final @ColorInt int newFillColorGreen = ResourcesCompat.getColor(res,
+ R.color.test_green, null);
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+
+ onView(menuItemMatcher).check(matches(withBackgroundFill(newFillColorGreen)));
+ }
+ }
+
+ /**
+ * Custom drawable class that provides a reliable way for testing various tinting scenarios
+ * across a range of platform versions. ColorDrawable doesn't support tinting on Kitkat and
+ * below, and BitmapDrawable (PNG sources) appears to slightly alter green and blue channels
+ * by a few units on some of the older platform versions (Gingerbread). Using GradientDrawable
+ * allows doing reliable tests at the level of individual channels (alpha / red / green / blue)
+ * for tinted and untinted icons in the testIconTinting method.
+ */
+ private class TestDrawable extends GradientDrawable {
+ private int mWidth;
+ private int mHeight;
+
+ public TestDrawable(@ColorInt int color, int width, int height) {
+ super(Orientation.TOP_BOTTOM, new int[] { color, color });
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testIconTinting() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int redFill = ResourcesCompat.getColor(res, R.color.test_red, null);
+ final @ColorInt int greenFill = ResourcesCompat.getColor(res, R.color.test_green, null);
+ final @ColorInt int blueFill = ResourcesCompat.getColor(res, R.color.test_blue, null);
+ final int iconSize = res.getDimensionPixelSize(R.dimen.drawable_small_size);
+ onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_home,
+ new TestDrawable(redFill, iconSize, iconSize)));
+ onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_profile,
+ new TestDrawable(greenFill, iconSize, iconSize)));
+ onView(withId(R.id.start_drawer)).perform(setIconForMenuItem(R.id.destination_people,
+ new TestDrawable(blueFill, iconSize, iconSize)));
+
+ final @ColorInt int defaultTintColor = ResourcesCompat.getColor(res,
+ R.color.emerald_translucent, null);
+
+ // We're allowing a margin of error in checking the color of the items' icons.
+ // This is due to the translucent color being used in the icon tinting
+ // and off-by-one discrepancies of SRC_IN when it's compositing
+ // translucent color. Note that all the checks below are written for the current
+ // logic on NavigationView that uses the default SRC_IN tint mode - effectively
+ // replacing all non-transparent pixels in the destination (original icon) with
+ // our translucent tint color.
+ final int allowedComponentVariance = 1;
+
+ // Note that here we're tying ourselves to the implementation details of the
+ // internal structure of the NavigationView. Specifically, we're checking the
+ // start drawable of the text view with the specific text. If the internal
+ // implementation of NavigationView changes, the second Matcher in the lookups
+ // below will need to be tweaked.
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(defaultTintColor, allowedComponentVariance)));
+
+ final @ColorInt int newTintColor = ResourcesCompat.getColor(res,
+ R.color.red_translucent, null);
+
+ onView(withId(R.id.start_drawer)).perform(setItemIconTintList(
+ ResourcesCompat.getColorStateList(res, R.color.color_state_list_red_translucent,
+ null)));
+ // Check that all menu items with icons now have icons tinted with the newly set color
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(newTintColor, allowedComponentVariance)));
+
+ // And now remove all icon tinting
+ onView(withId(R.id.start_drawer)).perform(setItemIconTintList(null));
+ // And verify that all menu items with icons now have the original colors for their icons.
+ // Note that since there is no tinting at this point, we don't allow any color variance
+ // in these checks.
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_home)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(redFill, 0)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(greenFill, 0)));
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).check(matches(
+ withStartDrawableFilledWith(blueFill, 0)));
+ }
+
+ /**
+ * Gets the list of header IDs (which can be empty) and verifies that the actual header content
+ * of our navigation view matches the expected header content.
+ */
+ private void verifyHeaders(@IdRes int ... expectedHeaderIds) {
+ final int expectedHeaderCount = (expectedHeaderIds != null) ? expectedHeaderIds.length : 0;
+ final int actualHeaderCount = mNavigationView.getHeaderCount();
+ assertEquals("Header count", expectedHeaderCount, actualHeaderCount);
+
+ if (expectedHeaderCount > 0) {
+ for (int i = 0; i < expectedHeaderCount; i++) {
+ final View currentHeader = mNavigationView.getHeaderView(i);
+ assertEquals("Header at #" + i, expectedHeaderIds[i], currentHeader.getId());
+ }
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testHeaders() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // We should have no headers at the start
+ verifyHeaders();
+
+ // Inflate one header and check that it's there in the navigation view
+ onView(withId(R.id.start_drawer)).perform(
+ inflateHeaderView(R.layout.design_navigation_view_header1));
+ verifyHeaders(R.id.header1);
+
+ final LayoutInflater inflater = LayoutInflater.from(mActivityTestRule.getActivity());
+
+ // Add one more header and check that it's there in the navigation view
+ onView(withId(R.id.start_drawer)).perform(
+ addHeaderView(inflater, R.layout.design_navigation_view_header2));
+ verifyHeaders(R.id.header1, R.id.header2);
+
+ final View header1 = mNavigationView.findViewById(R.id.header1);
+ // Remove the first header and check that we still have the second header
+ onView(withId(R.id.start_drawer)).perform(removeHeaderView(header1));
+ verifyHeaders(R.id.header2);
+
+ // Add one more header and check that we now have two headers
+ onView(withId(R.id.start_drawer)).perform(
+ inflateHeaderView(R.layout.design_navigation_view_header3));
+ verifyHeaders(R.id.header2, R.id.header3);
+
+ // Add another "copy" of the header from the just-added layout and check that we now
+ // have three headers
+ onView(withId(R.id.start_drawer)).perform(
+ addHeaderView(inflater, R.layout.design_navigation_view_header3));
+ verifyHeaders(R.id.header2, R.id.header3, R.id.header3);
+ }
+
+ @Test
+ @SmallTest
+ public void testNavigationSelectionListener() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // Click one of our items
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+ // Check that the drawer is still open
+ assertTrue("Drawer is still open after click",
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Register a listener
+ NavigationView.OnNavigationItemSelectedListener mockedListener =
+ mock(NavigationView.OnNavigationItemSelectedListener.class);
+ mNavigationView.setNavigationItemSelectedListener(mockedListener);
+
+ // Click one of our items
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_profile)),
+ isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+ // Check that the drawer is still open
+ assertTrue("Drawer is still open after click",
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ // And that our listener has been notified of the click
+ verify(mockedListener, times(1)).onNavigationItemSelected(
+ mNavigationView.getMenu().findItem(R.id.destination_profile));
+
+ // Set null listener to test that the next click is not going to notify the
+ // previously set listener
+ mNavigationView.setNavigationItemSelectedListener(null);
+
+ // Click one of our items
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)),
+ isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+ // Check that the drawer is still open
+ assertTrue("Drawer is still open after click",
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ // And that our previous listener has not been notified of the click
+ verifyNoMoreInteractions(mockedListener);
+ }
+
+ private void verifyCheckedAppearance(@IdRes int checkedItemId,
+ @ColorInt int uncheckedItemForeground, @ColorInt int checkedItemForeground,
+ @ColorInt int uncheckedItemBackground, @ColorInt int checkedItemBackground) {
+ for (int i = 0; i < MENU_CONTENT_ITEM_IDS.length; i++) {
+ final boolean expectedToBeChecked = (MENU_CONTENT_ITEM_IDS[i] == checkedItemId);
+ final @ColorInt int expectedItemForeground =
+ expectedToBeChecked ? checkedItemForeground : uncheckedItemForeground;
+ final @ColorInt int expectedItemBackground =
+ expectedToBeChecked ? checkedItemBackground : uncheckedItemBackground;
+
+ // For the background fill check we need to select a view that has its background
+ // set by the current implementation (see disclaimer in testBackground)
+ Matcher menuItemMatcher = allOf(
+ hasDescendant(withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i]))),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ isDescendantOfA(withId(R.id.start_drawer)));
+ onView(menuItemMatcher).check(matches(withBackgroundFill(expectedItemBackground)));
+
+ // And for the foreground color check we need to select a view with the text content
+ Matcher menuItemTextMatcher = allOf(
+ withText(mMenuStringContent.get(MENU_CONTENT_ITEM_IDS[i])),
+ isDescendantOfA(withId(R.id.start_drawer)));
+ onView(menuItemTextMatcher).check(matches(withTextColor(expectedItemForeground)));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testCheckedAppearance() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // Reconfigure our navigation view to use foreground (text) and background visuals
+ // with explicitly different colors for the checked state
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ onView(withId(R.id.start_drawer)).perform(setItemTextColor(
+ ResourcesCompat.getColorStateList(res, R.color.color_state_list_sand, null)));
+ onView(withId(R.id.start_drawer)).perform(setItemBackgroundResource(
+ R.drawable.test_drawable_state_list));
+
+ final @ColorInt int uncheckedItemForeground = ResourcesCompat.getColor(res,
+ R.color.sand_default, null);
+ final @ColorInt int checkedItemForeground = ResourcesCompat.getColor(res,
+ R.color.sand_checked, null);
+ final @ColorInt int uncheckedItemBackground = ResourcesCompat.getColor(res,
+ R.color.test_green, null);
+ final @ColorInt int checkedItemBackground = ResourcesCompat.getColor(res,
+ R.color.test_blue, null);
+
+ // Verify that all items are rendered with unchecked visuals
+ verifyCheckedAppearance(0, uncheckedItemForeground, checkedItemForeground,
+ uncheckedItemBackground, checkedItemBackground);
+
+ // Mark one of the items as checked
+ onView(withId(R.id.start_drawer)).perform(setCheckedItem(R.id.destination_profile));
+ // And verify that it's now rendered with checked visuals
+ verifyCheckedAppearance(R.id.destination_profile,
+ uncheckedItemForeground, checkedItemForeground,
+ uncheckedItemBackground, checkedItemBackground);
+
+ // Register a navigation listener that "marks" the selected item
+ mNavigationView.setNavigationItemSelectedListener(
+ new NavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ return true;
+ }
+ });
+
+ // Click one of our items
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_people)),
+ isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+ // and verify that it's now checked
+ verifyCheckedAppearance(R.id.destination_people,
+ uncheckedItemForeground, checkedItemForeground,
+ uncheckedItemBackground, checkedItemBackground);
+
+ // Register a navigation listener that doesn't "mark" the selected item
+ mNavigationView.setNavigationItemSelectedListener(
+ new NavigationView.OnNavigationItemSelectedListener() {
+ @Override
+ public boolean onNavigationItemSelected(MenuItem item) {
+ return false;
+ }
+ });
+
+ // Click another items
+ onView(allOf(withText(mMenuStringContent.get(R.id.destination_settings)),
+ isDescendantOfA(withId(R.id.start_drawer)))).perform(click());
+ // and verify that the checked state remains on the previously clicked item
+ // since the current navigation listener returns false from its callback
+ // implementation
+ verifyCheckedAppearance(R.id.destination_people,
+ uncheckedItemForeground, checkedItemForeground,
+ uncheckedItemBackground, checkedItemBackground);
+ }
+
+ @Test
+ @SmallTest
+ public void testActionLayout() {
+ // Open our drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // There are four conditions to "find" the menu item with action layout (switch):
+ // 1. Is in the NavigationView
+ // 2. Is direct child of a class that extends RecyclerView
+ // 3. Has a child with "people" text
+ // 4. Has fully displayed child that extends SwitchCompat
+ // Note that condition 2 makes a certain assumption about the internal implementation
+ // details of the NavigationMenu, while conditions 3 and 4 aim to be as generic as
+ // possible and to not rely on the internal details of the current layout implementation
+ // of an individual menu item in NavigationMenu.
+ Matcher menuItemMatcher = allOf(
+ isDescendantOfA(withId(R.id.start_drawer)),
+ isChildOfA(isAssignableFrom(RecyclerView.class)),
+ hasDescendant(withText(mMenuStringContent.get(R.id.destination_people))),
+ hasDescendant(allOf(
+ isAssignableFrom(SwitchCompat.class),
+ isCompletelyDisplayed())));
+
+ // While we don't need to perform any action on our row, the invocation of perform()
+ // makes our matcher actually run. If for some reason NavigationView fails to inflate and
+ // display our SwitchCompat action layout, the next line will fail in the matcher pass.
+ onView(menuItemMatcher).perform(click());
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/SnackbarActivity.java b/design/tests/src/android/support/design/widget/SnackbarActivity.java
new file mode 100644
index 0000000..5d8e118
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/SnackbarActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+public class SnackbarActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.test_design_snackbar;
+ }
+}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/SnackbarTest.java b/design/tests/src/android/support/design/widget/SnackbarTest.java
new file mode 100644
index 0000000..8a0381e
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/SnackbarTest.java
@@ -0,0 +1,268 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.content.res.Resources;
+import android.os.SystemClock;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.design.test.R;
+import android.support.design.testutils.SnackbarUtils;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.ViewAction;
+import android.support.test.espresso.ViewInteraction;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.design.testutils.TestUtilsActions.setLayoutDirection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.*;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class SnackbarTest extends BaseInstrumentationTestCase<SnackbarActivity> {
+ private static final String MESSAGE_TEXT = "Test Message";
+ private static final @StringRes int MESSAGE_ID = R.string.snackbar_text;
+ private static final String ACTION_TEXT = "Action";
+ private static final @StringRes int ACTION_ID = R.string.snackbar_action;
+
+ private CoordinatorLayout mCoordinatorLayout;
+
+ private static interface DismissAction {
+ void dismiss(Snackbar snackbar);
+ }
+
+ public SnackbarTest() {
+ super(SnackbarActivity.class);
+ }
+
+ @Before
+ public void setup() {
+ mCoordinatorLayout =
+ (CoordinatorLayout) mActivityTestRule.getActivity().findViewById(R.id.col);
+ }
+
+ private void verifySnackbarContent(final Snackbar snackbar, final String expectedMessage,
+ final String expectedAction) {
+ // Show the snackbar
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar);
+
+ // Verify that we're showing the message
+ withText(expectedMessage).matches(allOf(
+ isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ isCompletelyDisplayed()));
+
+ // If the action is not empty, verify that we're showing it
+ if (!TextUtils.isEmpty(expectedAction)) {
+ withText(expectedAction).matches(allOf(
+ isDescendantOfA(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ isCompletelyDisplayed()));
+ }
+
+ // Dismiss the snackbar
+ SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(snackbar);
+ }
+
+ @Test
+ @SmallTest
+ public void testBasicContent() {
+ // Verify different combinations of snackbar content (message and action) and duration
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final String resolvedMessage = res.getString(MESSAGE_ID);
+ final String resolvedAction = res.getString(ACTION_ID);
+
+ // String message and no action
+ verifySnackbarContent(
+ Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT),
+ MESSAGE_TEXT, null);
+
+ // Resource message and no action
+ verifySnackbarContent(
+ Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG),
+ resolvedMessage, null);
+
+ // String message and string action
+ verifySnackbarContent(
+ Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_INDEFINITE)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class)),
+ MESSAGE_TEXT, ACTION_TEXT);
+
+ // String message and resource action
+ verifySnackbarContent(
+ Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
+ .setAction(ACTION_ID, mock(View.OnClickListener.class)),
+ MESSAGE_TEXT, resolvedAction);
+
+ // Resource message and resource action
+ verifySnackbarContent(
+ Snackbar.make(mCoordinatorLayout, MESSAGE_ID, Snackbar.LENGTH_LONG)
+ .setAction(ACTION_ID, mock(View.OnClickListener.class)),
+ resolvedMessage, resolvedAction);
+ }
+
+ private void verifyDismissCallback(final ViewInteraction interaction,
+ final @Nullable ViewAction action, final @Nullable DismissAction dismissAction,
+ final int length, @Snackbar.Callback.DismissEvent final int expectedEvent) {
+ final Snackbar.Callback mockCallback = mock(Snackbar.Callback.class);
+ final Snackbar snackbar = Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, length)
+ .setAction(ACTION_TEXT, mock(View.OnClickListener.class))
+ .setCallback(mockCallback);
+
+ // Note that unlike other tests around Snackbar that use Espresso's IdlingResources
+ // to wait until the snackbar is shown (SnackbarUtils.showSnackbarAndWaitUntilFullyShown),
+ // here we want to verify our callback has been called with onShown after snackbar is shown
+ // and with onDismissed after snackbar is dismissed.
+
+ // Now show the Snackbar
+ snackbar.show();
+ // sleep for the animation
+ SystemClock.sleep(Snackbar.ANIMATION_DURATION + 50);
+
+ // Now perform the UI interaction
+ if (action != null) {
+ interaction.perform(action);
+ } else if (dismissAction != null) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ dismissAction.dismiss(snackbar);
+ }
+ });
+ }
+ // wait until the Snackbar has been removed from the view hierarchy
+ while (snackbar.isShownOrQueued()) {
+ SystemClock.sleep(20);
+ }
+ // and verify that our callback was invoked with onShown and onDismissed
+ verify(mockCallback, times(1)).onShown(snackbar);
+ verify(mockCallback, times(1)).onDismissed(snackbar, expectedEvent);
+ verifyNoMoreInteractions(mockCallback);
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaActionClick() {
+ verifyDismissCallback(
+ onView(withId(R.id.snackbar_action)),
+ click(),
+ null,
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_ACTION);
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaSwipe() {
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ swipeRight(),
+ null,
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_SWIPE);
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaSwipeRtl() {
+ onView(withId(R.id.col)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+ if (ViewCompat.getLayoutDirection(mCoordinatorLayout) == ViewCompat.LAYOUT_DIRECTION_RTL) {
+ // On devices that support RTL layout, the start-to-end dismiss swipe is done
+ // with swipeLeft() action
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ swipeLeft(),
+ null,
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_SWIPE);
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaApi() {
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ null,
+ new DismissAction() {
+ @Override
+ public void dismiss(Snackbar snackbar) {
+ snackbar.dismiss();
+ }
+ },
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_MANUAL);
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaTimeout() {
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ null,
+ null,
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_TIMEOUT);
+ }
+
+ @Test
+ @MediumTest
+ public void testDismissViaAnotherSnackbar() {
+ final Snackbar anotherSnackbar =
+ Snackbar.make(mCoordinatorLayout, "A different message", Snackbar.LENGTH_SHORT);
+
+ // Our dismiss action is to show another snackbar (and verify that the original snackbar
+ // is now dismissed with CONSECUTIVE event)
+ verifyDismissCallback(
+ onView(isAssignableFrom(Snackbar.SnackbarLayout.class)),
+ null,
+ new DismissAction() {
+ @Override
+ public void dismiss(Snackbar snackbar) {
+ anotherSnackbar.show();
+ }
+ },
+ Snackbar.LENGTH_LONG,
+ Snackbar.Callback.DISMISS_EVENT_CONSECUTIVE);
+
+ // And dismiss the second snackbar to get back to clean state
+ SnackbarUtils.dismissSnackbarAndWaitUntilFullyDismissed(anotherSnackbar);
+ }
+
+ @Test
+ @MediumTest
+ public void testActionClickListener() {
+ final View.OnClickListener mockClickListener = mock(View.OnClickListener.class);
+ final Snackbar snackbar =
+ Snackbar.make(mCoordinatorLayout, MESSAGE_TEXT, Snackbar.LENGTH_SHORT)
+ .setAction(ACTION_TEXT, mockClickListener);
+
+ // Show the snackbar
+ SnackbarUtils.showSnackbarAndWaitUntilFullyShown(snackbar);
+ // perform the action click
+ onView(withId(R.id.snackbar_action)).perform(click());
+ // and verify that our click listener has been called
+ verify(mockClickListener, times(1)).onClick(any(View.class));
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TabLayoutPoolingActivity.java b/design/tests/src/android/support/design/widget/TabLayoutPoolingActivity.java
new file mode 100644
index 0000000..2108235
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutPoolingActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import android.support.design.test.R;
+
+public class TabLayoutPoolingActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.design_tabs_twice;
+ }
+}
\ No newline at end of file
diff --git a/design/tests/src/android/support/design/widget/TabLayoutPoolingTest.java b/design/tests/src/android/support/design/widget/TabLayoutPoolingTest.java
new file mode 100755
index 0000000..09601db
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutPoolingTest.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+public class TabLayoutPoolingTest extends BaseInstrumentationTestCase<TabLayoutPoolingActivity> {
+
+ public TabLayoutPoolingTest() {
+ super(TabLayoutPoolingActivity.class);
+ }
+
+ @SmallTest
+ @Test
+ public void testUsingTabsFromOtherInstance() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final Activity activity = mActivityTestRule.getActivity();
+
+ // TabLayout1 has items added via the layout, so we'll just check they're
+ // there first
+ final TabLayout tabLayout1 = (TabLayout) activity.findViewById(R.id.tabs_1);
+ assertTrue(tabLayout1.getTabCount() > 0);
+
+ // Now remove all tabs. TabLayout will pool the Tab instances...
+ tabLayout1.removeAllTabs();
+
+ // Now add some tabs to the second TabLayout and make sure that we don't crash
+ final TabLayout tabLayout2 = (TabLayout) activity.findViewById(R.id.tabs_2);
+ tabLayout2.addTab(tabLayout2.newTab());
+ tabLayout2.addTab(tabLayout2.newTab());
+ tabLayout2.addTab(tabLayout2.newTab());
+ }
+ });
+ }
+
+}
diff --git a/design/tests/src/android/support/design/widget/TabLayoutTest.java b/design/tests/src/android/support/design/widget/TabLayoutTest.java
new file mode 100755
index 0000000..fcbf370
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutTest.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.support.design.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.v7.app.AppCompatActivity;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.InflateException;
+import android.view.LayoutInflater;
+
+import org.junit.Test;
+
+@SmallTest
+public class TabLayoutTest extends BaseInstrumentationTestCase<AppCompatActivity> {
+ public TabLayoutTest() {
+ super(AppCompatActivity.class);
+ }
+
+ @Test
+ public void testInflateTabLayoutWithTabItems() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final LayoutInflater inflater =
+ LayoutInflater.from(mActivityTestRule.getActivity());
+ final TabLayout tabLayout = (TabLayout) inflater.inflate(
+ R.layout.design_tabs_items, null);
+
+ assertEquals(3, tabLayout.getTabCount());
+
+ // Tab 0 has text, but no icon or custom view
+ TabLayout.Tab tab = tabLayout.getTabAt(0);
+ assertEquals(mActivityTestRule.getActivity().getString(R.string.tab_layout_text),
+ tab.getText());
+ assertNull(tab.getIcon());
+ assertNull(tab.getCustomView());
+
+ // Tab 1 has an icon, but no text or custom view
+ tab = tabLayout.getTabAt(1);
+ assertNull(tab.getText());
+ assertNotNull(tab.getIcon());
+ assertNull(tab.getCustomView());
+
+ // Tab 2 has a custom view, but no text or icon
+ tab = tabLayout.getTabAt(2);
+ assertNull(tab.getText());
+ assertNull(tab.getIcon());
+ assertNotNull(tab.getCustomView());
+ assertEquals(R.id.my_custom_tab, tab.getCustomView().getId());
+ }
+ });
+ }
+
+ @Test
+ public void testInflateTabLayoutWithNonTabItem() throws Throwable {
+ final Throwable[] exceptions = new Throwable[1];
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ try {
+ final LayoutInflater inflater =
+ LayoutInflater.from(mActivityTestRule.getActivity());
+ inflater.inflate(R.layout.design_tabs_with_non_tabitems, null);
+ } catch (Throwable throwable) {
+ exceptions[0] = throwable;
+ }
+ }
+ });
+
+ final Throwable thrown = exceptions[0];
+ assertNotNull(thrown);
+ // M+ will wrap the exception in an InflateException so we have to check for both
+ assertTrue(thrown instanceof InflateException
+ || thrown instanceof IllegalArgumentException);
+ }
+
+ @Test
+ public void testTabWithCustomLayoutSelection1() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final LayoutInflater inflater =
+ LayoutInflater.from(mActivityTestRule.getActivity());
+
+ final TabLayout tabLayout =
+ (TabLayout) inflater.inflate(R.layout.design_tabs, null);
+ final TabLayout.Tab tab = tabLayout.newTab();
+ tab.setCustomView(R.layout.design_tab_item_custom);
+ tabLayout.addTab(tab);
+
+ assertNotNull("Tab has custom view", tab.getCustomView());
+ assertEquals("First tab is selected", 0, tabLayout.getSelectedTabPosition());
+ assertTrue("Custom view for first tab is selected",
+ tab.getCustomView().isSelected());
+ }
+ });
+ }
+
+ @Test
+ public void testTabWithCustomLayoutSelection2() {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final LayoutInflater inflater =
+ LayoutInflater.from(mActivityTestRule.getActivity());
+
+ final TabLayout tabLayout =
+ (TabLayout) inflater.inflate(R.layout.design_tabs, null);
+ final TabLayout.Tab tab = tabLayout.newTab();
+ tabLayout.addTab(tab);
+ tab.setCustomView(R.layout.design_tab_item_custom);
+
+ assertNotNull("Tab has custom view", tab.getCustomView());
+ assertEquals("First tab is selected", 0, tabLayout.getSelectedTabPosition());
+ assertTrue("Custom view for first tab is selected",
+ tab.getCustomView().isSelected());
+ }
+ });
+ }
+}
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..ff92b36
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerActivity.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import 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..4b4c8c9
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TabLayoutWithViewPagerTest.java
@@ -0,0 +1,559 @@
+/*
+ * 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.content.res.Resources;
+import android.graphics.Color;
+import android.support.annotation.DimenRes;
+import android.support.annotation.LayoutRes;
+import android.support.design.test.R;
+import android.support.design.testutils.TabLayoutActions;
+import android.support.design.testutils.TestUtilsActions;
+import android.support.design.testutils.TestUtilsMatchers;
+import android.support.design.testutils.ViewPagerActions;
+import android.support.v4.view.PagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.HorizontalScrollView;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.ArrayList;
+
+import static android.support.design.testutils.TabLayoutActions.setupWithViewPager;
+import static android.support.design.testutils.ViewPagerActions.notifyAdapterContentChange;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotEquals;
+
+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);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final TabLayoutWithViewPagerActivity activity = mActivityTestRule.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));
+ }
+
+ private void setupTabLayoutWithViewPager() {
+ // And wire the tab layout to it
+ onView(withId(R.id.tabs)).perform(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() {
+ setupTabLayoutWithViewPager();
+
+ 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() {
+ setupTabLayoutWithViewPager();
+
+ assertEquals("Default selected page", 0, mViewPager.getCurrentItem());
+ assertEquals("Default selected tab", 0, mTabLayout.getSelectedTabPosition());
+
+ verifyTabLayoutSelection();
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterContentChange() {
+ setupTabLayoutWithViewPager();
+
+ // 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
+ mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+ mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+ final int newItemCount = mDefaultPagerAdapter.getCount();
+ onView(withId(R.id.tabs_viewpager)).perform(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 testAdapterContentChangeWithAutoRefreshDisabled() {
+ onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+ // 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
+ mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+ mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+ final int newItemCount = mDefaultPagerAdapter.getCount();
+
+ // Notify the adapter that it has changed
+ onView(withId(R.id.tabs_viewpager)).perform(notifyAdapterContentChange());
+
+ // Assert that the TabLayout did not update and add the new items
+ assertNotEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+ }
+
+ @Test
+ @SmallTest
+ public void testBasicAutoRefreshDisabled() {
+ onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+ // Check that the TabLayout has the same number of items are the adapter
+ PagerAdapter initialAdapter = mViewPager.getAdapter();
+ assertEquals("Initial adapter page count", initialAdapter.getCount(),
+ mTabLayout.getTabCount());
+
+ // Add two more entries to our adapter
+ mDefaultPagerAdapter.add("Yellow", Color.YELLOW);
+ mDefaultPagerAdapter.add("Magenta", Color.MAGENTA);
+ final int newItemCount = mDefaultPagerAdapter.getCount();
+
+ // Assert that the TabLayout did not update and add the new items
+ assertNotEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+
+ // Now setup again to update the tabs
+ onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager, false));
+
+ // Assert that the TabLayout updated and added the new items
+ assertEquals("Matching item count", newItemCount, mTabLayout.getTabCount());
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterChange() {
+ setupTabLayoutWithViewPager();
+
+ // 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(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();
+ }
+
+ @Test
+ @MediumTest
+ public void testFixedTabMode() {
+ // Create a new adapter (with no content)
+ final TextPagerAdapter newAdapter = new TextPagerAdapter();
+ // And set it on the ViewPager
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.setAdapter(newAdapter));
+ // As TabLayout doesn't track adapter changes, we need to re-wire the new adapter
+ onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
+
+ // Set fixed mode on the TabLayout
+ onView(withId(R.id.tabs)).perform(TabLayoutActions.setTabMode(TabLayout.MODE_FIXED));
+ assertEquals("Fixed tab mode", TabLayout.MODE_FIXED, mTabLayout.getTabMode());
+
+ // Add a bunch of tabs and verify that all of them are visible on the screen
+ for (int i = 0; i < 8; i++) {
+ newAdapter.add("Title " + i, "Body " + i);
+ onView(withId(R.id.tabs_viewpager)).perform(
+ notifyAdapterContentChange());
+
+ int expectedTabCount = i + 1;
+ assertEquals("Tab count after adding #" + i, expectedTabCount,
+ mTabLayout.getTabCount());
+ assertEquals("Page count after adding #" + i, expectedTabCount,
+ mViewPager.getAdapter().getCount());
+
+ verifyViewPagerSelection();
+ verifyTabLayoutSelection();
+
+ // Check that all tabs are fully visible (the content may or may not be elided)
+ for (int j = 0; j < expectedTabCount; j++) {
+ onView(allOf(isDescendantOfA(withId(R.id.tabs)), withText("Title " + j))).
+ check(matches(isCompletelyDisplayed()));
+ }
+ }
+ }
+
+ /**
+ * Helper method to verify support for min and max tab width on TabLayout in scrollable mode.
+ * It replaces the TabLayout based on the passed layout resource ID and then adds a bunch of
+ * tab titles to the wired ViewPager with progressively longer texts. After each tab is added
+ * this method then checks that all tab views respect the minimum and maximum tab width set
+ * on TabLayout.
+ *
+ * @param tabLayoutResId Layout resource for the TabLayout to be wired to the ViewPager.
+ * @param tabMinWidthResId If non zero, points to the dimension resource to use for tab min
+ * width check.
+ * @param tabMaxWidthResId If non zero, points to the dimension resource to use for tab max
+ * width check.
+ */
+ private void verifyMinMaxTabWidth(@LayoutRes int tabLayoutResId, @DimenRes int tabMinWidthResId,
+ @DimenRes int tabMaxWidthResId) {
+ setupTabLayoutWithViewPager();
+
+ assertEquals("Scrollable tab mode", TabLayout.MODE_SCROLLABLE, mTabLayout.getTabMode());
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final int minTabWidth = (tabMinWidthResId == 0) ? -1 :
+ res.getDimensionPixelSize(tabMinWidthResId);
+ final int maxTabWidth = (tabMaxWidthResId == 0) ? -1 :
+ res.getDimensionPixelSize(tabMaxWidthResId);
+
+ // Create a new adapter (with no content)
+ final TextPagerAdapter newAdapter = new TextPagerAdapter();
+ // And set it on the ViewPager
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.setAdapter(newAdapter));
+
+ // Replace the default TabLayout with the passed one
+ onView(withId(R.id.container)).perform(TestUtilsActions.replaceTabLayout(tabLayoutResId));
+
+ // Now that we have a new TabLayout, wire it to the new content of our ViewPager
+ onView(withId(R.id.tabs)).perform(setupWithViewPager(mViewPager));
+
+ // Since TabLayout doesn't expose a getter for fetching the configured max tab width,
+ // start adding a variety of tabs with progressively longer tab titles and test that
+ // no tab is wider than the configured max width. Before we start that test,
+ // verify that we're in the scrollable mode so that each tab title gets as much width
+ // as needed to display its text.
+ assertEquals("Scrollable tab mode", TabLayout.MODE_SCROLLABLE, mTabLayout.getTabMode());
+
+ final StringBuilder tabTitleBuilder = new StringBuilder();
+ for (int i = 0; i < 40; i++) {
+ final char titleComponent = (char) ('A' + i);
+ for (int j = 0; j <= (i + 1); j++) {
+ tabTitleBuilder.append(titleComponent);
+ }
+ final String tabTitle = tabTitleBuilder.toString();
+ newAdapter.add(tabTitle, "Body " + i);
+ onView(withId(R.id.tabs_viewpager)).perform(
+ notifyAdapterContentChange());
+
+ int expectedTabCount = i + 1;
+ // Check that all tabs are at least as wide as min width *and* at most as wide as max
+ // width specified in the XML for the newly loaded TabLayout
+ for (int j = 0; j < expectedTabCount; j++) {
+ // Find the view that is our tab title. It should be:
+ // 1. Descendant of our TabLayout
+ // 2. But not a direct child of the horizontal scroller
+ // 3. With just-added title text
+ // These conditions make sure that we're selecting the "top-level" tab view
+ // instead of the inner (and narrower) TextView
+ Matcher<View> tabMatcher = allOf(
+ isDescendantOfA(withId(R.id.tabs)),
+ not(withParent(isAssignableFrom(HorizontalScrollView.class))),
+ hasDescendant(withText(tabTitle)));
+ if (minTabWidth >= 0) {
+ onView(tabMatcher).check(matches(
+ TestUtilsMatchers.isNotNarrowerThan(minTabWidth)));
+ }
+ if (maxTabWidth >= 0) {
+ onView(tabMatcher).check(matches(
+ TestUtilsMatchers.isNotWiderThan(maxTabWidth)));
+ }
+ }
+
+ // Reset the title builder for the next tab
+ tabTitleBuilder.setLength(0);
+ tabTitleBuilder.trimToSize();
+ }
+
+ }
+
+ @Test
+ @MediumTest
+ public void testMinTabWidth() {
+ verifyMinMaxTabWidth(R.layout.tab_layout_bound_min, R.dimen.tab_width_limit_medium, 0);
+ }
+
+ @Test
+ @MediumTest
+ public void testMaxTabWidth() {
+ verifyMinMaxTabWidth(R.layout.tab_layout_bound_max, 0, R.dimen.tab_width_limit_medium);
+ }
+
+ @Test
+ @MediumTest
+ public void testMinMaxTabWidth() {
+ verifyMinMaxTabWidth(R.layout.tab_layout_bound_minmax, R.dimen.tab_width_limit_small,
+ R.dimen.tab_width_limit_large);
+ }
+
+ @Test
+ @SmallTest
+ public void testSetupAfterViewPagerScrolled() {
+ // Scroll to the last item
+ final int selected = mViewPager.getAdapter().getCount() - 1;
+ onView(withId(R.id.tabs_viewpager)).perform(ViewPagerActions.scrollToPage(selected));
+
+ // Now setup the TabLayout with the ViewPager
+ setupTabLayoutWithViewPager();
+
+ assertEquals("Selected page", selected, mViewPager.getCurrentItem());
+ assertEquals("Selected tab", selected, mTabLayout.getSelectedTabPosition());
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutActivity.java b/design/tests/src/android/support/design/widget/TextInputLayoutActivity.java
new file mode 100644
index 0000000..1ae3a29
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.design.widget;
+
+import android.support.design.test.R;
+import android.support.v7.widget.Toolbar;
+
+public class TextInputLayoutActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.design_text_input;
+ }
+}
diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
new file mode 100755
index 0000000..558f474
--- /dev/null
+++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.design.widget;
+
+import static android.support.design.testutils.TextInputLayoutActions.setError;
+import static android.support.design.testutils.TextInputLayoutActions.setErrorEnabled;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withChild;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+
+import static org.hamcrest.CoreMatchers.not;
+
+import android.support.design.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
+@SmallTest
+public class TextInputLayoutTest extends BaseInstrumentationTestCase<TextInputLayoutActivity> {
+
+ private static final String ERROR_MESSAGE_1 = "An error has occured";
+ private static final String ERROR_MESSAGE_2 = "Some other error has occured";
+
+ public TextInputLayoutTest() {
+ super(TextInputLayoutActivity.class);
+ }
+
+ @Test
+ public void testSetErrorEnablesErrorIsDisplayed() {
+ onView(withId(R.id.textinput)).perform(setError(ERROR_MESSAGE_1));
+ onView(withText(ERROR_MESSAGE_1)).check(matches(isDisplayed()));
+ }
+
+ @Test
+ public void testDisabledErrorIsNotDisplayed() {
+ // First show an error, and then disable error functionality
+ onView(withId(R.id.textinput))
+ .perform(setError(ERROR_MESSAGE_1))
+ .perform(setErrorEnabled(false));
+
+ // Check that the error is no longer there
+ onView(withText(ERROR_MESSAGE_1)).check(doesNotExist());
+ }
+
+ @Test
+ public void testSetErrorOnDisabledSetErrorIsDisplayed() {
+ // First show an error, and then disable error functionality
+ onView(withId(R.id.textinput))
+ .perform(setError(ERROR_MESSAGE_1))
+ .perform(setErrorEnabled(false));
+
+ // Now show a different error message
+ onView(withId(R.id.textinput)).perform(setError(ERROR_MESSAGE_2));
+ // And check that it is displayed
+ onView(withText(ERROR_MESSAGE_2)).check(matches(isDisplayed()));
+ }
+
+}
diff --git a/documents-archive/Android.mk b/documents-archive/Android.mk
new file mode 100644
index 0000000..98d28c3
--- /dev/null
+++ b/documents-archive/Android.mk
@@ -0,0 +1,49 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-documents-archive \
+# android-support-v4 \
+# android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
+LOCAL_MODULE := android-support-documents-archive
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_AIDL_INCLUDES := $(LOCAL_PATH)/src
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-annotations \
+ android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# API Check
+# ---------------------------------------------
+support_module := $(LOCAL_MODULE)
+support_module_api_dir := $(LOCAL_PATH)/api
+support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
+support_module_java_packages := android.support.provider.*
+include $(SUPPORT_API_CHECK)
diff --git a/documents-archive/AndroidManifest.xml b/documents-archive/AndroidManifest.xml
new file mode 100644
index 0000000..2cd0f7a
--- /dev/null
+++ b/documents-archive/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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"
+ package="android.support.provider">
+ <uses-sdk android:minSdkVersion="19"/>
+ <application />
+</manifest>
diff --git a/documents-archive/api/current.txt b/documents-archive/api/current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/current.txt
diff --git a/documents-archive/api/removed.txt b/documents-archive/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/api/removed.txt
diff --git a/documents-archive/src/android/support/provider/DocumentArchive.java b/documents-archive/src/android/support/provider/DocumentArchive.java
new file mode 100644
index 0000000..5db5b38
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchive.java
@@ -0,0 +1,534 @@
+/*
+ * 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.provider;
+
+import android.content.Context;
+import android.content.res.AssetFileDescriptor;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.media.ExifInterface;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.OperationCanceledException;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.webkit.MimeTypeMap;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.lang.IllegalArgumentException;
+import java.lang.IllegalStateException;
+import java.lang.UnsupportedOperationException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Stack;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipFile;
+import java.util.zip.ZipInputStream;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider. The id delimiter
+ * must be a character which is not used in document ids generated by the
+ * document provider.
+ *
+ * <p>This class is thread safe.
+ *
+ * @hide
+ */
+public class DocumentArchive implements Closeable {
+ private static final String TAG = "DocumentArchive";
+
+ private static final String[] DEFAULT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID,
+ Document.COLUMN_DISPLAY_NAME,
+ Document.COLUMN_MIME_TYPE,
+ Document.COLUMN_SIZE,
+ Document.COLUMN_FLAGS
+ };
+
+ private final Context mContext;
+ private final String mDocumentId;
+ private final char mIdDelimiter;
+ private final Uri mNotificationUri;
+ private final ZipFile mZipFile;
+ private final ExecutorService mExecutor;
+ private final Map<String, ZipEntry> mEntries;
+ private final Map<String, List<ZipEntry>> mTree;
+
+ private DocumentArchive(
+ Context context,
+ File file,
+ String documentId,
+ char idDelimiter,
+ @Nullable Uri notificationUri)
+ throws IOException {
+ mContext = context;
+ mDocumentId = documentId;
+ mIdDelimiter = idDelimiter;
+ mNotificationUri = notificationUri;
+ mZipFile = new ZipFile(file);
+ mExecutor = Executors.newSingleThreadExecutor();
+
+ // Build the tree structure in memory.
+ mTree = new HashMap<String, List<ZipEntry>>();
+ mTree.put("/", new ArrayList<ZipEntry>());
+
+ mEntries = new HashMap<String, ZipEntry>();
+ ZipEntry entry;
+ final List<? extends ZipEntry> entries = Collections.list(mZipFile.entries());
+ final Stack<ZipEntry> stack = new Stack<>();
+ for (int i = entries.size() - 1; i >= 0; i--) {
+ entry = entries.get(i);
+ if (entry.isDirectory() != entry.getName().endsWith("/")) {
+ throw new IOException(
+ "Directories must have a trailing slash, and files must not.");
+ }
+ if (mEntries.containsKey(entry.getName())) {
+ throw new IOException("Multiple entries with the same name are not supported.");
+ }
+ mEntries.put(entry.getName(), entry);
+ if (entry.isDirectory()) {
+ mTree.put(entry.getName(), new ArrayList<ZipEntry>());
+ }
+ stack.push(entry);
+ }
+
+ int delimiterIndex;
+ String parentPath;
+ ZipEntry parentEntry;
+ List<ZipEntry> parentList;
+
+ while (stack.size() > 0) {
+ entry = stack.pop();
+
+ delimiterIndex = entry.getName().lastIndexOf('/', entry.isDirectory()
+ ? entry.getName().length() - 2 : entry.getName().length() - 1);
+ parentPath =
+ delimiterIndex != -1 ? entry.getName().substring(0, delimiterIndex) + "/" : "/";
+ parentList = mTree.get(parentPath);
+
+ if (parentList == null) {
+ parentEntry = mEntries.get(parentPath);
+ if (parentEntry == null) {
+ // The ZIP file doesn't contain all directories leading to the entry.
+ // It's rare, but can happen in a valid ZIP archive. In such case create a
+ // fake ZipEntry and add it on top of the stack to process it next.
+ parentEntry = new ZipEntry(parentPath);
+ parentEntry.setSize(0);
+ parentEntry.setTime(entry.getTime());
+ mEntries.put(parentPath, parentEntry);
+ stack.push(parentEntry);
+ }
+ parentList = new ArrayList<ZipEntry>();
+ mTree.put(parentPath, parentList);
+ }
+
+ parentList.add(entry);
+ }
+ }
+
+ /**
+ * Creates a DocumentsArchive instance for opening, browsing and accessing
+ * documents within the archive passed as a local file.
+ *
+ * @param context Context of the provider.
+ * @param File Local file containing the archive.
+ * @param documentId ID of the archive document.
+ * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+ * The delimiter must never be used for IDs of other documents.
+ * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @see createForParcelFileDescriptor(DocumentsProvider, ParcelFileDescriptor, String, char,
+ * Uri)
+ */
+ public static DocumentArchive createForLocalFile(
+ Context context, File file, String documentId, char idDelimiter,
+ @Nullable Uri notificationUri)
+ throws IOException {
+ return new DocumentArchive(context, file, documentId, idDelimiter, notificationUri);
+ }
+
+ /**
+ * Creates a DocumentsArchive instance for opening, browsing and accessing
+ * documents within the archive passed as a file descriptor.
+ *
+ * <p>Note, that this method should be used only if the document does not exist
+ * on the local storage. A snapshot file will be created, which may be slower
+ * and consume significant resources, in contrast to using
+ * {@see createForLocalFile(Context, File, String, char, Uri}.
+ *
+ * @param context Context of the provider.
+ * @param descriptor File descriptor for the archive's contents.
+ * @param documentId ID of the archive document.
+ * @param idDelimiter Delimiter for constructing IDs of documents within the archive.
+ * The delimiter must never be used for IDs of other documents.
+ * @param Uri notificationUri Uri for notifying that the archive file has changed.
+ * @see createForLocalFile(Context, File, String, char, Uri)
+ */
+ public static DocumentArchive createForParcelFileDescriptor(
+ Context context, ParcelFileDescriptor descriptor, String documentId,
+ char idDelimiter, @Nullable Uri notificationUri)
+ throws IOException {
+ File snapshotFile = null;
+ try {
+ // Create a copy of the archive, as ZipFile doesn't operate on streams.
+ // Moreover, ZipInputStream would be inefficient for large files on
+ // pipes.
+ snapshotFile = File.createTempFile("android.support.provider.snapshot{",
+ "}.zip", context.getCacheDir());
+
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ snapshotFile, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(descriptor);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ return new DocumentArchive(context, snapshotFile, documentId, idDelimiter,
+ notificationUri);
+ }
+ } finally {
+ // On UNIX the file will be still available for processes which opened it, even
+ // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+ if (snapshotFile != null) {
+ snapshotFile.delete();
+ }
+ }
+ }
+
+ /**
+ * Lists child documents of an archive or a directory within an
+ * archive. Must be called only for archives with supported mime type,
+ * or for documents within archives.
+ *
+ * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+ */
+ public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+ @Nullable String sortOrder) throws FileNotFoundException {
+ final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+
+ final String parentPath = parsedParentId.mPath != null ? parsedParentId.mPath : "/";
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ if (mNotificationUri != null) {
+ result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+ }
+
+ final List<ZipEntry> parentList = mTree.get(parentPath);
+ if (parentList == null) {
+ throw new FileNotFoundException();
+ }
+ for (final ZipEntry entry : parentList) {
+ addCursorRow(result, entry);
+ }
+ return result;
+ }
+
+ /**
+ * Returns a MIME type of a document within an archive.
+ *
+ * @see DocumentsProvider.getDocumentType(String)
+ */
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mEntries.get(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+ return getMimeTypeForEntry(entry);
+ }
+
+ /**
+ * Returns true if a document within an archive is a child or any descendant of the archive
+ * document or another document within the archive.
+ *
+ * @see DocumentsProvider.isChildDocument(String, String)
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ final ParsedDocumentId parsedParentId = ParsedDocumentId.fromDocumentId(
+ parentDocumentId, mIdDelimiter);
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedParentId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath,
+ "Not a document within an archive.");
+
+ final ZipEntry entry = mEntries.get(parsedId.mPath);
+ if (entry == null) {
+ return false;
+ }
+
+ if (parsedParentId.mPath == null) {
+ // No need to compare paths. Every file in the archive is a child of the archive
+ // file.
+ return true;
+ }
+
+ final ZipEntry parentEntry = mEntries.get(parsedParentId.mPath);
+ if (parentEntry == null || !parentEntry.isDirectory()) {
+ return false;
+ }
+
+ final String parentPath = entry.getName();
+
+ // Add a trailing slash even if it's not a directory, so it's easy to check if the
+ // entry is a descendant.
+ final String pathWithSlash = entry.isDirectory() ? entry.getName() : entry.getName() + "/";
+ return pathWithSlash.startsWith(parentPath) && !parentPath.equals(pathWithSlash);
+ }
+
+ /**
+ * Returns metadata of a document within an archive.
+ *
+ * @see DocumentsProvider.queryDocument(String, String[])
+ */
+ public Cursor queryDocument(String documentId, @Nullable String[] projection)
+ throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mEntries.get(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ if (mNotificationUri != null) {
+ result.setNotificationUri(mContext.getContentResolver(), mNotificationUri);
+ }
+ addCursorRow(result, entry);
+ return result;
+ }
+
+ /**
+ * Opens a file within an archive.
+ *
+ * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+ */
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, @Nullable final CancellationSignal signal)
+ throws FileNotFoundException {
+ Preconditions.checkArgumentEquals("r", mode,
+ "Invalid mode. Only reading \"r\" supported, but got: \"%s\".");
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(
+ documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+
+ final ZipEntry entry = mEntries.get(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ ParcelFileDescriptor[] pipe;
+ InputStream inputStream = null;
+ try {
+ pipe = ParcelFileDescriptor.createReliablePipe();
+ inputStream = mZipFile.getInputStream(entry);
+ } catch (IOException e) {
+ if (inputStream != null) {
+ IoUtils.closeQuietly(inputStream);
+ }
+ // Ideally we'd simply throw IOException to the caller, but for consistency
+ // with DocumentsProvider::openDocument, converting it to IllegalStateException.
+ throw new IllegalStateException("Failed to open the document.", e);
+ }
+ final ParcelFileDescriptor outputPipe = pipe[1];
+ final InputStream finalInputStream = inputStream;
+ mExecutor.execute(
+ new Runnable() {
+ @Override
+ public void run() {
+ try (final ParcelFileDescriptor.AutoCloseOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(outputPipe)) {
+ try {
+ final byte buffer[] = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = finalInputStream.read(buffer)) != -1) {
+ if (Thread.interrupted()) {
+ throw new InterruptedException();
+ }
+ if (signal != null) {
+ signal.throwIfCanceled();
+ }
+ outputStream.write(buffer, 0, bytes);
+ }
+ } catch (IOException | InterruptedException e) {
+ // Catch the exception before the outer try-with-resource closes the
+ // pipe with close() instead of closeWithError().
+ try {
+ outputPipe.closeWithError(e.getMessage());
+ } catch (IOException e2) {
+ Log.e(TAG, "Failed to close the pipe after an error.", e2);
+ }
+ }
+ } catch (OperationCanceledException e) {
+ // Cancelled gracefully.
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to close the output stream gracefully.", e);
+ } finally {
+ IoUtils.closeQuietly(finalInputStream);
+ }
+ }
+ });
+
+ return pipe[0];
+ }
+
+ /**
+ * Opens a thumbnail of a file within an archive.
+ *
+ * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+ */
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId, Point sizeHint, final CancellationSignal signal)
+ throws FileNotFoundException {
+ final ParsedDocumentId parsedId = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+ Preconditions.checkArgumentEquals(mDocumentId, parsedId.mArchiveId,
+ "Mismatching document ID. Expected: %s, actual: %s.");
+ Preconditions.checkArgumentNotNull(parsedId.mPath, "Not a document within an archive.");
+ Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
+ "Thumbnails only supported for image/* MIME type.");
+
+ final ZipEntry entry = mEntries.get(parsedId.mPath);
+ if (entry == null) {
+ throw new FileNotFoundException();
+ }
+
+ InputStream inputStream = null;
+ try {
+ inputStream = mZipFile.getInputStream(entry);
+ final ExifInterface exif = new ExifInterface(inputStream);
+ if (exif.hasThumbnail()) {
+ Bundle extras = null;
+ switch (exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, -1)) {
+ case ExifInterface.ORIENTATION_ROTATE_90:
+ extras = new Bundle(1);
+ extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 90);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_180:
+ extras = new Bundle(1);
+ extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 180);
+ break;
+ case ExifInterface.ORIENTATION_ROTATE_270:
+ extras = new Bundle(1);
+ extras.putInt(DocumentsContract.EXTRA_ORIENTATION, 270);
+ break;
+ }
+ final long[] range = exif.getThumbnailRange();
+ return new AssetFileDescriptor(
+ openDocument(documentId, "r", signal), range[0], range[1], extras);
+ }
+ } catch (IOException e) {
+ // Ignore the exception, as reading the EXIF may legally fail.
+ Log.e(TAG, "Failed to obtain thumbnail from EXIF.", e);
+ } finally {
+ IoUtils.closeQuietly(inputStream);
+ }
+
+ return new AssetFileDescriptor(
+ openDocument(documentId, "r", signal), 0, entry.getSize(), null);
+ }
+
+ /**
+ * Schedules a gracefully close of the archive after any opened files are closed.
+ *
+ * <p>This method does not block until shutdown. Once called, other methods should not be
+ * called.
+ */
+ @Override
+ public void close() {
+ mExecutor.execute(new Runnable() {
+ @Override
+ public void run() {
+ IoUtils.closeQuietly(mZipFile);
+ }
+ });
+ mExecutor.shutdown();
+ }
+
+ private void addCursorRow(MatrixCursor cursor, ZipEntry entry) {
+ final MatrixCursor.RowBuilder row = cursor.newRow();
+ final ParsedDocumentId parsedId = new ParsedDocumentId(mDocumentId, entry.getName());
+ row.add(Document.COLUMN_DOCUMENT_ID, parsedId.toDocumentId(mIdDelimiter));
+
+ final File file = new File(entry.getName());
+ row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+ row.add(Document.COLUMN_SIZE, entry.getSize());
+
+ final String mimeType = getMimeTypeForEntry(entry);
+ row.add(Document.COLUMN_MIME_TYPE, mimeType);
+
+ final int flags = mimeType.startsWith("image/") ? Document.FLAG_SUPPORTS_THUMBNAIL : 0;
+ row.add(Document.COLUMN_FLAGS, flags);
+ }
+
+ private String getMimeTypeForEntry(ZipEntry entry) {
+ if (entry.isDirectory()) {
+ return Document.MIME_TYPE_DIR;
+ }
+
+ final int lastDot = entry.getName().lastIndexOf('.');
+ if (lastDot >= 0) {
+ final String extension = entry.getName().substring(lastDot + 1).toLowerCase(Locale.US);
+ final String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension);
+ if (mimeType != null) {
+ return mimeType;
+ }
+ }
+
+ return "application/octet-stream";
+ }
+};
diff --git a/documents-archive/src/android/support/provider/DocumentArchiveHelper.java b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
new file mode 100644
index 0000000..6cb42183
--- /dev/null
+++ b/documents-archive/src/android/support/provider/DocumentArchiveHelper.java
@@ -0,0 +1,369 @@
+/*
+ * 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.provider;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.Configuration;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.database.MatrixCursor;
+import android.graphics.Point;
+import android.net.Uri;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsProvider;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.util.LruCache;
+
+import java.io.Closeable;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.concurrent.Callable;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+
+/**
+ * Provides basic implementation for creating, extracting and accessing
+ * files within archives exposed by a document provider.
+ *
+ * <p>This class is thread safe. All methods can be called on any thread without
+ * synchronization.
+ *
+ * TODO: Update the documentation. b/26047732
+ * @hide
+ */
+public class DocumentArchiveHelper implements Closeable {
+ /**
+ * Cursor column to be used for passing the local file path for documents.
+ * If it's not specified, then a snapshot will be created, which is slower
+ * and consumes more resources.
+ *
+ * <p>Type: STRING
+ */
+ public static final String COLUMN_LOCAL_FILE_PATH = "local_file_path";
+
+ private static final String TAG = "DocumentArchiveHelper";
+ private static final int OPENED_ARCHIVES_CACHE_SIZE = 4;
+ private static final String[] ZIP_MIME_TYPES = {
+ "application/zip", "application/x-zip", "application/x-zip-compressed"
+ };
+
+ private final DocumentsProvider mProvider;
+ private final char mIdDelimiter;
+
+ // @GuardedBy("mArchives")
+ private final LruCache<String, Loader> mArchives =
+ new LruCache<String, Loader>(OPENED_ARCHIVES_CACHE_SIZE) {
+ @Override
+ public void entryRemoved(boolean evicted, String key,
+ Loader oldValue, Loader newValue) {
+ oldValue.getWriteLock().lock();
+ try {
+ oldValue.get().close();
+ } catch (FileNotFoundException e) {
+ Log.e(TAG, "Failed to close an archive as it no longer exists.");
+ } finally {
+ oldValue.getWriteLock().unlock();
+ }
+ }
+ };
+
+ /**
+ * Creates a helper for handling archived documents.
+ *
+ * @param provider Instance of a documents provider which provides archived documents.
+ * @param idDelimiter A character used to create document IDs within archives. Can be any
+ * character which is not used in any other document ID. If your provider uses
+ * numbers as document IDs, the delimiter can be eg. a colon. However if your
+ * provider uses paths, then a delimiter can be any character not allowed in the
+ * path, which is often \0.
+ */
+ public DocumentArchiveHelper(DocumentsProvider provider, char idDelimiter) {
+ mProvider = provider;
+ mIdDelimiter = idDelimiter;
+ }
+
+ /**
+ * Lists child documents of an archive or a directory within an
+ * archive. Must be called only for archives with supported mime type,
+ * or for documents within archives.
+ *
+ * @see DocumentsProvider.queryChildDocuments(String, String[], String)
+ */
+ public Cursor queryChildDocuments(String documentId, @Nullable String[] projection,
+ @Nullable String sortOrder)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().queryChildDocuments(documentId, projection, sortOrder);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns a MIME type of a document within an archive.
+ *
+ * @see DocumentsProvider.getDocumentType(String)
+ */
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().getDocumentType(documentId);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns true if a document within an archive is a child or any descendant of the archive
+ * document or another document within the archive.
+ *
+ * @see DocumentsProvider.isChildDocument(String, String)
+ */
+ public boolean isChildDocument(String parentDocumentId, String documentId) {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().isChildDocument(parentDocumentId, documentId);
+ } catch (FileNotFoundException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns metadata of a document within an archive.
+ *
+ * @see DocumentsProvider.queryDocument(String, String[])
+ */
+ public Cursor queryDocument(String documentId, @Nullable String[] projection)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().queryDocument(documentId, projection);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Opens a file within an archive.
+ *
+ * @see DocumentsProvider.openDocument(String, String, CancellationSignal))
+ */
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, final CancellationSignal signal)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().openDocument(documentId, mode, signal);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Opens a thumbnail of a file within an archive.
+ *
+ * @see DocumentsProvider.openDocumentThumbnail(String, Point, CancellationSignal))
+ */
+ public AssetFileDescriptor openDocumentThumbnail(
+ String documentId, Point sizeHint, final CancellationSignal signal)
+ throws FileNotFoundException {
+ Loader loader = null;
+ try {
+ loader = obtainInstance(documentId);
+ return loader.get().openDocumentThumbnail(documentId, sizeHint, signal);
+ } finally {
+ releaseInstance(loader);
+ }
+ }
+
+ /**
+ * Returns true if the passed document ID is for a document within an archive.
+ */
+ public boolean isArchivedDocument(String documentId) {
+ return ParsedDocumentId.hasPath(documentId, mIdDelimiter);
+ }
+
+ /**
+ * Returns true if the passed mime type is supported by the helper.
+ */
+ public boolean isSupportedArchiveType(String mimeType) {
+ for (final String zipMimeType : ZIP_MIME_TYPES) {
+ if (zipMimeType.equals(mimeType)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Closes the helper and disposes all existing archives. It will block until all ongoing
+ * operations on each opened archive are finished.
+ */
+ @Override
+ public void close() {
+ synchronized (mArchives) {
+ mArchives.evictAll();
+ }
+ }
+
+ /**
+ * Releases resources for an archive with the specified document ID. It will block until all
+ * operations on the archive are finished. If not opened, the method does nothing.
+ *
+ * <p>Calling this method is optional. The helper automatically closes the least recently used
+ * archives if too many archives are opened.
+ *
+ * @param archiveDocumentId ID of the archive file.
+ */
+ public void closeArchive(String documentId) {
+ synchronized (mArchives) {
+ mArchives.remove(documentId);
+ }
+ }
+
+ private Loader obtainInstance(String documentId) throws FileNotFoundException {
+ Loader loader;
+ synchronized (mArchives) {
+ loader = getInstanceUncheckedLocked(documentId);
+ loader.getReadLock().lock();
+ }
+ return loader;
+ }
+
+ private void releaseInstance(@Nullable Loader loader) {
+ if (loader != null) {
+ loader.getReadLock().unlock();
+ }
+ }
+
+ private Loader getInstanceUncheckedLocked(String documentId)
+ throws FileNotFoundException {
+ try {
+ final ParsedDocumentId id = ParsedDocumentId.fromDocumentId(documentId, mIdDelimiter);
+ if (mArchives.get(id.mArchiveId) != null) {
+ return mArchives.get(id.mArchiveId);
+ }
+
+ final Cursor cursor = mProvider.queryDocument(id.mArchiveId, new String[]
+ { Document.COLUMN_MIME_TYPE, COLUMN_LOCAL_FILE_PATH });
+ cursor.moveToFirst();
+ final String mimeType = cursor.getString(cursor.getColumnIndex(
+ Document.COLUMN_MIME_TYPE));
+ Preconditions.checkArgument(isSupportedArchiveType(mimeType),
+ "Unsupported archive type.");
+ final int columnIndex = cursor.getColumnIndex(COLUMN_LOCAL_FILE_PATH);
+ final String localFilePath = columnIndex != -1 ? cursor.getString(columnIndex) : null;
+ final File localFile = localFilePath != null ? new File(localFilePath) : null;
+ final Uri notificationUri = cursor.getNotificationUri();
+ final Loader loader = new Loader(mProvider, localFile, id, mIdDelimiter,
+ notificationUri);
+
+ // Remove the instance from mArchives collection once the archive file changes.
+ if (notificationUri != null) {
+ final LruCache<String, Loader> finalArchives = mArchives;
+ mProvider.getContext().getContentResolver().registerContentObserver(notificationUri,
+ false,
+ new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange, Uri uri) {
+ synchronized (mArchives) {
+ final Loader currentLoader = mArchives.get(id.mArchiveId);
+ if (currentLoader == loader) {
+ mArchives.remove(id.mArchiveId);
+ }
+ }
+ }
+ });
+ }
+
+ mArchives.put(id.mArchiveId, loader);
+ return loader;
+ } catch (IOException e) {
+ // DocumentsProvider doesn't use IOException. For consistency convert it to
+ // IllegalStateException.
+ throw new IllegalStateException(e);
+ }
+ }
+
+ /**
+ * Loads an instance of DocumentArchive lazily.
+ */
+ private static final class Loader {
+ private final DocumentsProvider mProvider;
+ private final File mLocalFile;
+ private final ParsedDocumentId mId;
+ private final char mIdDelimiter;
+ private final Uri mNotificationUri;
+ private final ReentrantReadWriteLock mLock = new ReentrantReadWriteLock();
+ private DocumentArchive mArchive = null;
+
+ Loader(DocumentsProvider provider, @Nullable File localFile, ParsedDocumentId id,
+ char idDelimiter, Uri notificationUri) {
+ this.mProvider = provider;
+ this.mLocalFile = localFile;
+ this.mId = id;
+ this.mIdDelimiter = idDelimiter;
+ this.mNotificationUri = notificationUri;
+ }
+
+ synchronized DocumentArchive get() throws FileNotFoundException {
+ if (mArchive != null) {
+ return mArchive;
+ }
+
+ try {
+ if (mLocalFile != null) {
+ mArchive = DocumentArchive.createForLocalFile(
+ mProvider.getContext(), mLocalFile, mId.mArchiveId, mIdDelimiter,
+ mNotificationUri);
+ } else {
+ mArchive = DocumentArchive.createForParcelFileDescriptor(
+ mProvider.getContext(),
+ mProvider.openDocument(mId.mArchiveId, "r", null /* signal */),
+ mId.mArchiveId, mIdDelimiter, mNotificationUri);
+ }
+ } catch (IOException e) {
+ throw new IllegalStateException(e);
+ }
+
+ return mArchive;
+ }
+
+ Lock getReadLock() {
+ return mLock.readLock();
+ }
+
+ Lock getWriteLock() {
+ return mLock.writeLock();
+ }
+ }
+}
diff --git a/documents-archive/src/android/support/provider/IoUtils.java b/documents-archive/src/android/support/provider/IoUtils.java
new file mode 100644
index 0000000..61047dc
--- /dev/null
+++ b/documents-archive/src/android/support/provider/IoUtils.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.provider;
+
+import android.support.annotation.Nullable;
+
+import java.io.Closeable;
+import java.io.InputStream;
+import java.util.Collection;
+
+/**
+ * Simple static methods to perform common IO operations.
+ * @hide
+ */
+final class IoUtils {
+ static void closeQuietly(@Nullable Closeable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+
+ static void closeQuietly(@Nullable InputStream stream) {
+ if (stream != null) {
+ try {
+ stream.close();
+ } catch (RuntimeException e) {
+ throw e;
+ } catch (Exception e) {
+ // Ignore.
+ }
+ }
+ }
+}
diff --git a/documents-archive/src/android/support/provider/ParsedDocumentId.java b/documents-archive/src/android/support/provider/ParsedDocumentId.java
new file mode 100644
index 0000000..ee2c366
--- /dev/null
+++ b/documents-archive/src/android/support/provider/ParsedDocumentId.java
@@ -0,0 +1,52 @@
+/*
+ * 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.provider;
+
+/**
+ * @hide
+ */
+class ParsedDocumentId {
+ public final String mArchiveId;
+ public final String mPath;
+
+ public ParsedDocumentId(String archiveId, String path) {
+ mArchiveId = archiveId;
+ mPath = path;
+ }
+
+ static public ParsedDocumentId fromDocumentId(String documentId, char idDelimiter) {
+ final int delimiterPosition = documentId.indexOf(idDelimiter);
+ if (delimiterPosition == -1) {
+ return new ParsedDocumentId(documentId, null);
+ } else {
+ return new ParsedDocumentId(documentId.substring(0, delimiterPosition),
+ documentId.substring((delimiterPosition + 1)));
+ }
+ }
+
+ static public boolean hasPath(String documentId, char idDelimiter) {
+ return documentId.indexOf(idDelimiter) != -1;
+ }
+
+ public String toDocumentId(char idDelimiter) {
+ if (mPath == null) {
+ return mArchiveId;
+ } else {
+ return mArchiveId + idDelimiter + mPath;
+ }
+ }
+};
diff --git a/documents-archive/src/android/support/provider/Preconditions.java b/documents-archive/src/android/support/provider/Preconditions.java
new file mode 100644
index 0000000..fd62a2e
--- /dev/null
+++ b/documents-archive/src/android/support/provider/Preconditions.java
@@ -0,0 +1,54 @@
+/*
+ * 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.provider;
+
+import android.support.annotation.Nullable;
+import android.text.TextUtils;
+
+import java.util.Collection;
+
+/**
+ * Simple static methods to be called at the start of your own methods to verify
+ * correct arguments and state.
+ * @hide
+ */
+final class Preconditions {
+ static void checkArgument(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ static void checkArgumentNotNull(Object object, String message) {
+ if (object == null) {
+ throw new IllegalArgumentException(message);
+ }
+ }
+
+ static void checkArgumentEquals(String expected, @Nullable String actual, String message) {
+ if (!TextUtils.equals(expected, actual)) {
+ throw new IllegalArgumentException(String.format(message, String.valueOf(expected),
+ String.valueOf(actual)));
+ }
+ }
+
+ static void checkState(boolean expression, String message) {
+ if (!expression) {
+ throw new IllegalStateException(message);
+ }
+ }
+}
diff --git a/documents-archive/tests/Android.mk b/documents-archive/tests/Android.mk
new file mode 100644
index 0000000..84cc3c3
--- /dev/null
+++ b/documents-archive/tests/Android.mk
@@ -0,0 +1,29 @@
+# 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.
+
+LOCAL_PATH := $(call my-dir)
+
+include $(CLEAR_VARS)
+
+LOCAL_MODULE_TAGS := tests
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, src)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-documents-archive
+LOCAL_AAPT_FLAGS := --auto-add-overlay -0 zip
+LOCAL_PACKAGE_NAME := AndroidSupportDocumentsArchiveTests
+
+include $(BUILD_PACKAGE)
diff --git a/documents-archive/tests/AndroidManifest.xml b/documents-archive/tests/AndroidManifest.xml
new file mode 100644
index 0000000..47da733
--- /dev/null
+++ b/documents-archive/tests/AndroidManifest.xml
@@ -0,0 +1,31 @@
+<?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"
+ package="android.support.provider.tests">
+ <application>
+ <uses-library android:name="android.test.runner" />
+ <provider
+ android:name="android.support.provider.tests.StubProvider"
+ android:authorities="android.support.provider.tests.mystubprovider"
+ android:grantUriPermissions="true"
+ android:exported="true"
+ android:permission="android.permission.MANAGE_DOCUMENTS" />
+ </application>
+
+ <instrumentation android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.provider.tests"
+ android:label="Tests for android.support.provider." />
+</manifest>
diff --git a/documents-archive/tests/NO_DOCS b/documents-archive/tests/NO_DOCS
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/documents-archive/tests/NO_DOCS
diff --git a/documents-archive/tests/res/raw/archive.zip b/documents-archive/tests/res/raw/archive.zip
new file mode 100644
index 0000000..c3b8d22
--- /dev/null
+++ b/documents-archive/tests/res/raw/archive.zip
Binary files differ
diff --git a/documents-archive/tests/res/raw/empty_dirs.zip b/documents-archive/tests/res/raw/empty_dirs.zip
new file mode 100644
index 0000000..1dd2251
--- /dev/null
+++ b/documents-archive/tests/res/raw/empty_dirs.zip
Binary files differ
diff --git a/documents-archive/tests/res/raw/no_dirs.zip b/documents-archive/tests/res/raw/no_dirs.zip
new file mode 100644
index 0000000..e178ae19
--- /dev/null
+++ b/documents-archive/tests/res/raw/no_dirs.zip
Binary files differ
diff --git a/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
new file mode 100644
index 0000000..98454125
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/DocumentArchiveTest.java
@@ -0,0 +1,273 @@
+/*
+ * 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.provider.tests;
+
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.support.provider.DocumentArchive;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Scanner;
+
+/**
+ * Tests for DocumentArchive.
+ */
+public class DocumentArchiveTest extends AndroidTestCase {
+ private static final String DOCUMENT_ID = "document-id";
+ private static final char DELIMITER = ':';
+ private static final String NOTIFICATION_URI = "content://notification-uri";
+ private DocumentArchive mArchive = null;
+
+ public void loadArchive(int resource) {
+ // Extract the file from resources.
+ File file = null;
+ try {
+ file = File.createTempFile("android.support.provider.tests{",
+ "}.zip", mContext.getCacheDir());
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final InputStream inputStream =
+ mContext.getResources().openRawResource(resource);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ mArchive = DocumentArchive.createForLocalFile(
+ mContext,
+ file,
+ DOCUMENT_ID,
+ DELIMITER,
+ Uri.parse(NOTIFICATION_URI));
+
+ }
+ } catch (IOException e) {
+ fail(String.valueOf(e));
+ } finally {
+ // On UNIX the file will be still available for processes which opened it, even
+ // after deleting it. Remove it ASAP, as it won't be used by anyone else.
+ if (file != null) {
+ file.delete();
+ }
+ }
+ }
+
+ @Override
+ public void tearDown() {
+ if (mArchive != null) {
+ mArchive.close();
+ }
+ }
+
+ public void testQueryChildDocument() throws IOException {
+ loadArchive(R.raw.archive);
+ final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir1/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir1",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertTrue(cursor.moveToNext());
+ assertEquals("document-id:dir2/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir2",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertTrue(cursor.moveToNext());
+ assertEquals("document-id:file1.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("file1.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(13,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertFalse(cursor.moveToNext());
+
+ // Check if querying children works too.
+ final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+ assertTrue(childCursor.moveToFirst());
+ assertEquals("document-id:dir1/cherries.txt",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("cherries.txt",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_MIME_TYPE)));
+ assertEquals(17,
+ childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ }
+
+ public void testQueryChildDocument_NoDirs() throws IOException {
+ loadArchive(R.raw.no_dirs);
+ final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir1/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir1",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ assertFalse(cursor.moveToNext());
+
+ final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+ assertTrue(childCursor.moveToFirst());
+ assertEquals("document-id:dir1/dir2/",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir2",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ assertFalse(childCursor.moveToNext());
+
+ final Cursor childCursor2 = mArchive.queryChildDocuments(
+ "document-id:dir1/dir2/", null, null);
+
+ assertTrue(childCursor2.moveToFirst());
+ assertEquals("document-id:dir1/dir2/cherries.txt",
+ childCursor2.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertFalse(childCursor2.moveToNext());
+ }
+
+ public void testQueryChildDocument_EmptyDirs() throws IOException {
+ loadArchive(R.raw.empty_dirs);
+ final Cursor cursor = mArchive.queryChildDocuments(DOCUMENT_ID, null, null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir1/",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir1",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ assertFalse(cursor.moveToNext());
+
+ final Cursor childCursor = mArchive.queryChildDocuments("document-id:dir1/", null, null);
+
+ assertTrue(childCursor.moveToFirst());
+ assertEquals("document-id:dir1/dir2/",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir2",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+
+ assertTrue(childCursor.moveToNext());
+ assertEquals("document-id:dir1/dir3/",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("dir3",
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_DISPLAY_NAME)));
+ assertEquals(Document.MIME_TYPE_DIR,
+ childCursor.getString(childCursor.getColumnIndexOrThrow(
+ Document.COLUMN_MIME_TYPE)));
+ assertEquals(0,
+ childCursor.getInt(childCursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ assertFalse(cursor.moveToNext());
+
+ final Cursor childCursor2 = mArchive.queryChildDocuments(
+ "document-id:dir1/dir2/", null, null);
+ assertFalse(childCursor2.moveToFirst());
+
+ final Cursor childCursor3 = mArchive.queryChildDocuments(
+ "document-id:dir1/dir3/", null, null);
+ assertFalse(childCursor3.moveToFirst());
+ }
+
+ public void testGetDocumentType() throws IOException {
+ loadArchive(R.raw.archive);
+ assertEquals(Document.MIME_TYPE_DIR, mArchive.getDocumentType("document-id:dir1/"));
+ assertEquals("text/plain", mArchive.getDocumentType("document-id:file1.txt"));
+ }
+
+ public void testIsChildDocument() throws IOException {
+ loadArchive(R.raw.archive);
+ assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1/"));
+ assertFalse(mArchive.isChildDocument(DOCUMENT_ID, "document-id:this-does-not-exist"));
+ assertTrue(mArchive.isChildDocument("document-id:dir1/", "document-id:dir1/cherries.txt"));
+ assertTrue(mArchive.isChildDocument(DOCUMENT_ID, "document-id:dir1/cherries.txt"));
+ }
+
+ public void testQueryDocument() throws IOException {
+ loadArchive(R.raw.archive);
+ final Cursor cursor = mArchive.queryDocument("document-id:dir2/strawberries.txt", null);
+
+ assertTrue(cursor.moveToFirst());
+ assertEquals("document-id:dir2/strawberries.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DOCUMENT_ID)));
+ assertEquals("strawberries.txt",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_DISPLAY_NAME)));
+ assertEquals("text/plain",
+ cursor.getString(cursor.getColumnIndexOrThrow(Document.COLUMN_MIME_TYPE)));
+ assertEquals(21,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_SIZE)));
+ }
+
+ public void testOpenDocument() throws IOException {
+ loadArchive(R.raw.archive);
+ final ParcelFileDescriptor descriptor = mArchive.openDocument(
+ "document-id:dir2/strawberries.txt", "r", null /* signal */);
+ try (final ParcelFileDescriptor.AutoCloseInputStream inputStream =
+ new ParcelFileDescriptor.AutoCloseInputStream(descriptor)) {
+ assertEquals("I love strawberries!", new Scanner(inputStream).nextLine());
+ }
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/IntegrationTest.java b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
new file mode 100644
index 0000000..0445d82
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/IntegrationTest.java
@@ -0,0 +1,136 @@
+/*
+ * 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.provider.tests;
+
+import android.content.ContentProviderClient;
+import android.database.ContentObserver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.ParcelFileDescriptor;
+import android.os.RemoteException;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.IllegalArgumentException;
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+
+/**
+ * Integration tests for DocumentsProvider and DocumentArchiveHelper.
+ *
+ * <p>Only checks if the provider, then helper are forwarding the calls to the
+ * underlying {@code ArchiveDocument} correctly. More detailed output testing is
+ * done in {@code DocumentArchiveTest}.
+ */
+public class IntegrationTest extends AndroidTestCase {
+ private ContentProviderClient mClient;
+
+ @Override
+ public void setUp() throws RemoteException {
+ mClient = getContext().getContentResolver().acquireContentProviderClient(
+ StubProvider.AUTHORITY);
+ assertNotNull(mClient);
+ mClient.call("reset", null, null);
+ }
+
+ @Override
+ public void tearDown() {
+ if (mClient != null) {
+ mClient.release();
+ mClient = null;
+ }
+ }
+
+ public void testQueryForChildren() throws IOException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildChildDocumentsUri(
+ StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(3, cursor.getCount());
+ }
+
+ public void testQueryForDocument_Archive()
+ throws IOException, RemoteException, InterruptedException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertTrue(cursor.moveToFirst());
+ assertEquals(Document.FLAG_ARCHIVE,
+ cursor.getInt(cursor.getColumnIndexOrThrow(Document.COLUMN_FLAGS)));
+ }
+
+ public void testQueryForDocument_ArchiveDescendant()
+ throws IOException, RemoteException, InterruptedException {
+ final Cursor cursor = mContext.getContentResolver().query(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ null, null, null, null);
+ assertEquals(1, cursor.getCount());
+ assertEquals(StubProvider.NOTIFY_URI, cursor.getNotificationUri());
+
+ final CountDownLatch changeSignal = new CountDownLatch(1);
+ final ContentObserver observer = new ContentObserver(null) {
+ @Override
+ public void onChange(boolean selfChange) {
+ changeSignal.countDown();
+ }
+ };
+
+ try {
+ getContext().getContentResolver().registerContentObserver(
+ cursor.getNotificationUri(), false /* notifyForDescendants */, observer);
+
+ // Simulate deleting the archive file, then confirm that the notification is
+ // propagated and the archive closed.
+ mClient.call("delete", null, null);
+ changeSignal.await();
+
+ mContext.getContentResolver().query(
+ DocumentsContract.buildChildDocumentsUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ null, null, null, null);
+ fail("Expected IllegalStateException, but succeeded.");
+ } catch (IllegalStateException e) {
+ // Expected, as the file is gone.
+ } finally {
+ getContext().getContentResolver().unregisterContentObserver(observer);
+ }
+ }
+
+ public void testGetType() throws IOException {
+ assertEquals("text/plain", mContext.getContentResolver().getType(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID)));
+ }
+
+ public void testOpenFileDescriptor() throws IOException {
+ final ParcelFileDescriptor descriptor = mContext.getContentResolver().openFileDescriptor(
+ DocumentsContract.buildDocumentUri(
+ StubProvider.AUTHORITY, StubProvider.FILE_DOCUMENT_ID),
+ "r", null);
+ assertNotNull(descriptor);
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/StubProvider.java b/documents-archive/tests/src/android/support/provider/StubProvider.java
new file mode 100644
index 0000000..3f72cd2
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/StubProvider.java
@@ -0,0 +1,154 @@
+/*
+ * 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.provider.tests;
+
+import android.database.Cursor;
+import android.database.MatrixCursor.RowBuilder;
+import android.database.MatrixCursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.CancellationSignal;
+import android.os.ParcelFileDescriptor;
+import android.provider.DocumentsContract.Document;
+import android.provider.DocumentsContract;
+import android.provider.DocumentsProvider;
+import android.support.provider.DocumentArchiveHelper;
+import android.util.Log;
+
+import java.io.FileNotFoundException;
+import java.io.File;
+import java.io.IOException;
+
+/**
+ * Stub provider for testing support for archives.
+ */
+public class StubProvider extends DocumentsProvider {
+ public static final String AUTHORITY = "android.support.provider.tests.mystubprovider";
+ public static final String DOCUMENT_ID = "document-id";
+ public static final String FILE_DOCUMENT_ID = "document-id:dir1/cherries.txt";
+ public static final Uri NOTIFY_URI = DocumentsContract.buildRootsUri(AUTHORITY);
+
+ private static final String TAG = "StubProvider";
+ private static final String[] DEFAULT_PROJECTION = new String[] {
+ Document.COLUMN_DOCUMENT_ID, Document.COLUMN_DISPLAY_NAME, Document.COLUMN_SIZE,
+ Document.COLUMN_MIME_TYPE, Document.COLUMN_FLAGS,
+ DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH
+ };
+
+ public File file;
+ public DocumentArchiveHelper archiveHelper;
+ public boolean simulatedDelete = false;
+
+ @Override
+ public Bundle call(String method, String args, Bundle extras) {
+ switch (method) {
+ case "reset":
+ simulatedDelete = false;
+ getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+ return null;
+ case "delete":
+ simulatedDelete = true;
+ getContext().getContentResolver().notifyChange(NOTIFY_URI, null);
+ return null;
+ default:
+ return super.call(method, args, extras);
+ }
+ }
+
+ @Override
+ public boolean onCreate() {
+ try {
+ archiveHelper = new DocumentArchiveHelper(this, ':');
+ file = TestUtils.createFileFromResource(getContext(), R.raw.archive);
+ return true;
+ } catch (IOException e) {
+ Log.e(TAG, "Failed to initialize StubProvider.");
+ return false;
+ }
+ }
+
+ @Override
+ public ParcelFileDescriptor openDocument(
+ String documentId, String mode, CancellationSignal signal)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.openDocument(documentId, mode, signal);
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor queryChildDocuments(
+ String parentDocumentId, String[] projection, String sortOrder)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(parentDocumentId) ||
+ archiveHelper.isSupportedArchiveType(getDocumentType(parentDocumentId))) {
+ return archiveHelper.queryChildDocuments(parentDocumentId, projection, sortOrder);
+ }
+
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Cursor queryDocument(String documentId, String[] projection)
+ throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.queryDocument(documentId, projection);
+ }
+
+ if (DOCUMENT_ID.equals(documentId)) {
+ if (simulatedDelete) {
+ throw new FileNotFoundException();
+ }
+
+ final MatrixCursor result = new MatrixCursor(
+ projection != null ? projection : DEFAULT_PROJECTION);
+ result.setNotificationUri(getContext().getContentResolver(), NOTIFY_URI);
+ final RowBuilder row = result.newRow();
+ row.add(Document.COLUMN_DOCUMENT_ID, DOCUMENT_ID);
+ row.add(Document.COLUMN_DISPLAY_NAME, file.getName());
+ row.add(Document.COLUMN_SIZE, file.length());
+ row.add(Document.COLUMN_MIME_TYPE, "application/zip");
+ final int flags = archiveHelper.isSupportedArchiveType("application/zip")
+ ? Document.FLAG_ARCHIVE : 0;
+ row.add(Document.COLUMN_FLAGS, flags);
+ row.add(DocumentArchiveHelper.COLUMN_LOCAL_FILE_PATH, file.getPath());
+ return result;
+ }
+
+ throw new FileNotFoundException();
+ }
+
+ @Override
+ public Cursor queryRoots(String[] projection) throws FileNotFoundException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public String getDocumentType(String documentId) throws FileNotFoundException {
+ if (archiveHelper.isArchivedDocument(documentId)) {
+ return archiveHelper.getDocumentType(documentId);
+ }
+
+ if (DOCUMENT_ID.equals(documentId)) {
+ return "application/zip";
+ }
+
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/documents-archive/tests/src/android/support/provider/TestUtils.java b/documents-archive/tests/src/android/support/provider/TestUtils.java
new file mode 100644
index 0000000..17ec3e1
--- /dev/null
+++ b/documents-archive/tests/src/android/support/provider/TestUtils.java
@@ -0,0 +1,55 @@
+/*
+ * 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.provider.tests;
+
+import android.content.Context;
+import android.os.ParcelFileDescriptor;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * Utilities for tests.
+ */
+final class TestUtils {
+ /**
+ * Saves a file from resources to a temporary location and returns a File instance for it.
+ *
+ * @param id Resource ID
+ */
+ static File createFileFromResource(Context context, int id) throws IOException {
+ final File file = File.createTempFile("android.support.provider.tests{",
+ "}.zip", context.getCacheDir());
+ try (
+ final FileOutputStream outputStream =
+ new ParcelFileDescriptor.AutoCloseOutputStream(
+ ParcelFileDescriptor.open(
+ file, ParcelFileDescriptor.MODE_WRITE_ONLY));
+ final InputStream inputStream = context.getResources().openRawResource(id);
+ ) {
+ final byte[] buffer = new byte[32 * 1024];
+ int bytes;
+ while ((bytes = inputStream.read(buffer)) != -1) {
+ outputStream.write(buffer, 0, bytes);
+ }
+ outputStream.flush();
+ return file;
+ }
+ }
+}
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index 800b59f..ed1b4ed 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -3,4 +3,4 @@
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=../../../../tools/external/gradle/gradle-2.2.1-bin.zip
+distributionUrl=../../../../tools/external/gradle/gradle-2.10-all.zip
diff --git a/graphics/drawable/Android.mk b/graphics/drawable/Android.mk
index d72c54f..2aa276733 100644
--- a/graphics/drawable/Android.mk
+++ b/graphics/drawable/Android.mk
@@ -14,31 +14,63 @@
LOCAL_PATH := $(call my-dir)
-#static vector drawable library
+# ---------------------------------------------
+#
+# Static vector drawable library
+#
+# ---------------------------------------------
include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v7-vectordrawable
+LOCAL_MODULE := android-support-vectordrawable
+
+ifdef SUPPORT_CURRENT_SDK_VERSION
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+else
LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, static util)
+endif
+
+LOCAL_SRC_FILES := $(call all-java-files-under, static/src)
LOCAL_JAVA_LIBRARIES := android-support-v4
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
-#Animated vector drawable library
+# Static API Check
+support_module := $(LOCAL_MODULE)
+support_module_api_dir := $(LOCAL_PATH)/static/api
+support_module_src_files := $(LOCAL_SRC_FILES)
+support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
+support_module_java_packages := android.support.graphics.drawable
+include $(SUPPORT_API_CHECK)
+
+
+# ---------------------------------------------
+#
+# Animated vector drawable library
+#
+# ---------------------------------------------
include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v11-animatedvectordrawable
+LOCAL_MODULE := android-support-animatedvectordrawable
+
+ifdef SUPPORT_CURRENT_SDK_VERSION
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+else
LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, animated util)
+endif
-LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_SRC_FILES := $(call all-java-files-under, animated/src)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-vectordrawable
+LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-vectordrawable
LOCAL_AAPT_FLAGS := --no-version-vectors
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
+
+# Animated API Check
+support_module := $(LOCAL_MODULE)
+support_module_api_dir := $(LOCAL_PATH)/animated/api
+support_module_src_files := $(LOCAL_SRC_FILES) \
+ static/src/android/support/graphics/drawable/VectorDrawableCommon.java
+support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
+support_module_java_packages := android.support.graphics.drawable
+include $(SUPPORT_API_CHECK)
diff --git a/graphics/drawable/animated/AndroidManifest.xml b/graphics/drawable/animated/AndroidManifest.xml
new file mode 100644
index 0000000..98f9e17
--- /dev/null
+++ b/graphics/drawable/animated/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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"
+ package="android.support.graphics.drawable.animated">
+ <application/>
+</manifest>
diff --git a/graphics/drawable/animated/api/current.txt b/graphics/drawable/animated/api/current.txt
new file mode 100644
index 0000000..1461956
--- /dev/null
+++ b/graphics/drawable/animated/api/current.txt
@@ -0,0 +1,19 @@
+package android.support.graphics.drawable {
+
+ public class AnimatedVectorDrawableCompat extends android.support.graphics.drawable.VectorDrawableCommon {
+ method public static android.support.graphics.drawable.AnimatedVectorDrawableCompat create(android.content.Context, int);
+ method public static android.support.graphics.drawable.AnimatedVectorDrawableCompat createFromXmlInner(android.content.Context, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method public int getOpacity();
+ method public boolean isRunning();
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void start();
+ method public void stop();
+ }
+
+ abstract class VectorDrawableCommon extends android.graphics.drawable.Drawable {
+ }
+
+}
+
diff --git a/graphics/drawable/animated/api/removed.txt b/graphics/drawable/animated/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/graphics/drawable/animated/api/removed.txt
diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle
new file mode 100644
index 0000000..02bb5f6
--- /dev/null
+++ b/graphics/drawable/animated/build.gradle
@@ -0,0 +1,122 @@
+apply plugin: 'com.android.library'
+
+archivesBaseName = 'animated-vector-drawable'
+
+dependencies {
+ compile project(':support-vector-drawable')
+ 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'
+}
+
+android {
+ compileSdkVersion 23
+ defaultConfig {
+ minSdkVersion 11
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ // This disables the builds tools automatic vector -> PNG generation
+ generatedDensities = []
+ }
+
+ sourceSets {
+ main.manifest.srcFile 'AndroidManifest.xml'
+ main.java.srcDir 'src'
+
+ androidTest.setRoot('tests')
+ androidTest.java.srcDir 'tests/src'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ lintOptions {
+ abortOnError true
+ }
+
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+
+ packagingOptions {
+ exclude 'LICENSE.txt'
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+}
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar) {
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri(rootProject.ext.supportRepoOut)) {
+ }
+
+ pom.project {
+ name 'Android Support AnimatedVectorDrawable'
+ description "Android Support AnimatedVectorDrawable"
+ url 'http://developer.android.com/tools/extras/support-library.html'
+ inceptionYear '2015'
+
+ licenses {
+ license {
+ name 'The Apache Software License, Version 2.0'
+ url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ distribution 'repo'
+ }
+ }
+
+ scm {
+ url "http://source.android.com"
+ connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+ }
+ developers {
+ developer {
+ name 'The Android Open Source Project'
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
index eff9670..a8b3230 100644
--- a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
+++ b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java
@@ -16,6 +16,10 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
+import android.animation.AnimatorSet;
+import android.animation.ArgbEvaluator;
+import android.animation.ObjectAnimator;
+import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.ColorStateList;
import android.content.res.Resources;
@@ -23,108 +27,43 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
-import android.graphics.PorterDuff.Mode;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Animatable;
+import android.graphics.drawable.AnimatedVectorDrawable;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.DrawableRes;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.util.ArrayMap;
import android.util.AttributeSet;
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.List;
/**
- * This class uses {@link android.animation.ObjectAnimator} and
+ * For API 23 and above, this class is delegating to the framework's {@link AnimatedVectorDrawable}.
+ * For older API version, this class uses {@link android.animation.ObjectAnimator} and
* {@link android.animation.AnimatorSet} to animate the properties of a
- * {@link android.support.graphics.drawable.VectorDrawableCompat} to create an animated drawable.
+ * {@link VectorDrawableCompat} to create an animated drawable.
* <p>
- * AnimatedVectorDrawableCompat are normally defined as 3 separate XML files.
+ * AnimatedVectorDrawableCompat are defined in the same XML format as {@link AnimatedVectorDrawable}.
* </p>
- * <p>
- * First is the XML file for {@link android.support.graphics.drawable.VectorDrawableCompat}. Note that we
- * allow the animation to happen on the group's attributes and path's attributes, which requires they
- * are uniquely named in this XML file. Groups and paths without animations do not need names.
- * </p>
- * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
- * <pre>
- * <vector xmlns:android="http://schemas.android.com/apk/res/android"
- * android:height="64dp"
- * android:width="64dp"
- * android:viewportHeight="600"
- * android:viewportWidth="600" >
- * <group
- * android:name="rotationGroup"
- * android:pivotX="300.0"
- * android:pivotY="300.0"
- * android:rotation="45.0" >
- * <path
- * android:name="v"
- * android:fillColor="#000000"
- * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
- * </group>
- * </vector>
- * </pre></li>
- * <p>
- * Second is the AnimatedVectorDrawableCompat's XML file, which defines the target
- * VectorDrawableCompat, the target paths and groups to animate, the properties of the path and
- * group to animate and the animations defined as the ObjectAnimators or AnimatorSets.
- * </p>
- * <li>Here is a simple AnimatedVectorDrawable defined in this avd.xml file.
- * Note how we use the names to refer to the groups and paths in the vectordrawable.xml.
- * <pre>
- * <animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- * android:drawable="@drawable/vectordrawable" >
- * <target
- * android:name="rotationGroup"
- * android:animation="@anim/rotation" />
- * <target
- * android:name="v"
- * android:animation="@anim/path_morph" />
- * </animated-vector>
- * </pre></li>
- * <p>
- * Last is the Animator XML file, which is the same as a normal ObjectAnimator or AnimatorSet. To
- * complete this example, here are the 2 animator files used in avd.xml: rotation.xml and
- * path_morph.xml.
- * </p>
- * <li>Here is the rotation.xml, which will rotate the target group for 360 degrees.
- * <pre>
- * <objectAnimator
- * android:duration="6000"
- * android:propertyName="rotation"
- * android:valueFrom="0"
- * android:valueTo="360" />
- * </pre></li>
- * <li>Here is the path_morph.xml, which will morph the path from one shape to
- * the other. Note that the paths must be compatible for morphing.
- * In more details, the paths should have exact same length of commands, and
- * exact same length of parameters for each commands.
- * Note that the path strings are better stored in strings.xml for reusing.
- * <pre>
- * <set xmlns:android="http://schemas.android.com/apk/res/android">
- * <objectAnimator
- * android:duration="3000"
- * android:propertyName="pathData"
- * android:valueFrom="M300,70 l 0,-70 70,70 0,0 -70,70z"
- * android:valueTo="M300,70 l 0,-70 70,0 0,140 -70,0 z"
- * android:valueType="pathType"/>
- * </set>
- * </pre></li>
- *
- * @attr ref android.R.styleable#AnimatedVectorDrawableCompat_drawable
- * @attr ref android.R.styleable#AnimatedVectorDrawableCompatTarget_name
- * @attr ref android.R.styleable#AnimatedVectorDrawableCompatTarget_animation
+ * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java
+ * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use
+ * app:srcCompat attribute in AppCompat library's ImageButton or ImageView.
*/
-public class AnimatedVectorDrawableCompat extends Drawable implements Animatable {
- private static final String LOGTAG = "AnimatedVectorDrawableCompat";
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class AnimatedVectorDrawableCompat extends VectorDrawableCommon implements Animatable {
+ private static final String LOGTAG = "AnimatedVDCompat";
private static final String ANIMATED_VECTOR = "animated-vector";
private static final String TARGET = "target";
@@ -133,35 +72,39 @@
private AnimatedVectorDrawableCompatState mAnimatedVectorState;
- private boolean mMutated;
-
private Context mContext;
- // Currently the only useful ctor.
- public AnimatedVectorDrawableCompat(Context context) {
+ private ArgbEvaluator mArgbEvaluator = null;
+
+ AnimatedVectorDrawableDelegateState mCachedConstantStateDelegate;
+
+ private AnimatedVectorDrawableCompat() {
+ this(null, null, null);
+ }
+
+ private AnimatedVectorDrawableCompat(@Nullable Context context) {
this(context, null, null);
}
- private AnimatedVectorDrawableCompat(Context context, AnimatedVectorDrawableCompatState state,
- Resources res) {
+ private AnimatedVectorDrawableCompat(@Nullable Context context,
+ @Nullable AnimatedVectorDrawableCompatState state,
+ @Nullable Resources res) {
mContext = context;
if (state != null) {
mAnimatedVectorState = state;
} else {
mAnimatedVectorState = new AnimatedVectorDrawableCompatState(context, state, mCallback,
- res);
+ res);
}
}
@Override
public Drawable mutate() {
- if (!mMutated && super.mutate() == this) {
- mAnimatedVectorState =
- new AnimatedVectorDrawableCompatState(null, mAnimatedVectorState, mCallback,
- null);
- mMutated = true;
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.mutate();
+ return this;
}
- return this;
+ throw new IllegalStateException("Mutate() is not supported for older platform");
}
@@ -169,12 +112,21 @@
* Create a AnimatedVectorDrawableCompat object.
*
* @param context the context for creating the animators.
- * @param resId the resource ID for AnimatedVectorDrawableCompat object.
+ * @param resId the resource ID for AnimatedVectorDrawableCompat object.
* @return a new AnimatedVectorDrawableCompat or null if parsing error is found.
*/
@Nullable
public static AnimatedVectorDrawableCompat create(@NonNull Context context,
- @DrawableRes int resId) {
+ @DrawableRes int resId) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
+ drawable.mDelegateDrawable = ResourcesCompat.getDrawable(context.getResources(), resId,
+ context.getTheme());
+ drawable.mDelegateDrawable.setCallback(drawable.mCallback);
+ drawable.mCachedConstantStateDelegate = new AnimatedVectorDrawableDelegateState(
+ drawable.mDelegateDrawable.getConstantState());
+ return drawable;
+ }
Resources resources = context.getResources();
try {
final XmlPullParser parser = resources.getXml(resId);
@@ -187,11 +139,8 @@
if (type != XmlPullParser.START_TAG) {
throw new XmlPullParserException("No start tag found");
}
-
- final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
- drawable.inflate(resources, parser, attrs, context.getTheme());
-
- return drawable;
+ return createFromXmlInner(context, context.getResources(), parser, attrs,
+ context.getTheme());
} catch (XmlPullParserException e) {
Log.e(LOGTAG, "parser error", e);
} catch (IOException e) {
@@ -200,19 +149,50 @@
return null;
}
+ /**
+ * Create a AnimatedVectorDrawableCompat 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 AnimatedVectorDrawableCompat createFromXmlInner(Context context, Resources r,
+ XmlPullParser parser, AttributeSet attrs, Theme theme)
+ throws XmlPullParserException, IOException {
+ final AnimatedVectorDrawableCompat drawable = new AnimatedVectorDrawableCompat(context);
+ drawable.inflate(r, parser, attrs, theme);
+ return drawable;
+ }
+
+ /**
+ * {@inheritDoc}
+ * <strong>Note</strong> that we don't support constant state when SDK < 23.
+ * Make sure you check the return value before using it.
+ */
@Override
public ConstantState getConstantState() {
- mAnimatedVectorState.mChangingConfigurations = getChangingConfigurations();
- return mAnimatedVectorState;
+ if (mDelegateDrawable != null) {
+ return new AnimatedVectorDrawableDelegateState(mDelegateDrawable.getConstantState());
+ }
+ // We can't support constant state in older platform.
+ // We need Context to create the animator, and we can't save the context in the constant
+ // state.
+ return null;
}
@Override
public int getChangingConfigurations() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getChangingConfigurations();
+ }
return super.getChangingConfigurations() | mAnimatedVectorState.mChangingConfigurations;
}
@Override
public void draw(Canvas canvas) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.draw(canvas);
+ return;
+ }
mAnimatedVectorState.mVectorDrawable.draw(canvas);
if (isStarted()) {
invalidateSelf();
@@ -221,63 +201,118 @@
@Override
protected void onBoundsChange(Rect bounds) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setBounds(bounds);
+ return;
+ }
mAnimatedVectorState.mVectorDrawable.setBounds(bounds);
}
@Override
protected boolean onStateChange(int[] state) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setState(state);
+ }
return mAnimatedVectorState.mVectorDrawable.setState(state);
}
@Override
protected boolean onLevelChange(int level) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setLevel(level);
+ }
return mAnimatedVectorState.mVectorDrawable.setLevel(level);
}
- // @Override
+ @Override
public int getAlpha() {
+ if (mDelegateDrawable != null) {
+ return DrawableCompat.getAlpha(mDelegateDrawable);
+ }
return mAnimatedVectorState.mVectorDrawable.getAlpha();
}
- // @Override
+ @Override
public void setAlpha(int alpha) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setAlpha(alpha);
+ return;
+ }
mAnimatedVectorState.mVectorDrawable.setAlpha(alpha);
}
@Override
public void setColorFilter(ColorFilter colorFilter) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setColorFilter(colorFilter);
+ return;
+ }
mAnimatedVectorState.mVectorDrawable.setColorFilter(colorFilter);
}
+ public void setTint(int tint) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTint(mDelegateDrawable, tint);
+ return;
+ }
+
+ mAnimatedVectorState.mVectorDrawable.setTint(tint);
+ }
+
public void setTintList(ColorStateList tint) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTintList(mDelegateDrawable, tint);
+ return;
+ }
+
mAnimatedVectorState.mVectorDrawable.setTintList(tint);
}
- public void setTintMode(Mode tintMode) {
+ public void setTintMode(PorterDuff.Mode tintMode) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
+ return;
+ }
+
mAnimatedVectorState.mVectorDrawable.setTintMode(tintMode);
}
@Override
public boolean setVisible(boolean visible, boolean restart) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setVisible(visible, restart);
+ }
mAnimatedVectorState.mVectorDrawable.setVisible(visible, restart);
return super.setVisible(visible, restart);
}
@Override
public boolean isStateful() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.isStateful();
+ }
return mAnimatedVectorState.mVectorDrawable.isStateful();
}
@Override
public int getOpacity() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getOpacity();
+ }
return mAnimatedVectorState.mVectorDrawable.getOpacity();
}
public int getIntrinsicWidth() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getIntrinsicWidth();
+ }
return mAnimatedVectorState.mVectorDrawable.getIntrinsicWidth();
}
public int getIntrinsicHeight() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getIntrinsicHeight();
+ }
return mAnimatedVectorState.mVectorDrawable.getIntrinsicHeight();
}
@@ -293,10 +328,14 @@
return theme.obtainStyledAttributes(set, attrs, 0, 0);
}
+ @Override
public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
+ return;
+ }
int eventType = parser.getEventType();
- float pathErrorScale = 1;
while (eventType != XmlPullParser.END_DOCUMENT) {
if (eventType == XmlPullParser.START_TAG) {
final String tagName = parser.getName();
@@ -305,10 +344,11 @@
}
if (ANIMATED_VECTOR.equals(tagName)) {
final TypedArray a =
- obtainAttributes(res, theme, attrs, AndroidResources.styleable_AnimatedVectorDrawable);
+ obtainAttributes(res, theme, attrs,
+ AndroidResources.styleable_AnimatedVectorDrawable);
- int drawableRes = a.getResourceId(AndroidResources.styleable_AnimatedVectorDrawable_drawable,
- 0);
+ int drawableRes = a.getResourceId(
+ AndroidResources.styleable_AnimatedVectorDrawable_drawable, 0);
if (DBG_ANIMATION_VECTOR_DRAWABLE) {
Log.v(LOGTAG, "drawableRes is " + drawableRes);
}
@@ -317,7 +357,6 @@
drawableRes, theme);
vectorDrawable.setAllowCaching(false);
vectorDrawable.setCallback(mCallback);
- pathErrorScale = vectorDrawable.getPixelSize();
if (mAnimatedVectorState.mVectorDrawable != null) {
mAnimatedVectorState.mVectorDrawable.setCallback(null);
}
@@ -326,14 +365,21 @@
a.recycle();
} else if (TARGET.equals(tagName)) {
final TypedArray a =
- res.obtainAttributes(attrs, AndroidResources.styleable_AnimatedVectorDrawableTarget);
+ res.obtainAttributes(attrs,
+ AndroidResources.styleable_AnimatedVectorDrawableTarget);
final String target = a.getString(
AndroidResources.styleable_AnimatedVectorDrawableTarget_name);
- int id = a.getResourceId(AndroidResources.styleable_AnimatedVectorDrawableTarget_animation, 0);
+ int id = a.getResourceId(
+ AndroidResources.styleable_AnimatedVectorDrawableTarget_animation, 0);
if (id != 0) {
- Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);
- setupAnimatorsForTarget(target, objectAnimator);
+ if (mContext != null) {
+ Animator objectAnimator = AnimatorInflater.loadAnimator(mContext, id);
+ setupAnimatorsForTarget(target, objectAnimator);
+ } else {
+ throw new IllegalStateException("Context can't be null when inflating" +
+ " animators");
+ }
}
a.recycle();
}
@@ -349,16 +395,79 @@
inflate(res, parser, attrs, null);
}
+ @Override
+ public void applyTheme(Theme t) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.applyTheme(mDelegateDrawable, t);
+ return;
+ }
+ // TODO: support theming in older platform.
+ return;
+ }
+
public boolean canApplyTheme() {
+ if (mDelegateDrawable != null) {
+ return DrawableCompat.canApplyTheme(mDelegateDrawable);
+ }
+ // TODO: support theming in older platform.
return false;
}
+ /**
+ * Constant state for delegating the creating drawable job.
+ * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
+ * a delegated VectorDrawable instance.
+ */
+ private static class AnimatedVectorDrawableDelegateState extends ConstantState {
+ private final ConstantState mDelegateState;
+
+ public AnimatedVectorDrawableDelegateState(ConstantState state) {
+ mDelegateState = state;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ AnimatedVectorDrawableCompat drawableCompat =
+ new AnimatedVectorDrawableCompat();
+ drawableCompat.mDelegateDrawable = mDelegateState.newDrawable();
+ drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
+ return drawableCompat;
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ AnimatedVectorDrawableCompat drawableCompat =
+ new AnimatedVectorDrawableCompat();
+ drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res);
+ drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
+ return drawableCompat;
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res, Theme theme) {
+ AnimatedVectorDrawableCompat drawableCompat =
+ new AnimatedVectorDrawableCompat();
+ drawableCompat.mDelegateDrawable = mDelegateState.newDrawable(res, theme);
+ drawableCompat.mDelegateDrawable.setCallback(drawableCompat.mCallback);
+ return drawableCompat;
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return mDelegateState.canApplyTheme();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mDelegateState.getChangingConfigurations();
+ }
+ }
+
private static class AnimatedVectorDrawableCompatState extends ConstantState {
int mChangingConfigurations;
VectorDrawableCompat mVectorDrawable;
ArrayList<Animator> mAnimators;
ArrayMap<Animator, String> mTargetNameMap;
- Context mContext;
public AnimatedVectorDrawableCompatState(Context context,
AnimatedVectorDrawableCompatState copy, Callback owner, Resources res) {
@@ -391,23 +500,16 @@
}
}
}
-
- if (context != null) {
- mContext = context;
- } else {
- mContext = copy.mContext;
- }
-
}
@Override
public Drawable newDrawable() {
- return new AnimatedVectorDrawableCompat(mContext, this, null);
+ throw new IllegalStateException("No constant state support for SDK < 23.");
}
@Override
public Drawable newDrawable(Resources res) {
- return new AnimatedVectorDrawableCompat(mContext, this, res);
+ throw new IllegalStateException("No constant state support for SDK < 23.");
}
@Override
@@ -416,9 +518,38 @@
}
}
+ /**
+ * Utility function to fix color interpolation prior to Lollipop. Without this fix, colors
+ * are evaluated as raw integers instead of as colors, which leads to artifacts during
+ * fillColor animations.
+ */
+ private void setupColorAnimator(Animator animator) {
+ if (animator instanceof AnimatorSet) {
+ List<Animator> childAnimators = ((AnimatorSet) animator).getChildAnimations();
+ if (childAnimators != null) {
+ for (int i = 0; i < childAnimators.size(); ++i) {
+ setupColorAnimator(childAnimators.get(i));
+ }
+ }
+ }
+ if (animator instanceof ObjectAnimator) {
+ ObjectAnimator objectAnim = (ObjectAnimator) animator;
+ final String propertyName = objectAnim.getPropertyName();
+ if ("fillColor".equals(propertyName) || "strokeColor".equals(propertyName)) {
+ if (mArgbEvaluator == null) {
+ mArgbEvaluator = new ArgbEvaluator();
+ }
+ objectAnim.setEvaluator(mArgbEvaluator);
+ }
+ }
+ }
+
private void setupAnimatorsForTarget(String name, Animator animator) {
Object target = mAnimatedVectorState.mVectorDrawable.getTargetByName(name);
animator.setTarget(target);
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ setupColorAnimator(animator);
+ }
if (mAnimatedVectorState.mAnimators == null) {
mAnimatedVectorState.mAnimators = new ArrayList<Animator>();
mAnimatedVectorState.mTargetNameMap = new ArrayMap<Animator, String>();
@@ -432,6 +563,9 @@
@Override
public boolean isRunning() {
+ if (mDelegateDrawable != null) {
+ return ((AnimatedVectorDrawable) mDelegateDrawable).isRunning();
+ }
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
@@ -460,6 +594,10 @@
@Override
public void start() {
+ if (mDelegateDrawable != null) {
+ ((AnimatedVectorDrawable) mDelegateDrawable).start();
+ return;
+ }
// If any one of the animator has not ended, do nothing.
if (isStarted()) {
return;
@@ -476,6 +614,10 @@
@Override
public void stop() {
+ if (mDelegateDrawable != null) {
+ ((AnimatedVectorDrawable) mDelegateDrawable).stop();
+ return;
+ }
final ArrayList<Animator> animators = mAnimatedVectorState.mAnimators;
final int size = animators.size();
for (int i = 0; i < size; i++) {
diff --git a/graphics/drawable/animated/tests/AndroidManifest.xml b/graphics/drawable/animated/tests/AndroidManifest.xml
new file mode 100644
index 0000000..8999852
--- /dev/null
+++ b/graphics/drawable/animated/tests/AndroidManifest.xml
@@ -0,0 +1,34 @@
+<?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.graphics.drawable.animated.test">
+ <uses-sdk
+ android:minSdkVersion="11"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <application>
+ <activity android:name="android.support.graphics.drawable.tests.DrawableStubActivity"/>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.graphics.drawable.animated.test" />
+</manifest>
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/animated/tests/res/anim/animation_grouping_1_01.xml b/graphics/drawable/animated/tests/res/anim/animation_grouping_1_01.xml
new file mode 100644
index 0000000..3119237
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/anim/animation_grouping_1_01.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.
+-->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+
+ <objectAnimator
+ android:duration="50"
+ android:propertyName="rotation"
+ android:valueFrom="0"
+ android:valueTo="180"
+ android:repeatCount="2"/>
+</set>
\ No newline at end of file
diff --git a/graphics/drawable/animated/tests/res/anim/color_anim.xml b/graphics/drawable/animated/tests/res/anim/color_anim.xml
new file mode 100644
index 0000000..b145120
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/anim/color_anim.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<set xmlns:android="http://schemas.android.com/apk/res/android"
+ android:ordering="together">
+ <objectAnimator
+ android:duration="1000"
+ android:propertyName="fillColor"
+ android:valueFrom="#ffff0000"
+ android:valueTo="#ff000000"
+ android:repeatCount="infinite"
+ android:repeatMode="reverse"/>
+ <objectAnimator
+ android:duration="1000"
+ android:propertyName="strokeColor"
+ android:valueFrom="#ffff0000"
+ android:valueTo="#ff000000"
+ android:repeatCount="infinite"
+ android:repeatMode="reverse"/>
+</set>
\ No newline at end of file
diff --git a/graphics/drawable/animated/tests/res/drawable/animated_color_fill.xml b/graphics/drawable/animated/tests/res/drawable/animated_color_fill.xml
new file mode 100644
index 0000000..aa38528
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/drawable/animated_color_fill.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/rect" >
+ <target
+ android:name="rectBody"
+ android:animation="@anim/color_anim" />
+</animated-vector>
diff --git a/graphics/drawable/animated/tests/res/drawable/animation_vector_drawable_grouping_1.xml b/graphics/drawable/animated/tests/res/drawable/animation_vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..0451a24
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/drawable/animation_vector_drawable_grouping_1.xml
@@ -0,0 +1,26 @@
+<!--
+ 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.
+-->
+<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:drawable="@drawable/vector_drawable_grouping_1">
+
+ <target
+ android:name="sun"
+ android:animation="@anim/animation_grouping_1_01"/>
+ <target
+ android:name="earth"
+ android:animation="@anim/animation_grouping_1_01"/>
+
+</animated-vector>
\ No newline at end of file
diff --git a/graphics/drawable/animated/tests/res/drawable/rect.xml b/graphics/drawable/animated/tests/res/drawable/rect.xml
new file mode 100644
index 0000000..b1fe3fc
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/drawable/rect.xml
@@ -0,0 +1,36 @@
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:width="24dp"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0">
+ <group
+ android:name="rectGroup"
+ android:pivotX="12"
+ android:pivotY="12"
+ android:rotation="0">
+ <path
+ android:name="rectBody"
+ android:fillColor="#ff0000"
+ android:strokeColor="#ff0000"
+ android:strokeWidth="2"
+ android:pathData="M0,0L24,0L24,24L0,24z" />
+
+ </group>
+
+
+</vector>
diff --git a/graphics/drawable/animated/tests/res/drawable/vector_drawable_grouping_1.xml b/graphics/drawable/animated/tests/res/drawable/vector_drawable_grouping_1.xml
new file mode 100644
index 0000000..f125c17
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/drawable/vector_drawable_grouping_1.xml
@@ -0,0 +1,52 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="256"
+ android:viewportWidth="256">
+
+ <group
+ android:name="shape_layer_1"
+ android:translateX="128"
+ android:translateY="128">
+ <group android:name="sun">
+ <path
+ android:name="ellipse_path_1"
+ android:fillColor="#ffff8000"
+ android:pathData="m -25 0 a 25,25 0 1,0 50,0 a 25,25 0 1,0 -50,0"/>
+
+ <group
+ android:name="earth"
+ android:translateX="75">
+ <path
+ android:name="ellipse_path_1_1"
+ android:fillColor="#ff5656ea"
+ android:pathData="m -10 0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0"/>
+
+ <group
+ android:name="moon"
+ android:translateX="25">
+ <path
+ android:name="ellipse_path_1_2"
+ android:fillColor="#ffadadad"
+ android:pathData="m -5 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0"/>
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/animated/tests/res/layout/avd_layout.xml b/graphics/drawable/animated/tests/res/layout/avd_layout.xml
new file mode 100644
index 0000000..404e8b3
--- /dev/null
+++ b/graphics/drawable/animated/tests/res/layout/avd_layout.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ >
+
+ <ImageButton
+ android:layout_width="128dp"
+ android:layout_height="128dp"
+ android:id="@+id/imageButton"
+ android:scaleType="fitCenter" />
+</LinearLayout>
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
new file mode 100644
index 0000000..5318653
--- /dev/null
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java
@@ -0,0 +1,268 @@
+/*
+ * 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.graphics.drawable.tests;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.Drawable.ConstantState;
+import android.support.annotation.DrawableRes;
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
+import android.support.graphics.drawable.animated.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.util.Xml;
+import android.view.View;
+import android.widget.ImageButton;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+public class AnimatedVectorDrawableTest {
+ @Rule public final ActivityTestRule<DrawableStubActivity> mActivityTestRule;
+ private static final String LOGTAG = AnimatedVectorDrawableTest.class.getSimpleName();
+
+ private static final int IMAGE_WIDTH = 64;
+ private static final int IMAGE_HEIGHT = 64;
+ private static final @DrawableRes int DRAWABLE_RES_ID =
+ R.drawable.animation_vector_drawable_grouping_1;
+
+ private Context mContext;
+ private Resources mResources;
+ private AnimatedVectorDrawableCompat mAnimatedVectorDrawable;
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private static final boolean DBG_DUMP_PNG = false;
+
+ public AnimatedVectorDrawableTest() {
+ mActivityTestRule = new ActivityTestRule<>(DrawableStubActivity.class);
+ }
+
+ @Before
+ public void setup() throws Exception {
+ mBitmap = Bitmap.createBitmap(IMAGE_WIDTH, IMAGE_HEIGHT, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+ mContext = mActivityTestRule.getActivity();
+ mResources = mContext.getResources();
+
+ mAnimatedVectorDrawable = AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+ }
+
+ // This is only for debugging or golden image (re)generation purpose.
+ private void saveVectorDrawableIntoPNG(Bitmap bitmap, int resId) throws IOException {
+ // Save the image to the disk.
+ FileOutputStream out = null;
+ try {
+ String outputFolder = "/sdcard/temp/";
+ File folder = new File(outputFolder);
+ if (!folder.exists()) {
+ folder.mkdir();
+ }
+ String originalFilePath = mResources.getString(resId);
+ File originalFile = new File(originalFilePath);
+ String fileFullName = originalFile.getName();
+ String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf("."));
+ String outputFilename = outputFolder + fileTitle + "_golden.png";
+ File outputFile = new File(outputFilename);
+ if (!outputFile.exists()) {
+ outputFile.createNewFile();
+ }
+
+ out = new FileOutputStream(outputFile, false);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ Log.v(LOGTAG, "Write test No." + outputFilename + " to file successfully.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ @Test
+ public void testInflate() throws Exception {
+ // Setup AnimatedVectorDrawableCompat from xml file
+ XmlPullParser parser = mResources.getXml(DRAWABLE_RES_ID);
+ 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");
+ }
+
+ mAnimatedVectorDrawable.inflate(mResources, parser, attrs);
+ mAnimatedVectorDrawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
+ mBitmap.eraseColor(0);
+ mAnimatedVectorDrawable.draw(mCanvas);
+ int sunColor = mBitmap.getPixel(IMAGE_WIDTH / 2, IMAGE_HEIGHT / 2);
+ int earthColor = mBitmap.getPixel(IMAGE_WIDTH * 3 / 4 + 2, IMAGE_HEIGHT / 2);
+ assertTrue(sunColor == 0xFFFF8000);
+ assertTrue(earthColor == 0xFF5656EA);
+
+ if (DBG_DUMP_PNG) {
+ saveVectorDrawableIntoPNG(mBitmap, DRAWABLE_RES_ID);
+ }
+ }
+
+ @Test
+ public void testGetChangingConfigurations() {
+ ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
+
+ if (constantState != null) {
+ // default
+ assertEquals(0, constantState.getChangingConfigurations());
+ assertEquals(0, mAnimatedVectorDrawable.getChangingConfigurations());
+
+ // change the drawable's configuration does not affect the state's configuration
+ mAnimatedVectorDrawable.setChangingConfigurations(0xff);
+ assertEquals(0xff, mAnimatedVectorDrawable.getChangingConfigurations());
+ assertEquals(0, constantState.getChangingConfigurations());
+
+ // the state's configuration get refreshed
+ constantState = mAnimatedVectorDrawable.getConstantState();
+ assertEquals(0xff, constantState.getChangingConfigurations());
+
+ // set a new configuration to drawable
+ mAnimatedVectorDrawable.setChangingConfigurations(0xff00);
+ assertEquals(0xff, constantState.getChangingConfigurations());
+ assertEquals(0xffff, mAnimatedVectorDrawable.getChangingConfigurations());
+ }
+ }
+
+ @Test
+ public void testGetConstantState() {
+ ConstantState constantState = mAnimatedVectorDrawable.getConstantState();
+ if (constantState != null) {
+ assertEquals(0, constantState.getChangingConfigurations());
+
+ mAnimatedVectorDrawable.setChangingConfigurations(1);
+ constantState = mAnimatedVectorDrawable.getConstantState();
+ assertNotNull(constantState);
+ assertEquals(1, constantState.getChangingConfigurations());
+ }
+ }
+
+ @Test
+ public void testAnimateColor() throws Throwable {
+ final ImageButton imageButton =
+ (ImageButton) mActivityTestRule.getActivity().findViewById(R.id.imageButton);
+ final int viewW = imageButton.getWidth();
+ final int viewH = imageButton.getHeight();
+ int pixelX = viewW / 2;
+ int pixelY = viewH / 2;
+ final int numTests = 5;
+ final Bitmap bitmap = Bitmap.createBitmap(imageButton.getWidth(), imageButton.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ final Canvas c = new Canvas(bitmap);
+ CountDownLatch latch = new CountDownLatch(numTests);
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ AnimatedVectorDrawableCompat avd = AnimatedVectorDrawableCompat.create(mContext,
+ R.drawable.animated_color_fill);
+ imageButton.setBackgroundDrawable(avd);
+ avd.start();
+ }
+ });
+ // Check the view several times during the animation to verify that it only
+ // has red color in it
+ for (int i = 0; i < numTests; ++i) {
+ Thread.sleep(100);
+ // check fill
+ verifyRedOnly(pixelX, pixelY, imageButton, bitmap, c, latch);
+ // check stroke
+ verifyRedOnly(1, 1, imageButton, bitmap, c, latch);
+ }
+ latch.await(1000, TimeUnit.MILLISECONDS);
+ }
+
+ /**
+ * Utility method to verify that the pixel at the given location has only red values.
+ */
+ private void verifyRedOnly(final int pixelX, final int pixelY, final View button,
+ final Bitmap bitmap, final Canvas canvas, final CountDownLatch latch) throws Throwable {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ button.draw(canvas);
+ int pixel = bitmap.getPixel(pixelX, pixelY);
+ int blue = pixel & 0xff;
+ int green = pixel & 0xff00 >> 8;
+ assertEquals("Blue channel not zero", 0, blue);
+ assertEquals("Green channel not zero", 0, green);
+ latch.countDown();
+ }
+ });
+ }
+
+ @Test
+ public void testMutate() {
+ AnimatedVectorDrawableCompat d1 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+ AnimatedVectorDrawableCompat d2 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+ AnimatedVectorDrawableCompat d3 =
+ AnimatedVectorDrawableCompat.create(mContext, DRAWABLE_RES_ID);
+
+ if (d1.getConstantState() != null) {
+ int originalAlpha = d2.getAlpha();
+ int newAlpha = (originalAlpha + 1) % 255;
+
+ // AVD is different than VectorDrawable. Every instance of it is a deep copy
+ // of the VectorDrawable.
+ // So every setAlpha operation will happen only to that specific object.
+ d1.setAlpha(newAlpha);
+ assertEquals(newAlpha, d1.getAlpha());
+ assertEquals(originalAlpha, d2.getAlpha());
+ assertEquals(originalAlpha, d3.getAlpha());
+
+ d1.mutate();
+ d1.setAlpha(0x40);
+ assertEquals(0x40, d1.getAlpha());
+ assertEquals(originalAlpha, d2.getAlpha());
+ assertEquals(originalAlpha, d3.getAlpha());
+
+ d2.setAlpha(0x20);
+ assertEquals(0x40, d1.getAlpha());
+ assertEquals(0x20, d2.getAlpha());
+ assertEquals(originalAlpha, d3.getAlpha());
+ }
+ }
+}
diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
new file mode 100644
index 0000000..0cd197e
--- /dev/null
+++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/DrawableStubActivity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.graphics.drawable.tests;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.graphics.drawable.animated.test.R;
+import android.view.WindowManager;
+
+public class DrawableStubActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(R.layout.avd_layout);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+}
+
+
diff --git a/graphics/drawable/res/values/attrs.xml b/graphics/drawable/res/values/attrs.xml
deleted file mode 100644
index b54c9a6..0000000
--- a/graphics/drawable/res/values/attrs.xml
+++ /dev/null
@@ -1,183 +0,0 @@
-<?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>
- <!-- If set, specifies the color to apply to the drawable as a tint. By default,
- no tint is applied. May be a color state list. -->
- <attr name="tint" format="color" />
- <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
- default value is src_in, which treats the drawable as an alpha mask. -->
- <attr name="tintMode">
- <!-- The tint is drawn on top of the drawable.
- [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
- <enum name="src_over" value="3" />
- <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
- color channels are thrown out. [Sa * Da, Sc * Da] -->
- <enum name="src_in" value="5" />
- <!-- The tint is drawn above the drawable, but with the drawable’s alpha
- channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
- <enum name="src_atop" value="9" />
- <!-- Multiplies the color and alpha channels of the drawable with those of
- the tint. [Sa * Da, Sc * Dc] -->
- <enum name="multiply" value="14" />
- <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
- <enum name="screen" value="15" />
- <!-- Combines the tint and drawable color and alpha channels, clamping the
- result to valid color values. Saturate(S + D) -->
- <enum name="add" value="16" />
- </attr>
- <!-- Animation to use on each child. -->
- <attr name="animation" format="reference" />
- <!-- alpha property of the view, as a value between 0 (completely transparent) and 1
- (completely opaque). -->
- <attr name="alpha" format="float" />
- <attr name="pivotX" format="float|fraction" />
- <attr name="pivotY" format="float|fraction" />
- <!-- rotation of the view, in degrees. -->
- <attr name="rotation" format="float" />
-
- <!-- scale of the view in the x direction. -->
- <attr name="scaleX" format="float" />
-
- <!-- scale of the view in the y direction. -->
- <attr name="scaleY" format="float" />
- <!-- Makes the TextView be exactly this many pixels tall.
- You could get the same effect by specifying this number in the
- layout parameters. -->
- <attr name="height" format="dimension" />
- <!-- Makes the TextView be exactly this many pixels wide.
- You could get the same effect by specifying this number in the
- layout parameters. -->
- <attr name="width" format="dimension" />
- <!-- A unique name for the given item. This must use a Java-style naming
- convention to ensure the name is unique, for example
- "com.mycompany.MyName". -->
- <attr name="name" format="string" />
-
- <!-- ========================== -->
- <!-- VectorDrawable class -->
- <!-- ========================== -->
- <eat-comment />
-
- <!-- Drawable used to draw vector paths. -->
- <declare-styleable name="VectorDrawable">
- <!-- If set, specifies the color to apply to the drawable as a tint. By default,
- no tint is applied. May be a color state list. -->
- <attr name="tint" />
- <!-- When a tint color is set, specifies its Porter-Duff blending mode. The
- default value is src_in, which treats the drawable as an alpha mask. -->
- <attr name="tintMode" />
- <!-- Indicates if the drawable needs to be mirrored when its layout direction is
- RTL (right-to-left). -->
- <attr name="autoMirrored" format="boolean" />
- <!-- The intrinsic width of the Vector Drawable. -->
- <attr name="width" />
- <!-- The intrinsic height of the Vector Drawable. -->
- <attr name="height" />
- <!-- The width of the canvas the drawing is on. -->
- <attr name="viewportWidth" format="float"/>
- <!-- The height of the canvas the drawing is on. -->
- <attr name="viewportHeight" format="float"/>
- <!-- The name of this vector drawable -->
- <attr name="name" />
- <!-- The opacity of the whole vector drawable, as a value between 0
- (completely transparent) and 1 (completely opaque). -->
- <attr name="alpha" />
- </declare-styleable>
-
- <!-- Defines the group used in VectorDrawables. -->
- <declare-styleable name="VectorDrawableGroup">
- <!-- The name of this group -->
- <attr name="name" />
- <!-- The amount to rotate the group -->
- <attr name="rotation" />
- <!-- The X coordinate of the center of rotation of a group -->
- <attr name="pivotX" />
- <!-- The Y coordinate of the center of rotation of a group -->
- <attr name="pivotY" />
- <!-- The amount to translate the group on X coordinate -->
- <attr name="translateX" format="float"/>
- <!-- The amount to translate the group on Y coordinate -->
- <attr name="translateY" format="float"/>
- <!-- The amount to scale the group on X coordinate -->
- <attr name="scaleX" />
- <!-- The amount to scale the group on X coordinate -->
- <attr name="scaleY" />
- </declare-styleable>
-
- <!-- Defines the path used in VectorDrawables. -->
- <declare-styleable name="VectorDrawablePath">
- <!-- The name of this path -->
- <attr name="name" />
- <!-- The width a path stroke -->
- <attr name="strokeWidth" format="float" />
- <!-- The color to stroke the path if not defined implies no stroke-->
- <attr name="strokeColor" format="color" />
- <!-- The opacity of a path stroke, as a value between 0 (completely transparent)
- and 1 (completely opaque) -->
- <attr name="strokeAlpha" format="float" />
- <!-- The color to fill the path if not defined implies no fill-->
- <attr name="fillColor" format="color" />
- <!-- The alpha of the path fill, as a value between 0 (completely transparent)
- and 1 (completely opaque)-->
- <attr name="fillAlpha" format="float" />
- <!-- The specification of the operations that define the path -->
- <attr name="pathData" format="string" />
- <!-- The fraction of the path to trim from the start from 0 to 1 -->
- <attr name="trimPathStart" format="float" />
- <!-- The fraction of the path to trim from the end from 0 to 1 -->
- <attr name="trimPathEnd" format="float" />
- <!-- Shift trim region (allows visible region to include the start and end) from 0 to 1 -->
- <attr name="trimPathOffset" format="float" />
- <!-- The linecap for a stroked path -->
- <attr name="strokeLineCap" format="enum">
- <enum name="butt" value="0"/>
- <enum name="round" value="1"/>
- <enum name="square" value="2"/>
- </attr>
- <!-- The lineJoin for a stroked path -->
- <attr name="strokeLineJoin" format="enum">
- <enum name="miter" value="0"/>
- <enum name="round" value="1"/>
- <enum name="bevel" value="2"/>
- </attr>
- <!-- The Miter limit for a stroked path -->
- <attr name="strokeMiterLimit" format="float"/>
- </declare-styleable>
-
- <!-- Defines the clip path used in VectorDrawables. -->
- <declare-styleable name="VectorDrawableClipPath">
- <!-- The Name of this path -->
- <attr name="name" />
- <!-- The specification of the operations that define the path -->
- <attr name="pathData"/>
- </declare-styleable>
-
- <!-- ========================== -->
- <!-- AnimatedVectorDrawable class -->
- <!-- ========================== -->
- <eat-comment />
-
- <!-- Define the AnimatedVectorDrawable. -->
- <declare-styleable name="AnimatedVectorDrawable">
- <!-- The static vector drawable. -->
- <attr name="drawable" format="reference" />
- </declare-styleable>
-
- <!-- Defines the target used in the AnimatedVectorDrawable. -->
- <declare-styleable name="AnimatedVectorDrawableTarget">
- <!-- The name of the target path, group or vector drawable -->
- <attr name="name" />
- <!-- The animation for the target path, group or vector drawable -->
- <attr name="animation" />
- </declare-styleable>
-</resources>
diff --git a/graphics/drawable/runavdtest.sh b/graphics/drawable/runavdtest.sh
deleted file mode 100755
index 023ad3e..0000000
--- a/graphics/drawable/runavdtest.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-. ../../../../build/envsetup.sh
-mmm -j20 . && mmm -j20 ./testanimated/ && \
-adb install -r $OUT/data/app/AndroidAnimatedVectorDrawableTests/AndroidAnimatedVectorDrawableTests.apk && \
-adb shell am start -n android.support.test.vectordrawable/android.support.test.vectordrawable.TestAVDActivity
-
diff --git a/graphics/drawable/runtest.sh b/graphics/drawable/runtest.sh
deleted file mode 100755
index 6f69780..0000000
--- a/graphics/drawable/runtest.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-. ../../../../build/envsetup.sh
-mmm -j20 . && mmm -j20 ./teststatic/ && \
-adb install -r $OUT/data/app/AndroidVectorDrawableTests/AndroidVectorDrawableTests.apk && \
-adb shell am start -n android.support.test.vectordrawable/android.support.test.vectordrawable.TestActivity
-
diff --git a/graphics/drawable/static/AndroidManifest.xml b/graphics/drawable/static/AndroidManifest.xml
new file mode 100644
index 0000000..e91290d
--- /dev/null
+++ b/graphics/drawable/static/AndroidManifest.xml
@@ -0,0 +1,19 @@
+<?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 package="android.support.graphics.drawable">
+ <application/>
+</manifest>
diff --git a/graphics/drawable/static/api/current.txt b/graphics/drawable/static/api/current.txt
new file mode 100644
index 0000000..db07bf2
--- /dev/null
+++ b/graphics/drawable/static/api/current.txt
@@ -0,0 +1,16 @@
+package android.support.graphics.drawable {
+
+ abstract class VectorDrawableCommon extends android.graphics.drawable.Drawable {
+ }
+
+ public class VectorDrawableCompat extends android.support.graphics.drawable.VectorDrawableCommon {
+ method public static android.support.graphics.drawable.VectorDrawableCompat create(android.content.res.Resources, int, android.content.res.Resources.Theme);
+ method public static android.support.graphics.drawable.VectorDrawableCompat createFromXmlInner(android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
+ method public void draw(android.graphics.Canvas);
+ method public int getOpacity();
+ method public void setAlpha(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ }
+
+}
+
diff --git a/graphics/drawable/static/api/removed.txt b/graphics/drawable/static/api/removed.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/graphics/drawable/static/api/removed.txt
diff --git a/graphics/drawable/static/build.gradle b/graphics/drawable/static/build.gradle
new file mode 100644
index 0000000..f7a5b97
--- /dev/null
+++ b/graphics/drawable/static/build.gradle
@@ -0,0 +1,123 @@
+apply plugin: 'com.android.library'
+
+archivesBaseName = 'support-vector-drawable'
+
+dependencies {
+ compile project(':support-v4')
+ 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'
+}
+
+android {
+ compileSdkVersion 23
+
+ defaultConfig {
+ minSdkVersion 7
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ // This disables the builds tools automatic vector -> PNG generation
+ generatedDensities = []
+ }
+
+ sourceSets {
+ main.manifest.srcFile 'AndroidManifest.xml'
+ main.java.srcDir 'src'
+
+ androidTest.setRoot('tests')
+ androidTest.java.srcDir 'tests/src'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
+ }
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
+ lintOptions {
+ abortOnError true
+ }
+
+ aaptOptions {
+ additionalParameters "--no-version-vectors"
+ }
+
+ packagingOptions {
+ exclude 'LICENSE.txt'
+ }
+
+ testOptions {
+ unitTests.returnDefaultValues = true
+ }
+}
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar) {
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
+
+uploadArchives {
+ repositories {
+ mavenDeployer {
+ repository(url: uri(rootProject.ext.supportRepoOut)) {
+ }
+
+ pom.project {
+ name 'Android Support VectorDrawable'
+ description "Android Support VectorDrawable"
+ url 'http://developer.android.com/tools/extras/support-library.html'
+ inceptionYear '2015'
+
+ licenses {
+ license {
+ name 'The Apache Software License, Version 2.0'
+ url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
+ distribution 'repo'
+ }
+ }
+
+ scm {
+ url "http://source.android.com"
+ connection "scm:git:https://android.googlesource.com/platform/frameworks/support"
+ }
+ developers {
+ developer {
+ name 'The Android Open Source Project'
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/AndroidResources.java b/graphics/drawable/static/src/android/support/graphics/drawable/AndroidResources.java
new file mode 100644
index 0000000..165d011
--- /dev/null
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/AndroidResources.java
@@ -0,0 +1,82 @@
+/*
+ * 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.graphics.drawable;
+
+class AndroidResources {
+
+ // Resources ID generated in the latest R.java for framework.
+ static final int[] styleable_VectorDrawableTypeArray = {
+ android.R.attr.name, android.R.attr.tint, android.R.attr.height,
+ android.R.attr.width, android.R.attr.alpha, android.R.attr.autoMirrored,
+ android.R.attr.tintMode, android.R.attr.viewportWidth, android.R.attr.viewportHeight
+ };
+ static final int styleable_VectorDrawable_alpha = 4;
+ static final int styleable_VectorDrawable_autoMirrored = 5;
+ static final int styleable_VectorDrawable_height = 2;
+ static final int styleable_VectorDrawable_name = 0;
+ static final int styleable_VectorDrawable_tint = 1;
+ static final int styleable_VectorDrawable_tintMode = 6;
+ static final int styleable_VectorDrawable_viewportHeight = 8;
+ static final int styleable_VectorDrawable_viewportWidth = 7;
+ static final int styleable_VectorDrawable_width = 3;
+ static final int[] styleable_VectorDrawableGroup = {
+ android.R.attr.name, android.R.attr.pivotX, android.R.attr.pivotY,
+ android.R.attr.scaleX, android.R.attr.scaleY, android.R.attr.rotation,
+ android.R.attr.translateX, android.R.attr.translateY
+ };
+ static final int styleable_VectorDrawableGroup_name = 0;
+ static final int styleable_VectorDrawableGroup_pivotX = 1;
+ static final int styleable_VectorDrawableGroup_pivotY = 2;
+ static final int styleable_VectorDrawableGroup_rotation = 5;
+ static final int styleable_VectorDrawableGroup_scaleX = 3;
+ static final int styleable_VectorDrawableGroup_scaleY = 4;
+ static final int styleable_VectorDrawableGroup_translateX = 6;
+ static final int styleable_VectorDrawableGroup_translateY = 7;
+ static final int[] styleable_VectorDrawablePath = {
+ android.R.attr.name, android.R.attr.fillColor, android.R.attr.pathData,
+ android.R.attr.strokeColor, android.R.attr.strokeWidth, android.R.attr.trimPathStart,
+ android.R.attr.trimPathEnd, android.R.attr.trimPathOffset, android.R.attr.strokeLineCap,
+ android.R.attr.strokeLineJoin, android.R.attr.strokeMiterLimit,
+ android.R.attr.strokeAlpha, android.R.attr.fillAlpha
+ };
+ static final int styleable_VectorDrawablePath_fillAlpha = 12;
+ static final int styleable_VectorDrawablePath_fillColor = 1;
+ static final int styleable_VectorDrawablePath_name = 0;
+ static final int styleable_VectorDrawablePath_pathData = 2;
+ static final int styleable_VectorDrawablePath_strokeAlpha = 11;
+ static final int styleable_VectorDrawablePath_strokeColor = 3;
+ static final int styleable_VectorDrawablePath_strokeLineCap = 8;
+ static final int styleable_VectorDrawablePath_strokeLineJoin = 9;
+ static final int styleable_VectorDrawablePath_strokeMiterLimit = 10;
+ static final int styleable_VectorDrawablePath_strokeWidth = 4;
+ static final int styleable_VectorDrawablePath_trimPathEnd = 6;
+ static final int styleable_VectorDrawablePath_trimPathOffset = 7;
+ static final int styleable_VectorDrawablePath_trimPathStart = 5;
+ static final int[] styleable_VectorDrawableClipPath = {
+ android.R.attr.name, android.R.attr.pathData
+ };
+ static final int styleable_VectorDrawableClipPath_name = 0;
+ static final int styleable_VectorDrawableClipPath_pathData = 1;
+
+ static final int[] styleable_AnimatedVectorDrawable = {
+ android.R.attr.drawable
+ };
+ static final int styleable_AnimatedVectorDrawable_drawable = 0;
+ static final int[] styleable_AnimatedVectorDrawableTarget = {
+ android.R.attr.name, android.R.attr.animation
+ };
+ static final int styleable_AnimatedVectorDrawableTarget_animation = 1;
+ static final int styleable_AnimatedVectorDrawableTarget_name = 0;
+}
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java b/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java
index 8503fae..2ee43c2 100644
--- a/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/PathParser.java
@@ -25,6 +25,7 @@
private static final String LOGTAG = "PathParser";
// Copy from Arrays.copyOfRange() which is only available from API level 9.
+
/**
* Copies elements from {@code original} into a new array, from indexes start (inclusive) to
* end (exclusive). The original order of elements is preserved.
@@ -32,12 +33,12 @@
* with the value {@code 0.0f}.
*
* @param original the original array
- * @param start the start index, inclusive
- * @param end the end index, exclusive
+ * @param start the start index, inclusive
+ * @param end the end index, exclusive
* @return the new array
* @throws ArrayIndexOutOfBoundsException if {@code start < 0 || start > original.length}
- * @throws IllegalArgumentException if {@code start > end}
- * @throws NullPointerException if {@code original == null}
+ * @throws IllegalArgumentException if {@code start > end}
+ * @throws NullPointerException if {@code original == null}
*/
private static float[] copyOfRange(float[] original, int start, int end) {
if (start > end) {
@@ -110,7 +111,7 @@
return null;
}
PathDataNode[] copy = new PathParser.PathDataNode[source.length];
- for (int i = 0; i < source.length; i ++) {
+ for (int i = 0; i < source.length; i++) {
copy[i] = new PathDataNode(source[i]);
}
return copy;
@@ -118,7 +119,7 @@
/**
* @param nodesFrom The source path represented in an array of PathDataNode
- * @param nodesTo The target path represented in an array of PathDataNode
+ * @param nodesTo The target path represented in an array of PathDataNode
* @return whether the <code>nodesFrom</code> can morph into <code>nodesTo</code>
*/
public static boolean canMorph(PathDataNode[] nodesFrom, PathDataNode[] nodesTo) {
@@ -130,7 +131,7 @@
return false;
}
- for (int i = 0; i < nodesFrom.length; i ++) {
+ for (int i = 0; i < nodesFrom.length; i++) {
if (nodesFrom[i].type != nodesTo[i].type
|| nodesFrom[i].params.length != nodesTo[i].params.length) {
return false;
@@ -147,9 +148,9 @@
* @param source The source path represented in an array of PathDataNode
*/
public static void updateNodes(PathDataNode[] target, PathDataNode[] source) {
- for (int i = 0; i < source.length; i ++) {
+ for (int i = 0; i < source.length; i++) {
target[i].type = source[i].type;
- for (int j = 0; j < source[i].params.length; j ++) {
+ for (int j = 0; j < source[i].params.length; j++) {
target[i].params[j] = source[i].params[j];
}
}
@@ -231,10 +232,11 @@
/**
* Calculate the position of the next comma or space or negative sign
- * @param s the string to search
- * @param start the position to start searching
+ *
+ * @param s the string to search
+ * @param start the position to start searching
* @param result the result of the extraction, including the position of the
- * the starting position of next number, whether it is ending with a '-'.
+ * the starting position of next number, whether it is ending with a '-'.
*/
private static void extract(String s, int start, ExtractFloatResult result) {
// Now looking for ' ', ',', '.' or '-' from the start.
@@ -323,11 +325,11 @@
* <code>fraction</code>.
*
* @param nodeFrom The start value as a PathDataNode.
- * @param nodeTo The end value as a PathDataNode
+ * @param nodeTo The end value as a PathDataNode
* @param fraction The fraction to interpolate.
*/
public void interpolatePathDataNode(PathDataNode nodeFrom,
- PathDataNode nodeTo, float fraction) {
+ PathDataNode nodeTo, float fraction) {
for (int i = 0; i < nodeFrom.params.length; i++) {
params[i] = nodeFrom.params[i] * (1 - fraction)
+ nodeTo.params[i] * fraction;
@@ -335,7 +337,7 @@
}
private static void addCommand(Path path, float[] current,
- char previousCmd, char cmd, float[] val) {
+ char previousCmd, char cmd, float[] val) {
int incr = 2;
float currentX = current[0];
@@ -393,18 +395,32 @@
for (int k = 0; k < val.length; k += incr) {
switch (cmd) {
case 'm': // moveto - Start a new sub-path (relative)
- path.rMoveTo(val[k + 0], val[k + 1]);
currentX += val[k + 0];
currentY += val[k + 1];
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.rLineTo(val[k + 0], val[k + 1]);
+ } else {
+ path.rMoveTo(val[k + 0], val[k + 1]);
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
break;
case 'M': // moveto - Start a new sub-path
- path.moveTo(val[k + 0], val[k + 1]);
currentX = val[k + 0];
currentY = val[k + 1];
- currentSegmentStartX = currentX;
- currentSegmentStartY = currentY;
+ if (k > 0) {
+ // According to the spec, if a moveto is followed by multiple
+ // pairs of coordinates, the subsequent pairs are treated as
+ // implicit lineto commands.
+ path.lineTo(val[k + 0], val[k + 1]);
+ } else {
+ path.moveTo(val[k + 0], val[k + 1]);
+ currentSegmentStartX = currentX;
+ currentSegmentStartY = currentY;
+ }
break;
case 'l': // lineto - Draw a line from the current point (relative)
path.rLineTo(val[k + 0], val[k + 1]);
@@ -571,15 +587,15 @@
}
private static void drawArc(Path p,
- float x0,
- float y0,
- float x1,
- float y1,
- float a,
- float b,
- float theta,
- boolean isMoreThanHalf,
- boolean isPositiveArc) {
+ float x0,
+ float y0,
+ float x1,
+ float y1,
+ float a,
+ float b,
+ float theta,
+ boolean isMoreThanHalf,
+ boolean isPositiveArc) {
/* Convert rotation angle from degrees to radians */
double thetaD = Math.toRadians(theta);
@@ -650,32 +666,32 @@
/**
* Converts an arc to cubic Bezier segments and records them in p.
*
- * @param p The target for the cubic Bezier segments
- * @param cx The x coordinate center of the ellipse
- * @param cy The y coordinate center of the ellipse
- * @param a The radius of the ellipse in the horizontal direction
- * @param b The radius of the ellipse in the vertical direction
- * @param e1x E(eta1) x coordinate of the starting point of the arc
- * @param e1y E(eta2) y coordinate of the starting point of the arc
+ * @param p The target for the cubic Bezier segments
+ * @param cx The x coordinate center of the ellipse
+ * @param cy The y coordinate center of the ellipse
+ * @param a The radius of the ellipse in the horizontal direction
+ * @param b The radius of the ellipse in the vertical direction
+ * @param e1x E(eta1) x coordinate of the starting point of the arc
+ * @param e1y E(eta2) y coordinate of the starting point of the arc
* @param theta The angle that the ellipse bounding rectangle makes with horizontal plane
* @param start The start angle of the arc on the ellipse
* @param sweep The angle (positive or negative) of the sweep of the arc on the ellipse
*/
private static void arcToBezier(Path p,
- double cx,
- double cy,
- double a,
- double b,
- double e1x,
- double e1y,
- double theta,
- double start,
- double sweep) {
+ double cx,
+ double cy,
+ double a,
+ double b,
+ double e1x,
+ double e1y,
+ double theta,
+ double start,
+ double sweep) {
// Taken from equations at: http://spaceroots.org/documents/ellipse/node8.html
// and http://www.spaceroots.org/documents/ellipse/node22.html
// Maximum of 45 degrees per cubic Bezier segment
- int numSegments = Math.abs((int) Math.ceil(sweep * 4 / Math.PI));
+ int numSegments = (int) Math.ceil(Math.abs(sweep * 4 / Math.PI));
double eta1 = start;
double cosTheta = Math.cos(theta);
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/TypedArrayUtils.java b/graphics/drawable/static/src/android/support/graphics/drawable/TypedArrayUtils.java
new file mode 100644
index 0000000..aedd502
--- /dev/null
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/TypedArrayUtils.java
@@ -0,0 +1,66 @@
+/*
+ * 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.graphics.drawable;
+
+import android.content.res.TypedArray;
+import org.xmlpull.v1.XmlPullParser;
+
+class TypedArrayUtils {
+ private static final String NAMESPACE = "http://schemas.android.com/apk/res/android";
+
+ public static boolean hasAttribute(XmlPullParser parser, String attrName) {
+ return parser.getAttributeValue(NAMESPACE, attrName) != null;
+ }
+
+ public static float getNamedFloat(TypedArray a, XmlPullParser parser, String attrName,
+ int resId, float defaultValue) {
+ final boolean hasAttr = hasAttribute(parser, attrName);
+ if (!hasAttr) {
+ return defaultValue;
+ } else {
+ return a.getFloat(resId, defaultValue);
+ }
+ }
+
+ public static boolean getNamedBoolean(TypedArray a, XmlPullParser parser, String attrName,
+ int resId, boolean defaultValue) {
+ final boolean hasAttr = hasAttribute(parser, attrName);
+ if (!hasAttr) {
+ return defaultValue;
+ } else {
+ return a.getBoolean(resId, defaultValue);
+ }
+ }
+
+ public static int getNamedInt(TypedArray a, XmlPullParser parser, String attrName,
+ int resId, int defaultValue) {
+ final boolean hasAttr = hasAttribute(parser, attrName);
+ if (!hasAttr) {
+ return defaultValue;
+ } else {
+ return a.getInt(resId, defaultValue);
+ }
+ }
+
+ public static int getNamedColor(TypedArray a, XmlPullParser parser, String attrName,
+ int resId, int defaultValue) {
+ final boolean hasAttr = hasAttribute(parser, attrName);
+ if (!hasAttr) {
+ return defaultValue;
+ } else {
+ return a.getColor(resId, defaultValue);
+ }
+ }
+}
diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCommon.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCommon.java
new file mode 100644
index 0000000..5f2dc6d
--- /dev/null
+++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCommon.java
@@ -0,0 +1,229 @@
+/*
+ * 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.graphics.drawable;
+
+import android.annotation.TargetApi;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.ColorFilter;
+import android.graphics.Outline;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.graphics.drawable.TintAwareDrawable;
+import android.util.AttributeSet;
+import android.view.View;
+
+/**
+ * Internal common delegation shared by VectorDrawableCompat and AnimatedVectorDrawableCompat
+ */
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+abstract class VectorDrawableCommon extends Drawable implements TintAwareDrawable {
+ /**
+ * Obtains styled attributes from the theme, if available, or unstyled
+ * resources if the theme is null.
+ */
+ static TypedArray obtainAttributes(
+ Resources res, Resources.Theme theme, AttributeSet set, int[] attrs) {
+ if (theme == null) {
+ return res.obtainAttributes(set, attrs);
+ }
+ return theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ // Drawable delegation for Lollipop and above.
+ Drawable mDelegateDrawable;
+
+ @Override
+ public void setColorFilter(int color, PorterDuff.Mode mode) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setColorFilter(color, mode);
+ return;
+ }
+ super.setColorFilter(color, mode);
+ }
+
+ @Override
+ public ColorFilter getColorFilter() {
+ if (mDelegateDrawable != null) {
+ return DrawableCompat.getColorFilter(mDelegateDrawable);
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean onLevelChange(int level) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setLevel(level);
+ }
+ return super.onLevelChange(level);
+ }
+
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setBounds(bounds);
+ return;
+ }
+ super.onBoundsChange(bounds);
+ }
+
+ @Override
+ public void setHotspot(float x, float y) {
+ // API >= 21 only.
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setHotspot(mDelegateDrawable, x, y);
+ }
+ return;
+ }
+
+ @Override
+ public void setHotspotBounds(int left, int top, int right, int bottom) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setHotspotBounds(mDelegateDrawable, left, top, right, bottom);
+ return;
+ }
+ }
+
+ @Override
+ public void setFilterBitmap(boolean filter) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setFilterBitmap(filter);
+ return;
+ }
+ }
+
+ @Override
+ public void jumpToCurrentState() {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.jumpToCurrentState(mDelegateDrawable);
+ return;
+ }
+ }
+
+ @Override
+ public void setAutoMirrored(boolean mirrored) {
+ // API >= 21 only.
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setAutoMirrored(mDelegateDrawable, mirrored);
+
+ return;
+ }
+ }
+
+ @Override
+ public boolean isAutoMirrored() {
+ // API >= 21 only.
+ if (mDelegateDrawable != null) {
+ DrawableCompat.isAutoMirrored(mDelegateDrawable);
+ }
+ return false;
+ }
+
+ @Override
+ public void applyTheme(Resources.Theme t) {
+ // API >= 21 only.
+ if (mDelegateDrawable != null) {
+ DrawableCompat.applyTheme(mDelegateDrawable, t);
+ return;
+ }
+ }
+
+ @Override
+ public int getLayoutDirection() {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.getLayoutDirection(mDelegateDrawable);
+ }
+ return View.LAYOUT_DIRECTION_LTR;
+ }
+
+ @Override
+ public void clearColorFilter() {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.clearColorFilter();
+ return;
+ }
+ super.clearColorFilter();
+ }
+
+ @Override
+ public Drawable getCurrent() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getCurrent();
+ }
+ return super.getCurrent();
+ }
+
+ @Override
+ public int getMinimumWidth() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getMinimumWidth();
+ }
+ return super.getMinimumWidth();
+ }
+
+ @Override
+ public int getMinimumHeight() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getMinimumHeight();
+ }
+ return super.getMinimumHeight();
+ }
+
+ @Override
+ public boolean getPadding(Rect padding) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getPadding(padding);
+ }
+ return super.getPadding(padding);
+ }
+
+ @Override
+ public int[] getState() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getState();
+ }
+ return super.getState();
+ }
+
+
+ @Override
+ public Region getTransparentRegion() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getTransparentRegion();
+ }
+ return super.getTransparentRegion();
+ }
+
+ @Override
+ public void setChangingConfigurations(int configs) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setChangingConfigurations(configs);
+ return;
+ }
+ super.setChangingConfigurations(configs);
+ }
+
+ @Override
+ public boolean setState(int[] stateSet) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setState(stateSet);
+ }
+ return super.setState(stateSet);
+ }
+}
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 c0e5b40..7e82be5 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,12 @@
package android.support.graphics.drawable;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.annotation.TargetApi;
import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.content.res.Resources.Theme;
@@ -33,152 +39,32 @@
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.VectorDrawable;
+import android.os.Build;
+import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
-import android.support.annotation.DrawableRes;
import android.support.v4.util.ArrayMap;
import android.util.AttributeSet;
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;
/**
- * This lets you create a drawable based on an XML vector graphic. It can be defined in an XML file
- * with the <code><vector></code> element.
- * <p/>
- * The vector drawable has the following elements:
- * <p/>
- * <dt><code><vector></code></dt>
- * <dl>
- * <dd>Used to define a vector drawable
- * <dl>
- * <dt><code>android:name</code></dt>
- * <dd>Defines the name of this vector drawable.</dd>
- * <dt><code>android:width</code></dt>
- * <dd>Used to define the intrinsic width of the drawable. This support all the dimension units,
- * normally specified with dp.</dd>
- * <dt><code>android:height</code></dt>
- * <dd>Used to define the intrinsic height the drawable. This support all the dimension units,
- * normally specified with dp.</dd>
- * <dt><code>android:viewportWidth</code></dt>
- * <dd>Used to define the width of the viewport space. Viewport is basically the virtual canvas
- * where the paths are drawn on.</dd>
- * <dt><code>android:viewportHeight</code></dt>
- * <dd>Used to define the height of the viewport space. Viewport is basically the virtual canvas
- * where the paths are drawn on.</dd>
- * <dt><code>android:tint</code></dt>
- * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd>
- * <dt><code>android:tintMode</code></dt>
- * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd>
- * <dt><code>android:autoMirrored</code></dt>
- * <dd>Indicates if the drawable needs to be mirrored when its layout direction is RTL
- * (right-to-left).</dd>
- * <dt><code>android:alpha</code></dt>
- * <dd>The opacity of this drawable.</dd>
- * </dl>
- * </dd>
- * </dl>
- * <dl>
- * <dt><code><group></code></dt>
- * <dd>Defines a group of paths or subgroups, plus transformation information. The transformations
- * are defined in the same coordinates as the viewport. And the transformations are applied in the
- * order of scale, rotate then translate.
- * <dl>
- * <dt><code>android:name</code></dt>
- * <dd>Defines the name of the group.</dd>
- * <dt><code>android:rotation</code></dt>
- * <dd>The degrees of rotation of the group.</dd>
- * <dt><code>android:pivotX</code></dt>
- * <dd>The X coordinate of the pivot for the scale and rotation of the group. This is defined in the
- * viewport space.</dd>
- * <dt><code>android:pivotY</code></dt>
- * <dd>The Y coordinate of the pivot for the scale and rotation of the group. This is defined in the
- * viewport space.</dd>
- * <dt><code>android:scaleX</code></dt>
- * <dd>The amount of scale on the X Coordinate.</dd>
- * <dt><code>android:scaleY</code></dt>
- * <dd>The amount of scale on the Y coordinate.</dd>
- * <dt><code>android:translateX</code></dt>
- * <dd>The amount of translation on the X coordinate. This is defined in the viewport space.</dd>
- * <dt><code>android:translateY</code></dt>
- * <dd>The amount of translation on the Y coordinate. This is defined in the viewport space.</dd>
- * </dl>
- * </dd>
- * </dl>
- * <dl>
- * <dt><code><path></code></dt>
- * <dd>Defines paths to be drawn.
- * <dl>
- * <dt><code>android:name</code></dt>
- * <dd>Defines the name of the path.</dd>
- * <dt><code>android:pathData</code></dt>
- * <dd>Defines path data using exactly same format as "d" attribute in the SVG's path
- * data. This is defined in the viewport space.</dd>
- * <dt><code>android:fillColor</code></dt>
- * <dd>Defines the color to fill the path (none if not present).</dd>
- * <dt><code>android:strokeColor</code></dt>
- * <dd>Defines the color to draw the path outline (none if not present).</dd>
- * <dt><code>android:strokeWidth</code></dt>
- * <dd>The width a path stroke.</dd>
- * <dt><code>android:strokeAlpha</code></dt>
- * <dd>The opacity of a path stroke.</dd>
- * <dt><code>android:fillAlpha</code></dt>
- * <dd>The opacity to fill the path with.</dd>
- * <dt><code>android:trimPathStart</code></dt>
- * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd>
- * <dt><code>android:trimPathEnd</code></dt>
- * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd>
- * <dt><code>android:trimPathOffset</code></dt>
- * <dd>Shift trim region (allows showed region to include the start and end), in the range from 0 to
- * 1.</dd>
- * <dt><code>android:strokeLineCap</code></dt>
- * <dd>Sets the linecap for a stroked path: butt, round, square.</dd>
- * <dt><code>android:strokeLineJoin</code></dt>
- * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd>
- * <dt><code>android:strokeMiterLimit</code></dt>
- * <dd>Sets the Miter limit for a stroked path.</dd>
- * </dl>
- * </dd>
- * </dl>
- * <dl>
- * <dt><code><clip-path></code></dt>
- * <dd>Defines path to be the current clip.
- * <dl>
- * <dt><code>android:name</code></dt>
- * <dd>Defines the name of the clip path.</dd>
- * <dt><code>android:pathData</code></dt>
- * <dd>Defines clip path data using the same format as "d" attribute in the SVG's
- * path data.</dd>
- * </dl>
- * </dd>
- * </dl>
- * <li>Here is a simple VectorDrawable in this vectordrawable.xml file.
- * <pre>
- * <vector xmlns:android="http://schemas.android.com/apk/res/android"
- * android:height="64dp"
- * android:width="64dp"
- * android:viewportHeight="600"
- * android:viewportWidth="600" >
- * <group
- * android:name="rotationGroup"
- * android:pivotX="300.0"
- * android:pivotY="300.0"
- * android:rotation="45.0" >
- * <path
- * android:name="v"
- * android:fillColor="#000000"
- * android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
- * </group>
- * </vector>
- * </pre></li>
+ * For API 23 and above, this class is delegating to the framework's {@link VectorDrawable}.
+ * For older API version, this class lets you create a drawable based on an XML vector graphic.
+ * <p>
+ * VectorDrawableCompat are defined in the same XML format as {@link VectorDrawable}.
+ * </p>
+ * You can always create a VectorDrawableCompat object and use it as a Drawable by the Java API.
+ * In order to refer to VectorDrawableCompat inside a XML file, you can use app:srcCompat attribute
+ * in AppCompat library's ImageButton or ImageView.
*/
-public class VectorDrawableCompat extends Drawable {
+@TargetApi(Build.VERSION_CODES.LOLLIPOP)
+public class VectorDrawableCompat extends VectorDrawableCommon {
static final String LOGTAG = "VectorDrawableCompat";
static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
@@ -196,9 +82,14 @@
private static final int LINEJOIN_ROUND = 1;
private static final int LINEJOIN_BEVEL = 2;
+ // Cap the bitmap size, such that it won't hurt the performance too much
+ // and it won't crash due to a very large scale.
+ // The drawable will look blurry above this size.
+ private static final int MAX_CACHED_BITMAP_SIZE = 2048;
+
private static final boolean DBG_VECTOR_DRAWABLE = false;
- private VectorDrawableState mVectorState;
+ private VectorDrawableCompatState mVectorState;
private PorterDuffColorFilter mTintFilter;
private ColorFilter mColorFilter;
@@ -209,19 +100,32 @@
// caching the bitmap by default is allowed.
private boolean mAllowCaching = true;
+ // The Constant state associated with the <code>mDelegateDrawable</code>.
+ private ConstantState mCachedConstantStateDelegate;
+
+ // Temp variable, only for saving "new" operation at the draw() time.
+ private final float[] mTmpFloats = new float[9];
+ private final Matrix mTmpMatrix = new Matrix();
+ private final Rect mTmpBounds = new Rect();
+
private VectorDrawableCompat() {
- mVectorState = new VectorDrawableState();
+ mVectorState = new VectorDrawableCompatState();
}
- private VectorDrawableCompat(VectorDrawableState state) {
+ private VectorDrawableCompat(@NonNull VectorDrawableCompatState state) {
mVectorState = state;
mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
}
@Override
public Drawable mutate() {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.mutate();
+ return this;
+ }
+
if (!mMutated && super.mutate() == this) {
- mVectorState = new VectorDrawableState(mVectorState);
+ mVectorState = new VectorDrawableCompatState(mVectorState);
mMutated = true;
}
return this;
@@ -233,59 +137,101 @@
@Override
public ConstantState getConstantState() {
+ if (mDelegateDrawable != null) {
+ // Such that the configuration can be refreshed.
+ return new VectorDrawableDelegateState(mDelegateDrawable.getConstantState());
+ }
mVectorState.mChangingConfigurations = getChangingConfigurations();
return mVectorState;
}
@Override
public void draw(Canvas canvas) {
- final Rect bounds = getBounds();
- if (bounds.width() == 0 || bounds.height() == 0) {
- // too small to draw
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.draw(canvas);
+ return;
+ }
+ // We will offset the bounds for drawBitmap, so copyBounds() here instead
+ // of getBounds().
+ copyBounds(mTmpBounds);
+ if (mTmpBounds.width() <= 0 || mTmpBounds.height() <= 0) {
+ // Nothing to draw
+ return;
+ }
+
+ // Color filters always override tint filters.
+ final ColorFilter colorFilter = (mColorFilter == null ? mTintFilter : mColorFilter);
+
+ // The imageView can scale the canvas in different ways, in order to
+ // avoid blurry scaling, we have to draw into a bitmap with exact pixel
+ // size first. This bitmap size is determined by the bounds and the
+ // canvas scale.
+ canvas.getMatrix(mTmpMatrix);
+ mTmpMatrix.getValues(mTmpFloats);
+ float canvasScaleX = Math.abs(mTmpFloats[Matrix.MSCALE_X]);
+ float canvasScaleY = Math.abs(mTmpFloats[Matrix.MSCALE_Y]);
+
+ float canvasSkewX = Math.abs(mTmpFloats[Matrix.MSKEW_X]);
+ float canvasSkewY = Math.abs(mTmpFloats[Matrix.MSKEW_Y]);
+
+ // When there is any rotation / skew, then the scale value is not valid.
+ if (canvasSkewX != 0 || canvasSkewY != 0) {
+ canvasScaleX = 1.0f;
+ canvasScaleY = 1.0f;
+ }
+
+ int scaledWidth = (int) (mTmpBounds.width() * canvasScaleX);
+ int scaledHeight = (int) (mTmpBounds.height() * canvasScaleY);
+ scaledWidth = Math.min(MAX_CACHED_BITMAP_SIZE, scaledWidth);
+ scaledHeight = Math.min(MAX_CACHED_BITMAP_SIZE, scaledHeight);
+
+ if (scaledWidth <= 0 || scaledHeight <= 0) {
return;
}
final int saveCount = canvas.save();
- final boolean needMirroring = needMirroring();
+ canvas.translate(mTmpBounds.left, mTmpBounds.top);
- canvas.translate(bounds.left, bounds.top);
+ // Handle RTL mirroring.
+ final boolean needMirroring = needMirroring();
if (needMirroring) {
- canvas.translate(bounds.width(), 0);
+ canvas.translate(mTmpBounds.width(), 0);
canvas.scale(-1.0f, 1.0f);
}
- // Color filters always override tint filters.
- final ColorFilter colorFilter = mColorFilter == null ? mTintFilter : mColorFilter;
+ // At this point, canvas has been translated to the right position.
+ // And we use this bound for the destination rect for the drawBitmap, so
+ // we offset to (0, 0);
+ mTmpBounds.offsetTo(0, 0);
+ mVectorState.createCachedBitmapIfNeeded(scaledWidth, scaledHeight);
if (!mAllowCaching) {
- // AnimatedVectorDrawable
- if (!mVectorState.hasTranslucentRoot()) {
- mVectorState.mVPathRenderer.draw(
- canvas, bounds.width(), bounds.height(), colorFilter);
- } else {
- mVectorState.createCachedBitmapIfNeeded(bounds);
- mVectorState.updateCachedBitmap(bounds);
- mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
- }
+ mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
} else {
- // Static Vector Drawable case.
- mVectorState.createCachedBitmapIfNeeded(bounds);
if (!mVectorState.canReuseCache()) {
- mVectorState.updateCachedBitmap(bounds);
+ mVectorState.updateCachedBitmap(scaledWidth, scaledHeight);
mVectorState.updateCacheStates();
}
- mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter);
}
-
+ mVectorState.drawCachedBitmapWithRootAlpha(canvas, colorFilter, mTmpBounds);
canvas.restoreToCount(saveCount);
}
public int getAlpha() {
+ if (mDelegateDrawable != null) {
+ return DrawableCompat.getAlpha(mDelegateDrawable);
+ }
+
return mVectorState.mVPathRenderer.getRootAlpha();
}
@Override
public void setAlpha(int alpha) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setAlpha(alpha);
+ return;
+ }
+
if (mVectorState.mVPathRenderer.getRootAlpha() != alpha) {
mVectorState.mVPathRenderer.setRootAlpha(alpha);
invalidateSelf();
@@ -294,6 +240,11 @@
@Override
public void setColorFilter(ColorFilter colorFilter) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setColorFilter(colorFilter);
+ return;
+ }
+
mColorFilter = colorFilter;
invalidateSelf();
}
@@ -303,7 +254,7 @@
* mode.
*/
PorterDuffColorFilter updateTintFilter(PorterDuffColorFilter tintFilter, ColorStateList tint,
- PorterDuff.Mode tintMode) {
+ PorterDuff.Mode tintMode) {
if (tint == null || tintMode == null) {
return null;
}
@@ -314,11 +265,21 @@
}
public void setTint(int tint) {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTint(mDelegateDrawable, tint);
+ return;
+ }
+
setTintList(ColorStateList.valueOf(tint));
}
public void setTintList(ColorStateList tint) {
- final VectorDrawableState state = mVectorState;
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTintList(mDelegateDrawable, tint);
+ return;
+ }
+
+ final VectorDrawableCompatState state = mVectorState;
if (state.mTint != tint) {
state.mTint = tint;
mTintFilter = updateTintFilter(mTintFilter, tint, state.mTintMode);
@@ -327,7 +288,12 @@
}
public void setTintMode(Mode tintMode) {
- final VectorDrawableState state = mVectorState;
+ if (mDelegateDrawable != null) {
+ DrawableCompat.setTintMode(mDelegateDrawable, tintMode);
+ return;
+ }
+
+ final VectorDrawableCompatState state = mVectorState;
if (state.mTintMode != tintMode) {
state.mTintMode = tintMode;
mTintFilter = updateTintFilter(mTintFilter, state.mTint, tintMode);
@@ -337,15 +303,23 @@
@Override
public boolean isStateful() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.isStateful();
+ }
+
return super.isStateful() || (mVectorState != null && mVectorState.mTint != null
&& mVectorState.mTint.isStateful());
}
@Override
protected boolean onStateChange(int[] stateSet) {
- final VectorDrawableState state = mVectorState;
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setState(stateSet);
+ }
+
+ final VectorDrawableCompatState state = mVectorState;
if (state.mTint != null && state.mTintMode != null) {
- // mTintFilter = updateTintFilter(this, mTintFilter, state.mTint, state.mTintMode);
+ mTintFilter = updateTintFilter(mTintFilter, state.mTint, state.mTintMode);
invalidateSelf();
return true;
}
@@ -354,21 +328,37 @@
@Override
public int getOpacity() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getOpacity();
+ }
+
return PixelFormat.TRANSLUCENT;
}
@Override
public int getIntrinsicWidth() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getIntrinsicWidth();
+ }
+
return (int) mVectorState.mVPathRenderer.mBaseWidth;
}
@Override
public int getIntrinsicHeight() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getIntrinsicHeight();
+ }
+
return (int) mVectorState.mVPathRenderer.mBaseHeight;
}
// Don't support re-applying themes. The initial theme loading is working.
public boolean canApplyTheme() {
+ if (mDelegateDrawable != null) {
+ DrawableCompat.canApplyTheme(mDelegateDrawable);
+ }
+
return false;
}
@@ -398,14 +388,22 @@
/**
* Create a VectorDrawableCompat object.
*
- * @param res the resources.
+ * @param res the resources.
* @param resId the resource ID for VectorDrawableCompat object.
* @param theme the theme of this vector drawable, it can be null.
* @return a new VectorDrawableCompat or null if parsing error is found.
*/
@Nullable
public static VectorDrawableCompat create(@NonNull Resources res, @DrawableRes int resId,
- @Nullable Theme theme) {
+ @Nullable Theme theme) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ final VectorDrawableCompat drawable = new VectorDrawableCompat();
+ drawable.mDelegateDrawable = ResourcesCompat.getDrawable(res, resId, theme);
+ drawable.mCachedConstantStateDelegate = new VectorDrawableDelegateState(
+ drawable.mDelegateDrawable.getConstantState());
+ return drawable;
+ }
+
try {
final XmlPullParser parser = res.getXml(resId);
final AttributeSet attrs = Xml.asAttributeSet(parser);
@@ -417,10 +415,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) {
@@ -429,6 +424,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;
@@ -436,32 +444,30 @@
return color;
}
- /**
- * Obtains styled attributes from the theme, if available, or unstyled
- * resources if the theme is null.
- */
- static TypedArray obtainAttributes(
- Resources res, Theme theme, AttributeSet set, int[] attrs) {
- if (theme == null) {
- return res.obtainAttributes(set, attrs);
- }
- return theme.obtainStyledAttributes(set, attrs, 0, 0);
- }
-
-
@Override
public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs)
throws XmlPullParserException, IOException {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.inflate(res, parser, attrs);
+ return;
+ }
+
inflate(res, parser, attrs, null);
}
public void inflate(Resources res, XmlPullParser parser, AttributeSet attrs, Theme theme)
throws XmlPullParserException, IOException {
- final VectorDrawableState state = mVectorState;
+ if (mDelegateDrawable != null) {
+ DrawableCompat.inflate(mDelegateDrawable, res, parser, attrs, theme);
+ return;
+ }
+
+ final VectorDrawableCompatState state = mVectorState;
final VPathRenderer pathRenderer = new VPathRenderer();
state.mVPathRenderer = pathRenderer;
- final TypedArray a = obtainAttributes(res, theme, attrs, AndroidResources.styleable_VectorDrawableTypeArray);
+ final TypedArray a = obtainAttributes(res, theme, attrs,
+ AndroidResources.styleable_VectorDrawableTypeArray);
updateStateFromTypedArray(a, parser);
a.recycle();
@@ -477,42 +483,53 @@
* Parses a {@link android.graphics.PorterDuff.Mode} from a tintMode
* attribute's enum value.
*/
- private static PorterDuff.Mode parseTintMode(int value, Mode defaultMode) {
+ private static PorterDuff.Mode parseTintModeCompat(int value, Mode defaultMode) {
switch (value) {
- case 3: return Mode.SRC_OVER;
- case 5: return Mode.SRC_IN;
- case 9: return Mode.SRC_ATOP;
- case 14: return Mode.MULTIPLY;
- case 15: return Mode.SCREEN;
- case 16: return Mode.ADD;
- default: return defaultMode;
+ case 3:
+ return Mode.SRC_OVER;
+ case 5:
+ return Mode.SRC_IN;
+ case 9:
+ return Mode.SRC_ATOP;
+ case 14:
+ return Mode.MULTIPLY;
+ case 15:
+ return Mode.SCREEN;
+ case 16:
+ return Mode.ADD;
+ default:
+ return defaultMode;
}
}
- private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser) throws XmlPullParserException {
- final VectorDrawableState state = mVectorState;
+ private void updateStateFromTypedArray(TypedArray a, XmlPullParser parser)
+ throws XmlPullParserException {
+ final VectorDrawableCompatState state = mVectorState;
final VPathRenderer pathRenderer = state.mVPathRenderer;
// Account for any configuration changes.
// state.mChangingConfigurations |= Utils.getChangingConfigurations(a);
final int mode = TypedArrayUtils.getNamedInt(a, parser, "tintMode",
- AndroidResources.styleable_VectorDrawable_Mode, -1);
- state.mTintMode = parseTintMode(mode, Mode.SRC_IN);
+ AndroidResources.styleable_VectorDrawable_tintMode, -1);
+ state.mTintMode = parseTintModeCompat(mode, Mode.SRC_IN);
- final ColorStateList tint = a.getColorStateList(AndroidResources.styleable_VectorDrawable_tint);
+ final ColorStateList tint =
+ a.getColorStateList(AndroidResources.styleable_VectorDrawable_tint);
if (tint != null) {
state.mTint = tint;
}
state.mAutoMirrored = TypedArrayUtils.getNamedBoolean(a, parser, "autoMirrored",
- AndroidResources.styleable_VectorDrawable_autoMirrored, state.mAutoMirrored);
+ AndroidResources.styleable_VectorDrawable_autoMirrored, state.mAutoMirrored);
pathRenderer.mViewportWidth = TypedArrayUtils.getNamedFloat(a, parser, "viewportWidth",
- AndroidResources.styleable_VectorDrawable_viewportWidth, pathRenderer.mViewportWidth);
+ AndroidResources.styleable_VectorDrawable_viewportWidth,
+ pathRenderer.mViewportWidth);
pathRenderer.mViewportHeight = TypedArrayUtils.getNamedFloat(a, parser, "viewportHeight",
- AndroidResources.styleable_VectorDrawable_viewportHeight, pathRenderer.mViewportHeight);
+ AndroidResources.styleable_VectorDrawable_viewportHeight,
+ pathRenderer.mViewportHeight);
if (pathRenderer.mViewportWidth <= 0) {
throw new XmlPullParserException(a.getPositionDescription() +
@@ -536,7 +553,7 @@
// shown up from API 11.
final float alphaInFloat = TypedArrayUtils.getNamedFloat(a, parser, "alpha",
- AndroidResources.styleable_VectorDrawable_alpha, pathRenderer.getAlpha());
+ AndroidResources.styleable_VectorDrawable_alpha, pathRenderer.getAlpha());
pathRenderer.setAlpha(alphaInFloat);
final String name = a.getString(AndroidResources.styleable_VectorDrawable_name);
@@ -547,8 +564,8 @@
}
private void inflateInternal(Resources res, XmlPullParser parser, AttributeSet attrs,
- Theme theme) throws XmlPullParserException, IOException {
- final VectorDrawableState state = mVectorState;
+ Theme theme) throws XmlPullParserException, IOException {
+ final VectorDrawableCompatState state = mVectorState;
final VPathRenderer pathRenderer = state.mVPathRenderer;
boolean noPathTag = true;
@@ -645,7 +662,103 @@
return false;
}
- private static class VectorDrawableState extends ConstantState {
+ // Extra override functions for delegation for SDK >= 7.
+ @Override
+ protected void onBoundsChange(Rect bounds) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.setBounds(bounds);
+ }
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.getChangingConfigurations();
+ }
+ return super.getChangingConfigurations() | mVectorState.getChangingConfigurations();
+ }
+
+ @Override
+ public void invalidateSelf() {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.invalidateSelf();
+ return;
+ }
+ super.invalidateSelf();
+ }
+
+ @Override
+ public void scheduleSelf(Runnable what, long when) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.scheduleSelf(what, when);
+ return;
+ }
+ super.scheduleSelf(what, when);
+ }
+
+ @Override
+ public boolean setVisible(boolean visible, boolean restart) {
+ if (mDelegateDrawable != null) {
+ return mDelegateDrawable.setVisible(visible, restart);
+ }
+ return super.setVisible(visible, restart);
+ }
+
+ @Override
+ public void unscheduleSelf(Runnable what) {
+ if (mDelegateDrawable != null) {
+ mDelegateDrawable.unscheduleSelf(what);
+ return;
+ }
+ super.unscheduleSelf(what);
+ }
+
+ /**
+ * Constant state for delegating the creating drawable job for SDK >= 23.
+ * Instead of creating a VectorDrawable, create a VectorDrawableCompat instance which contains
+ * a delegated VectorDrawable instance.
+ */
+ private static class VectorDrawableDelegateState extends ConstantState {
+ private final ConstantState mDelegateState;
+
+ public VectorDrawableDelegateState(ConstantState state) {
+ mDelegateState = state;
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
+ drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable();
+ return drawableCompat;
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res) {
+ VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
+ drawableCompat.mDelegateDrawable = (VectorDrawable) mDelegateState.newDrawable(res);
+ return drawableCompat;
+ }
+
+ @Override
+ public Drawable newDrawable(Resources res, Theme theme) {
+ VectorDrawableCompat drawableCompat = new VectorDrawableCompat();
+ drawableCompat.mDelegateDrawable =
+ (VectorDrawable) mDelegateState.newDrawable(res, theme);
+ return drawableCompat;
+ }
+
+ @Override
+ public boolean canApplyTheme() {
+ return mDelegateState.canApplyTheme();
+ }
+
+ @Override
+ public int getChangingConfigurations() {
+ return mDelegateState.getChangingConfigurations();
+ }
+ }
+
+ private static class VectorDrawableCompatState extends ConstantState {
int mChangingConfigurations;
VPathRenderer mVPathRenderer;
ColorStateList mTint = null;
@@ -660,11 +773,13 @@
boolean mCachedAutoMirrored;
boolean mCacheDirty;
- /** Temporary paint object used to draw cached bitmaps. */
+ /**
+ * Temporary paint object used to draw cached bitmaps.
+ */
Paint mTempPaint;
// Deep copy for mutate() or implicitly mutate.
- public VectorDrawableState(VectorDrawableState copy) {
+ public VectorDrawableCompatState(VectorDrawableCompatState copy) {
if (copy != null) {
mChangingConfigurations = copy.mChangingConfigurations;
mVPathRenderer = new VPathRenderer(copy.mVPathRenderer);
@@ -680,10 +795,11 @@
}
}
- public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter) {
+ public void drawCachedBitmapWithRootAlpha(Canvas canvas, ColorFilter filter,
+ Rect originalBounds) {
// The bitmap's size is the same as the bounds.
final Paint p = getPaint(filter);
- canvas.drawBitmap(mCachedBitmap, 0, 0, p);
+ canvas.drawBitmap(mCachedBitmap, null, originalBounds, p);
}
public boolean hasTranslucentRoot() {
@@ -707,16 +823,15 @@
return mTempPaint;
}
- public void updateCachedBitmap(Rect bounds) {
+ public void updateCachedBitmap(int width, int height) {
mCachedBitmap.eraseColor(Color.TRANSPARENT);
Canvas tmpCanvas = new Canvas(mCachedBitmap);
- mVPathRenderer.draw(tmpCanvas, bounds.width(), bounds.height(), null);
+ mVPathRenderer.draw(tmpCanvas, width, height, null);
}
- public void createCachedBitmapIfNeeded(Rect bounds) {
- if (mCachedBitmap == null || !canReuseBitmap(bounds.width(),
- bounds.height())) {
- mCachedBitmap = Bitmap.createBitmap(bounds.width(), bounds.height(),
+ public void createCachedBitmapIfNeeded(int width, int height) {
+ if (mCachedBitmap == null || !canReuseBitmap(width, height)) {
+ mCachedBitmap = Bitmap.createBitmap(width, height,
Bitmap.Config.ARGB_8888);
mCacheDirty = true;
}
@@ -752,7 +867,7 @@
mCacheDirty = false;
}
- public VectorDrawableState() {
+ public VectorDrawableCompatState() {
mVPathRenderer = new VPathRenderer();
}
@@ -851,7 +966,7 @@
}
private void drawGroupTree(VGroup currentGroup, Matrix currentMatrix,
- Canvas canvas, int w, int h, ColorFilter filter) {
+ Canvas canvas, int w, int h, ColorFilter filter) {
// Calculate current group's matrix by preConcat the parent's and
// and the current one on the top of the stack.
// Basically the Mfinal = Mviewport * M0 * M1 * M2;
@@ -880,14 +995,21 @@
}
private void drawPath(VGroup vGroup, VPath vPath, Canvas canvas, int w, int h,
- ColorFilter filter) {
+ ColorFilter filter) {
final float scaleX = w / mViewportWidth;
final float scaleY = h / mViewportHeight;
final float minScale = Math.min(scaleX, scaleY);
+ final Matrix groupStackedMatrix = vGroup.mStackedMatrix;
- mFinalPathMatrix.set(vGroup.mStackedMatrix);
+ mFinalPathMatrix.set(groupStackedMatrix);
mFinalPathMatrix.postScale(scaleX, scaleY);
+
+ final float matrixScale = getMatrixScale(groupStackedMatrix);
+ if (matrixScale == 0) {
+ // When either x or y is scaled to 0, we don't need to draw anything.
+ return;
+ }
vPath.toPath(mPath);
final Path path = mPath;
@@ -953,11 +1075,45 @@
strokePaint.setStrokeMiter(fullPath.mStrokeMiterlimit);
strokePaint.setColor(applyAlpha(fullPath.mStrokeColor, fullPath.mStrokeAlpha));
strokePaint.setColorFilter(filter);
- strokePaint.setStrokeWidth(fullPath.mStrokeWidth * minScale);
+ final float finalStrokeScale = minScale * matrixScale;
+ strokePaint.setStrokeWidth(fullPath.mStrokeWidth * finalStrokeScale);
canvas.drawPath(mRenderPath, strokePaint);
}
}
}
+
+ private static float cross(float v1x, float v1y, float v2x, float v2y) {
+ return v1x * v2y - v1y * v2x;
+ }
+
+ private float getMatrixScale(Matrix groupStackedMatrix) {
+ // Given unit vectors A = (0, 1) and B = (1, 0).
+ // After matrix mapping, we got A' and B'. Let theta = the angel b/t A' and B'.
+ // Therefore, the final scale we want is min(|A'| * sin(theta), |B'| * sin(theta)),
+ // which is (|A'| * |B'| * sin(theta)) / max (|A'|, |B'|);
+ // If max (|A'|, |B'|) = 0, that means either x or y has a scale of 0.
+ //
+ // For non-skew case, which is most of the cases, matrix scale is computing exactly the
+ // scale on x and y axis, and take the minimal of these two.
+ // For skew case, an unit square will mapped to a parallelogram. And this function will
+ // return the minimal height of the 2 bases.
+ float[] unitVectors = new float[]{0, 1, 1, 0};
+ groupStackedMatrix.mapVectors(unitVectors);
+ float scaleX = (float) Math.hypot(unitVectors[0], unitVectors[1]);
+ float scaleY = (float) Math.hypot(unitVectors[2], unitVectors[3]);
+ float crossProduct = cross(unitVectors[0], unitVectors[1], unitVectors[2],
+ unitVectors[3]);
+ float maxScale = Math.max(scaleX, scaleY);
+
+ float matrixScale = 0;
+ if (maxScale > 0) {
+ matrixScale = Math.abs(crossProduct) / maxScale;
+ }
+ if (DBG_VECTOR_DRAWABLE) {
+ Log.d(LOGTAG, "Scale x " + scaleX + " y " + scaleY + " final " + matrixScale);
+ }
+ return matrixScale;
+ }
}
private static class VGroup {
@@ -1069,7 +1225,8 @@
mTranslateY = TypedArrayUtils.getNamedFloat(a, parser, "translateY",
AndroidResources.styleable_VectorDrawableGroup_translateY, mTranslateY);
- final String groupName = a.getString(AndroidResources.styleable_VectorDrawableGroup_name);
+ final String groupName =
+ a.getString(AndroidResources.styleable_VectorDrawableGroup_name);
if (groupName != null) {
mGroupName = groupName;
}
@@ -1287,12 +1444,14 @@
// Account for any configuration changes.
// mChangingConfigurations |= Utils.getChangingConfigurations(a);;
- final String pathName = a.getString(AndroidResources.styleable_VectorDrawableClipPath_name);
+ final String pathName =
+ a.getString(AndroidResources.styleable_VectorDrawableClipPath_name);
if (pathName != null) {
mPathName = pathName;
}
- final String pathData = a.getString(AndroidResources.styleable_VectorDrawableClipPath_pathData);
+ final String pathData =
+ a.getString(AndroidResources.styleable_VectorDrawableClipPath_pathData);
if (pathData != null) {
mNodes = PathParser.createNodesFromPathData(pathData);
}
@@ -1395,14 +1554,15 @@
// Extract the theme attributes, if any.
mThemeAttrs = null; // TODO TINT THEME Not supported yet a.extractThemeAttrs();
- // In order to work around the conflicting id issue, we need to double check the existence
- // of the attribute.
- // B/c if the attribute existed in the compiled XML, then calling TypedArray will be safe
- // since the framework will look up in the XML first.
- // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay here.
+ // In order to work around the conflicting id issue, we need to double check the
+ // existence of the attribute.
+ // B/c if the attribute existed in the compiled XML, then calling TypedArray will be
+ // safe since the framework will look up in the XML first.
+ // Note that each getAttributeValue take roughly 0.03ms, it is a price we have to pay.
final boolean hasPathData = TypedArrayUtils.hasAttribute(parser, "pathData");
if (!hasPathData) {
- //If there is no pathData in the <path> tag, then this is an empty path, nothing need to be drawn.
+ // If there is no pathData in the <path> tag, then this is an empty path,
+ // nothing need to be drawn.
return;
}
@@ -1410,14 +1570,15 @@
if (pathName != null) {
mPathName = pathName;
}
- final String pathData = a.getString(AndroidResources.styleable_VectorDrawablePath_pathData);
+ final String pathData =
+ a.getString(AndroidResources.styleable_VectorDrawablePath_pathData);
if (pathData != null) {
mNodes = PathParser.createNodesFromPathData(pathData);
}
mFillColor = TypedArrayUtils.getNamedColor(a, parser, "fillColor",
AndroidResources.styleable_VectorDrawablePath_fillColor, mFillColor);
- mFillAlpha = TypedArrayUtils.getNamedFloat(a, parser, "alpha",
+ mFillAlpha = TypedArrayUtils.getNamedFloat(a, parser, "fillAlpha",
AndroidResources.styleable_VectorDrawablePath_fillAlpha, mFillAlpha);
final int lineCap = TypedArrayUtils.getNamedInt(a, parser, "strokeLineCap",
AndroidResources.styleable_VectorDrawablePath_strokeLineCap, -1);
@@ -1426,7 +1587,8 @@
AndroidResources.styleable_VectorDrawablePath_strokeLineJoin, -1);
mStrokeLineJoin = getStrokeLineJoin(lineJoin, mStrokeLineJoin);
mStrokeMiterlimit = TypedArrayUtils.getNamedFloat(a, parser, "strokeMiterLimit",
- AndroidResources.styleable_VectorDrawablePath_strokeMiterLimit, mStrokeMiterlimit);
+ AndroidResources.styleable_VectorDrawablePath_strokeMiterLimit,
+ mStrokeMiterlimit);
mStrokeColor = TypedArrayUtils.getNamedColor(a, parser, "strokeColor",
AndroidResources.styleable_VectorDrawablePath_strokeColor, mStrokeColor);
mStrokeAlpha = TypedArrayUtils.getNamedFloat(a, parser, "strokeAlpha",
diff --git a/graphics/drawable/static/tests/AndroidManifest.xml b/graphics/drawable/static/tests/AndroidManifest.xml
new file mode 100644
index 0000000..27f3fbd
--- /dev/null
+++ b/graphics/drawable/static/tests/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?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.graphics.drawable.test">
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
+ <application/>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.graphics.drawable.test" />
+</manifest>
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/res/color/vector_icon_fill_state_list.xml b/graphics/drawable/static/tests/res/color/vector_icon_fill_state_list.xml
new file mode 100644
index 0000000..7c12997
--- /dev/null
+++ b/graphics/drawable/static/tests/res/color/vector_icon_fill_state_list.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#00ff00" android:state_pressed="true"/>
+ <item android:color="#0000ff"/>
+</selector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/color/vector_icon_stroke_state_list.xml b/graphics/drawable/static/tests/res/color/vector_icon_stroke_state_list.xml
new file mode 100644
index 0000000..e1ea931
--- /dev/null
+++ b/graphics/drawable/static/tests/res/color/vector_icon_stroke_state_list.xml
@@ -0,0 +1,22 @@
+<?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.
+ */
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="#0000ff" android:state_pressed="true"/>
+ <item android:color="#00ff00"/>
+</selector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
new file mode 100644
index 0000000..71a02d1
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_clip_path_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_create_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_create_golden.png
new file mode 100644
index 0000000..47631667
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_create_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_delete_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_delete_golden.png
new file mode 100644
index 0000000..9255247
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_delete_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_heart_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_heart_golden.png
new file mode 100644
index 0000000..aef4ed6
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_heart_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png
new file mode 100644
index 0000000..e7f4f03
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png
new file mode 100644
index 0000000..89ed107
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_random_path_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_1_golden.png
new file mode 100644
index 0000000..29c6be4
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_2_golden.png
new file mode 100644
index 0000000..fa7f743
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_render_order_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
new file mode 100644
index 0000000..3b0c4cc
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
new file mode 100644
index 0000000..ed3ba89
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_a_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
new file mode 100644
index 0000000..84881da
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_cq_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png
new file mode 100644
index 0000000..74c0fc5
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_repeated_st_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_1_golden.png
new file mode 100644
index 0000000..9704178
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_2_golden.png
new file mode 100644
index 0000000..d3e5125
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_scale_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_schedule_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_schedule_golden.png
new file mode 100644
index 0000000..c3edca9
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_schedule_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_settings_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_settings_golden.png
new file mode 100644
index 0000000..e1c2b83
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_settings_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_1_golden.png
new file mode 100644
index 0000000..6cad0ea
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_2_golden.png
new file mode 100644
index 0000000..3e3dac2
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_3_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_3_golden.png
new file mode 100644
index 0000000..3ba60ad
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_stroke_3_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png
new file mode 100644
index 0000000..05da94d
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_1_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png
new file mode 100644
index 0000000..a4a7e668
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_2_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png
new file mode 100644
index 0000000..365c7b6
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_3_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png
new file mode 100644
index 0000000..9b8f6ab
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_4_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png
new file mode 100644
index 0000000..d864469
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_5_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png
new file mode 100644
index 0000000..16be452
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable-nodpi/vector_icon_transformation_6_golden.png
Binary files differ
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_clip_path_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_clip_path_1.xml
new file mode 100644
index 0000000..b9905c1
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_clip_path_1.xml
@@ -0,0 +1,79 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="12.25"
+ android:viewportWidth="7.30625"
+ android:width="64dp">
+
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="-30">
+ <clip-path
+ android:name="clip1"
+ android:pathData="
+ M 0, 6.125
+ l 7.3, 0
+ l 0, 12.25
+ l -7.3, 0
+ z"/>
+
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="30">
+ <path
+ android:name="one"
+ android:fillColor="#ff88ff"
+ android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0-6.671875 -2.109375,0.421875 0.0-1.078125
+ l 2.09375-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
+ l -5.046875,0.0 0.0-1.0Z"/>
+ </group>
+ </group>
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="-30">
+ <clip-path
+ android:name="clip2"
+ android:pathData="
+ M 0, 0
+ l 7.3, 0
+ l 0, 6.125
+ l -7.3, 0
+ z"/>
+
+ <group
+ android:pivotX="3.65"
+ android:pivotY="6.125"
+ android:rotation="30">
+ <path
+ android:name="two"
+ android:fillColor="#ff88ff"
+ android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0 -5.5625,0.0 0.0-1.0q 0.671875-0.6875 1.828125-1.859375
+ q 1.1718752-1.1875 1.4687502-1.53125 0.578125-0.625 0.796875-1.0625
+ q 0.234375-0.453125 0.234375-0.875 0.0-0.703125 -0.5-1.140625
+ q -0.484375-0.4375 -1.2656252-0.4375 -0.5625,0.0 -1.1875,0.1875
+ q -0.609375,0.1875 -1.3125,0.59375l 0.0-1.203125q 0.71875-0.28125 1.328125-0.421875
+ q 0.625-0.15625 1.140625-0.15625 1.3593752,0.0 2.1718752,0.6875
+ q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125 -0.203125,1.015625
+ q -0.203125,0.484375 -0.734375,1.140625 -0.15625,0.171875 -0.9375,0.984375
+ q -0.78125024,0.8125 -2.2187502,2.265625Z"/>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_create.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_create.xml
new file mode 100644
index 0000000..2aaf7bb
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_create.xml
@@ -0,0 +1,29 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M3.0,17.25L3.0,21.0l3.75,0.0L17.813995,9.936001l-3.75-3.75L3.0,17.25zM20.707,7.0429993c0.391-0.391 0.391-1.023 0.0-1.414l-2.336-2.336c-0.391-0.391-1.023-0.391 -1.414,0.0l-1.832,1.832l3.75,3.75L20.707,7.0429993z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_delete.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_delete.xml
new file mode 100644
index 0000000..ae32092
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_delete.xml
@@ -0,0 +1,29 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M6.0,19.0c0.0,1.104 896e-3,2.0 2.0,2.0l8.0,0.0c1.104,0.0 2.0-896e-3 2.0-2.0l0.0-12.0L6.0,7.0L6.0,19.0zM18.0,4.0l-2.5,0.0l-1.0-1.0l-5.0,0.0l-1.0,1.0L6.0,4.0C5.4469986,4.0 5.0,4.4469986 5.0,5.0l0.0,1.0l14.0,0.0l0.0-1.0C19.0,4.4469986 18.552002,4.0 18.0,4.0z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_heart.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_heart.xml
new file mode 100644
index 0000000..bf6aca3
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_heart.xml
@@ -0,0 +1,29 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M16.0,5.0c-1.955.0 -3.83,1.268 -4.5,3.0c-0.67-1.732 -2.547-3.0 -4.5-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207-5.242 9.0-7.971 9.0-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_1.xml
new file mode 100644
index 0000000..6b268a1
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_1.xml
@@ -0,0 +1,49 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="
+ m 0.0 0.0
+ c 58.357853 57.648304 47.260395 2.2044754 3.0 3.0
+ s 61.29288 10.748665 6.0 6.0
+ s 0.12015152 45.193787 9.0 9.0
+ s 32.573513 46.862522 12.0 12.0
+ C 52.051823 62.050003 14.197739 51.99994 15.0 15.0
+ S 58.365482 51.877937 18.0 18.0
+ S 26.692455 3.9604378 21.0 21.0
+ S 21.433464 52.17514 24.0 24.0
+ M 27.0 27.0
+ s 0.77630234 20.606667 30.0 30.0
+ M 33.0 33.0
+ S 31.06879 21.506374 36.0 36.0
+ m 39.0 39.0
+ s 11.699013 23.684185 42.0 42.0
+ m 45.0 45.0
+ S 3.7642136 38.589584 48.0 48.0
+ Q 27.203026 53.329338 51.0 51.0
+ s 39.229023 15.1781845 54.0 54.0
+ Q 47.946877 23.706299 57.0 57.0
+ S 45.63452 56.15198 60.0 60.0 "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_2.xml
new file mode 100644
index 0000000..ae76308
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_random_path_2.xml
@@ -0,0 +1,49 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="
+ m 0.0 0.0
+ q 4.7088394 36.956432 3.0 3.0
+ s 29.470345 16.754963 6.0 6.0
+ q 20.278355 7.4670525 9.0 9.0
+ S 30.897224 17.732414 12.0 12.0
+ T 15.0 15.0
+ s 63.47204 45.67142 18.0 18.0
+ T 21.0 21.0
+ S 0.3184204 24.808247 24.0 24.0
+ t 27.0 27.0
+ s 39.02275 38.261158 30.0 30.0
+ t 33.0 33.0
+ S 50.709816 16.067192 36.0 36.0
+ a 62.50911 7.7131805 51.932335 0 0 39.0 39.0
+ s 5.155651 15.749123 42.0 42.0
+ a 51.87415 40.30564 49.804344 0 0 45.0 45.0
+ S 16.16534 62.55986 48.0 48.0
+ A 39.90161 43.904438 41.642593 1 0 51.0 51.0
+ s 46.258068 32.12831 54.0 54.0
+ A 22.962704 55.05604 42.912285 1 1 57.0 57.0
+ S 36.47731 54.216763 60.0 60.0 "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_1.xml
new file mode 100644
index 0000000..7958c622
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_1.xml
@@ -0,0 +1,89 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400">
+
+ <group
+ android:name="FirstLevelGroup"
+ android:translateX="100.0"
+ android:translateY="0.0">
+ <path
+ android:fillColor="#FFFF0000"
+ android:fillAlpha="0.9"
+ android:pathData="@string/rectangle200"/>
+
+ <group
+ android:name="SecondLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillColor="#FF00FF00"
+ android:fillAlpha="0.81"
+ android:pathData="@string/rectangle200"/>
+
+ <group
+ android:name="ThridLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillColor="#FF0000FF"
+ android:fillAlpha="0.729"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ <group
+ android:name="ThridLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.72"
+ android:fillColor="#FF000000"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ </group>
+ <group
+ android:name="SecondLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillColor="#FF0000FF"
+ android:fillAlpha="0.72"
+ android:pathData="@string/rectangle200"/>
+
+ <group
+ android:name="ThridLevelGroup3"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FFFF0000"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ <group
+ android:name="ThridLevelGroup4"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.576"
+ android:fillColor="#FF00FF00"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_2.xml
new file mode 100644
index 0000000..5a1c7e2
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_render_order_2.xml
@@ -0,0 +1,89 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400">
+
+ <group
+ android:name="FirstLevelGroup"
+ android:translateX="100.0"
+ android:translateY="0.0">
+ <group
+ android:name="SecondLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.81"
+ android:fillColor="#FF00FF00"
+ android:pathData="@string/rectangle200"/>
+
+ <group
+ android:name="ThridLevelGroup1"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.729"
+ android:fillColor="#FF0000FF"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ <group
+ android:name="ThridLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FF000000"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ </group>
+ <group
+ android:name="SecondLevelGroup2"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.72"
+ android:fillColor="#FF0000FF"
+ android:pathData="@string/rectangle200"/>
+
+ <group
+ android:name="ThridLevelGroup3"
+ android:translateX="-100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.648"
+ android:fillColor="#FFFF0000"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ <group
+ android:name="ThridLevelGroup4"
+ android:translateX="100.0"
+ android:translateY="50.0">
+ <path
+ android:fillAlpha="0.576"
+ android:fillColor="#FF00FF00"
+ android:pathData="@string/rectangle200"/>
+ </group>
+ </group>
+
+ <path
+ android:fillAlpha="0.9"
+ android:fillColor="#FFFF0000"
+ android:pathData="@string/rectangle200"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_1.xml
new file mode 100644
index 0000000..b6a69a3
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_1.xml
@@ -0,0 +1,43 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ a 59.3415 26.877445 22.398209 1 1 3.3506432 1.6524277
+ a 34.922844 36.72583 13.569004 0 0 24.409462 20.931156
+ a 43.47134 32.61542 52.534607 1 0 7.187504 61.509724
+ A 30.621132 41.44202 50.885685 0 0 23.235489 26.638653
+ A 7.251148 15.767811 44.704533 1 1 19.989803 21.33052
+ A 55.645584 46.20288 19.40316 0 1 32.881298 53.410923
+ c 30.649612 4.8525085 21.96682 1.3304634 17.300182 14.747681
+ a 9.375069 44.365055 57.169727 0 0 56.01326 52.59596
+ A 50.071907 37.331825 56.301754 1 0 14.676102 62.04976
+ C 36.531925 4.6217957 47.59332 54.793385 13.562473 13.753647
+ A 2.3695297 42.578487 54.250687 0 1 33.1337 41.511288
+ a 39.4827 38.844944 54.52335 1 1 13.549484 46.81581
+ c 56.943657 51.96854 27.938824 61.148792 24.168636 46.642727
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_2.xml
new file mode 100644
index 0000000..a66d63e
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_a_2.xml
@@ -0,0 +1,45 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 45.712063 19.109837
+ H 24.509682
+ A 37.689938 2.3916092 17.462616 1 0 24.958328 48.110596
+ q 45.248383 30.396336 5.777027 3.4086685
+ a 30.966236 62.67946 50.532032 1 0 29.213684 60.63014
+ L 56.16764 8.342098
+ Q 61.172253 1.4613304 4.4721107 38.287144
+ A 6.284897 22.991482 47.409508 1 1 44.10166 60.998764
+ t 36.36881 55.68292
+ a 51.938667 35.22107 22.272938 1 1 28.572739 60.848858
+ A 19.610851 11.569599 51.407906 1 1 56.82705 24.386292
+ T 36.918854 59.542286
+ a 33.191364 10.553429 53.047726 1 0 54.874985 7.409252
+ s 30.186714 42.154182 59.73551 35.50219
+ A 47.9379 5.776497 28.307701 1 1 3.3323975 30.113499
+ a 22.462494 28.096004 55.76455 0 0 25.58981 30.816948
+ S 43.91107 54.679676 19.540264 0.34284973
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_cq.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_cq.xml
new file mode 100644
index 0000000..50b6021
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_cq.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 30.81895 41.37989
+ v 31.00579
+ c 24.291603 52.03364 40.6086 24.840137 29.56704 6.5204926
+ 45.133224 22.913471 33.052887 21.727486 33.369 61.60278
+ 9.647232 22.098152 48.939598 47.470215 53.653687 62.32235
+ C 2.0560722 1.4615479 7.0928993 26.005287 40.137558 36.75628
+ 11.246731 32.178127 59.367462 60.34823 57.254383 37.357815
+ 47.75605 11.424667 3.3105545 51.886635 56.63027 17.12133
+ q 28.37534 32.85535 25.85654 33.57151
+ 10.356537 51.850616 54.085087 35.653175
+ 12.530029 52.87991 17.44696 11.780586
+ Q 2.585228 51.92801 60.000664 56.79912
+ 54.18275 51.500694 9.375679 23.836113
+ 60.35329 59.026245 31.058632 35.14934
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_st.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_st.xml
new file mode 100644
index 0000000..c55d550
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_repeated_st.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="128"
+ android:viewportWidth="128">
+
+ <path
+ android:fillColor="#FF00FF00"
+ android:pathData="m 20.20005 8.139153
+ h 10.053165
+ s 14.2943 49.612846 35.520653 54.904068
+ 50.1405 17.044182 5.470337 40.180553
+ 3.125019 34.221123 53.212563 32.862965
+ S 35.985264 35.74349 0.15337753 59.27337
+ 2.2951508 44.56783 51.089413 29.829689
+ 8.5599785 22.649555 4.3914986 28.139206
+ t 11.932453 44.041077
+ 62.629326 7.40921
+ 23.302986 54.116184
+ T 43.560753 63.370514
+ 40.156204 17.60786
+ 40.12051 60.803394
+ "
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="1"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_scale_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_scale_1.xml
new file mode 100644
index 0000000..f6c0fd7
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_scale_1.xml
@@ -0,0 +1,52 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp">
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"/>
+ </group>
+ <group
+ android:scaleX="-1"
+ android:scaleY="-1">
+ <group
+ android:scaleX="-1"
+ android:scaleY="-1">
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="45">
+ <path
+ android:name="twoLines"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 100, 0 l 0, 100, -100, 0 z"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10"/>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_scale_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_scale_2.xml
new file mode 100644
index 0000000..fe9272c
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_scale_2.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp">
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"/>
+ </group>
+ <group
+ android:scaleX="2"
+ android:scaleY="0.5">
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="45">
+ <path
+ android:name="twoLines"
+ android:fillColor="#FFFF0000"
+ android:pathData="M 100, 0 l 0, 100, -100, 0 z"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10"/>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_schedule.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_schedule.xml
new file mode 100644
index 0000000..db1e45b
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_schedule.xml
@@ -0,0 +1,32 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="#E6000000"
+ android:pathData="M11.994999,2.0C6.4679985,2.0 2.0,6.4780006 2.0,12.0s4.468,10.0 9.995,10.0S22.0,17.522 22.0,12.0S17.521,2.0 11.994999,2.0zM12.0,20.0c-4.42,0.0 -8.0-3.582-8.0-8.0s3.58-8.0 8.0-8.0s8.0,3.582 8.0,8.0S16.419998,20.0 12.0,20.0z"/>
+ <path
+ android:fillColor="#E6000000"
+ android:pathData="M12.5,6.0l-1.5,0.0 0.0,7.0 5.3029995,3.1819992 0.75-1.249999-4.5529995-2.7320004z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_settings.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_settings.xml
new file mode 100644
index 0000000..0574654
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_settings.xml
@@ -0,0 +1,29 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="#FF000000"
+ android:pathData="M19.429,12.975998c0.042-0.32 0.07-0.645 0.07-0.976s-0.029-0.655-0.07-0.976l2.113-1.654c0.188-0.151 0.243-0.422 0.118-0.639l-2.0-3.463c-0.125-0.217-0.386-0.304-0.612-0.218l-2.49,1.004c-0.516-0.396-1.081-0.731-1.69-0.984l-0.375-2.648C14.456,2.1829987 14.25,2.0 14.0,2.0l-4.0,0.0C9.75,2.0 9.544,2.1829987 9.506,2.422001L9.131,5.0699997C8.521,5.322998 7.957,5.6570015 7.44,6.054001L4.952,5.0509987C4.726,4.965 4.464,5.052002 4.34,5.269001l-2.0,3.463C2.2150002,8.947998 2.27,9.219002 2.4580002,9.369999l2.112,1.653C4.528,11.344002 4.5,11.668999 4.5,12.0s0.029,0.656 0.071,0.977L2.4580002,14.630001c-0.188,0.151-0.243,0.422-0.118,0.639l2.0,3.463c0.125,0.217 0.386,0.304 0.612,0.218l2.489-1.004c0.516,0.396 1.081,0.731 1.69,0.984l0.375,2.648C9.544,21.817001 9.75,22.0 10.0,22.0l4.0,0.0c0.25,0.0 0.456-0.183 0.494-0.422l0.375-2.648c0.609-0.253 1.174-0.588 1.689-0.984l2.49,1.004c0.226,0.086 0.487-0.001 0.612-0.218l2.0-3.463c0.125-0.217 0.07-0.487-0.118-0.639L19.429,12.975998zM12.0,16.0c-2.21,0.0-4.0-1.791-4.0-4.0c0.0-2.21 1.79-4.0 4.0-4.0c2.208,0.0 4.0,1.79 4.0,4.0C16.0,14.209 14.208,16.0 12.0,16.0z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_state_list.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_state_list.xml
new file mode 100644
index 0000000..5b82a35
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_state_list.xml
@@ -0,0 +1,31 @@
+<?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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="24"
+ android:viewportWidth="24">
+
+ <path
+ android:fillColor="@color/vector_icon_fill_state_list"
+ android:strokeColor="@color/vector_icon_stroke_state_list"
+ android:strokeWidth="3"
+ android:pathData="M16.0,5.0c-1.955.0 -3.83,1.268 -4.5,3.0c-0.67-1.732 -2.547-3.0 -4.5-3.0C4.4570007,5.0 2.5,6.931999 2.5,9.5c0.0,3.529 3.793,6.258 9.0,11.5c5.207-5.242 9.0-7.971 9.0-11.5C20.5,6.931999 18.543,5.0 16.0,5.0z"/>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_1.xml
new file mode 100644
index 0000000..9d820dd
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_1.xml
@@ -0,0 +1,46 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp">
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"/>
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="butt"
+ android:strokeLineJoin="miter"
+ android:strokeMiterLimit="6"
+ android:strokeWidth="20"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_2.xml
new file mode 100644
index 0000000..0693460
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_2.xml
@@ -0,0 +1,46 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp">
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"/>
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="round"
+ android:strokeLineJoin="round"
+ android:strokeMiterLimit="10"
+ android:strokeWidth="20"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_3.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_3.xml
new file mode 100644
index 0000000..dcae16e
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_stroke_3.xml
@@ -0,0 +1,46 @@
+<!--
+ 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:height="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200"
+ android:width="64dp">
+
+ <group>
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"/>
+ </group>
+ <group
+ android:translateX="50"
+ android:translateY="50">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,20 l 0 80 l -30 -80"
+ android:fillColor="#FF000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeLineCap="square"
+ android:strokeLineJoin="bevel"
+ android:strokeMiterLimit="10"
+ android:strokeWidth="20"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_1.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_1.xml
new file mode 100644
index 0000000..d751d4a0
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_1.xml
@@ -0,0 +1,38 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="500"
+ android:viewportWidth="800">
+
+ <group
+ android:pivotX="90"
+ android:pivotY="100"
+ android:rotation="20">
+ <path
+ android:name="pie2"
+ android:pathData="M200,350 l 50,-25
+ a25,12 -30 0,1 100,-50 l 50,-25
+ a25,25 -30 0,1 100,-50 l 50,-25
+ a25,37 -30 0,1 100,-50 l 50,-25
+ a25,50 -30 0,1 100,-50 l 50,-25"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10"/>
+ </group>
+
+</vector>
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_2.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_2.xml
new file mode 100644
index 0000000..c8d8915
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_2.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200">
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="100"
+ android:pivotY="100"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="0.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_3.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_3.xml
new file mode 100644
index 0000000..6e23d2b9
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_3.xml
@@ -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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="200"
+ android:viewportWidth="200">
+
+ <group>
+ <path
+ android:name="background1"
+ android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ <path
+ android:name="background2"
+ android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
+ android:fillColor="#FF000000"/>
+ </group>
+ <group
+ android:pivotX="0"
+ android:pivotY="0"
+ android:rotation="90"
+ android:scaleX="0.75"
+ android:scaleY="0.5"
+ android:translateX="100.0"
+ android:translateY="100.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 100,10 v 90 M 10,100 h 90"
+ android:fillColor="#00000000"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="10"/>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_4.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_4.xml
new file mode 100644
index 0000000..d1d648e
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_4.xml
@@ -0,0 +1,68 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400">
+
+ <group android:name="backgroundGroup">
+ <path
+ android:name="background1"
+ android:fillColor="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z"/>
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20"/>
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0">
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20"/>
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0">
+ <group android:name="scaleGroup">
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"/>
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_5.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_5.xml
new file mode 100644
index 0000000..e4eb76c
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_5.xml
@@ -0,0 +1,81 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400">
+
+ <group android:name="backgroundGroup">
+ <path
+ android:name="background1"
+ android:fillColor="#80000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#80000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z"/>
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 150 M 0,0 h 150"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20"/>
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0">
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20"/>
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0">
+ <group android:name="scaleGroup">
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"/>
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0">
+ <group android:name="scaleGroup">
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"/>
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_6.xml b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_6.xml
new file mode 100644
index 0000000..3ccc070
--- /dev/null
+++ b/graphics/drawable/static/tests/res/drawable/vector_icon_transformation_6.xml
@@ -0,0 +1,85 @@
+<!--
+ 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:height="64dp"
+ android:width="64dp"
+ android:viewportHeight="400"
+ android:viewportWidth="400"
+ android:alpha="0.5">
+
+ <group android:name="backgroundGroup">
+ <path
+ android:name="background1"
+ android:fillColor="#FF000000"
+ android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z"/>
+ <path
+ android:name="background2"
+ android:fillColor="#FF000000"
+ android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z"/>
+ </group>
+ <group
+ android:name="translateToCenterGroup"
+ android:translateX="50.0"
+ android:translateY="90.0">
+ <path
+ android:name="twoLines"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FFFF0000"
+ android:strokeWidth="20"/>
+
+ <group
+ android:name="rotationGroup"
+ android:pivotX="0.0"
+ android:pivotY="0.0"
+ android:rotation="-45.0">
+ <path
+ android:name="twoLines1"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF00FF00"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.5"/>
+
+ <group
+ android:name="translateGroup"
+ android:translateX="130.0"
+ android:translateY="160.0">
+ <group android:name="scaleGroup">
+ <path
+ android:name="twoLines3"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.25"/>
+ </group>
+ </group>
+
+ <group
+ android:name="translateGroupHalf"
+ android:translateX="65.0"
+ android:translateY="80.0">
+ <group android:name="scaleGroup">
+ <path
+ android:name="twoLines2"
+ android:pathData="M 0,0 v 100 M 0,0 h 100"
+ android:strokeColor="#FF0000FF"
+ android:strokeWidth="20"
+ android:strokeAlpha="0.5"/>
+ </group>
+ </group>
+ </group>
+ </group>
+
+</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/values/colors.xml b/graphics/drawable/static/tests/res/values/colors.xml
similarity index 100%
rename from graphics/drawable/teststatic/res/values/colors.xml
rename to graphics/drawable/static/tests/res/values/colors.xml
diff --git a/graphics/drawable/static/tests/res/values/strings.xml b/graphics/drawable/static/tests/res/values/strings.xml
new file mode 100644
index 0000000..c279cd2
--- /dev/null
+++ b/graphics/drawable/static/tests/res/values/strings.xml
@@ -0,0 +1,35 @@
+<?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>
+
+ <string name="twoLinePathData">"M 0,0 v 100 M 0,0 h 100"</string>
+ <string name="triangle">"M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
+ <string name="rectangle">"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
+ <string name="rectangle2">"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
+ <string name="equal2">"M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
+ <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0
+ 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25
+ 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"
+ </string>
+ <string name="heart">"m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3
+ -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707
+ 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"
+ </string>
+ <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
+ <string name="triangle100">"M 100, 0 l 0, 100, -100, 0 z"</string>
+</resources>
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
new file mode 100644
index 0000000..72af9bd
--- /dev/null
+++ b/graphics/drawable/static/tests/src/android/support/graphics/drawable/tests/VectorDrawableTest.java
@@ -0,0 +1,349 @@
+/*
+ * 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.graphics.drawable.tests;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.Resources.Theme;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.graphics.drawable.VectorDrawableCompat;
+import android.support.graphics.drawable.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import static org.junit.Assert.*;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class VectorDrawableTest {
+ private static final String LOGTAG = "VectorDrawableTest";
+
+ private static final int[] ICON_RES_IDS = new int[]{
+ R.drawable.vector_icon_create,
+ R.drawable.vector_icon_delete,
+ R.drawable.vector_icon_heart,
+ R.drawable.vector_icon_schedule,
+ R.drawable.vector_icon_settings,
+ R.drawable.vector_icon_random_path_1,
+ R.drawable.vector_icon_random_path_2,
+ R.drawable.vector_icon_repeated_cq,
+ R.drawable.vector_icon_repeated_st,
+ R.drawable.vector_icon_repeated_a_1,
+ R.drawable.vector_icon_repeated_a_2,
+ R.drawable.vector_icon_clip_path_1,
+ R.drawable.vector_icon_transformation_1,
+ R.drawable.vector_icon_transformation_4,
+ R.drawable.vector_icon_transformation_5,
+ R.drawable.vector_icon_transformation_6,
+ R.drawable.vector_icon_render_order_1,
+ R.drawable.vector_icon_render_order_2,
+ R.drawable.vector_icon_stroke_1,
+ R.drawable.vector_icon_stroke_2,
+ R.drawable.vector_icon_stroke_3,
+ R.drawable.vector_icon_scale_1,
+ };
+
+ private static final int[] GOLDEN_IMAGES = new int[]{
+ R.drawable.vector_icon_create_golden,
+ R.drawable.vector_icon_delete_golden,
+ R.drawable.vector_icon_heart_golden,
+ R.drawable.vector_icon_schedule_golden,
+ R.drawable.vector_icon_settings_golden,
+ R.drawable.vector_icon_random_path_1_golden,
+ R.drawable.vector_icon_random_path_2_golden,
+ R.drawable.vector_icon_repeated_cq_golden,
+ R.drawable.vector_icon_repeated_st_golden,
+ R.drawable.vector_icon_repeated_a_1_golden,
+ R.drawable.vector_icon_repeated_a_2_golden,
+ R.drawable.vector_icon_clip_path_1_golden,
+ R.drawable.vector_icon_transformation_1_golden,
+ R.drawable.vector_icon_transformation_4_golden,
+ R.drawable.vector_icon_transformation_5_golden,
+ R.drawable.vector_icon_transformation_6_golden,
+ R.drawable.vector_icon_render_order_1_golden,
+ R.drawable.vector_icon_render_order_2_golden,
+ R.drawable.vector_icon_stroke_1_golden,
+ R.drawable.vector_icon_stroke_2_golden,
+ R.drawable.vector_icon_stroke_3_golden,
+ R.drawable.vector_icon_scale_1_golden,
+ };
+
+ private static final int TEST_ICON = R.drawable.vector_icon_create;
+
+ private static final int IMAGE_WIDTH = 64;
+ private static final int IMAGE_HEIGHT = 64;
+ // A small value is actually making sure that the values are matching
+ // exactly with the golden image.
+ // We can increase the threshold if the Skia is drawing with some variance
+ // on different devices. So far, the tests show they are matching correctly.
+ private static final float PIXEL_ERROR_THRESHOLD = 0.3f;
+ private static final float PIXEL_DIFF_COUNT_THRESHOLD = 0.1f;
+ private static final float PIXEL_DIFF_THRESHOLD = 0.025f;
+
+ private static final boolean DBG_DUMP_PNG = false;
+
+ private Context mContext;
+ private Resources mResources;
+ private VectorDrawableCompat mVectorDrawable;
+ private Bitmap mBitmap;
+ private Canvas mCanvas;
+ private Theme mTheme;
+
+ @Before
+ public void setup() {
+ final int width = IMAGE_WIDTH;
+ final int height = IMAGE_HEIGHT;
+
+ mBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ mCanvas = new Canvas(mBitmap);
+
+ mContext = InstrumentationRegistry.getContext();
+ mResources = mContext.getResources();
+ mTheme = mContext.getTheme();
+ }
+
+ @Test
+ public void testSimpleVectorDrawables() throws Exception {
+ verifyVectorDrawables(ICON_RES_IDS, GOLDEN_IMAGES, null);
+ }
+
+ private void verifyVectorDrawables(int[] resIds, int[] goldenImages, int[] stateSet)
+ throws XmlPullParserException, IOException {
+ for (int i = 0; i < resIds.length; i++) {
+ // Setup VectorDrawable from xml file and draw into the bitmap.
+ mVectorDrawable = VectorDrawableCompat.create(mResources, resIds[i], mTheme);
+ mVectorDrawable.setBounds(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT);
+ if (stateSet != null) {
+ mVectorDrawable.setState(stateSet);
+ }
+
+ mBitmap.eraseColor(0);
+ mVectorDrawable.draw(mCanvas);
+
+ if (DBG_DUMP_PNG) {
+ saveVectorDrawableIntoPNG(mBitmap, resIds, i, stateSet);
+ } else {
+ // Start to compare
+ Bitmap golden = BitmapFactory.decodeResource(mResources, goldenImages[i]);
+ compareImages(mBitmap, golden, mResources.getString(resIds[i]));
+ }
+ }
+ }
+
+ // This is only for debugging or golden image (re)generation purpose.
+ private void saveVectorDrawableIntoPNG(Bitmap bitmap, int[] resIds, int index, int[] stateSet)
+ throws IOException {
+ // Save the image to the disk.
+ FileOutputStream out = null;
+ try {
+ String outputFolder = "/sdcard/temp/";
+ File folder = new File(outputFolder);
+ if (!folder.exists()) {
+ folder.mkdir();
+ }
+ String originalFilePath = mResources.getString(resIds[index]);
+ File originalFile = new File(originalFilePath);
+ String fileFullName = originalFile.getName();
+ String fileTitle = fileFullName.substring(0, fileFullName.lastIndexOf("."));
+ String stateSetTitle = getTitleForStateSet(stateSet);
+ String outputFilename = outputFolder + fileTitle + "_golden" + stateSetTitle + ".png";
+ File outputFile = new File(outputFilename);
+ if (!outputFile.exists()) {
+ outputFile.createNewFile();
+ }
+
+ out = new FileOutputStream(outputFile, false);
+ bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
+ Log.v(LOGTAG, "Write test No." + index + " to file successfully.");
+ } catch (Exception e) {
+ e.printStackTrace();
+ } finally {
+ if (out != null) {
+ out.close();
+ }
+ }
+ }
+
+ /**
+ * Generates an underline-delimited list of states in a given state set.
+ * <p/>
+ * For example, the array {@code {R.attr.state_pressed}} would return
+ * {@code "_pressed"}.
+ *
+ * @param stateSet a state set
+ * @return a string representing the state set, or the empty string if the
+ * state set is empty or {@code null}
+ */
+ private String getTitleForStateSet(int[] stateSet) {
+ if (stateSet == null || stateSet.length == 0) {
+ return "";
+ }
+
+ final StringBuilder builder = new StringBuilder();
+ for (int i = 0; i < stateSet.length; i++) {
+ builder.append('_');
+
+ final String state = mResources.getResourceName(stateSet[i]);
+ final int stateIndex = state.indexOf("state_");
+ if (stateIndex >= 0) {
+ builder.append(state.substring(stateIndex + 6));
+ } else {
+ builder.append(stateSet[i]);
+ }
+ }
+
+ return builder.toString();
+ }
+
+ private void compareImages(Bitmap ideal, Bitmap given, String filename) {
+ int idealWidth = ideal.getWidth();
+ int idealHeight = ideal.getHeight();
+
+ assertTrue(idealWidth == given.getWidth());
+ assertTrue(idealHeight == given.getHeight());
+
+ int totalDiffPixelCount = 0;
+ float totalPixelCount = idealWidth * idealHeight;
+ for (int x = 0; x < idealWidth; x++) {
+ for (int y = 0; y < idealHeight; y++) {
+ int idealColor = ideal.getPixel(x, y);
+ int givenColor = given.getPixel(x, y);
+ if (idealColor == givenColor)
+ continue;
+
+ float totalError = 0;
+ totalError += Math.abs(Color.red(idealColor) - Color.red(givenColor));
+ totalError += Math.abs(Color.green(idealColor) - Color.green(givenColor));
+ totalError += Math.abs(Color.blue(idealColor) - Color.blue(givenColor));
+ totalError += Math.abs(Color.alpha(idealColor) - Color.alpha(givenColor));
+
+ if ((totalError / 1024.0f) >= PIXEL_ERROR_THRESHOLD) {
+ fail((filename + ": totalError is " + totalError));
+ }
+
+ if ((totalError / 1024.0f) >= PIXEL_DIFF_THRESHOLD) {
+ totalDiffPixelCount++;
+ }
+ }
+ }
+ if ((totalDiffPixelCount / totalPixelCount) >= PIXEL_DIFF_COUNT_THRESHOLD) {
+ fail((filename + ": totalDiffPixelCount is " + totalDiffPixelCount));
+ }
+
+ }
+
+ @Test
+ public void testGetChangingConfigurations() {
+ VectorDrawableCompat vectorDrawable =
+ VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
+ Drawable.ConstantState constantState = vectorDrawable.getConstantState();
+
+ // default
+ assertEquals(0, constantState.getChangingConfigurations());
+ assertEquals(0, vectorDrawable.getChangingConfigurations());
+
+ // change the drawable's configuration does not affect the state's configuration
+ vectorDrawable.setChangingConfigurations(0xff);
+ assertEquals(0xff, vectorDrawable.getChangingConfigurations());
+ assertEquals(0, constantState.getChangingConfigurations());
+
+ // the state's configuration get refreshed
+ constantState = vectorDrawable.getConstantState();
+ assertEquals(0xff, constantState.getChangingConfigurations());
+
+ // set a new configuration to drawable
+ vectorDrawable.setChangingConfigurations(0xff00);
+ assertEquals(0xff, constantState.getChangingConfigurations());
+ assertEquals(0xffff, vectorDrawable.getChangingConfigurations());
+ }
+
+ @Test
+ public void testGetConstantState() {
+ VectorDrawableCompat vectorDrawable =
+ VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
+ Drawable.ConstantState constantState = vectorDrawable.getConstantState();
+ assertNotNull(constantState);
+ assertEquals(0, constantState.getChangingConfigurations());
+
+ vectorDrawable.setChangingConfigurations(1);
+ constantState = vectorDrawable.getConstantState();
+ assertNotNull(constantState);
+ assertEquals(1, constantState.getChangingConfigurations());
+ }
+
+ @Test
+ public void testMutate() {
+ VectorDrawableCompat d1 =
+ VectorDrawableCompat.create(mResources, TEST_ICON, mTheme);
+ VectorDrawableCompat d2 =
+ (VectorDrawableCompat) d1.getConstantState().newDrawable(mResources);
+ VectorDrawableCompat d3 =
+ (VectorDrawableCompat) d1.getConstantState().newDrawable(mResources);
+
+ // d1 will be mutated, while d2 / d3 will not.
+ int originalAlpha = d2.getAlpha();
+
+ d1.setAlpha(0x80);
+ assertEquals(0x80, d1.getAlpha());
+ assertEquals(0x80, d2.getAlpha());
+ assertEquals(0x80, d3.getAlpha());
+
+ d1.mutate();
+ d1.setAlpha(0x40);
+ assertEquals(0x40, d1.getAlpha());
+ assertEquals(0x80, d2.getAlpha());
+ assertEquals(0x80, d3.getAlpha());
+
+ d2.setAlpha(0x20);
+ assertEquals(0x40, d1.getAlpha());
+ assertEquals(0x20, d2.getAlpha());
+ assertEquals(0x20, d3.getAlpha());
+
+ d2.setAlpha(originalAlpha);
+ }
+
+ public void testBounds() {
+ VectorDrawableCompat vectorDrawable =
+ VectorDrawableCompat.create(mResources, R.drawable.vector_icon_delete, mTheme);
+ Rect expectedRect = new Rect(0, 0, 100, 100);
+ vectorDrawable.setBounds(0, 0, 100, 100);
+ Rect rect = vectorDrawable.getBounds();
+ assertEquals("Bounds should be same value for setBound(int ...)", rect, expectedRect);
+
+ vectorDrawable.setBounds(expectedRect);
+ rect = vectorDrawable.getBounds();
+ assertEquals("Bounds should be same value for setBound(Rect)", rect, expectedRect);
+
+ vectorDrawable.copyBounds(rect);
+ assertEquals("Bounds should be same value for copyBounds", rect, expectedRect);
+ }
+}
diff --git a/graphics/drawable/testanimated/Android.mk b/graphics/drawable/testanimated/Android.mk
deleted file mode 100644
index af26f87..0000000
--- a/graphics/drawable/testanimated/Android.mk
+++ /dev/null
@@ -1,38 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR = \
- $(LOCAL_PATH)/res \
- frameworks/support/graphics/drawable/res \
-
-LOCAL_PACKAGE_NAME := AndroidAnimatedVectorDrawableTests
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v11-animatedvectordrawable android-support-v4
-
-LOCAL_AAPT_FLAGS += --auto-add-overlay \
- --extra-packages android.support.graphics.drawable \
- --no-version-vectors
-
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_PACKAGE)
diff --git a/graphics/drawable/testanimated/AndroidManifest.xml b/graphics/drawable/testanimated/AndroidManifest.xml
deleted file mode 100644
index 16171f2..0000000
--- a/graphics/drawable/testanimated/AndroidManifest.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?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"
- package="android.support.test.vectordrawable" >
-
- <uses-sdk android:minSdkVersion="11" />
-
- <application android:icon="@drawable/app_sample_code" android:label="AnimatedVectorDrawableCompatTest" >
- <activity android:name="android.support.test.vectordrawable.TestAVDActivity" />
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </application>
-
-</manifest>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml b/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml
deleted file mode 100644
index 2463a89..0000000
--- a/graphics/drawable/testanimated/res/anim/alpha_animation_progress_bar.xml
+++ /dev/null
@@ -1,24 +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.
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <objectAnimator
- android:duration="3350"
- android:propertyName="alpha"
- android:valueFrom="1"
- android:valueTo="0.2" />
-
-</set>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml b/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml
deleted file mode 100644
index 36c297f..0000000
--- a/graphics/drawable/testanimated/res/anim/animation_grouping_1_01.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="3300"
- android:propertyName="rotation"
- android:valueFrom="0"
- android:valueTo="450" />
diff --git a/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml b/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml
deleted file mode 100644
index 388c759..0000000
--- a/graphics/drawable/testanimated/res/anim/trim_path_animation_progress_bar.xml
+++ /dev/null
@@ -1,45 +0,0 @@
-<?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.
--->
-
-<set xmlns:android="http://schemas.android.com/apk/res/android" >
-
- <objectAnimator
- android:duration="1300"
- android:interpolator="@android:anim/linear_interpolator"
- android:propertyName="trimPathStart"
- android:repeatCount="-1"
- android:valueFrom="0"
- android:valueTo="0.75"
- android:valueType="floatType" />
- <objectAnimator
- android:duration="1300"
- android:interpolator="@android:anim/linear_interpolator"
- android:propertyName="trimPathEnd"
- android:repeatCount="-1"
- android:valueFrom="0.25"
- android:valueTo="1.0"
- android:valueType="floatType" />
- <objectAnimator
- android:duration="1300"
- android:interpolator="@android:anim/linear_interpolator"
- android:propertyName="trimPathOffset"
- android:repeatCount="-1"
- android:valueFrom="0"
- android:valueTo="0.25"
- android:valueType="floatType" />
-
-</set>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml b/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml
deleted file mode 100644
index dac981b..0000000
--- a/graphics/drawable/testanimated/res/drawable/animation_vector_drawable_grouping_1.xml
+++ /dev/null
@@ -1,26 +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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/vector_drawable_grouping_1" >
-
- <target
- android:name="sun"
- android:animation="@anim/animation_grouping_1_01" />
- <target
- android:name="earth"
- android:animation="@anim/animation_grouping_1_01" />
-
-</animated-vector>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml b/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml
deleted file mode 100644
index 2944dc2..0000000
--- a/graphics/drawable/testanimated/res/drawable/animation_vector_progress_bar.xml
+++ /dev/null
@@ -1,25 +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.
--->
-<animated-vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:drawable="@drawable/vector_drawable_progress_bar" >
-
- <target
- android:name="pie1"
- android:animation="@anim/trim_path_animation_progress_bar" />
- <target
- android:name="root_bar"
- android:animation="@anim/alpha_animation_progress_bar" />
-</animated-vector>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/drawable/app_sample_code.png b/graphics/drawable/testanimated/res/drawable/app_sample_code.png
deleted file mode 100755
index 66a1984..0000000
--- a/graphics/drawable/testanimated/res/drawable/app_sample_code.png
+++ /dev/null
Binary files differ
diff --git a/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml b/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml
deleted file mode 100644
index 06f098e..0000000
--- a/graphics/drawable/testanimated/res/drawable/vector_drawable_grouping_1.xml
+++ /dev/null
@@ -1,52 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="256"
- android:viewportWidth="256" >
-
- <group
- android:name="shape_layer_1"
- android:translateX="128"
- android:translateY="128" >
- <group android:name="sun" >
- <path
- android:name="ellipse_path_1"
- android:fillColor="#ffff8000"
- android:pathData="m -25 0 a 25,25 0 1,0 50,0 a 25,25 0 1,0 -50,0" />
-
- <group
- android:name="earth"
- android:translateX="75" >
- <path
- android:name="ellipse_path_1_1"
- android:fillColor="#ff5656ea"
- android:pathData="m -10 0 a 10,10 0 1,0 20,0 a 10,10 0 1,0 -20,0" />
-
- <group
- android:name="moon"
- android:translateX="25" >
- <path
- android:name="ellipse_path_1_2"
- android:fillColor="#ffadadad"
- android:pathData="m -5 0 a 5,5 0 1,0 10,0 a 5,5 0 1,0 -10,0" />
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml b/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml
deleted file mode 100644
index 535265e..0000000
--- a/graphics/drawable/testanimated/res/drawable/vector_drawable_progress_bar.xml
+++ /dev/null
@@ -1,49 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="64"
- android:viewportWidth="64"
- android:name="root_bar" >
-
- <group
- android:name="root"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="0"
- android:translateX="32.0"
- android:translateY="32.0" >
- <group
- android:name="rotationGroup"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="0" >
- <path
- android:name="pie1"
- android:fillColor="#00000000"
- android:pathData="M0, 0 m 0, -9.5 a 9.5,9.5 0 1,1 0,19 a 9.5,9.5 0 1,1 0,-19"
- android:strokeColor="#FF00FFFF"
- android:strokeLineCap="round"
- android:strokeLineJoin="miter"
- android:strokeWidth="2"
- android:trimPathEnd="0.1"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/res/values/strings.xml b/graphics/drawable/testanimated/res/values/strings.xml
deleted file mode 100644
index c5451c88..0000000
--- a/graphics/drawable/testanimated/res/values/strings.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?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>
-
- <string name="twoLinePathData">"M 0,0 v 100 M 0,0 h 100"</string>
- <string name="triangle"> "M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
- <string name="rectangle">"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
- <string name="rectangle2">"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
- <string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
- <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
- <string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
- <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
-</resources>
\ No newline at end of file
diff --git a/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java b/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java
deleted file mode 100644
index c63c69f..0000000
--- a/graphics/drawable/testanimated/src/android/support/test/vectordrawable/TestAVDActivity.java
+++ /dev/null
@@ -1,98 +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.test.vectordrawable;
-
-import android.animation.ObjectAnimator;
-import android.app.Activity;
-import android.content.res.Resources;
-import android.os.Bundle;
-import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.text.DecimalFormat;
-
-public class TestAVDActivity extends Activity implements View.OnClickListener{
- private static final String LOG_TAG = "TestActivity";
-
- private static final String LOGCAT = "VectorDrawable1";
- protected int[] icon = {
- R.drawable.animation_vector_drawable_grouping_1,
- R.drawable.animation_vector_progress_bar,
- };
-
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- ObjectAnimator oa = new ObjectAnimator();
- super.onCreate(savedInstanceState);
- ScrollView scrollView = new ScrollView(this);
- LinearLayout container = new LinearLayout(this);
- scrollView.addView(container);
- container.setOrientation(LinearLayout.VERTICAL);
- Resources res = this.getResources();
- container.setBackgroundColor(0xFF888888);
- AnimatedVectorDrawableCompat []d = new AnimatedVectorDrawableCompat[icon.length];
- long time = android.os.SystemClock.currentThreadTimeMillis();
- for (int i = 0; i < icon.length; i++) {
- d[i] = AnimatedVectorDrawableCompat.create(this, icon[i]);
- }
- time = android.os.SystemClock.currentThreadTimeMillis()-time;
- TextView t = new TextView(this);
- DecimalFormat df = new DecimalFormat("#.##");
- t.setText("avgL=" + df.format(time / (icon.length)) + " ms");
- container.addView(t);
-
- addDrawableButtons(container, d);
-
- // Now test constant state and mutate a bit.
- AnimatedVectorDrawableCompat []copies = new AnimatedVectorDrawableCompat[3];
- copies[0] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable();
- copies[1] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable();
- copies[2] = (AnimatedVectorDrawableCompat) d[0].getConstantState().newDrawable();
- copies[0].setAlpha(128);
-
- // Expect to see the copies[0, 1] are showing alpha 128, and [2] are showing 255.
- copies[2].mutate();
- copies[2].setAlpha(255);
-
- addDrawableButtons(container, copies);
-
- setContentView(scrollView);
- }
-
- private void addDrawableButtons(LinearLayout container, AnimatedVectorDrawableCompat[] d) {
- for (int i = 0; i < d.length; i++) {
- Button button = new Button(this);
- button.setWidth(200);
- button.setHeight(200);
- button.setBackgroundDrawable(d[i]);
- container.addView(button);
- button.setOnClickListener(this);
- }
- }
-
- @Override
- public void onClick(View v) {
- AnimatedVectorDrawableCompat d = (AnimatedVectorDrawableCompat) v.getBackground();
- d.start();
- }
-}
diff --git a/graphics/drawable/teststatic/Android.mk b/graphics/drawable/teststatic/Android.mk
deleted file mode 100644
index 4c4a7ba..0000000
--- a/graphics/drawable/teststatic/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR = \
- $(LOCAL_PATH)/res \
- frameworks/support/graphics/drawable/res \
-
-LOCAL_PACKAGE_NAME := AndroidVectorDrawableTests
-
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-vectordrawable android-support-v4
-
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.graphics.drawable \
- --no-version-vectors
-
-include $(BUILD_PACKAGE)
-
diff --git a/graphics/drawable/teststatic/AndroidManifest.xml b/graphics/drawable/teststatic/AndroidManifest.xml
deleted file mode 100644
index 39ac8ba..0000000
--- a/graphics/drawable/teststatic/AndroidManifest.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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"
- package="android.support.test.vectordrawable" >
-
- <uses-sdk android:minSdkVersion="7"/>
-
- <application android:icon="@drawable/app_sample_code" android:label="VectorDrawableCompatTest" >
- <activity android:name="android.support.test.vectordrawable.TestActivity" />
-
- <intent-filter>
- <action android:name="android.intent.action.MAIN" />
-
- <category android:name="android.intent.category.LAUNCHER" />
- </intent-filter>
- </application>
-
-</manifest>
diff --git a/graphics/drawable/teststatic/res/drawable/app_sample_code.png b/graphics/drawable/teststatic/res/drawable/app_sample_code.png
deleted file mode 100755
index 66a1984..0000000
--- a/graphics/drawable/teststatic/res/drawable/app_sample_code.png
+++ /dev/null
Binary files differ
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml
deleted file mode 100644
index 286b487..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable01.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="480"
- android:viewportWidth="480" >
-
- <group>
- <path
- android:name="box1"
- android:pathData="m20,200l100,90l180-180l-35-35l-145,145l-60-60l-40,40z"
- android:strokeLineCap="round"
- android:strokeLineJoin="round" />
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml
deleted file mode 100644
index 7567887..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable02.xml
+++ /dev/null
@@ -1,32 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp" android:viewportWidth="320"
- android:viewportHeight="320">
- <group
- android:rotation="180"
- android:pivotX="70"
- android:pivotY="120">
- <path
- android:name="house"
- android:pathData="M 130,225 L 130,115 L 130,115 L 70,15 L 10,115 L 10,115 L 10,225 z"
- android:fillColor="#ff440000"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10"
- android:trimPathStart=".1"
- android:trimPathEnd=".9"/>
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml
deleted file mode 100644
index 454468a..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable03.xml
+++ /dev/null
@@ -1,79 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="12.25"
- android:viewportWidth="7.30625"
- android:width="64dp" >
-
- <group
- android:pivotX="3.65"
- android:pivotY="6.125"
- android:rotation="-30" >
- <clip-path
- android:name="clip1"
- android:pathData="
- M 0, 6.125
- l 7.3, 0
- l 0, 12.25
- l-7.3, 0
- z" />
-
- <group
- android:pivotX="3.65"
- android:pivotY="6.125"
- android:rotation="30" >
- <path
- android:name="one"
- android:fillColor="#ff88ff"
- android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0-6.671875-2.109375,0.421875 0.0-1.078125
- l 2.09375-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
- l-5.046875,0.0 0.0-1.0Z" />
- </group>
- </group>
- <group
- android:pivotX="3.65"
- android:pivotY="6.125"
- android:rotation="-30" >
- <clip-path
- android:name="clip2"
- android:pathData="
- M 0, 0
- l 7.3, 0
- l 0, 6.125
- l-7.3, 0
- z" />
-
- <group
- android:pivotX="3.65"
- android:pivotY="6.125"
- android:rotation="30" >
- <path
- android:name="two"
- android:fillColor="#ff88ff"
- android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0-5.5625,0.0 0.0-1.0q 0.671875-0.6875 1.828125-1.859375
- q 1.1718752-1.1875 1.4687502-1.53125 0.578125-0.625 0.796875-1.0625
- q 0.234375-0.453125 0.234375-0.875 0.0-0.703125-0.5-1.140625
- q-0.484375-0.4375-1.2656252-0.4375-0.5625,0.0-1.1875,0.1875
- q-0.609375,0.1875-1.3125,0.59375l 0.0-1.203125q 0.71875-0.28125 1.328125-0.421875
- q 0.625-0.15625 1.140625-0.15625 1.3593752,0.0 2.1718752,0.6875
- q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125-0.203125,1.015625
- q-0.203125,0.484375-0.734375,1.140625-0.15625,0.171875-0.9375,0.984375
- q-0.78125024,0.8125-2.2187502,2.265625Z" />
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml
deleted file mode 100644
index e6658a6..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable04.xml
+++ /dev/null
@@ -1,61 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:autoMirrored="true"
- android:height="64dp"
- android:viewportHeight="12.25"
- android:viewportWidth="7.30625"
- android:width="64dp" >
-
- <group>
- <clip-path
- android:name="clip1"
- android:pathData="
- M 3.65, 6.125
- m-.001, 0
- a .001,.001 0 1,0 .002,0
- a .001,.001 0 1,0-.002,0z" />
-
- <path
- android:name="one"
- android:fillColor="#ff88ff"
- android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0-6.671875-2.109375,0.421875 0.0-1.078125
- l 2.09375-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
- l-5.046875,0.0 0.0-1.0Z" />
- </group>
- <group>
- <clip-path
- android:name="clip2"
- android:pathData="
- M 3.65, 6.125
- m-6, 0
- a 6,6 0 1,0 12,0
- a 6,6 0 1,0-12,0z" />
-
- <path
- android:name="two"
- android:fillColor="#ff88ff"
- android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0-5.5625,0.0 0.0-1.0q 0.671875-0.6875 1.828125-1.859375
- q 1.1718752-1.1875 1.4687502-1.53125 0.578125-0.625 0.796875-1.0625
- q 0.234375-0.453125 0.234375-0.875 0.0-0.703125-0.5-1.140625
- q-0.484375-0.4375-1.2656252-0.4375-0.5625,0.0-1.1875,0.1875
- q-0.609375,0.1875-1.3125,0.59375l 0.0-1.203125q 0.71875-0.28125 1.328125-0.421875
- q 0.625-0.15625 1.140625-0.15625 1.3593752,0.0 2.1718752,0.6875
- q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125-0.203125,1.015625
- q-0.203125,0.484375-0.734375,1.140625-0.15625,0.171875-0.9375,0.984375
- q-0.78125024,0.8125-2.2187502,2.265625Z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml
deleted file mode 100644
index d1723dc..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable05.xml
+++ /dev/null
@@ -1,43 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="12.25"
- android:viewportWidth="7.30625" >
-
- <group>
- <path
- android:name="one"
- android:fillColor="#ffff00"
- android:pathData="M 1.215625,9.5l 1.9375,0.0 0.0-6.671875-2.109375,0.421875 0.0-1.078125
- l 2.09375-0.421875 1.1874998,0.0 0.0,7.75 1.9375,0.0 0.0,1.0
- l-5.046875,0.0 0.0-1.0Z" />
- <path
- android:name="two"
- android:fillColor="#ffff00"
- android:fillAlpha="0"
- android:pathData="M 2.534375,9.6875l 4.140625,0.0 0.0,1.0-5.5625,0.0 0.0-1.0q 0.671875-0.6875 1.828125-1.859375
- q 1.1718752-1.1875 1.4687502-1.53125 0.578125-0.625 0.796875-1.0625
- q 0.234375-0.453125 0.234375-0.875 0.0-0.703125-0.5-1.140625
- q-0.484375-0.4375-1.2656252-0.4375-0.5625,0.0-1.1875,0.1875
- q-0.609375,0.1875-1.3125,0.59375l 0.0-1.203125q 0.71875-0.28125 1.328125-0.421875
- q 0.625-0.15625 1.140625-0.15625 1.3593752,0.0 2.1718752,0.6875
- q 0.8125,0.671875 0.8125,1.8125 0.0,0.53125-0.203125,1.015625
- q-0.203125,0.484375-0.734375,1.140625-0.15625,0.171875-0.9375,0.984375
- q-0.78125024,0.8125-2.2187502,2.265625Z" />
- </group>
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml
deleted file mode 100644
index 4b530fd..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable06.xml
+++ /dev/null
@@ -1,48 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp"
- android:viewportWidth="700"
- android:viewportHeight="700">
-
- <group>
- <path android:pathData="M 569.374 461.472L 569.374 160.658L 160.658 160.658L 160.658 461.472L 569.374 461.472z"
- android:name="path2451"
- android:fillColor="#00000000"
- android:strokeColor="#FF000000"
- android:strokeWidth="30.65500000000000"/>
- <path android:pathData="M 365.015 311.066"
- android:name="path2453"
- android:fillColor="#00000000"
- android:strokeColor="#FF000000"
- android:strokeWidth="30.655000000000001"/>
- <path android:pathData="M 164.46 164.49L 340.78 343.158C 353.849 356.328 377.63 356.172 390.423 343.278L 566.622 165.928"
- android:name="path2455"
- android:strokeColor="#FF000000"
- android:fillColor="#FFFFFFFF"
- android:strokeWidth="30.655000000000001"/>
- <path android:pathData="M 170.515 451.566L 305.61 313.46"
- android:name="path2457"
- android:fillColor="#00000000"
- android:strokeColor="#000000"
- android:strokeWidth="30.655000000000001"/>
- <path android:pathData="M 557.968 449.974L 426.515 315.375"
- android:name="path2459"
- android:fillColor="#00000000"
- android:strokeColor="#000000"
- android:strokeWidth="30.655000000000001"/>
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml
deleted file mode 100644
index bbf2451..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable07.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp" android:viewportWidth="140"
- android:viewportHeight="110">
-
- <group>
- <path
- android:name="back"
- android:pathData="M 20,55 l 35.3-35.3 7.07,7.07-35.3,35.3 z
- M 27,50 l 97,0 0,10-97,0 z
- M 20,55 l 7.07-7.07 35.3,35.3-7.07,7.07 z"
- android:fillColor="#ffffffff"
- />
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml
deleted file mode 100644
index e5b59df..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable08.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp" android:viewportWidth="600"
- android:viewportHeight="600">
-
- <group>
- <path
- android:name="pie1"
- android:pathData="M535.441,412.339A280.868,280.868 0 1,1 536.186,161.733L284.493,286.29Z"
- android:fillColor="#ffffcc00"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="1"/>
- </group>
-
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml
deleted file mode 100644
index ce2441d..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable09.xml
+++ /dev/null
@@ -1,32 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200" >
-
- <group
- android:pivotX="100"
- android:pivotY="100"
- android:rotation="90">
- <path
- android:name="house"
- android:fillColor="#ffffffff"
- android:pathData="M 100,20 l 0,0 0,140-80,0 z M 100,20 l 0,0 80,140-80,0 z"/>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml
deleted file mode 100644
index 935d4a5..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable10.xml
+++ /dev/null
@@ -1,42 +0,0 @@
-<?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:height="64dp"
- android:width="64dp"
- android:viewportWidth="200"
- android:viewportHeight="200">
-
- <group>
- <path
- android:name="bar3"
- android:fillColor="#FFFFFFFF"
- android:pathData="M49.001,60c-5.466,0-9.899,4.478-9.899,10s4.434,10,9.899,10c5.468,0,9.899-4.478,9.899-10S54.469,60,49.001,60z" />
- <path
- android:name="bar2"
- android:fillColor="#FFFFFFFF"
- android:pathData="M28.001,48.787l7,7.07c7.731-7.811,20.269-7.81,28.001,0l6.999-7.07C58.403,37.071,39.599,37.071,28.001,48.787z" />
- <path
- android:name="bar1"
- android:fillColor="#FF555555"
- android:pathData="M14.001,34.645 L21,41.716c15.464-15.621,40.536-15.621,56,0l7.001-7.071C64.672,15.119,33.33,15.119,14.001,34.645z" />
- <path
- android:name="bar0"
- android:fillColor="#FF555555"
- android:pathData="M0,20.502l6.999,7.071 c23.196-23.431,60.806-23.431,84.002,0L98,20.503C70.938-6.834,27.063-6.834,0,20.502z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml
deleted file mode 100644
index 05f481b..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable11.xml
+++ /dev/null
@@ -1,35 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="80"
- android:viewportWidth="40" >
-
- <group>
- <path
- android:name="battery"
- android:fillColor="#3388ff"
- android:pathData="M 20.28125,2.0000002 C 17.352748,2.0000002 15,4.3527485 15,7.2812502 L 15,8.0000002 L 13.15625,8.0000002 C 9.7507553,8.0000002 7,10.750759 7,14.15625 L 7,39.84375 C 7,43.24924 9.7507558,46 13.15625,46 L 33.84375,46 C 37.249245,46 39.999999,43.24924 40,39.84375 L 40,14.15625 C 40,10.75076 37.249243,8.0000002 33.84375,8.0000002 L 32,8.0000002 L 32,7.2812502 C 32,4.3527485 29.647252,2.0000002 26.71875,2.0000002 L 20.28125,2.0000002 z"
- android:strokeColor="#ff8833"
- android:strokeWidth="1" />
- <path
- android:name="spark"
- android:fillColor="#FFFF0000"
- android:pathData="M 30,18.031528 L 25.579581,23.421071 L 29.370621,26.765348 L 20.096792,37 L 21.156922,28.014053 L 17,24.902844 L 20.880632,18 L 30,18.031528 z" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml
deleted file mode 100644
index 94338a7..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable12.xml
+++ /dev/null
@@ -1,97 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:name="rootGroup"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="600"
- android:viewportWidth="600"
- android:alpha="0.5" >
-
- <group
- android:name="rotationGroup"
- android:pivotX="300.0"
- android:pivotY="300.0"
- android:rotation="45.0" >
- <path
- android:name="pie1"
- android:fillColor="#00000000"
- android:pathData="M300,70 a230,230 0 1,0 1,0 z"
- android:strokeColor="#FF777777"
- android:strokeWidth="70"
- android:trimPathEnd=".75"
- android:trimPathOffset="0"
- android:trimPathStart="0" />
- <path
- android:name="v"
- android:fillColor="#000000"
- android:pathData="M300,70 l 0,-70 70,70 0,0 -70,70z" />
-
- <group
- android:name="translateToCenterGroup"
- android:rotation="0.0"
- android:translateX="200.0"
- android:translateY="200.0" >
- <group
- android:name="rotationGroup2"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="-45.0" >
- <path
- android:name="twoLines1"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="20" />
-
- <group
- android:name="translateGroupHalf"
- android:translateX="65.0"
- android:translateY="80.0" >
- <group
- android:name="rotationGroup3"
- android:pivotX="-65.0"
- android:pivotY="-80.0"
- android:rotation="-45.0" >
- <path
- android:name="twoLines2"
- android:fillColor="#FF00FF00"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="20" />
-
- <group
- android:name="translateGroup"
- android:translateX="65.0"
- android:translateY="80.0" >
- <group
- android:name="rotationGroupBlue"
- android:pivotX="-65.0"
- android:pivotY="-80.0"
- android:rotation="-45.0" >
- <path
- android:name="twoLines3"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="20" />
- </group>
- </group>
- </group>
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml
deleted file mode 100644
index 097e028..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable13.xml
+++ /dev/null
@@ -1,37 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="600" >
-
- <group>
- <path
- android:name="pie1"
- android:fillColor="#ffffffff"
- android:pathData="M300,200 h-150 a150,150 0 1,0 150,-150 z"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="1" />
- <path
- android:name="half"
- android:fillColor="#FFFF0000"
- android:pathData="M275,175 v-150 a150,150 0 0,0 -150,150 z"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="5" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml
deleted file mode 100644
index 102ae7a..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable14.xml
+++ /dev/null
@@ -1,38 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="500"
- android:viewportWidth="800" >
-
- <group
- android:pivotX="90"
- android:pivotY="100"
- android:rotation="20">
- <path
- android:name="pie2"
- android:pathData="M200,350 l 50,-25
- a25,12 -30 0,1 100,-50 l 50,-25
- a25,25 -30 0,1 100,-50 l 50,-25
- a25,37 -30 0,1 100,-50 l 50,-25
- a25,50 -30 0,1 100,-50 l 50,-25"
- android:fillColor="#00000000"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml
deleted file mode 100644
index bdfcf81..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable15.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="500" >
-
- <group
- android:pivotX="250"
- android:pivotY="200"
- android:rotation="180">
- <path
- android:name="house"
- android:fillColor="#ff440000"
- android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml
deleted file mode 100644
index ed1efa0..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable16.xml
+++ /dev/null
@@ -1,47 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200" >
-
- <group>
- <path
- android:name="background1"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
- android:fillColor="#FF000000"/>
- <path
- android:name="background2"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
- android:fillColor="#FF000000"/>
- </group>
- <group
- android:pivotX="100"
- android:pivotY="100"
- android:rotation="90"
- android:scaleX="0.75"
- android:scaleY="0.5"
- android:translateX="0.0"
- android:translateY="100.0">
- <path
- android:name="twoLines"
- android:pathData="M 100,10 v 90 M 10,100 h 90"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml
deleted file mode 100644
index ba15f41..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable17.xml
+++ /dev/null
@@ -1,29 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:width="64dp"
- android:height="64dp" android:viewportWidth="1200"
- android:viewportHeight="600">
-
- <group>
- <path
- android:name="house"
- android:pathData="M200,300 Q400,50 600,300 T1000,300"
- android:fillColor="#00000000"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="10"/>
- </group>
-
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml
deleted file mode 100644
index ee2122a..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable18.xml
+++ /dev/null
@@ -1,31 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="500" >
-
- <group>
- <path
- android:name="house"
- android:pathData="M100,200 C100,100 250,100 250,200 S400,300 400,200"
- android:fillColor="#00000000"
- android:strokeColor="#FFFFFF00"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml
deleted file mode 100644
index b98e1de..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable19.xml
+++ /dev/null
@@ -1,33 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="800"
- android:viewportWidth="1000" >
-
- <group>
- <path
- android:name="house"
- android:pathData="M10,300 Q400,550 600,300 T1000,300"
- android:pivotX="90"
- android:pivotY="100"
- android:fillColor="#00000000"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="60" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml
deleted file mode 100644
index 1c86818..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable20.xml
+++ /dev/null
@@ -1,34 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="480"
- android:viewportWidth="480" >
-
- <group>
- <path
- android:name="edit"
- android:fillColor="#FF00FFFF"
- android:pathData="M406.667,180c0,0 -100 -100 -113.334 -113.333
- c-13.333 -13.334 -33.333,0 -33.333,0l-160,160c0,0 -40,153.333 -40,173.333c0,13.333,13.333,13.333,13.333,13.333l173.334 -40
- c0,0,146.666 -146.666,160 -160C420,200,406.667,180,406.667,180z M226.399,356.823L131.95,378.62l-38.516 -38.522
- c7.848 -34.675,20.152 -82.52,23.538 -95.593l3.027,2.162l106.667,106.666L226.399,356.823z"
- android:strokeColor="#FF000000"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml
deleted file mode 100644
index 247f6bc..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable21.xml
+++ /dev/null
@@ -1,47 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200" >
-
- <group>
- <path
- android:name="background1"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z"
- android:fillColor="#FF000000"/>
- <path
- android:name="background2"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z"
- android:fillColor="#FF000000"/>
- </group>
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="90"
- android:scaleX="0.75"
- android:scaleY="0.5"
- android:translateX="100.0"
- android:translateY="100.0">
- <path
- android:name="twoLines"
- android:pathData="M 100,10 v 90 M 10,100 h 90"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml
deleted file mode 100644
index 39d891f..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable22.xml
+++ /dev/null
@@ -1,68 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="400" >
-
- <group android:name="backgroundGroup" >
- <path
- android:name="background1"
- android:fillColor="#80000000"
- android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#80000000"
- android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
- </group>
- <group
- android:name="translateToCenterGroup"
- android:translateX="50.0"
- android:translateY="90.0" >
- <path
- android:name="twoLines"
- android:pathData="M 0,0 v 100 M 0,0 h 100"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="20" />
-
- <group
- android:name="rotationGroup"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="-45.0" >
- <path
- android:name="twoLines1"
- android:pathData="M 0,0 v 100 M 0,0 h 100"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="20" />
-
- <group
- android:name="translateGroup"
- android:translateX="130.0"
- android:translateY="160.0" >
- <group android:name="scaleGroup" >
- <path
- android:name="twoLines2"
- android:pathData="M 0,0 v 100 M 0,0 h 100"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="20" />
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml
deleted file mode 100644
index 4a1c062..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable23.xml
+++ /dev/null
@@ -1,82 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="400" >
-
- <group android:name="backgroundGroup" >
- <path
- android:name="background1"
- android:fillColor="#80000000"
- android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#80000000"
- android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
- </group>
- <group
- android:name="translateToCenterGroup"
- android:translateX="50.0"
- android:translateY="90.0" >
- <path
- android:name="twoLines"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="20" />
-
- <group
- android:name="rotationGroup"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="-45.0" >
- <path
- android:name="twoLines1"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="20" />
-
- <group
- android:name="translateGroup"
- android:translateX="130.0"
- android:translateY="160.0" >
- <group android:name="scaleGroup" >
- <path
- android:name="twoLines3"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="20" />
- </group>
- </group>
-
- <group
- android:name="translateGroupHalf"
- android:translateX="65.0"
- android:translateY="80.0" >
- <group android:name="scaleGroup" >
- <path
- android:name="twoLines2"
- android:pathData="@string/twoLinePathData"
- android:fillColor="?android:attr/colorForeground"
- android:strokeColor="?android:attr/colorForeground"
- android:strokeWidth="20" />
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml
deleted file mode 100644
index a7a8bd3..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable24.xml
+++ /dev/null
@@ -1,82 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="400" >
-
- <group android:name="backgroundGroup">
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 200,0 l 0, 200 l -200, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 200,200 l 200,0 l 0, 200 l -200, 0 z" />
- </group>
- <group
- android:name="translateToCenterGroup"
- android:translateX="50.0"
- android:translateY="90.0" >
- <path
- android:name="twoLines"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FFFF0000"
- android:strokeWidth="20" />
-
- <group
- android:name="rotationGroup"
- android:pivotX="0.0"
- android:pivotY="0.0"
- android:rotation="-45.0">
- <path
- android:name="twoLines1"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="20" />
-
- <group
- android:name="translateGroup"
- android:translateX="130.0"
- android:translateY="160.0">
- <group android:name="scaleGroup" >
- <path
- android:name="twoLines3"
- android:pathData="@string/twoLinePathData"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="20" />
- </group>
- </group>
-
- <group
- android:name="translateGroupHalf"
- android:translateX="65.0"
- android:translateY="80.0">
- <group android:name="scaleGroup" >
- <path
- android:name="twoLines2"
- android:pathData="@string/twoLinePathData"
- android:fillColor="?android:attr/colorForeground"
- android:strokeColor="?android:attr/colorForeground"
- android:strokeWidth="20" />
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml
deleted file mode 100644
index 7c9e771..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable25.xml
+++ /dev/null
@@ -1,82 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:width="64dp"
- android:viewportHeight="400"
- android:viewportWidth="400" >
-
- <group
- android:name="FirstLevelGroup"
- android:translateX="100.0"
- android:translateY="0.0" >
- <group
- android:name="SecondLevelGroup1"
- android:translateX="-100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FF00FF00"
- android:pathData="@string/rectangle200" />
-
- <group
- android:name="ThridLevelGroup1"
- android:translateX="-100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FF0000FF"
- android:pathData="@string/rectangle200" />
- </group>
- <group
- android:name="ThridLevelGroup2"
- android:translateX="100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FF000000"
- android:pathData="@string/rectangle200" />
- </group>
- </group>
- <group
- android:name="SecondLevelGroup2"
- android:translateX="100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FF0000FF"
- android:pathData="@string/rectangle200" />
-
- <group
- android:name="ThridLevelGroup3"
- android:translateX="-100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FFFF0000"
- android:pathData="@string/rectangle200" />
- </group>
- <group
- android:name="ThridLevelGroup4"
- android:translateX="100.0"
- android:translateY="50.0" >
- <path
- android:fillColor="#FF00FF00"
- android:pathData="@string/rectangle200" />
- </group>
- </group>
-
- <path
- android:fillColor="#FFFF0000"
- android:pathData="@string/rectangle200" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml
deleted file mode 100644
index eda06d8..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable26.xml
+++ /dev/null
@@ -1,45 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:translateX="50"
- android:translateY="50" >
- <path
- android:name="twoLines"
- android:pathData="M 100,20 l 0 80 l -30 -80"
- android:strokeColor="#FF00FF00"
- android:strokeLineCap="butt"
- android:strokeLineJoin="miter"
- android:strokeMiterLimit="5"
- android:strokeWidth="20" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml
deleted file mode 100644
index cd46dd9..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable27.xml
+++ /dev/null
@@ -1,45 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:translateX="50"
- android:translateY="50" >
- <path
- android:name="twoLines"
- android:pathData="M 100,20 l 0 80 l -30 -80"
- android:strokeColor="#FF00FF00"
- android:strokeLineCap="round"
- android:strokeLineJoin="round"
- android:strokeMiterLimit="10"
- android:strokeWidth="20" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml
deleted file mode 100644
index 812af6b..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable28.xml
+++ /dev/null
@@ -1,46 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp"
- android:autoMirrored="true" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:translateX="50"
- android:translateY="50" >
- <path
- android:name="twoLines"
- android:pathData="M 100,20 l 0 80 l -30 -80"
- android:strokeColor="#FF00FF00"
- android:strokeLineCap="square"
- android:strokeLineJoin="bevel"
- android:strokeMiterLimit="10"
- android:strokeWidth="20" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml
deleted file mode 100644
index b24d31c..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable29.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="1"
- android:viewportWidth="1" >
-
- <group>
- <path
- android:name="box1"
- android:pathData="l0.0.0.5.0.0.5-0.5.0.0-.5z"
- android:fillColor="#ff00ff00"/>
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml
deleted file mode 100644
index 24f7372..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable30.xml
+++ /dev/null
@@ -1,28 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="48"
- android:viewportWidth="48" >
-
- <group>
- <path
- android:name="plus1"
- android:pathData="M20 16h-4v8h-8v4h8v8h4v-8h8v-4h-8zm9-3.84v3.64l5-1v21.2h4v-26z"
- android:fillColor="#ff00ff00"/>
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale0.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable_scale0.xml
deleted file mode 100644
index 828f0d9..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale0.xml
+++ /dev/null
@@ -1,57 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="@color/color0"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="@color/color2"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="90" >
- <group
- android:scaleX="1.5"
- android:scaleY="1" >
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="-90" >
- <group
- android:scaleX="1.5"
- android:scaleY="1" >
- <path
- android:name="twoLines"
- android:fillColor="#FFFF0000"
- android:pathData="@string/triangle100"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale1.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable_scale1.xml
deleted file mode 100644
index 530c73b..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale1.xml
+++ /dev/null
@@ -1,52 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:scaleX="-1"
- android:scaleY="-1" >
- <group
- android:scaleX="-1"
- android:scaleY="-1" >
- <group
- android:pivotX="100"
- android:pivotY="100"
- android:rotation="45" >
- <path
- android:name="twoLines"
- android:fillColor="#FFFF0000"
- android:pathData="M 100, 0 l 0, 100, -100, 0 z"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale2.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable_scale2.xml
deleted file mode 100644
index 200eb61..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale2.xml
+++ /dev/null
@@ -1,48 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:scaleX="2"
- android:scaleY="0.5" >
- <group
- android:pivotX="100"
- android:pivotY="100"
- android:rotation="45" >
- <path
- android:name="twoLines"
- android:fillColor="#FFFF0000"
- android:pathData="M 100, 0 l 0, 100, -100, 0 z"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale3.xml b/graphics/drawable/teststatic/res/drawable/vector_drawable_scale3.xml
deleted file mode 100644
index a40fc9c2..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_drawable_scale3.xml
+++ /dev/null
@@ -1,62 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="64dp"
- android:viewportHeight="200"
- android:viewportWidth="200"
- android:width="64dp" >
-
- <group>
- <path
- android:name="background1"
- android:fillColor="#FF000000"
- android:pathData="M 0,0 l 100,0 l 0, 100 l -100, 0 z" />
- <path
- android:name="background2"
- android:fillColor="#FF000000"
- android:pathData="M 100,100 l 100,0 l 0, 100 l -100, 0 z" />
- </group>
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="45" >
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="90" >
- <group
- android:scaleX="1.5"
- android:scaleY="1" >
- <group
- android:pivotX="0"
- android:pivotY="0"
- android:rotation="-90" >
- <group
- android:scaleX="1.5"
- android:scaleY="1" >
- <path
- android:name="twoLines"
- android:fillColor="#FFFF0000"
- android:pathData="M 100, 0 l 0, 100, -100, 0 z"
- android:strokeColor="#FF00FF00"
- android:strokeWidth="10" />
- </group>
- </group>
- </group>
- </group>
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_test01.xml b/graphics/drawable/teststatic/res/drawable/vector_test01.xml
deleted file mode 100644
index 8b891d6..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_test01.xml
+++ /dev/null
@@ -1,31 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="128dp"
- android:width="128dp"
- android:viewportHeight="512"
- android:viewportWidth="512" >
-
- <group>
- <path
- android:name="002b"
- android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0t-200,299"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="4"
- android:fillColor="#00000000" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/drawable/vector_test02.xml b/graphics/drawable/teststatic/res/drawable/vector_test02.xml
deleted file mode 100644
index e0af323..0000000
--- a/graphics/drawable/teststatic/res/drawable/vector_test02.xml
+++ /dev/null
@@ -1,31 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- android:height="128dp"
- android:width="128dp"
- android:viewportHeight="512"
- android:viewportWidth="512" >
-
- <group>
- <path
- android:name="002b"
- android:pathData="M100,200c0,-100 150,-100 150,0s150,100 150,0T-200,299"
- android:strokeColor="#FF0000FF"
- android:strokeWidth="4"
- android:fillColor="#00000000" />
- </group>
-
-</vector>
\ No newline at end of file
diff --git a/graphics/drawable/teststatic/res/raw/vector_drawable01.xml b/graphics/drawable/teststatic/res/raw/vector_drawable01.xml
deleted file mode 100644
index baa3fc7..0000000
--- a/graphics/drawable/teststatic/res/raw/vector_drawable01.xml
+++ /dev/null
@@ -1,32 +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.
--->
-<vector xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:auto="http://schemas.android.com/apk/res-auto"
- android:height="48dp"
- android:width="48dp"
- android:viewportHeight="480"
- android:viewportWidth="480" >
-
- <group>
- <path
- android:name="box1"
- android:pathData="m20,200l100,90l180-180l-35-35l-145,145l-60-60l-40,40z"
- android:fillColor="?android:attr/colorControlActivated"
- android:strokeColor="?android:attr/colorControlActivated"
- android:strokeLineCap="round"
- android:strokeLineJoin="round" />
- </group>
-</vector>
diff --git a/graphics/drawable/teststatic/res/values/strings.xml b/graphics/drawable/teststatic/res/values/strings.xml
deleted file mode 100644
index 065e7d9..0000000
--- a/graphics/drawable/teststatic/res/values/strings.xml
+++ /dev/null
@@ -1,29 +0,0 @@
-<?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>
-
- <string name="twoLinePathData">"M 0,0 v 100 M 0,0 h 100"</string>
- <string name="triangle"> "M300,70 l 0,-70 70,70 0,0 -70,70z"</string>
- <string name="rectangle">"M300,70 l 0,-70 70,0 0,140 -70,0 z"</string>
- <string name="rectangle2">"M300,70 l 0,-70 70,0 0,70z M300,70 l 70,0 0,70 -70,0z"</string>
- <string name="equal2"> "M300,35 l 0,-35 70,0 0,35z M300,105 l 70,0 0,35 -70,0z"</string>
- <string name="round_box">"m2.10001,-6c-1.9551,0 -0.5,0.02499 -2.10001,0.02499c-1.575,0 0.0031,-0.02499 -1.95,-0.02499c-2.543,0 -4,2.2816 -4,4.85001c0,3.52929 0.25,6.25 5.95,6.25c5.7,0 6,-2.72071 6,-6.25c0,-2.56841 -1.35699,-4.85001 -3.89999,-4.85001"</string>
- <string name="heart"> "m4.5,-7c-1.95509,0 -3.83009,1.26759 -4.5,3c-0.66991,-1.73241 -2.54691,-3 -4.5,-3c-2.543,0 -4.5,1.93159 -4.5,4.5c0,3.5293 3.793,6.2578 9,11.5c5.207,-5.2422 9,-7.9707 9,-11.5c0,-2.56841 -1.957,-4.5 -4.5,-4.5"</string>
- <string name="rectangle200">"M 0,0 l 200,0 l 0, 200 l -200, 0 z"</string>
- <string name="triangle100">"M 100, 0 l 0, 100, -100, 0 z"</string>
-</resources>
diff --git a/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java b/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java
deleted file mode 100644
index c92ff47..0000000
--- a/graphics/drawable/teststatic/src/android/support/test/vectordrawable/TestActivity.java
+++ /dev/null
@@ -1,136 +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.test.vectordrawable;
-
-import android.app.Activity;
-import android.content.res.Resources;
-import android.graphics.PorterDuff.Mode;
-import android.graphics.drawable.Drawable.ConstantState;
-import android.os.Bundle;
-import android.support.graphics.drawable.VectorDrawableCompat;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.ScrollView;
-import android.widget.TextView;
-
-import java.text.DecimalFormat;
-
-public class TestActivity extends Activity {
- private static final String LOG_TAG = "TestActivity";
-
- private static final String LOGCAT = "VectorDrawable1";
- protected int[] icon = {
- R.drawable.vector_drawable_scale0,
- R.drawable.vector_drawable_scale1,
- R.drawable.vector_drawable_scale2,
- R.drawable.vector_drawable_scale3,
- R.drawable.vector_drawable01,
- R.drawable.vector_drawable02,
- R.drawable.vector_drawable03,
- R.drawable.vector_drawable04,
- R.drawable.vector_drawable05,
- R.drawable.vector_drawable06,
- R.drawable.vector_drawable07,
- R.drawable.vector_drawable08,
- R.drawable.vector_drawable09,
- R.drawable.vector_drawable10,
- R.drawable.vector_drawable11,
- R.drawable.vector_drawable12,
- R.drawable.vector_drawable13,
- R.drawable.vector_drawable14,
- R.drawable.vector_drawable15,
- R.drawable.vector_drawable16,
- R.drawable.vector_drawable17,
- R.drawable.vector_drawable18,
- R.drawable.vector_drawable19,
- R.drawable.vector_drawable20,
- R.drawable.vector_drawable21,
- R.drawable.vector_drawable22,
- R.drawable.vector_drawable23,
- R.drawable.vector_drawable24,
- R.drawable.vector_drawable25,
- R.drawable.vector_drawable26,
- R.drawable.vector_drawable27,
- R.drawable.vector_drawable28,
- R.drawable.vector_drawable29,
- R.drawable.vector_drawable30,
- R.drawable.vector_test01,
- R.drawable.vector_test02
- };
-
- private static final int EXTRA_TESTS = 2;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- ScrollView scrollView = new ScrollView(this);
- LinearLayout container = new LinearLayout(this);
- scrollView.addView(container);
- container.setOrientation(LinearLayout.VERTICAL);
- Resources res = this.getResources();
- container.setBackgroundColor(0xFF888888);
- VectorDrawableCompat []d = new VectorDrawableCompat[icon.length];
- long time = android.os.SystemClock.currentThreadTimeMillis();
- for (int i = 0; i < icon.length; i++) {
- d[i] = VectorDrawableCompat.create(res, icon[i], getTheme());
- }
- time = android.os.SystemClock.currentThreadTimeMillis()-time;
-
- // Testing Tint on one particular case.
- if (d.length > 3) {
- d[3].setTint(0x8000FF00);
- d[3].setTintMode(Mode.MULTIPLY);
- }
-
- // Testing Constant State like operation by creating the first 2 icons
- // from the 3rd one's constant state.
- VectorDrawableCompat []extras = new VectorDrawableCompat[EXTRA_TESTS];
- ConstantState state = d[0].getConstantState();
- extras[0] = (VectorDrawableCompat) state.newDrawable();
- extras[1] = (VectorDrawableCompat) state.newDrawable();
-
- // This alpha change is expected to affect both extra 0, 1, and d0.
- extras[0].setAlpha(128);
-
- d[0].mutate();
- d[0].setAlpha(255);
-
- // Just show the average create time as the first view.
- TextView t = new TextView(this);
- DecimalFormat df = new DecimalFormat("#.##");
- t.setText("avgL=" + df.format(time / (icon.length)) + " ms");
- container.addView(t);
-
- addDrawableButtons(container, extras);
-
- addDrawableButtons(container, d);
-
- setContentView(scrollView);
- }
-
- private void addDrawableButtons(LinearLayout container, VectorDrawableCompat[] d) {
- // Add the VD into consequent views.
- for (int i = 0; i < d.length; i++) {
- Button button = new Button(this);
- button.setWidth(200);
- // Note that setBackgroundResource() will fail b/c createFromXmlInner() failed
- // to recognize <vector> pre-L.
- button.setBackgroundDrawable(d[i]);
- container.addView(button);
- }
- }
-}
diff --git a/graphics/drawable/util/src/android/support/graphics/drawable/AndroidResources.java b/graphics/drawable/util/src/android/support/graphics/drawable/AndroidResources.java
deleted file mode 100644
index e6b2e14..0000000
--- a/graphics/drawable/util/src/android/support/graphics/drawable/AndroidResources.java
+++ /dev/null
@@ -1,82 +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.graphics.drawable;
-
-public class AndroidResources {
-
- // Resources ID generated in the latest R.java for framework.
- static final int[] styleable_VectorDrawableTypeArray = {
- android.R.attr.name, android.R.attr.tint, android.R.attr.height,
- android.R.attr.width, android.R.attr.alpha, android.R.attr.autoMirrored,
- android.R.attr.mode, android.R.attr.viewportWidth, android.R.attr.viewportHeight
- };
- static final int styleable_VectorDrawable_alpha = 4;
- static final int styleable_VectorDrawable_autoMirrored = 5;
- static final int styleable_VectorDrawable_height = 2;
- static final int styleable_VectorDrawable_name = 0;
- static final int styleable_VectorDrawable_tint = 1;
- static final int styleable_VectorDrawable_Mode = 6;
- static final int styleable_VectorDrawable_viewportHeight = 8;
- static final int styleable_VectorDrawable_viewportWidth = 7;
- static final int styleable_VectorDrawable_width = 3;
- static final int[] styleable_VectorDrawableGroup = {
- android.R.attr.name, android.R.attr.pivotX, android.R.attr.pivotY,
- android.R.attr.scaleX, android.R.attr.scaleY, android.R.attr.rotation,
- android.R.attr.translateX, android.R.attr.translateY
- };
- static final int styleable_VectorDrawableGroup_name = 0;
- static final int styleable_VectorDrawableGroup_pivotX = 1;
- static final int styleable_VectorDrawableGroup_pivotY = 2;
- static final int styleable_VectorDrawableGroup_rotation = 5;
- static final int styleable_VectorDrawableGroup_scaleX = 3;
- static final int styleable_VectorDrawableGroup_scaleY = 4;
- static final int styleable_VectorDrawableGroup_translateX = 6;
- static final int styleable_VectorDrawableGroup_translateY = 7;
- static final int[] styleable_VectorDrawablePath = {
- android.R.attr.name, android.R.attr.fillColor, android.R.attr.pathData,
- android.R.attr.strokeColor, android.R.attr.strokeWidth, android.R.attr.trimPathStart,
- android.R.attr.trimPathEnd, android.R.attr.trimPathOffset, android.R.attr.strokeLineCap,
- android.R.attr.strokeLineJoin, android.R.attr.strokeMiterLimit,
- android.R.attr.strokeAlpha, android.R.attr.fillAlpha
- };
- static final int styleable_VectorDrawablePath_fillAlpha = 12;
- static final int styleable_VectorDrawablePath_fillColor = 1;
- static final int styleable_VectorDrawablePath_name = 0;
- static final int styleable_VectorDrawablePath_pathData = 2;
- static final int styleable_VectorDrawablePath_strokeAlpha = 11;
- static final int styleable_VectorDrawablePath_strokeColor = 3;
- static final int styleable_VectorDrawablePath_strokeLineCap = 8;
- static final int styleable_VectorDrawablePath_strokeLineJoin = 9;
- static final int styleable_VectorDrawablePath_strokeMiterLimit = 10;
- static final int styleable_VectorDrawablePath_strokeWidth = 4;
- static final int styleable_VectorDrawablePath_trimPathEnd = 6;
- static final int styleable_VectorDrawablePath_trimPathOffset = 7;
- static final int styleable_VectorDrawablePath_trimPathStart = 5;
- static final int[] styleable_VectorDrawableClipPath = {
- android.R.attr.name, android.R.attr.pathData
- };
- static final int styleable_VectorDrawableClipPath_name = 0;
- static final int styleable_VectorDrawableClipPath_pathData = 1;
-
- static final int[] styleable_AnimatedVectorDrawable = {
- android.R.attr.drawable
- };
- static final int styleable_AnimatedVectorDrawable_drawable = 0;
- static final int[] styleable_AnimatedVectorDrawableTarget = {
- android.R.attr.name, android.R.attr.animation
- };
- static final int styleable_AnimatedVectorDrawableTarget_animation = 1;
- static final int styleable_AnimatedVectorDrawableTarget_name = 0;
-}
diff --git a/graphics/drawable/util/src/android/support/graphics/drawable/TypedArrayUtils.java b/graphics/drawable/util/src/android/support/graphics/drawable/TypedArrayUtils.java
deleted file mode 100644
index 161eae6..0000000
--- a/graphics/drawable/util/src/android/support/graphics/drawable/TypedArrayUtils.java
+++ /dev/null
@@ -1,67 +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.graphics.drawable;
-
-import android.content.res.TypedArray;
-import org.xmlpull.v1.XmlPullParser;
-
-
-public class TypedArrayUtils {
- private static final String NAMESPACE = "http://schemas.android.com/apk/res/android";
-
- public static boolean hasAttribute(XmlPullParser parser, String attrName) {
- return parser.getAttributeValue(NAMESPACE, attrName) != null;
- }
-
- public static float getNamedFloat(TypedArray a, XmlPullParser parser, String attrName,
- int resId, float defaultValue) {
- final boolean hasAttr = hasAttribute(parser, attrName);
- if (!hasAttr) {
- return defaultValue;
- } else {
- return a.getFloat(resId, defaultValue);
- }
- }
-
- public static boolean getNamedBoolean(TypedArray a, XmlPullParser parser, String attrName,
- int resId, boolean defaultValue) {
- final boolean hasAttr = hasAttribute(parser, attrName);
- if (!hasAttr) {
- return defaultValue;
- } else {
- return a.getBoolean(resId, defaultValue);
- }
- }
-
- public static int getNamedInt(TypedArray a, XmlPullParser parser, String attrName,
- int resId, int defaultValue) {
- final boolean hasAttr = hasAttribute(parser, attrName);
- if (!hasAttr) {
- return defaultValue;
- } else {
- return a.getInt(resId, defaultValue);
- }
- }
-
- public static int getNamedColor(TypedArray a, XmlPullParser parser, String attrName,
- int resId, int defaultValue) {
- final boolean hasAttr = hasAttribute(parser, attrName);
- if (!hasAttr) {
- return defaultValue;
- } else {
- return a.getColor(resId, defaultValue);
- }
- }
-}
diff --git a/local.properties b/local.properties
index 8aea008..7b7f7a0 100644
--- a/local.properties
+++ b/local.properties
@@ -7,5 +7,6 @@
# Location of the SDK. This is only used by Gradle.
# For customization when using a Version Control System, please read the
# header note.
-#Fri Aug 21 16:47:26 KST 2015
+#Mon Apr 11 17:16:19 PDT 2016
+#do not define sdk.dir here, it will confuse android studio
android.dir=../../
diff --git a/percent/Android.mk b/percent/Android.mk
index b291e9f..59556c0 100644
--- a/percent/Android.mk
+++ b/percent/Android.mk
@@ -14,30 +14,24 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources. The resources library and the R class that it
-# contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-percent-res
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must include it with
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-percent \
+# android-support-v4
+#
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-percent
LOCAL_SDK_VERSION := 8
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android-support-percent-res \
- android-support-v4
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
diff --git a/percent/api/23.1.1.txt b/percent/api/23.1.1.txt
new file mode 100644
index 0000000..eb10df8
--- /dev/null
+++ b/percent/api/23.1.1.txt
@@ -0,0 +1,65 @@
+package android.support.percent {
+
+ public class PercentFrameLayout extends android.widget.FrameLayout {
+ ctor public PercentFrameLayout(android.content.Context);
+ ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet);
+ ctor public PercentFrameLayout(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public static class PercentFrameLayout.LayoutParams extends android.widget.FrameLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
+ ctor public PercentFrameLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public PercentFrameLayout.LayoutParams(int, int);
+ ctor public PercentFrameLayout.LayoutParams(int, int, int);
+ ctor public PercentFrameLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public PercentFrameLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public PercentFrameLayout.LayoutParams(android.widget.FrameLayout.LayoutParams);
+ ctor public PercentFrameLayout.LayoutParams(android.support.percent.PercentFrameLayout.LayoutParams);
+ method public android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo();
+ }
+
+ public class PercentLayoutHelper {
+ ctor public PercentLayoutHelper(android.view.ViewGroup);
+ method public void adjustChildren(int, int);
+ method public static void fetchWidthAndHeight(android.view.ViewGroup.LayoutParams, android.content.res.TypedArray, int, int);
+ method public static android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo(android.content.Context, android.util.AttributeSet);
+ method public boolean handleMeasuredStateTooSmall();
+ method public void restoreOriginalParams();
+ }
+
+ public static class PercentLayoutHelper.PercentLayoutInfo {
+ ctor public PercentLayoutHelper.PercentLayoutInfo();
+ method public void fillLayoutParams(android.view.ViewGroup.LayoutParams, int, int);
+ method public void fillMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams, int, int);
+ method public void restoreLayoutParams(android.view.ViewGroup.LayoutParams);
+ method public void restoreMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ field public float aspectRatio;
+ field public float bottomMarginPercent;
+ field public float endMarginPercent;
+ field public float heightPercent;
+ field public float leftMarginPercent;
+ field public float rightMarginPercent;
+ field public float startMarginPercent;
+ field public float topMarginPercent;
+ field public float widthPercent;
+ }
+
+ public static abstract interface PercentLayoutHelper.PercentLayoutParams {
+ method public abstract android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo();
+ }
+
+ public class PercentRelativeLayout extends android.widget.RelativeLayout {
+ ctor public PercentRelativeLayout(android.content.Context);
+ ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet);
+ ctor public PercentRelativeLayout(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public static class PercentRelativeLayout.LayoutParams extends android.widget.RelativeLayout.LayoutParams implements android.support.percent.PercentLayoutHelper.PercentLayoutParams {
+ ctor public PercentRelativeLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public PercentRelativeLayout.LayoutParams(int, int);
+ ctor public PercentRelativeLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public PercentRelativeLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ method public android.support.percent.PercentLayoutHelper.PercentLayoutInfo getPercentLayoutInfo();
+ }
+
+}
+
diff --git a/percent/api/current.txt b/percent/api/current.txt
index eb10df8..8559ddf 100644
--- a/percent/api/current.txt
+++ b/percent/api/current.txt
@@ -29,7 +29,8 @@
public static class PercentLayoutHelper.PercentLayoutInfo {
ctor public PercentLayoutHelper.PercentLayoutInfo();
method public void fillLayoutParams(android.view.ViewGroup.LayoutParams, int, int);
- method public void fillMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams, int, int);
+ method public deprecated void fillMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams, int, int);
+ method public void fillMarginLayoutParams(android.view.View, android.view.ViewGroup.MarginLayoutParams, int, int);
method public void restoreLayoutParams(android.view.ViewGroup.LayoutParams);
method public void restoreMarginLayoutParams(android.view.ViewGroup.MarginLayoutParams);
field public float aspectRatio;
diff --git a/percent/build.gradle b/percent/build.gradle
index 6fbc478..0603ad9 100644
--- a/percent/build.gradle
+++ b/percent/build.gradle
@@ -1,13 +1,27 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'percent'
dependencies {
compile project(':support-v4')
+
+ 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'
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
+
+ defaultConfig {
+ minSdkVersion 8
+
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -20,7 +34,9 @@
// 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.java.srcDir 'tests/java'
+ androidTest.res.srcDir 'tests/res'
+ androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
compileOptions {
diff --git a/percent/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index b670385..a390c81 100644
--- a/percent/src/android/support/percent/PercentLayoutHelper.java
+++ b/percent/src/android/support/percent/PercentLayoutHelper.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.support.annotation.NonNull;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.ViewCompat;
import android.util.AttributeSet;
@@ -72,9 +73,15 @@
public class PercentLayoutHelper {
private static final String TAG = "PercentLayout";
+ private static final boolean DEBUG = false;
+ private static final boolean VERBOSE = false;
+
private final ViewGroup mHost;
- public PercentLayoutHelper(ViewGroup host) {
+ public PercentLayoutHelper(@NonNull ViewGroup host) {
+ if (host == null) {
+ throw new IllegalArgumentException("host must be non-null");
+ }
mHost = host;
}
@@ -96,29 +103,32 @@
* @param heightMeasureSpec Height MeasureSpec of the parent ViewGroup.
*/
public void adjustChildren(int widthMeasureSpec, int heightMeasureSpec) {
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "adjustChildren: " + mHost + " widthMeasureSpec: "
+ View.MeasureSpec.toString(widthMeasureSpec) + " heightMeasureSpec: "
+ View.MeasureSpec.toString(heightMeasureSpec));
}
- int widthHint = View.MeasureSpec.getSize(widthMeasureSpec);
- int heightHint = View.MeasureSpec.getSize(heightMeasureSpec);
+ // Calculate available space, accounting for host's paddings
+ int widthHint = View.MeasureSpec.getSize(widthMeasureSpec) - mHost.getPaddingLeft()
+ - mHost.getPaddingRight();
+ int heightHint = View.MeasureSpec.getSize(heightMeasureSpec) - mHost.getPaddingTop()
+ - mHost.getPaddingBottom();
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should adjust " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "using " + info);
}
if (info != null) {
if (params instanceof ViewGroup.MarginLayoutParams) {
- info.fillMarginLayoutParams((ViewGroup.MarginLayoutParams) params,
+ info.fillMarginLayoutParams(view, (ViewGroup.MarginLayoutParams) params,
widthHint, heightHint);
} else {
info.fillLayoutParams(params, widthHint, heightHint);
@@ -139,7 +149,7 @@
float value = array.getFraction(R.styleable.PercentLayout_Layout_layout_widthPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent width: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -147,7 +157,7 @@
}
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_heightPercent, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent height: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -155,7 +165,7 @@
}
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginPercent, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -167,7 +177,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginLeftPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent left margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -176,7 +186,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginTopPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent top margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -185,7 +195,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginRightPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent right margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -194,7 +204,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginBottomPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent bottom margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -203,7 +213,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginStartPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent start margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -212,7 +222,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_marginEndPercent, 1, 1,
-1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "percent end margin: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -221,7 +231,7 @@
value = array.getFraction(R.styleable.PercentLayout_Layout_layout_aspectRatio, 1, 1, -1f);
if (value != -1f) {
- if (Log.isLoggable(TAG, Log.VERBOSE)) {
+ if (VERBOSE) {
Log.v(TAG, "aspect ratio: " + value);
}
info = info != null ? info : new PercentLayoutInfo();
@@ -229,7 +239,7 @@
}
array.recycle();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "constructed: " + info);
}
return info;
@@ -244,13 +254,13 @@
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should restore " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
PercentLayoutInfo info =
((PercentLayoutParams) params).getPercentLayoutInfo();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "using " + info);
}
if (info != null) {
@@ -283,7 +293,7 @@
for (int i = 0, N = mHost.getChildCount(); i < N; i++) {
View view = mHost.getChildAt(i);
ViewGroup.LayoutParams params = view.getLayoutParams();
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should handle measured state too small " + view + " " + params);
}
if (params instanceof PercentLayoutParams) {
@@ -301,7 +311,7 @@
}
}
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "should trigger second measure pass: " + needsSecondMeasure);
}
return needsSecondMeasure;
@@ -319,30 +329,53 @@
info.mPreservedParams.height == ViewGroup.LayoutParams.WRAP_CONTENT;
}
+ /* package */ static class PercentMarginLayoutParams extends ViewGroup.MarginLayoutParams {
+ // These two flags keep track of whether we're computing the LayoutParams width and height
+ // in the fill pass based on the aspect ratio. This allows the fill pass to be re-entrant
+ // as the framework code can call onMeasure() multiple times before the onLayout() is
+ // called. Those multiple invocations of onMeasure() are not guaranteed to be called with
+ // the same set of width / height.
+ private boolean mIsHeightComputedFromAspectRatio;
+ private boolean mIsWidthComputedFromAspectRatio;
+
+ public PercentMarginLayoutParams(int width, int height) {
+ super(width, height);
+ }
+ }
+
/**
* Container for information about percentage dimensions and margins. It acts as an extension
* for {@code LayoutParams}.
*/
public static class PercentLayoutInfo {
+ /** The decimal value of the percentage-based width. */
public float widthPercent;
+ /** The decimal value of the percentage-based height. */
public float heightPercent;
+ /** The decimal value of the percentage-based left margin. */
public float leftMarginPercent;
+ /** The decimal value of the percentage-based top margin. */
public float topMarginPercent;
+ /** The decimal value of the percentage-based right margin. */
public float rightMarginPercent;
+ /** The decimal value of the percentage-based bottom margin. */
public float bottomMarginPercent;
+ /** The decimal value of the percentage-based start margin. */
public float startMarginPercent;
+ /** The decimal value of the percentage-based end margin. */
public float endMarginPercent;
+ /** The decimal value of the percentage-based aspect ratio. */
public float aspectRatio;
- /* package */ final ViewGroup.MarginLayoutParams mPreservedParams;
+ /* package */ final PercentMarginLayoutParams mPreservedParams;
public PercentLayoutInfo() {
widthPercent = -1f;
@@ -353,11 +386,13 @@
bottomMarginPercent = -1f;
startMarginPercent = -1f;
endMarginPercent = -1f;
- mPreservedParams = new ViewGroup.MarginLayoutParams(0, 0);
+ mPreservedParams = new PercentMarginLayoutParams(0, 0);
}
/**
- * Fills {@code ViewGroup.LayoutParams} dimensions based on percentage values.
+ * Fills the {@link ViewGroup.LayoutParams#width} and {@link ViewGroup.LayoutParams#height}
+ * fields of the passed {@link ViewGroup.LayoutParams} object based on currently set
+ * percentage values.
*/
public void fillLayoutParams(ViewGroup.LayoutParams params, int widthHint,
int heightHint) {
@@ -369,8 +404,12 @@
// necessarily be true, as the user might explicitly set it to 0. However, we use this
// information only for the aspect ratio. If the user set the aspect ratio attribute,
// it means they accept or soon discover that it will be disregarded.
- final boolean widthNotSet = params.width == 0 && widthPercent < 0;
- final boolean heightNotSet = params.height == 0 && heightPercent < 0;
+ final boolean widthNotSet =
+ (mPreservedParams.mIsWidthComputedFromAspectRatio
+ || mPreservedParams.width == 0) && (widthPercent < 0);
+ final boolean heightNotSet =
+ (mPreservedParams.mIsHeightComputedFromAspectRatio
+ || mPreservedParams.height == 0) && (heightPercent < 0);
if (widthPercent >= 0) {
params.width = (int) (widthHint * widthPercent);
@@ -383,26 +422,42 @@
if (aspectRatio >= 0) {
if (widthNotSet) {
params.width = (int) (params.height * aspectRatio);
+ // Keep track that we've filled the width based on the height and aspect ratio.
+ mPreservedParams.mIsWidthComputedFromAspectRatio = true;
}
if (heightNotSet) {
params.height = (int) (params.width / aspectRatio);
+ // Keep track that we've filled the height based on the width and aspect ratio.
+ mPreservedParams.mIsHeightComputedFromAspectRatio = true;
}
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (DEBUG) {
Log.d(TAG, "after fillLayoutParams: (" + params.width + ", " + params.height + ")");
}
}
/**
- * Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage
- * values.
+ * @deprecated Use
+ * {@link #fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}
+ * for proper RTL support.
*/
- public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params, int widthHint,
- int heightHint) {
+ @Deprecated
+ public void fillMarginLayoutParams(ViewGroup.MarginLayoutParams params,
+ int widthHint, int heightHint) {
+ fillMarginLayoutParams(null, params, widthHint, heightHint);
+ }
+
+ /**
+ * Fills the margin fields of the passed {@link ViewGroup.MarginLayoutParams} object based
+ * on currently set percentage values and the current layout direction of the passed
+ * {@link View}.
+ */
+ public void fillMarginLayoutParams(View view, ViewGroup.MarginLayoutParams params,
+ int widthHint, int heightHint) {
fillLayoutParams(params, widthHint, heightHint);
- // Preserver the original margins, so we can restore them after the measure step.
+ // Preserve the original margins, so we can restore them after the measure step.
mPreservedParams.leftMargin = params.leftMargin;
mPreservedParams.topMargin = params.topMargin;
mPreservedParams.rightMargin = params.rightMargin;
@@ -424,15 +479,24 @@
if (bottomMarginPercent >= 0) {
params.bottomMargin = (int) (heightHint * bottomMarginPercent);
}
+ boolean shouldResolveLayoutDirection = false;
if (startMarginPercent >= 0) {
MarginLayoutParamsCompat.setMarginStart(params,
(int) (widthHint * startMarginPercent));
+ shouldResolveLayoutDirection = true;
}
if (endMarginPercent >= 0) {
MarginLayoutParamsCompat.setMarginEnd(params,
(int) (widthHint * endMarginPercent));
+ shouldResolveLayoutDirection = true;
}
- if (Log.isLoggable(TAG, Log.DEBUG)) {
+ if (shouldResolveLayoutDirection && (view != null)) {
+ // Force the resolve pass so that start / end margins are propagated to the
+ // matching left / right fields
+ MarginLayoutParamsCompat.resolveLayoutDirection(params,
+ ViewCompat.getLayoutDirection(view));
+ }
+ if (DEBUG) {
Log.d(TAG, "after fillMarginLayoutParams: (" + params.width + ", " + params.height
+ ")");
}
@@ -448,9 +512,9 @@
}
/**
- * Restores original dimensions and margins after they were changed for percentage based
- * values. Calling this method only makes sense if you previously called
- * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams}.
+ * Restores the original dimensions and margins after they were changed for percentage based
+ * values. You should call this method only if you previously called
+ * {@link PercentLayoutHelper.PercentLayoutInfo#fillMarginLayoutParams(View, ViewGroup.MarginLayoutParams, int, int)}.
*/
public void restoreMarginLayoutParams(ViewGroup.MarginLayoutParams params) {
restoreLayoutParams(params);
@@ -465,13 +529,25 @@
}
/**
- * Restores original dimensions after they were changed for percentage based values. Calling
- * this method only makes sense if you previously called
- * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams}.
+ * Restores original dimensions after they were changed for percentage based values.
+ * You should call this method only if you previously called
+ * {@link PercentLayoutHelper.PercentLayoutInfo#fillLayoutParams(ViewGroup.LayoutParams, int, int)}.
*/
public void restoreLayoutParams(ViewGroup.LayoutParams params) {
- params.width = mPreservedParams.width;
- params.height = mPreservedParams.height;
+ if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {
+ // Only restore the width if we didn't compute it based on the height and
+ // aspect ratio in the fill pass.
+ params.width = mPreservedParams.width;
+ }
+ if (!mPreservedParams.mIsHeightComputedFromAspectRatio) {
+ // Only restore the height if we didn't compute it based on the width and
+ // aspect ratio in the fill pass.
+ params.height = mPreservedParams.height;
+ }
+
+ // Reset the tracking flags.
+ mPreservedParams.mIsWidthComputedFromAspectRatio = false;
+ mPreservedParams.mIsHeightComputedFromAspectRatio = false;
}
}
diff --git a/percent/src/android/support/percent/PercentRelativeLayout.java b/percent/src/android/support/percent/PercentRelativeLayout.java
index 02a9188..1c00c4b 100644
--- a/percent/src/android/support/percent/PercentRelativeLayout.java
+++ b/percent/src/android/support/percent/PercentRelativeLayout.java
@@ -40,7 +40,7 @@
* app:layout_heightPercent="50%"
* app:layout_marginTopPercent="25%"
* app:layout_marginLeftPercent="25%"/>
- * </android.support.percent.PercentFrameLayout/>
+ * </android.support.percent.PercentFrameLayout>
* </pre>
*
* The attributes that you can use are:
diff --git a/percent/tests/AndroidManifest.xml b/percent/tests/AndroidManifest.xml
new file mode 100644
index 0000000..993e69b
--- /dev/null
+++ b/percent/tests/AndroidManifest.xml
@@ -0,0 +1,38 @@
+<?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.percent.test">
+ <uses-sdk
+ android:minSdkVersion="8"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling" />
+
+ <application android:supportsRtl="true">
+ <uses-library android:name="android.test.runner" />
+
+ <activity android:name="android.support.percent.TestFrameActivity"/>
+ <activity android:name="android.support.percent.TestRelativeActivity"/>
+ <activity android:name="android.support.percent.TestRelativeRtlActivity"/>
+ <activity android:name="android.support.percent.PercentDynamicLayoutActivity"/>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.percent.test"/>
+</manifest>
diff --git a/percent/tests/NO_DOCS b/percent/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/percent/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/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java b/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java
new file mode 100644
index 0000000..1fff432
--- /dev/null
+++ b/percent/tests/java/android/support/percent/BaseInstrumentationTestCase.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.percent;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.ActivityInstrumentationTestCase2;
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+import static junit.framework.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+ @Rule
+ public final ActivityTestRule<A> mActivityTestRule;
+
+ protected BaseInstrumentationTestCase(Class<A> activityClass) {
+ mActivityTestRule = new ActivityTestRule<A>(activityClass);
+ }
+
+ protected static void assertFuzzyEquals(String description, float expected, float actual) {
+ // On devices with certain screen densities we may run into situations where multiplying
+ // container width / height by a certain fraction ends up in a number that is almost but
+ // not exactly a round float number. For example, we can do float math to compute 15%
+ // of 1440 pixels and get 216.00002 due to inexactness of float math. This is why our
+ // tolerance is slightly bigger than 1 pixel in the comparison below.
+ assertEquals(description, expected, actual, 1.1f);
+ }
+}
diff --git a/percent/tests/java/android/support/percent/BaseTestActivity.java b/percent/tests/java/android/support/percent/BaseTestActivity.java
new file mode 100755
index 0000000..c0c9c7d
--- /dev/null
+++ b/percent/tests/java/android/support/percent/BaseTestActivity.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.percent;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+abstract class BaseTestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ 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);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
+ protected abstract int getContentViewLayoutResId();
+
+ protected void onContentViewSet() {}
+}
diff --git a/percent/tests/java/android/support/percent/LayoutDirectionActions.java b/percent/tests/java/android/support/percent/LayoutDirectionActions.java
new file mode 100755
index 0000000..8af7d51
--- /dev/null
+++ b/percent/tests/java/android/support/percent/LayoutDirectionActions.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.percent;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class LayoutDirectionActions {
+ /**
+ * Sets layout direction on the view.
+ */
+ public static ViewAction setLayoutDirection(final int layoutDirection) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set layout direction";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setLayoutDirection(view, layoutDirection);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java b/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java
new file mode 100644
index 0000000..6490123
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentDynamicLayoutActivity.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.percent;
+
+import android.support.percent.test.R;
+
+/**
+ * Test activity for testing presence of single and multiple drawers in percent layouts.
+ */
+public class PercentDynamicLayoutActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.percent_dynamic_layout;
+ }
+}
diff --git a/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java b/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java
new file mode 100755
index 0000000..08a96bc
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentDynamicLayoutTest.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.percent;
+
+import android.support.annotation.LayoutRes;
+import android.support.percent.test.R;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewStub;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.core.AllOf.allOf;
+
+/**
+ * Test cases to verify that percent layouts properly account for their own paddings.
+ */
+@SmallTest
+public class PercentDynamicLayoutTest
+ extends BaseInstrumentationTestCase<PercentDynamicLayoutActivity> {
+ public PercentDynamicLayoutTest() {
+ super(PercentDynamicLayoutActivity.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Now that the test is done, replace the activity content view with ViewStub so
+ // that it's ready to be replaced for the next test.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final PercentDynamicLayoutActivity activity = mActivityTestRule.getActivity();
+ activity.setContentView(R.layout.percent_dynamic_layout);
+ }
+ });
+ }
+
+ /**
+ * Matches views that have parents.
+ */
+ private Matcher<View> hasParent() {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has parent");
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ return view.getParent() != null;
+ }
+ };
+ }
+
+ /**
+ * Inflates the <code>ViewStub</code> with the passed layout resource.
+ */
+ private ViewAction inflateViewStub(final @LayoutRes int layoutResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isAssignableFrom(ViewStub.class), hasParent());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Inflates view stub";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewStub viewStub = (ViewStub) view;
+ viewStub.setLayoutResource(layoutResId);
+ viewStub.inflate();
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ @Test
+ public void testPercentFrameWithHorizontalPaddings() {
+ onView(withId(R.id.percent_layout)).check(doesNotExist());
+ onView(withId(R.id.percent_stub)).perform(
+ inflateViewStub(R.layout.percent_frame_layout_hpaddings));
+
+ final PercentFrameLayout percentFrameLayout =
+ (PercentFrameLayout) mActivityTestRule.getActivity().findViewById(
+ R.id.percent_layout);
+ final int containerWidth = percentFrameLayout.getWidth();
+ final int containerHeight = percentFrameLayout.getHeight();
+
+ final int availableWidth = containerWidth - percentFrameLayout.getPaddingLeft()
+ - percentFrameLayout.getPaddingRight();
+ final int availableHeight = containerHeight - percentFrameLayout.getPaddingTop()
+ - percentFrameLayout.getPaddingBottom();
+
+ final View child1 = percentFrameLayout.findViewById(R.id.child1);
+ final View child2 = percentFrameLayout.findViewById(R.id.child2);
+
+ assertFuzzyEquals("Child 1 width as 50% of the container's available width",
+ 0.5f * availableWidth, child1.getWidth());
+ assertFuzzyEquals("Child 1 height as 100% of the container's available height",
+ availableHeight, child1.getHeight());
+ assertFuzzyEquals("Child 2 width as 50% of the container's available width",
+ 0.5f * availableWidth, child2.getWidth());
+ assertFuzzyEquals("Child 2 height as 100% of the container's available height",
+ availableHeight, child2.getHeight());
+ }
+
+ @Test
+ public void testPercentFrameWithVerticalPaddings() {
+ onView(withId(R.id.percent_layout)).check(doesNotExist());
+ onView(withId(R.id.percent_stub)).perform(
+ inflateViewStub(R.layout.percent_frame_layout_vpaddings));
+
+ final PercentFrameLayout percentFrameLayout =
+ (PercentFrameLayout) mActivityTestRule.getActivity().findViewById(
+ R.id.percent_layout);
+ final int containerWidth = percentFrameLayout.getWidth();
+ final int containerHeight = percentFrameLayout.getHeight();
+
+ final int availableWidth = containerWidth - percentFrameLayout.getPaddingLeft()
+ - percentFrameLayout.getPaddingRight();
+ final int availableHeight = containerHeight - percentFrameLayout.getPaddingTop()
+ - percentFrameLayout.getPaddingBottom();
+
+ final View child1 = percentFrameLayout.findViewById(R.id.child1);
+ final View child2 = percentFrameLayout.findViewById(R.id.child2);
+
+ assertFuzzyEquals("Child 1 width as 100% of the container's available width",
+ availableWidth, child1.getWidth());
+ assertFuzzyEquals("Child 1 height as 50% of the container's available height",
+ 0.5f * availableHeight, child1.getHeight());
+ assertFuzzyEquals("Child 2 width as 100% of the container's available width",
+ availableWidth, child2.getWidth());
+ assertFuzzyEquals("Child 2 height as 50% of the container's available height",
+ 0.5f* availableHeight, child2.getHeight());
+ }
+
+ @Test
+ public void testPercentRelativeWithHorizontalPaddings() {
+ onView(withId(R.id.percent_layout)).check(doesNotExist());
+ onView(withId(R.id.percent_stub)).perform(
+ inflateViewStub(R.layout.percent_relative_layout_hpaddings));
+
+ final PercentRelativeLayout percentRelativeLayout =
+ (PercentRelativeLayout) mActivityTestRule.getActivity().findViewById(
+ R.id.percent_layout);
+ final int containerWidth = percentRelativeLayout.getWidth();
+ final int containerHeight = percentRelativeLayout.getHeight();
+
+ final int availableWidth = containerWidth - percentRelativeLayout.getPaddingLeft()
+ - percentRelativeLayout.getPaddingRight();
+ final int availableHeight = containerHeight - percentRelativeLayout.getPaddingTop()
+ - percentRelativeLayout.getPaddingBottom();
+
+ final View child1 = percentRelativeLayout.findViewById(R.id.child1);
+ final View child2 = percentRelativeLayout.findViewById(R.id.child2);
+
+ assertFuzzyEquals("Child 1 width as 50% of the container's available width",
+ 0.5f * availableWidth, child1.getWidth());
+ assertFuzzyEquals("Child 1 height as 100% of the container's available height",
+ availableHeight, child1.getHeight());
+ assertFuzzyEquals("Child 2 width as 50% of the container's available width",
+ 0.5f * availableWidth, child2.getWidth());
+ assertFuzzyEquals("Child 2 height as 100% of the container's available height",
+ availableHeight, child2.getHeight());
+ }
+
+ @Test
+ public void testPercentRelaticeWithVerticalPaddings() {
+ onView(withId(R.id.percent_layout)).check(doesNotExist());
+ onView(withId(R.id.percent_stub)).perform(
+ inflateViewStub(R.layout.percent_relative_layout_vpaddings));
+
+ final PercentRelativeLayout percentRelativeLayout =
+ (PercentRelativeLayout) mActivityTestRule.getActivity().findViewById(
+ R.id.percent_layout);
+ final int containerWidth = percentRelativeLayout.getWidth();
+ final int containerHeight = percentRelativeLayout.getHeight();
+
+ final int availableWidth = containerWidth - percentRelativeLayout.getPaddingLeft()
+ - percentRelativeLayout.getPaddingRight();
+ final int availableHeight = containerHeight - percentRelativeLayout.getPaddingTop()
+ - percentRelativeLayout.getPaddingBottom();
+
+ final View child1 = percentRelativeLayout.findViewById(R.id.child1);
+ final View child2 = percentRelativeLayout.findViewById(R.id.child2);
+
+ assertFuzzyEquals("Child 1 width as 100% of the container's available width",
+ availableWidth, child1.getWidth());
+ assertFuzzyEquals("Child 1 height as 50% of the container's available height",
+ 0.5f * availableHeight, child1.getHeight());
+ assertFuzzyEquals("Child 2 width as 100% of the container's available width",
+ availableWidth, child2.getWidth());
+ assertFuzzyEquals("Child 2 height as 50% of the container's available height",
+ 0.5f* availableHeight, child2.getHeight());
+ }
+}
diff --git a/percent/tests/java/android/support/percent/PercentFrameTest.java b/percent/tests/java/android/support/percent/PercentFrameTest.java
new file mode 100644
index 0000000..8b2fe8b
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentFrameTest.java
@@ -0,0 +1,250 @@
+/*
+ * 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.percent;
+
+import android.os.Build;
+import android.support.percent.test.R;
+import android.support.v4.view.ViewCompat;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.Log;
+import android.view.View;
+import junit.framework.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.percent.LayoutDirectionActions.setLayoutDirection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+@SmallTest
+public class PercentFrameTest extends BaseInstrumentationTestCase<TestFrameActivity> {
+ private PercentFrameLayout mPercentFrameLayout;
+ private int mContainerWidth;
+ private int mContainerHeight;
+
+ public PercentFrameTest() {
+ super(TestFrameActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final TestFrameActivity activity = mActivityTestRule.getActivity();
+ mPercentFrameLayout = (PercentFrameLayout) activity.findViewById(R.id.container);
+ mContainerWidth = mPercentFrameLayout.getWidth();
+ mContainerHeight = mPercentFrameLayout.getHeight();
+ }
+
+ @Test
+ public void testWidthHeight() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_height);
+
+ int childWidth = childToTest.getWidth();
+ int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 50% of the container",
+ 0.5f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.5f * mContainerHeight, childHeight);
+ }
+
+ @Test
+ public void testWidthAspectRatio() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_ratio);
+
+ int childWidth = childToTest.getWidth();
+ int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 60% of the container",
+ 0.6f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child aspect ratio of 120%",
+ childWidth / 1.2f, childHeight);
+ }
+
+ @Test
+ public void testHeightAspectRatio() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_height_ratio);
+
+ int childWidth = childToTest.getWidth();
+ int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.5f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child aspect ratio of 150%",
+ 1.5f * childHeight, childWidth);
+ }
+
+ @Test
+ public void testMarginsSingle() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_single);
+
+ int childLeft = childToTest.getLeft();
+ int childTop = childToTest.getTop();
+ int childRight = childToTest.getRight();
+ int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child left margin as 30% of the container",
+ 0.3f * mContainerWidth, childLeft);
+ assertFuzzyEquals("Child top margin as 30% of the container",
+ 0.3f * mContainerHeight, childTop);
+ assertFuzzyEquals("Child right margin as 30% of the container",
+ 0.3f * mContainerWidth, mContainerWidth - childRight);
+ assertFuzzyEquals("Child bottom margin as 30% of the container",
+ 0.3f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testMarginsMultiple() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_multiple);
+
+ int childLeft = childToTest.getLeft();
+ int childTop = childToTest.getTop();
+ int childRight = childToTest.getRight();
+ int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child top margin as 10% of the container",
+ 0.1f * mContainerHeight, childTop);
+ assertFuzzyEquals("Child left margin as 15% of the container",
+ 0.15f * mContainerWidth, childLeft);
+ assertFuzzyEquals("Child bottom margin as 20% of the container",
+ 0.2f * mContainerHeight, mContainerHeight - childBottom);
+ assertFuzzyEquals("Child right margin as 25% of the container",
+ 0.25f * mContainerWidth, mContainerWidth - childRight);
+ }
+
+ @Test
+ public void testMarginsTopLeft() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_top_left);
+
+ int childWidth = childToTest.getWidth();
+ int childHeight = childToTest.getHeight();
+ int childLeft = childToTest.getLeft();
+ int childTop = childToTest.getTop();
+
+ assertFuzzyEquals("Child width as 50% of the container",
+ 0.5f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.5f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child left margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ assertFuzzyEquals("Child top margin as 20% of the container",
+ 0.2f * mContainerHeight, childTop);
+ }
+
+ @Test
+ public void testMarginsBottomRight() {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_bottom_right);
+
+ int childWidth = childToTest.getWidth();
+ int childHeight = childToTest.getHeight();
+ int childRight = childToTest.getRight();
+ int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child width as 60% of the container",
+ 0.6f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child height as 60% of the container",
+ 0.6f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child right margin as 10% of the container",
+ 0.1f * mContainerWidth, mContainerWidth - childRight);
+ assertFuzzyEquals("Child bottom margin as 10% of the container",
+ 0.1f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testMarginStart() throws Throwable {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
+
+ // Under LTR test that start is treated as left
+ int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child start margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ }
+
+ @Test
+ public void testMarginStartRtl() throws Throwable {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ // Force our child to inherit parent's layout direction
+ onView(withId(R.id.child_margin_start)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
+ // And force the container to RTL mode
+ onView(withId(R.id.container)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ // Force a full measure + layout pass on the container
+ mPercentFrameLayout.measure(
+ View.MeasureSpec.makeMeasureSpec(mContainerWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(mContainerHeight, View.MeasureSpec.EXACTLY));
+ mPercentFrameLayout.layout(mPercentFrameLayout.getLeft(),
+ mPercentFrameLayout.getTop(), mPercentFrameLayout.getRight(),
+ mPercentFrameLayout.getBottom());
+
+ // Start under RTL should be treated as right
+ int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child start margin as 20% of the container",
+ 0.2f * mContainerWidth, mContainerWidth - childRight);
+ } else {
+ // On pre-v17 devices test that start is treated as left
+ int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child start margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ }
+ }
+
+ @Test
+ public void testMarginEnd() throws Throwable {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
+
+ // Under LTR test that end is treated as right
+ int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child end margin as 30% of the container",
+ 0.3f * mContainerWidth, mContainerWidth - childRight);
+ }
+
+ @Test
+ public void testMarginEndRtl() throws Throwable {
+ View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ // Force our child to inherit parent's layout direction
+ onView(withId(R.id.child_margin_end)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_INHERIT));
+ // And force the container to RTL mode
+ onView(withId(R.id.container)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ // Force a full measure + layout pass on the container
+ mPercentFrameLayout.measure(
+ View.MeasureSpec.makeMeasureSpec(mContainerWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(mContainerHeight, View.MeasureSpec.EXACTLY));
+ mPercentFrameLayout.layout(mPercentFrameLayout.getLeft(),
+ mPercentFrameLayout.getTop(), mPercentFrameLayout.getRight(),
+ mPercentFrameLayout.getBottom());
+
+ // End under RTL should be treated as left
+ int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child end margin as 30% of the container",
+ 0.3f * mContainerWidth, childLeft);
+ } else {
+ // On pre-v17 devices test that end is treated as right
+ int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child end margin as 30% of the container",
+ 0.3f * mContainerWidth, mContainerWidth - childRight);
+ }
+ }
+}
diff --git a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
new file mode 100644
index 0000000..e688612
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.percent;
+
+import android.os.Build;
+import android.support.percent.test.R;
+import android.support.v4.view.ViewCompat;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.percent.LayoutDirectionActions.setLayoutDirection;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assume.*;
+/**
+ * The arrangement of child views in the layout class in the default LTR (left-to-right) direction
+ * is as follows:
+ *
+ * +---------------------------------------------+
+ * | |
+ * | TTTTTTTTTTTTTTTTTTTTT |
+ * | |
+ * | S |
+ * | S CCCCCCCCCCCCCCCCCC |
+ * | S CCCCCCCCCCCCCCCCCC |
+ * | S CCCCCCCCCCCCCCCCCC E |
+ * | S CCCCCCCCCCCCCCCCCC E |
+ * | S CCCCCCCCCCCCCCCCCC E |
+ * | CCCCCCCCCCCCCCCCCC E |
+ * | CCCCCCCCCCCCCCCCCC E |
+ * | E |
+ * | |
+ * | BBBBBBBBBBBBBBBBBBBBB |
+ * | |
+ * +---------------------------------------------+
+ *
+ * The arrangement of child views in the layout class in the RTL (right-to-left) direction
+ * is as follows:
+ *
+ * +---------------------------------------------+
+ * | |
+ * | TTTTTTTTTTTTTTTTTTTTT |
+ * | |
+ * | S |
+ * | CCCCCCCCCCCCCCCCCC S |
+ * | CCCCCCCCCCCCCCCCCC S |
+ * | E CCCCCCCCCCCCCCCCCC S |
+ * | E CCCCCCCCCCCCCCCCCC S |
+ * | E CCCCCCCCCCCCCCCCCC S |
+ * | E CCCCCCCCCCCCCCCCCC |
+ * | E CCCCCCCCCCCCCCCCCC |
+ * | E |
+ * | |
+ * | BBBBBBBBBBBBBBBBBBBBB |
+ * | |
+ * +---------------------------------------------+
+ *
+ * Child views are exercising the following percent-based constraints supported by
+ * <code>PercentRelativeLayout</code>:
+ *
+ * <ul>
+ * <li>Top child (marked with T) - width, aspect ratio, top margin, start margin.</li>
+ * <li>Start child (marked with S) - height, aspect ratio, top margin, start margin.</li>
+ * <li>Bottom child (marked with B) - width, aspect ratio, bottom margin, end margin.</li>
+ * <li>Right child (marked with E) - height, aspect ratio, bottom margin, end margin.</li>
+ * <li>Center child (marked with C) - margin (all sides) from the other four children.</li>
+ * </ul>
+ *
+ * Under LTR direction (pre-v17 devices and v17+ with default direction of en-US locale) we are
+ * testing the same assertions as <code>PercentRelativeTest</code>. Under RTL direction (on v17+
+ * devices with Espresso-powered direction switch) we are testing the reverse assertions along the
+ * X axis for all child views.
+ *
+ * Note that due to a bug in the core {@link RelativeLayout} (base class of
+ * {@link PercentRelativeLayout}) in how it treats end margin of child views on v17 devices, we are
+ * skipping all tests in this class for v17 devices. This is in line with the overall contract
+ * of percent-based layouts provided by the support library - we do not work around / fix bugs in
+ * the core classes, but rather just provide a translation layer between percentage-based values
+ * and pixel-based ones.
+ */
+@SmallTest
+public class PercentRelativeRtlTest extends BaseInstrumentationTestCase<TestRelativeRtlActivity> {
+ private PercentRelativeLayout mPercentRelativeLayout;
+ private int mContainerWidth;
+ private int mContainerHeight;
+
+ public PercentRelativeRtlTest() {
+ super(TestRelativeRtlActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ assumeTrue(Build.VERSION.SDK_INT != 17);
+
+ final TestRelativeRtlActivity activity = mActivityTestRule.getActivity();
+ mPercentRelativeLayout = (PercentRelativeLayout) activity.findViewById(R.id.container);
+ mContainerWidth = mPercentRelativeLayout.getWidth();
+ mContainerHeight = mPercentRelativeLayout.getHeight();
+ }
+
+ private void switchToRtl() {
+ // Force the container to RTL mode
+ onView(withId(R.id.container)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ // Force a full measure + layout pass on the container
+ mPercentRelativeLayout.measure(
+ View.MeasureSpec.makeMeasureSpec(mContainerWidth, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(mContainerHeight, View.MeasureSpec.EXACTLY));
+ mPercentRelativeLayout.layout(mPercentRelativeLayout.getLeft(),
+ mPercentRelativeLayout.getTop(), mPercentRelativeLayout.getRight(),
+ mPercentRelativeLayout.getBottom());
+ }
+
+ @Test
+ public void testTopChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ switchToRtl();
+
+ final int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child start margin as 20% of the container",
+ 0.2f * mContainerWidth, mContainerWidth - childRight);
+ } else {
+ final int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child start margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ }
+
+ final int childTop = childToTest.getTop();
+ assertFuzzyEquals("Child top margin as 5% of the container",
+ 0.05f * mContainerHeight, childTop);
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 50% of the container",
+ 0.5f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child aspect ratio of 2000%",
+ 0.05f * childWidth, childHeight);
+ }
+
+ @Test
+ public void testStartChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_start);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ switchToRtl();
+
+ final int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child start margin as 5% of the container",
+ 0.05f * mContainerWidth, mContainerWidth - childRight);
+ } else {
+ final int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child start margin as 5% of the container",
+ 0.05f * mContainerWidth, childLeft);
+ }
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.5f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child aspect ratio of 5%",
+ 0.05f * childHeight, childWidth);
+
+ final int childTop = childToTest.getTop();
+
+ assertFuzzyEquals("Child top margin as 20% of the container",
+ 0.2f * mContainerHeight, childTop);
+ }
+
+ @Test
+ public void testBottomChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ switchToRtl();
+
+ final int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child end margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ } else {
+ final int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child end margin as 20% of the container",
+ 0.2f * mContainerWidth, mContainerWidth - childRight);
+ }
+
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 40% of the container",
+ 0.4f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child aspect ratio of 2000%",
+ 0.05f * childWidth, childHeight);
+
+ final int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child bottom margin as 5% of the container",
+ 0.05f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testEndChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_end);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ switchToRtl();
+
+ final int childLeft = childToTest.getLeft();
+ assertFuzzyEquals("Child end margin as 5% of the container",
+ 0.05f * mContainerWidth, childLeft);
+ } else {
+ final int childRight = childToTest.getRight();
+ assertFuzzyEquals("Child end margin as 5% of the container",
+ 0.05f * mContainerWidth, mContainerWidth - childRight);
+ }
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.4f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child aspect ratio of 5%",
+ 0.05f * childHeight, childWidth);
+
+ final int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child bottom margin as 20% of the container",
+ 0.2f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testCenterChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
+
+ boolean supportsRtl = Build.VERSION.SDK_INT >= 17;
+ if (supportsRtl) {
+ switchToRtl();
+ }
+
+ final int childLeft = childToTest.getLeft();
+ final int childTop = childToTest.getTop();
+ final int childRight = childToTest.getRight();
+ final int childBottom = childToTest.getBottom();
+
+ final View leftChild = supportsRtl
+ ? mPercentRelativeLayout.findViewById(R.id.child_end)
+ : mPercentRelativeLayout.findViewById(R.id.child_start);
+ assertFuzzyEquals("Child left margin as 10% of the container",
+ leftChild.getRight() + 0.1f * mContainerWidth, childLeft);
+
+ final View topChild = mPercentRelativeLayout.findViewById(R.id.child_top);
+ assertFuzzyEquals("Child top margin as 10% of the container",
+ topChild.getBottom() + 0.1f * mContainerHeight, childTop);
+
+ final View rightChild = supportsRtl
+ ? mPercentRelativeLayout.findViewById(R.id.child_start)
+ : mPercentRelativeLayout.findViewById(R.id.child_end);
+ assertFuzzyEquals("Child right margin as 10% of the container",
+ rightChild.getLeft() - 0.1f * mContainerWidth, childRight);
+
+ final View bottomChild = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+ assertFuzzyEquals("Child bottom margin as 10% of the container",
+ bottomChild.getTop() - 0.1f * mContainerHeight, childBottom);
+ }
+}
diff --git a/percent/tests/java/android/support/percent/PercentRelativeTest.java b/percent/tests/java/android/support/percent/PercentRelativeTest.java
new file mode 100644
index 0000000..eaa38f6
--- /dev/null
+++ b/percent/tests/java/android/support/percent/PercentRelativeTest.java
@@ -0,0 +1,184 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.percent;
+
+import android.support.percent.test.R;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * The arrangement of child views in the layout class is as follows:
+ *
+ * +---------------------------------------------+
+ * | |
+ * | TTTTTTTTTTTTTTTTTTTTT |
+ * | |
+ * | L |
+ * | L CCCCCCCCCCCCCCCCCC |
+ * | L CCCCCCCCCCCCCCCCCC |
+ * | L CCCCCCCCCCCCCCCCCC R |
+ * | L CCCCCCCCCCCCCCCCCC R |
+ * | L CCCCCCCCCCCCCCCCCC R |
+ * | CCCCCCCCCCCCCCCCCC R |
+ * | CCCCCCCCCCCCCCCCCC R |
+ * | R |
+ * | |
+ * | BBBBBBBBBBBBBBBBBBBBB |
+ * | |
+ * +---------------------------------------------+
+ *
+ * Child views are exercising the following percent-based constraints supported by
+ * <code>PercentRelativeLayout</code>:
+ *
+ * <ul>
+ * <li>Top child (marked with T) - width, aspect ratio, top margin, left margin.</li>
+ * <li>Left child (marked with L) - height, aspect ratio, top margin, left margin.</li>
+ * <li>Bottom child (marked with B) - width, aspect ratio, bottom margin, right margin.</li>
+ * <li>Right child (marked with R) - height, aspect ratio, bottom margin, right margin.</li>
+ * <li>Center child (marked with C) - margin (all sides) from the other four children.</li>
+ * </ul>
+ */
+@SmallTest
+public class PercentRelativeTest extends BaseInstrumentationTestCase<TestRelativeActivity> {
+ private PercentRelativeLayout mPercentRelativeLayout;
+ private int mContainerWidth;
+ private int mContainerHeight;
+
+ public PercentRelativeTest() {
+ super(TestRelativeActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final TestRelativeActivity activity = mActivityTestRule.getActivity();
+ mPercentRelativeLayout = (PercentRelativeLayout) activity.findViewById(R.id.container);
+ mContainerWidth = mPercentRelativeLayout.getWidth();
+ mContainerHeight = mPercentRelativeLayout.getHeight();
+ }
+
+ @Test
+ public void testTopChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 50% of the container",
+ 0.5f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child aspect ratio of 2000%",
+ 0.05f * childWidth, childHeight);
+
+ final int childLeft = childToTest.getLeft();
+ final int childTop = childToTest.getTop();
+
+ assertFuzzyEquals("Child left margin as 20% of the container",
+ 0.2f * mContainerWidth, childLeft);
+ assertFuzzyEquals("Child top margin as 5% of the container",
+ 0.05f * mContainerHeight, childTop);
+ }
+
+ @Test
+ public void testLeftChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_left);
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.5f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child aspect ratio of 5%",
+ 0.05f * childHeight, childWidth);
+
+ final int childLeft = childToTest.getLeft();
+ final int childTop = childToTest.getTop();
+
+ assertFuzzyEquals("Child left margin as 5% of the container",
+ 0.05f * mContainerWidth, childLeft);
+ assertFuzzyEquals("Child top margin as 20% of the container",
+ 0.2f * mContainerHeight, childTop);
+ }
+
+ @Test
+ public void testBottomChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child width as 40% of the container",
+ 0.4f * mContainerWidth, childWidth);
+ assertFuzzyEquals("Child aspect ratio of 2000%",
+ 0.05f * childWidth, childHeight);
+
+ final int childRight = childToTest.getRight();
+ final int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child right margin as 20% of the container",
+ 0.2f * mContainerWidth, mContainerWidth - childRight);
+ assertFuzzyEquals("Child bottom margin as 5% of the container",
+ 0.05f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testRightChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_right);
+
+ final int childWidth = childToTest.getWidth();
+ final int childHeight = childToTest.getHeight();
+
+ assertFuzzyEquals("Child height as 50% of the container",
+ 0.4f * mContainerHeight, childHeight);
+ assertFuzzyEquals("Child aspect ratio of 5%",
+ 0.05f * childHeight, childWidth);
+
+ final int childRight = childToTest.getRight();
+ final int childBottom = childToTest.getBottom();
+
+ assertFuzzyEquals("Child right margin as 5% of the container",
+ 0.05f * mContainerWidth, mContainerWidth - childRight);
+ assertFuzzyEquals("Child bottom margin as 20% of the container",
+ 0.2f * mContainerHeight, mContainerHeight - childBottom);
+ }
+
+ @Test
+ public void testCenterChild() {
+ final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
+
+ final int childLeft = childToTest.getLeft();
+ final int childTop = childToTest.getTop();
+ final int childRight = childToTest.getRight();
+ final int childBottom = childToTest.getBottom();
+
+ final View leftChild = mPercentRelativeLayout.findViewById(R.id.child_left);
+ assertFuzzyEquals("Child left margin as 10% of the container",
+ leftChild.getRight() + 0.1f * mContainerWidth, childLeft);
+
+ final View topChild = mPercentRelativeLayout.findViewById(R.id.child_top);
+ assertFuzzyEquals("Child top margin as 10% of the container",
+ topChild.getBottom() + 0.1f * mContainerHeight, childTop);
+
+ final View rightChild = mPercentRelativeLayout.findViewById(R.id.child_right);
+ assertFuzzyEquals("Child right margin as 10% of the container",
+ rightChild.getLeft() - 0.1f * mContainerWidth, childRight);
+
+ final View bottomChild = mPercentRelativeLayout.findViewById(R.id.child_bottom);
+ assertFuzzyEquals("Child bottom margin as 10% of the container",
+ bottomChild.getTop() - 0.1f * mContainerHeight, childBottom);
+ }
+}
diff --git a/percent/tests/java/android/support/percent/TestFrameActivity.java b/percent/tests/java/android/support/percent/TestFrameActivity.java
new file mode 100644
index 0000000..1eacdf6
--- /dev/null
+++ b/percent/tests/java/android/support/percent/TestFrameActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.percent;
+
+import android.support.percent.test.R;
+
+public class TestFrameActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.percent_frame_layout;
+ }
+}
diff --git a/percent/tests/java/android/support/percent/TestRelativeActivity.java b/percent/tests/java/android/support/percent/TestRelativeActivity.java
new file mode 100644
index 0000000..e0d451f
--- /dev/null
+++ b/percent/tests/java/android/support/percent/TestRelativeActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.percent;
+
+import android.support.percent.test.R;
+
+public class TestRelativeActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.percent_relative_layout;
+ }
+}
diff --git a/percent/tests/java/android/support/percent/TestRelativeRtlActivity.java b/percent/tests/java/android/support/percent/TestRelativeRtlActivity.java
new file mode 100644
index 0000000..02a2fff
--- /dev/null
+++ b/percent/tests/java/android/support/percent/TestRelativeRtlActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.percent;
+
+import android.support.percent.test.R;
+
+public class TestRelativeRtlActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.percent_relative_layout_rtl;
+ }
+}
diff --git a/percent/tests/res/layout/percent_dynamic_layout.xml b/percent/tests/res/layout/percent_dynamic_layout.xml
new file mode 100644
index 0000000..3ecdccf
--- /dev/null
+++ b/percent/tests/res/layout/percent_dynamic_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- ViewStub that will be inflated to a PercentLayout at test runtime. -->
+<ViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/percent_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:inflatedId="@+id/percent_layout"
+ android:fitsSystemWindows="true" />
diff --git a/percent/tests/res/layout/percent_frame_layout.xml b/percent/tests/res/layout/percent_frame_layout.xml
new file mode 100644
index 0000000..d73334a
--- /dev/null
+++ b/percent/tests/res/layout/percent_frame_layout.xml
@@ -0,0 +1,104 @@
+<?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.
+-->
+
+<android.support.percent.PercentFrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- Note the usage of layout_gravity in all child views that use margin attributes that
+ addresses a bug in FrameLayout on v9 and older devices that didn't correctly treat the
+ margin attributes. -->
+
+ <!-- For testing width + height only -->
+ <View
+ android:id="@+id/child_width_height"
+ app:layout_widthPercent="50%"
+ app:layout_heightPercent="50%" />
+
+ <!-- For testing width + aspect ratio only -->
+ <View
+ android:id="@+id/child_width_ratio"
+ app:layout_widthPercent="60%"
+ app:layout_aspectRatio="120%" />
+
+ <!-- For testing height + aspect ratio only -->
+ <View
+ android:id="@+id/child_height_ratio"
+ app:layout_heightPercent="50%"
+ app:layout_aspectRatio="150%" />
+
+ <!-- For testing margins from a single attribute. Note that we still need core width / height
+ attributes since otherwise the logic in core FrameLayout will give size 0x0 to this
+ child. -->
+ <View
+ android:id="@+id/child_margins_single"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_marginPercent="30%"
+ android:layout_gravity="top" />
+
+ <!-- For testing margins from different attributes. Note that we still need core width / height
+ attributes since otherwise the logic in core FrameLayout will give size 0x0 to this
+ child. -->
+ <View
+ android:id="@+id/child_margins_multiple"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ app:layout_marginTopPercent="10%"
+ app:layout_marginLeftPercent="15%"
+ app:layout_marginBottomPercent="20%"
+ app:layout_marginRightPercent="25%"
+ android:layout_gravity="top" />
+
+ <!-- For testing top + left margins with width + height -->
+ <View
+ android:id="@+id/child_margins_top_left"
+ app:layout_widthPercent="50%"
+ app:layout_heightPercent="50%"
+ app:layout_marginTopPercent="20%"
+ app:layout_marginLeftPercent="20%"
+ android:layout_gravity="top|left" />
+
+ <!-- For testing bottom + right margin with width + height -->
+ <View
+ android:id="@+id/child_margins_bottom_right"
+ app:layout_widthPercent="60%"
+ app:layout_heightPercent="60%"
+ app:layout_marginBottomPercent="10%"
+ app:layout_marginRightPercent="10%"
+ android:layout_gravity="bottom|right" />
+
+ <!-- For testing start margin -->
+ <View
+ android:id="@+id/child_margin_start"
+ app:layout_widthPercent="50%"
+ app:layout_heightPercent="50%"
+ app:layout_marginStartPercent="20%"
+ android:layout_gravity="start" />
+
+ <!-- For testing end margin -->
+ <View
+ android:id="@+id/child_margin_end"
+ app:layout_widthPercent="50%"
+ app:layout_heightPercent="50%"
+ app:layout_marginEndPercent="30%"
+ android:layout_gravity="end" />
+
+</android.support.percent.PercentFrameLayout>
+
diff --git a/percent/tests/res/layout/percent_frame_layout_hpaddings.xml b/percent/tests/res/layout/percent_frame_layout_hpaddings.xml
new file mode 100644
index 0000000..5cc476d
--- /dev/null
+++ b/percent/tests/res/layout/percent_frame_layout_hpaddings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.percent.PercentFrameLayout
+ 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:paddingLeft="48dip"
+ android:paddingRight="64dip">
+ <View
+ android:id="@+id/child1"
+ app:layout_widthPercent="50%"
+ android:layout_height="match_parent"
+ android:layout_gravity="left"
+ android:background="#80FF0000" />
+ <View
+ android:id="@+id/child2"
+ app:layout_widthPercent="50%"
+ android:layout_height="match_parent"
+ android:layout_gravity="right"
+ android:background="#800000FF" />
+</android.support.percent.PercentFrameLayout>
diff --git a/percent/tests/res/layout/percent_frame_layout_vpaddings.xml b/percent/tests/res/layout/percent_frame_layout_vpaddings.xml
new file mode 100644
index 0000000..d71c368
--- /dev/null
+++ b/percent/tests/res/layout/percent_frame_layout_vpaddings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.percent.PercentFrameLayout
+ 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:paddingTop="48dip"
+ android:paddingBottom="64dip">
+ <View
+ android:id="@+id/child1"
+ android:layout_width="match_parent"
+ app:layout_heightPercent="50%"
+ android:layout_gravity="top"
+ android:background="#80FF0000" />
+ <View
+ android:id="@+id/child2"
+ android:layout_width="match_parent"
+ app:layout_heightPercent="50%"
+ android:layout_gravity="bottom"
+ android:background="#800000FF" />
+</android.support.percent.PercentFrameLayout>
diff --git a/percent/tests/res/layout/percent_relative_layout.xml b/percent/tests/res/layout/percent_relative_layout.xml
new file mode 100644
index 0000000..7bdfe9b
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.percent.PercentRelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- This child is anchored along the top edge of the parent, testing percent-based
+ left margin, top margin and width, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_top"
+ android:layout_alignParentTop="true"
+ app:layout_widthPercent="50%"
+ app:layout_aspectRatio="2000%"
+ app:layout_marginLeftPercent="20%"
+ app:layout_marginTopPercent="5%"
+ android:background="#5690E0" />
+
+ <!-- This child is anchored along the left edge of the parent, testing percent-based
+ left margin, top margin and height, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_left"
+ android:layout_alignParentLeft="true"
+ app:layout_heightPercent="50%"
+ app:layout_aspectRatio="5%"
+ app:layout_marginLeftPercent="5%"
+ app:layout_marginTopPercent="20%"
+ android:background="#902030" />
+
+ <!-- This child is anchored along the bottom edge of the parent, testing percent-based
+ right margin, bottom margin and width, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_bottom"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ app:layout_widthPercent="40%"
+ app:layout_aspectRatio="2000%"
+ app:layout_marginRightPercent="20%"
+ app:layout_marginBottomPercent="5%"
+ android:background="#A0B040" />
+
+ <!-- This child is anchored along the right edge of the parent, testing percent-based
+ right margin, bottom margin and height, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_right"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ app:layout_heightPercent="40%"
+ app:layout_aspectRatio="5%"
+ app:layout_marginRightPercent="5%"
+ app:layout_marginBottomPercent="20%"
+ android:background="#20C0A0" />
+
+ <!-- This child is anchored to be in the "center" of the parent by anchoring it with
+ percent-based margins relatively to its edge-aligned siblings. -->
+ <View
+ android:id="@+id/child_center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@+id/child_top"
+ android:layout_toRightOf="@+id/child_left"
+ android:layout_toLeftOf="@+id/child_right"
+ android:layout_above="@+id/child_bottom"
+ app:layout_marginPercent="10%"
+ android:background="#F07020" />
+
+</android.support.percent.PercentRelativeLayout>
+
diff --git a/percent/tests/res/layout/percent_relative_layout_hpaddings.xml b/percent/tests/res/layout/percent_relative_layout_hpaddings.xml
new file mode 100644
index 0000000..2d6f1e0
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_hpaddings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.percent.PercentRelativeLayout
+ 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:paddingLeft="48dip"
+ android:paddingRight="64dip">
+ <View
+ android:id="@+id/child1"
+ app:layout_widthPercent="50%"
+ android:layout_height="match_parent"
+ android:layout_alignParentLeft="true"
+ android:background="#80FF0000" />
+ <View
+ android:id="@+id/child2"
+ app:layout_widthPercent="50%"
+ android:layout_height="match_parent"
+ android:layout_toRightOf="@id/child1"
+ android:background="#800000FF" />
+</android.support.percent.PercentRelativeLayout>
diff --git a/percent/tests/res/layout/percent_relative_layout_rtl.xml b/percent/tests/res/layout/percent_relative_layout_rtl.xml
new file mode 100644
index 0000000..2d0e813
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_rtl.xml
@@ -0,0 +1,97 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Test layout for testing RTL-aware attributes on PercentRelativeLayout. Note that
+ we do not need to use layout_marginStartPercent *and* layout_marginLeftPercent
+ on the same child view to make it work correctly on different platform versions,
+ while we do have to use layout_alignParentEnd *and* layout_alignParentRight (as
+ the core RelativeLayout needs the old attribute on older pre-v17 platform versions).
+-->
+<android.support.percent.PercentRelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <!-- This child is anchored along the top edge of the parent, testing percent-based
+ start margin, top margin and width, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_top"
+ android:layout_alignParentTop="true"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentLeft="true"
+ app:layout_widthPercent="50%"
+ app:layout_aspectRatio="2000%"
+ app:layout_marginStartPercent="20%"
+ app:layout_marginTopPercent="5%"
+ android:background="#5690E0" />
+
+ <!-- This child is anchored along the start edge of the parent, testing percent-based
+ start margin, top margin and height, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_start"
+ android:layout_alignParentStart="true"
+ android:layout_alignParentLeft="true"
+ app:layout_heightPercent="50%"
+ app:layout_aspectRatio="5%"
+ app:layout_marginStartPercent="5%"
+ app:layout_marginTopPercent="20%"
+ android:background="#902030" />
+
+ <!-- This child is anchored along the bottom edge of the parent, testing percent-based
+ end margin, bottom margin and width, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_bottom"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ app:layout_widthPercent="40%"
+ app:layout_aspectRatio="2000%"
+ app:layout_marginEndPercent="20%"
+ app:layout_marginBottomPercent="5%"
+ android:background="#A0B040" />
+
+ <!-- This child is anchored along the end edge of the parent, testing percent-based
+ end margin, bottom margin and height, as well as the aspect ratio. -->
+ <View
+ android:id="@+id/child_end"
+ android:layout_alignParentBottom="true"
+ android:layout_alignParentRight="true"
+ android:layout_alignParentEnd="true"
+ app:layout_heightPercent="40%"
+ app:layout_aspectRatio="5%"
+ app:layout_marginEndPercent="5%"
+ app:layout_marginBottomPercent="20%"
+ android:background="#20C0A0" />
+
+ <!-- This child is anchored to be in the "center" of the parent by anchoring it with
+ percent-based margins relatively to its edge-aligned siblings. -->
+ <View
+ android:id="@+id/child_center"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_below="@+id/child_top"
+ android:layout_toEndOf="@+id/child_start"
+ android:layout_toRightOf="@+id/child_start"
+ android:layout_toStartOf="@+id/child_end"
+ android:layout_toLeftOf="@+id/child_end"
+ android:layout_above="@+id/child_bottom"
+ app:layout_marginPercent="10%"
+ android:background="#F07020" />
+
+</android.support.percent.PercentRelativeLayout>
+
diff --git a/percent/tests/res/layout/percent_relative_layout_vpaddings.xml b/percent/tests/res/layout/percent_relative_layout_vpaddings.xml
new file mode 100644
index 0000000..33defde
--- /dev/null
+++ b/percent/tests/res/layout/percent_relative_layout_vpaddings.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<android.support.percent.PercentRelativeLayout
+ 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:paddingTop="48dip"
+ android:paddingBottom="64dip">
+ <View
+ android:id="@+id/child1"
+ android:layout_width="match_parent"
+ app:layout_heightPercent="50%"
+ android:layout_alignParentTop="true"
+ android:background="#80FF0000" />
+ <View
+ android:id="@+id/child2"
+ android:layout_width="match_parent"
+ app:layout_heightPercent="50%"
+ android:layout_below="@id/child1"
+ android:background="#800000FF" />
+</android.support.percent.PercentRelativeLayout>
diff --git a/previewsdk/Android.mk b/previewsdk/Android.mk
index 61a3d1f..ccd0547 100644
--- a/previewsdk/Android.mk
+++ b/previewsdk/Android.mk
@@ -26,7 +26,7 @@
bash $< > $@
LOCAL_MODULE := android-support-previewsdk
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_GENERATED_SOURCES := $(previewsdk_gen_java_files)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
diff --git a/recommendation/Android.mk b/recommendation/Android.mk
index d60bb2b..a46c565 100644
--- a/recommendation/Android.mk
+++ b/recommendation/Android.mk
@@ -21,7 +21,6 @@
$(call all-java-files-under, src) \
$(call all-html-files-under, src)
recommendation.docs.java_libraries := \
- framework \
android-support-v4 \
android-support-recommendation
diff --git a/recommendation/api/23.1.1.txt b/recommendation/api/23.1.1.txt
new file mode 100644
index 0000000..ce69b31
--- /dev/null
+++ b/recommendation/api/23.1.1.txt
@@ -0,0 +1,132 @@
+package android.support.app.recommendation {
+
+ public final class ContentRecommendation {
+ method public java.lang.String getBackgroundImageUri();
+ method public int getBadgeImageResourceId();
+ method public int getColor();
+ method public android.graphics.Bitmap getContentImage();
+ method public android.support.app.recommendation.ContentRecommendation.IntentData getContentIntent();
+ method public java.lang.String[] getContentTypes();
+ method public android.support.app.recommendation.ContentRecommendation.IntentData getDismissIntent();
+ method public java.lang.String[] getGenres();
+ method public java.lang.String getGroup();
+ method public java.lang.String getIdTag();
+ method public java.lang.String getMaturityRating();
+ method public android.app.Notification getNotificationObject(android.content.Context);
+ method public java.lang.String getPricingType();
+ method public java.lang.String getPricingValue();
+ method public java.lang.String getPrimaryContentType();
+ method public int getProgressMax();
+ method public int getProgressValue();
+ method public long getRunningTime();
+ method public java.lang.String getSortKey();
+ method public java.lang.String getSourceName();
+ method public int getStatus();
+ method public java.lang.String getText();
+ method public java.lang.String getTitle();
+ method public boolean hasProgressInfo();
+ method public boolean isAutoDismiss();
+ method public void setAutoDismiss(boolean);
+ method public void setGroup(java.lang.String);
+ method public void setProgress(int, int);
+ method public void setSortKey(java.lang.String);
+ method public void setStatus(int);
+ field public static final java.lang.String CONTENT_MATURITY_ALL = "android.contentMaturity.all";
+ field public static final java.lang.String CONTENT_MATURITY_HIGH = "android.contentMaturity.high";
+ field public static final java.lang.String CONTENT_MATURITY_LOW = "android.contentMaturity.low";
+ field public static final java.lang.String CONTENT_MATURITY_MEDIUM = "android.contentMaturity.medium";
+ field public static final java.lang.String CONTENT_PRICING_FREE = "android.contentPrice.free";
+ field public static final java.lang.String CONTENT_PRICING_PREORDER = "android.contentPrice.preorder";
+ field public static final java.lang.String CONTENT_PRICING_PURCHASE = "android.contentPrice.purchase";
+ field public static final java.lang.String CONTENT_PRICING_RENTAL = "android.contentPrice.rental";
+ field public static final java.lang.String CONTENT_PRICING_SUBSCRIPTION = "android.contentPrice.subscription";
+ field public static final int CONTENT_STATUS_AVAILABLE = 2; // 0x2
+ field public static final int CONTENT_STATUS_PENDING = 1; // 0x1
+ field public static final int CONTENT_STATUS_READY = 0; // 0x0
+ field public static final int CONTENT_STATUS_UNAVAILABLE = 3; // 0x3
+ field public static final java.lang.String CONTENT_TYPE_APP = "android.contentType.app";
+ field public static final java.lang.String CONTENT_TYPE_BOOK = "android.contentType.book";
+ field public static final java.lang.String CONTENT_TYPE_COMIC = "android.contentType.comic";
+ field public static final java.lang.String CONTENT_TYPE_GAME = "android.contentType.game";
+ field public static final java.lang.String CONTENT_TYPE_MAGAZINE = "android.contentType.magazine";
+ field public static final java.lang.String CONTENT_TYPE_MOVIE = "android.contentType.movie";
+ field public static final java.lang.String CONTENT_TYPE_MUSIC = "android.contentType.music";
+ field public static final java.lang.String CONTENT_TYPE_NEWS = "android.contentType.news";
+ field public static final java.lang.String CONTENT_TYPE_PODCAST = "android.contentType.podcast";
+ field public static final java.lang.String CONTENT_TYPE_RADIO = "android.contentType.radio";
+ field public static final java.lang.String CONTENT_TYPE_SERIAL = "android.contentType.serial";
+ field public static final java.lang.String CONTENT_TYPE_SPORTS = "android.contentType.sports";
+ field public static final java.lang.String CONTENT_TYPE_TRAILER = "android.contentType.trailer";
+ field public static final java.lang.String CONTENT_TYPE_VIDEO = "android.contentType.video";
+ field public static final java.lang.String CONTENT_TYPE_WEBSITE = "android.contentType.website";
+ field public static final int INTENT_TYPE_ACTIVITY = 1; // 0x1
+ field public static final int INTENT_TYPE_BROADCAST = 2; // 0x2
+ field public static final int INTENT_TYPE_SERVICE = 3; // 0x3
+ }
+
+ public static final class ContentRecommendation.Builder {
+ ctor public ContentRecommendation.Builder();
+ method public android.support.app.recommendation.ContentRecommendation build();
+ method public android.support.app.recommendation.ContentRecommendation.Builder setAutoDismiss(boolean);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setBackgroundImageUri(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setBadgeIcon(int);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setColor(int);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setContentImage(android.graphics.Bitmap);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setContentIntentData(int, android.content.Intent, int, android.os.Bundle);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setContentTypes(java.lang.String[]);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setDismissIntentData(int, android.content.Intent, int, android.os.Bundle);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setGenres(java.lang.String[]);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setGroup(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setIdTag(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setMaturityRating(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setPricingInformation(java.lang.String, java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setProgress(int, int);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setRunningTime(long);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setSortKey(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setSourceName(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setStatus(int);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setText(java.lang.String);
+ method public android.support.app.recommendation.ContentRecommendation.Builder setTitle(java.lang.String);
+ }
+
+ public static abstract class ContentRecommendation.ContentMaturity implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class ContentRecommendation.ContentPricing implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class ContentRecommendation.ContentStatus implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract class ContentRecommendation.ContentType implements java.lang.annotation.Annotation {
+ }
+
+ public static class ContentRecommendation.IntentData {
+ ctor public ContentRecommendation.IntentData();
+ }
+
+ public static abstract class ContentRecommendation.IntentType implements java.lang.annotation.Annotation {
+ }
+
+ public final class RecommendationExtender implements android.app.Notification.Extender {
+ ctor public RecommendationExtender();
+ ctor public RecommendationExtender(android.app.Notification);
+ method public android.app.Notification.Builder extend(android.app.Notification.Builder);
+ method public java.lang.String[] getContentTypes();
+ method public java.lang.String[] getGenres();
+ method public java.lang.String getMaturityRating();
+ method public java.lang.String getPricingType();
+ method public java.lang.String getPricingValue();
+ method public java.lang.String getPrimaryContentType();
+ method public long getRunningTime();
+ method public int getStatus();
+ method public android.support.app.recommendation.RecommendationExtender setContentTypes(java.lang.String[]);
+ method public android.support.app.recommendation.RecommendationExtender setGenres(java.lang.String[]);
+ method public android.support.app.recommendation.RecommendationExtender setMaturityRating(java.lang.String);
+ method public android.support.app.recommendation.RecommendationExtender setPricingInformation(java.lang.String, java.lang.String);
+ method public android.support.app.recommendation.RecommendationExtender setRunningTime(long);
+ method public android.support.app.recommendation.RecommendationExtender setStatus(int);
+ }
+
+}
+
diff --git a/recommendation/build.gradle b/recommendation/build.gradle
index a9db906..75d68d1 100644
--- a/recommendation/build.gradle
+++ b/recommendation/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'recommendation'
@@ -7,7 +7,7 @@
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
defaultConfig {
minSdkVersion 21
@@ -32,3 +32,36 @@
targetCompatibility JavaVersion.VERSION_1_7
}
}
+
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
diff --git a/settings.gradle b/settings.gradle
index d33c2ac..0e5c223 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,3 +1,4 @@
+apply from:'buildSrc/studioCompat.gradle'
include ':support-annotations'
project(':support-annotations').projectDir = new File(rootDir, 'annotations')
@@ -51,3 +52,10 @@
include ':support-recommendation'
project(':support-recommendation').projectDir = new File(rootDir, 'recommendation')
+
+include ':support-vector-drawable'
+project(':support-vector-drawable').projectDir = new File(rootDir, 'graphics/drawable/static')
+
+include ':support-animated-vector-drawable'
+project(':support-animated-vector-drawable').projectDir = new File(rootDir, 'graphics/drawable/animated')
+
diff --git a/tests/Android.mk b/tests/Android.mk
index 7993eed..bfdb396 100644
--- a/tests/Android.mk
+++ b/tests/Android.mk
@@ -4,9 +4,13 @@
LOCAL_MODULE_TAGS := tests
LOCAL_SRC_FILES := $(call all-java-files-under, java)
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_STATIC_JAVA_LIBRARIES := mockito-target android-support-v4 junit-runner
-LOCAL_JAVA_LIBRARIES := android.test.runner
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-test \
+ android-support-v4 \
+ mockito-target
+
LOCAL_PACKAGE_NAME := AndroidSupportTests
include $(BUILD_PACKAGE)
diff --git a/tests/AndroidManifest.xml b/tests/AndroidManifest.xml
index 35314ae..6117cba 100644
--- a/tests/AndroidManifest.xml
+++ b/tests/AndroidManifest.xml
@@ -17,9 +17,21 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="android.support.tests">
+ <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+
<application>
<uses-library android:name="android.test.runner" />
+ <provider
+ android:name="android.support.v4.content.FileProvider"
+ android:authorities="moocow"
+ android:grantUriPermissions="true">
+ <meta-data
+ android:name="android.support.FILE_PROVIDER_PATHS"
+ android:resource="@xml/paths" />
+ </provider>
+
<activity android:name="android.support.tests.GrantActivity" android:label="_GrantActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
diff --git a/tests/java/android/support/v4/content/FileProviderTest.java b/tests/java/android/support/v4/content/FileProviderTest.java
new file mode 100644
index 0000000..23f8e0e
--- /dev/null
+++ b/tests/java/android/support/v4/content/FileProviderTest.java
@@ -0,0 +1,390 @@
+/*
+ * Copyright (C) 2013 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content;
+
+import static android.provider.OpenableColumns.DISPLAY_NAME;
+import static android.provider.OpenableColumns.SIZE;
+
+import android.content.ContentResolver;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Environment;
+import android.support.v4.content.FileProvider.SimplePathStrategy;
+import android.test.AndroidTestCase;
+import android.test.MoreAsserts;
+import android.test.suitebuilder.annotation.Suppress;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+/**
+ * Tests for {@link FileProvider}
+ */
+@Suppress
+public class FileProviderTest extends AndroidTestCase {
+ private static final String TEST_AUTHORITY = "moocow";
+
+ private static final String TEST_FILE = "file.test";
+ private static final byte[] TEST_DATA = new byte[] { (byte) 0xf0, 0x00, 0x0d };
+ private static final byte[] TEST_DATA_ALT = new byte[] { (byte) 0x33, 0x66 };
+
+ private ContentResolver mResolver;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ mResolver = getContext().getContentResolver();
+ }
+
+ public void testStrategyUriSimple() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ File file = buildPath(mContext.getFilesDir(), "file.test");
+ assertEquals("content://authority/tag/file.test",
+ strat.getUriForFile(file).toString());
+
+ file = buildPath(mContext.getFilesDir(), "subdir", "file.test");
+ assertEquals("content://authority/tag/subdir/file.test",
+ strat.getUriForFile(file).toString());
+
+ file = buildPath(Environment.getExternalStorageDirectory(), "file.test");
+ try {
+ strat.getUriForFile(file);
+ fail("somehow got uri for file outside roots?");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testStrategyUriJumpOutside() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ File file = buildPath(mContext.getFilesDir(), "..", "file.test");
+ try {
+ strat.getUriForFile(file);
+ fail("file escaped!");
+ } catch (IllegalArgumentException e) {
+ }
+ }
+
+ public void testStrategyUriShortestRoot() throws Exception {
+ SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag1", mContext.getFilesDir());
+ strat.addRoot("tag2", new File("/"));
+
+ File file = buildPath(mContext.getFilesDir(), "file.test");
+ assertEquals("content://authority/tag1/file.test",
+ strat.getUriForFile(file).toString());
+
+ strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag1", new File("/"));
+ strat.addRoot("tag2", mContext.getFilesDir());
+
+ file = buildPath(mContext.getFilesDir(), "file.test");
+ assertEquals("content://authority/tag2/file.test",
+ strat.getUriForFile(file).toString());
+ }
+
+ public void testStrategyFileSimple() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ File file = buildPath(mContext.getFilesDir(), "file.test");
+ assertEquals(file.getPath(),
+ strat.getFileForUri(Uri.parse("content://authority/tag/file.test")).getPath());
+
+ file = buildPath(mContext.getFilesDir(), "subdir", "file.test");
+ assertEquals(file.getPath(), strat.getFileForUri(
+ Uri.parse("content://authority/tag/subdir/file.test")).getPath());
+ }
+
+ public void testStrategyFileJumpOutside() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ try {
+ strat.getFileForUri(Uri.parse("content://authority/tag/../file.test"));
+ fail("file escaped!");
+ } catch (SecurityException e) {
+ }
+ }
+
+ public void testStrategyEscaping() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("t/g", mContext.getFilesDir());
+
+ File file = buildPath(mContext.getFilesDir(), "lol\"wat?foo&bar", "wat.txt");
+ final String expected = "content://authority/t%2Fg/lol%22wat%3Ffoo%26bar/wat.txt";
+
+ assertEquals(expected,
+ strat.getUriForFile(file).toString());
+ assertEquals(file.getPath(),
+ strat.getFileForUri(Uri.parse(expected)).getPath());
+ }
+
+ public void testStrategyExtraParams() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ File file = buildPath(mContext.getFilesDir(), "file.txt");
+ assertEquals(file.getPath(), strat.getFileForUri(
+ Uri.parse("content://authority/tag/file.txt?extra=foo")).getPath());
+ }
+
+ public void testStrategyExtraSeparators() throws Exception {
+ final SimplePathStrategy strat = new SimplePathStrategy("authority");
+ strat.addRoot("tag", mContext.getFilesDir());
+
+ // When canonicalized, the path separators are trimmed
+ File inFile = new File(mContext.getFilesDir(), "//foo//bar//");
+ File outFile = new File(mContext.getFilesDir(), "/foo/bar");
+ final String expected = "content://authority/tag/foo/bar";
+
+ assertEquals(expected,
+ strat.getUriForFile(inFile).toString());
+ assertEquals(outFile.getPath(),
+ strat.getFileForUri(Uri.parse(expected)).getPath());
+ }
+
+ public void testQueryProjectionNull() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ // Verify that null brings out default columns
+ Cursor cursor = mResolver.query(uri, null, null, null, null);
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(TEST_FILE, cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
+ assertEquals(TEST_DATA.length, cursor.getLong(cursor.getColumnIndex(SIZE)));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void testQueryProjectionOrder() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ // Verify that swapped order works
+ Cursor cursor = mResolver.query(uri, new String[] {
+ SIZE, DISPLAY_NAME }, null, null, null);
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(TEST_DATA.length, cursor.getLong(0));
+ assertEquals(TEST_FILE, cursor.getString(1));
+ } finally {
+ cursor.close();
+ }
+
+ cursor = mResolver.query(uri, new String[] {
+ DISPLAY_NAME, SIZE }, null, null, null);
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(TEST_FILE, cursor.getString(0));
+ assertEquals(TEST_DATA.length, cursor.getLong(1));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void testQueryExtraColumn() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ // Verify that extra column doesn't gook things up
+ Cursor cursor = mResolver.query(uri, new String[] {
+ SIZE, "foobar", DISPLAY_NAME }, null, null, null);
+ try {
+ assertEquals(1, cursor.getCount());
+ cursor.moveToFirst();
+ assertEquals(TEST_DATA.length, cursor.getLong(0));
+ assertEquals(TEST_FILE, cursor.getString(1));
+ } finally {
+ cursor.close();
+ }
+ }
+
+ public void testReadFile() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ assertContentsEquals(TEST_DATA, uri);
+ }
+
+ public void testWriteFile() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ assertContentsEquals(TEST_DATA, uri);
+
+ final OutputStream out = mResolver.openOutputStream(uri);
+ try {
+ out.write(TEST_DATA_ALT);
+ } finally {
+ closeQuietly(out);
+ }
+
+ assertContentsEquals(TEST_DATA_ALT, uri);
+ }
+
+ public void testWriteMissingFile() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, null);
+
+ try {
+ assertContentsEquals(new byte[0], uri);
+ fail("Somehow read missing file?");
+ } catch(FileNotFoundException e) {
+ }
+
+ final OutputStream out = mResolver.openOutputStream(uri);
+ try {
+ out.write(TEST_DATA_ALT);
+ } finally {
+ closeQuietly(out);
+ }
+
+ assertContentsEquals(TEST_DATA_ALT, uri);
+ }
+
+ public void testDelete() throws Exception {
+ final File file = new File(mContext.getFilesDir(), TEST_FILE);
+ final Uri uri = stageFileAndGetUri(file, TEST_DATA);
+
+ assertContentsEquals(TEST_DATA, uri);
+
+ assertEquals(1, mResolver.delete(uri, null, null));
+ assertEquals(0, mResolver.delete(uri, null, null));
+
+ try {
+ assertContentsEquals(new byte[0], uri);
+ fail("Somehow read missing file?");
+ } catch(FileNotFoundException e) {
+ }
+ }
+
+ public void testMetaDataTargets() {
+ Uri actual;
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ new File("/proc/version"));
+ assertEquals("content://moocow/test_root/proc/version", actual.toString());
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ new File("/proc/1/mountinfo"));
+ assertEquals("content://moocow/test_init/mountinfo", actual.toString());
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ buildPath(mContext.getFilesDir(), "meow"));
+ assertEquals("content://moocow/test_files/meow", actual.toString());
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ buildPath(mContext.getFilesDir(), "thumbs", "rawr"));
+ assertEquals("content://moocow/test_thumbs/rawr", actual.toString());
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ buildPath(mContext.getCacheDir(), "up", "down"));
+ assertEquals("content://moocow/test_cache/up/down", actual.toString());
+
+ actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
+ buildPath(Environment.getExternalStorageDirectory(), "Android", "obb", "foobar"));
+ assertEquals("content://moocow/test_external/Android/obb/foobar", actual.toString());
+ }
+
+ private void assertContentsEquals(byte[] expected, Uri actual) throws Exception {
+ final InputStream in = mResolver.openInputStream(actual);
+ try {
+ MoreAsserts.assertEquals(expected, readFully(in));
+ } finally {
+ closeQuietly(in);
+ }
+ }
+
+ private Uri stageFileAndGetUri(File file, byte[] data) throws Exception {
+ if (data != null) {
+ final FileOutputStream out = new FileOutputStream(file);
+ try {
+ out.write(data);
+ } finally {
+ out.close();
+ }
+ } else {
+ file.delete();
+ }
+ return FileProvider.getUriForFile(mContext, TEST_AUTHORITY, file);
+ }
+
+ private static File buildPath(File base, String... segments) {
+ File cur = base;
+ for (String segment : segments) {
+ if (cur == null) {
+ cur = new File(segment);
+ } else {
+ cur = new File(cur, segment);
+ }
+ }
+ return cur;
+ }
+
+ /**
+ * Closes 'closeable', ignoring any checked exceptions. Does nothing if 'closeable' is null.
+ */
+ private static void closeQuietly(AutoCloseable closeable) {
+ if (closeable != null) {
+ try {
+ closeable.close();
+ } catch (RuntimeException rethrown) {
+ throw rethrown;
+ } catch (Exception ignored) {
+ }
+ }
+ }
+
+ /**
+ * Returns a byte[] containing the remainder of 'in', closing it when done.
+ */
+ private static byte[] readFully(InputStream in) throws IOException {
+ try {
+ return readFullyNoClose(in);
+ } finally {
+ in.close();
+ }
+ }
+
+ /**
+ * Returns a byte[] containing the remainder of 'in'.
+ */
+ private static byte[] readFullyNoClose(InputStream in) throws IOException {
+ ByteArrayOutputStream bytes = new ByteArrayOutputStream();
+ byte[] buffer = new byte[1024];
+ int count;
+ while ((count = in.read(buffer)) != -1) {
+ bytes.write(buffer, 0, count);
+ }
+ return bytes.toByteArray();
+ }
+}
diff --git a/tests/java/android/support/v4/provider/DocumentFileTest.java b/tests/java/android/support/v4/provider/DocumentFileTest.java
index db735f6..26c10a4 100644
--- a/tests/java/android/support/v4/provider/DocumentFileTest.java
+++ b/tests/java/android/support/v4/provider/DocumentFileTest.java
@@ -20,10 +20,9 @@
import android.content.UriPermission;
import android.net.Uri;
import android.os.Environment;
-import android.os.FileUtils;
import android.os.SystemClock;
-import android.support.v4.provider.DocumentFile;
import android.test.AndroidTestCase;
+import android.util.Log;
import java.io.DataInputStream;
import java.io.DataOutputStream;
@@ -79,10 +78,10 @@
private void resetRoot() throws Exception {
final File tmp = new File(root, "bark.pdf");
- FileUtils.deleteContents(tmp);
+ deleteContents(tmp);
tmp.delete();
- FileUtils.deleteContents(rootMeow);
+ deleteContents(rootMeow);
rootMeow.mkdir();
rootMeowBar.mkdir();
@@ -91,6 +90,22 @@
writeInt(rootMeowDog, 48);
}
+ public static boolean deleteContents(File dir) {
+ File[] files = dir.listFiles();
+ boolean success = true;
+ if (files != null) {
+ for (File file : files) {
+ if (file.isDirectory()) {
+ success &= deleteContents(file);
+ }
+ if (!file.delete()) {
+ success = false;
+ }
+ }
+ }
+ return success;
+ }
+
private interface DocumentTest {
public void exec(DocumentFile doc) throws Exception;
}
diff --git a/tests/res/xml/paths.xml b/tests/res/xml/paths.xml
new file mode 100644
index 0000000..baa2908
--- /dev/null
+++ b/tests/res/xml/paths.xml
@@ -0,0 +1,14 @@
+<paths xmlns:android="http://schemas.android.com/apk/res/android">
+ <!-- / -->
+ <root-path name="test_root" />
+ <!-- /proc/1 -->
+ <root-path name="test_init" path="proc/1/" />
+ <!-- /data/data/com.example/files -->
+ <files-path name="test_files" />
+ <!-- /data/data/com.example/files/thumbs -->
+ <files-path name="test_thumbs" path="thumbs/" />
+ <!-- /data/data/com.example/cache -->
+ <cache-path name="test_cache" />
+ <!-- /storage/emulated/0/Android/com.example/files -->
+ <external-path name="test_external" />
+</paths>
diff --git a/v13/Android.mk b/v13/Android.mk
index c23d566..4feb7ff 100644
--- a/v13/Android.mk
+++ b/v13/Android.mk
@@ -37,12 +37,21 @@
# A helper sub-library that makes direct use of MNC APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v13-mnc
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 23
LOCAL_SRC_FILES := $(call all-java-files-under, api23)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-ics-mr1
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
+# A helper sub-library that makes direct use of NYC APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v13-nyc
+LOCAL_SDK_VERSION := current
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v13-mnc
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
# -----------------------------------------------------------------------
include $(CLEAR_VARS)
@@ -50,8 +59,7 @@
LOCAL_SDK_VERSION := 13
LOCAL_SRC_FILES := $(call all-java-files-under, java)
LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4 \
- android-support-v13-ics-mr1 \
- android-support-v13-mnc
+ android-support-v13-nyc
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v13/AndroidManifest.xml b/v13/AndroidManifest.xml
index c862c99..ea25a74 100644
--- a/v13/AndroidManifest.xml
+++ b/v13/AndroidManifest.xml
@@ -14,6 +14,8 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="android.support.v13">
+ <uses-sdk android:minSdkVersion="13" tools:overrideLibrary="android.support.v13"/>
<application />
</manifest>
diff --git a/v13/api/23.1.1.txt b/v13/api/23.1.1.txt
new file mode 100644
index 0000000..36ea6c0
--- /dev/null
+++ b/v13/api/23.1.1.txt
@@ -0,0 +1,38 @@
+package android.support.v13.app {
+
+ public class FragmentCompat {
+ ctor public FragmentCompat();
+ method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
+ method public static void setMenuVisibility(android.app.Fragment, boolean);
+ method public static void setUserVisibleHint(android.app.Fragment, boolean);
+ method public static boolean shouldShowRequestPermissionRationale(android.app.Fragment, java.lang.String);
+ }
+
+ public static abstract interface FragmentCompat.OnRequestPermissionsResultCallback {
+ method public abstract void onRequestPermissionsResult(int, java.lang.String[], int[]);
+ }
+
+ public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public FragmentPagerAdapter(android.app.FragmentManager);
+ method public abstract android.app.Fragment getItem(int);
+ method public long getItemId(int);
+ method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ }
+
+ public abstract class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public FragmentStatePagerAdapter(android.app.FragmentManager);
+ method public abstract android.app.Fragment getItem(int);
+ method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ }
+
+ public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+ ctor public FragmentTabHost(android.content.Context);
+ ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
+ method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
+ method public void onTabChanged(java.lang.String);
+ method public void setup(android.content.Context, android.app.FragmentManager);
+ method public void setup(android.content.Context, android.app.FragmentManager, int);
+ }
+
+}
+
diff --git a/v13/api/current.txt b/v13/api/current.txt
index 36ea6c0..e141fe4 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -1,5 +1,9 @@
package android.support.v13.app {
+ public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+ method public static android.support.v13.view.DragAndDropPermissionsCompat requestDragAndDropPermissions(android.app.Activity, android.view.DragEvent);
+ }
+
public class FragmentCompat {
ctor public FragmentCompat();
method public static void requestPermissions(android.app.Fragment, java.lang.String[], int);
@@ -36,3 +40,30 @@
}
+package android.support.v13.view {
+
+ public final class DragAndDropPermissionsCompat {
+ method public void release();
+ }
+
+ public class DragStartHelper {
+ ctor public DragStartHelper(android.view.View, android.support.v13.view.DragStartHelper.OnDragStartListener);
+ method public void attach();
+ method public void detach();
+ method public void getTouchPosition(android.graphics.Point);
+ method public boolean onLongClick(android.view.View);
+ method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ }
+
+ public static abstract interface DragStartHelper.OnDragStartListener {
+ method public abstract boolean onDragStart(android.view.View, android.support.v13.view.DragStartHelper);
+ }
+
+ public class ViewCompat extends android.support.v4.view.ViewCompat {
+ method public static void cancelDragAndDrop(android.view.View);
+ method public static boolean startDragAndDrop(android.view.View, android.content.ClipData, android.view.View.DragShadowBuilder, java.lang.Object, int);
+ method public static void updateDragShadow(android.view.View, android.view.View.DragShadowBuilder);
+ }
+
+}
+
diff --git a/v13/api24/android/support/v13/app/FragmentCompatApi24.java b/v13/api24/android/support/v13/app/FragmentCompatApi24.java
new file mode 100644
index 0000000..4baf02a
--- /dev/null
+++ b/v13/api24/android/support/v13/app/FragmentCompatApi24.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v13.app;
+
+import android.app.Fragment;
+
+class FragmentCompatApi24 {
+ public static void setUserVisibleHint(Fragment f, boolean isVisible) {
+ f.setUserVisibleHint(isVisible);
+ }
+}
diff --git a/v13/api24/android/support/v13/view/DragAndDropPermissionsCompatApi24.java b/v13/api24/android/support/v13/view/DragAndDropPermissionsCompatApi24.java
new file mode 100644
index 0000000..363cf8d
--- /dev/null
+++ b/v13/api24/android/support/v13/view/DragAndDropPermissionsCompatApi24.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v13.view;
+
+import android.app.Activity;
+import android.view.DragAndDropPermissions;
+import android.view.DragEvent;
+
+class DragAndDropPermissionsCompatApi24 {
+ public static Object request(Activity activity, DragEvent dragEvent) {
+ return activity.requestDragAndDropPermissions(dragEvent);
+ }
+
+ public static void release(Object dragAndDropPermissions) {
+ ((DragAndDropPermissions)dragAndDropPermissions).release();
+ }
+}
diff --git a/v13/api24/android/support/v13/view/ViewCompatApi24.java b/v13/api24/android/support/v13/view/ViewCompatApi24.java
new file mode 100644
index 0000000..725cad0
--- /dev/null
+++ b/v13/api24/android/support/v13/view/ViewCompatApi24.java
@@ -0,0 +1,37 @@
+/*
+ * 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.v13.view;
+
+import android.content.ClipData;
+import android.view.View;
+
+class ViewCompatApi24 {
+ public static boolean startDragAndDrop(View v, ClipData data,
+ View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+ return v.startDragAndDrop(data, shadowBuilder, localState, flags);
+ }
+
+ public static void cancelDragAndDrop(View v) {
+ v.cancelDragAndDrop();
+ }
+
+ public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ v.updateDragShadow(shadowBuilder);
+ }
+
+ private ViewCompatApi24() {}
+}
diff --git a/v13/build.gradle b/v13/build.gradle
index c1e624b..4baaa27 100644
--- a/v13/build.gradle
+++ b/v13/build.gradle
@@ -1,56 +1,12 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'support-v13'
-// --------------------------
-// TO ADD NEW PLATFORM SPECIFIC CODE, UPDATE THIS:
-// create and configure the sourcesets/dependencies for platform-specific code.
-// values are: sourceset name, source folder name, api level, previous sourceset.
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
-ext.allSS = []
-
-def icsSS = createApiSourceset('ics', 'ics', '14', null)
-def icsMr1SS = createApiSourceset('icsmr1', 'ics-mr1', '15', icsSS)
-def api23SS = createApiSourceset('api23', 'api23', 'current', icsMr1SS)
-
-def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
- def sourceSet = sourceSets.create(name)
- sourceSet.java.srcDirs = [folder]
-
- def configName = sourceSet.getCompileConfigurationName()
-
- project.getDependencies().add(configName, getAndroidPrebuilt(apiLevel))
- if (previousSource != null) {
- setupDependencies(configName, previousSource)
- }
- ext.allSS.add(sourceSet)
- return sourceSet
-}
-
-def setupDependencies(String configName, SourceSet previousSourceSet) {
- project.getDependencies().add(configName, previousSourceSet.output)
- project.getDependencies().add(configName, previousSourceSet.compileClasspath)
-}
-
-// create a jar task for the code above
-tasks.create(name: "internalJar", type: Jar) {
- baseName "internal_impl"
-}
-
-ext.allSS.each { ss ->
- internalJar.from ss.output
-}
-
-dependencies {
- compile project(':support-v4')
-
- // add the internal implementation as a dependency.
- // this is not enough to make the regular compileJava task
- // depend on the generation of this jar. This is done below
- // when manipulating the libraryVariants.
- compile files(internalJar.archivePath)
-}
-
+createApiSourceSets(project, gradle.ext.studioCompat.modules.v13.apiTargets)
+setApiModuleDependencies(project, dependencies, gradle.ext.studioCompat.modules.v13.dependencies)
android {
compileSdkVersion 13
@@ -60,7 +16,6 @@
//targetSdkVersion 19
}
-
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDirs = ['java']
@@ -70,15 +25,23 @@
androidTest.java.srcDir 'tests/java'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
}
+
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
}
android.libraryVariants.all { variant ->
- variant.javaCompile.dependsOn internalJar
-
def name = variant.buildType.name
if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
diff --git a/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java b/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
index 39f0922..ea40376 100644
--- a/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
+++ b/v13/ics-mr1/android/support/v13/app/FragmentCompatICSMR1.java
@@ -20,6 +20,8 @@
class FragmentCompatICSMR1 {
public static void setUserVisibleHint(Fragment f, boolean isVisible) {
- f.setUserVisibleHint(isVisible);
+ if (f.getFragmentManager() != null) {
+ f.setUserVisibleHint(isVisible);
+ }
}
}
diff --git a/v13/java/android/support/v13/app/ActivityCompat.java b/v13/java/android/support/v13/app/ActivityCompat.java
new file mode 100644
index 0000000..e2a8a2c
--- /dev/null
+++ b/v13/java/android/support/v13/app/ActivityCompat.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2011 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.v13.app;
+
+import android.app.Activity;
+import android.support.v13.view.DragAndDropPermissionsCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.app.Activity}
+ * introduced after API level 13 in a backwards compatible fashion.
+ */
+public class ActivityCompat extends android.support.v4.app.ActivityCompat {
+
+ /**
+ * Create {@link DragAndDropPermissionsCompat} object bound to this activity and controlling
+ * the access permissions for content URIs associated with the {@link android.view.DragEvent}.
+ * @param dragEvent Drag event to request permission for
+ * @return The {@link DragAndDropPermissionsCompat} object used to control access to the content
+ * URIs. {@code null} if no content URIs are associated with the event or if permissions could
+ * not be granted.
+ */
+ public static DragAndDropPermissionsCompat requestDragAndDropPermissions(Activity activity,
+ DragEvent dragEvent) {
+ return DragAndDropPermissionsCompat.request(activity, dragEvent);
+ }
+
+ private ActivityCompat() {}
+}
diff --git a/v13/java/android/support/v13/app/FragmentCompat.java b/v13/java/android/support/v13/app/FragmentCompat.java
index 535092b..3812a97 100644
--- a/v13/java/android/support/v13/app/FragmentCompat.java
+++ b/v13/java/android/support/v13/app/FragmentCompat.java
@@ -23,6 +23,7 @@
import android.os.Handler;
import android.os.Looper;
import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
import java.util.Arrays;
@@ -101,9 +102,18 @@
}
}
+ static class NFragmentCompatImpl extends MncFragmentCompatImpl {
+ @Override
+ public void setUserVisibleHint(Fragment f, boolean deferStart) {
+ FragmentCompatApi24.setUserVisibleHint(f, deferStart);
+ }
+ }
+
static final FragmentCompatImpl IMPL;
static {
- if (Build.VERSION.SDK_INT >= 23) {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new NFragmentCompatImpl();
+ } else if (Build.VERSION.SDK_INT >= 23) {
IMPL = new MncFragmentCompatImpl();
} else if (android.os.Build.VERSION.SDK_INT >= 15) {
IMPL = new ICSMR1FragmentCompatImpl();
diff --git a/v13/java/android/support/v13/app/FragmentPagerAdapter.java b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
index 60aa5d0..adc08c23b 100644
--- a/v13/java/android/support/v13/app/FragmentPagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentPagerAdapter.java
@@ -81,6 +81,10 @@
@Override
public void startUpdate(ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
}
@Override
diff --git a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
index 89be82e..a28f6e8 100644
--- a/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
+++ b/v13/java/android/support/v13/app/FragmentStatePagerAdapter.java
@@ -87,6 +87,10 @@
@Override
public void startUpdate(ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
}
@Override
@@ -127,7 +131,7 @@
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
- Fragment fragment = (Fragment)object;
+ Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
@@ -137,7 +141,8 @@
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
- mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+ mSavedState.set(position, fragment.isAdded()
+ ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
diff --git a/v13/java/android/support/v13/app/FragmentTabHost.java b/v13/java/android/support/v13/app/FragmentTabHost.java
index 01378d9..fa1aad2 100644
--- a/v13/java/android/support/v13/app/FragmentTabHost.java
+++ b/v13/java/android/support/v13/app/FragmentTabHost.java
@@ -292,7 +292,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState)state;
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCurrentTabByTag(ss.curTab);
}
diff --git a/v13/java/android/support/v13/view/DragAndDropPermissionsCompat.java b/v13/java/android/support/v13/view/DragAndDropPermissionsCompat.java
new file mode 100644
index 0000000..4ade66f
--- /dev/null
+++ b/v13/java/android/support/v13/view/DragAndDropPermissionsCompat.java
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v13.view;
+
+import android.app.Activity;
+import android.support.v4.os.BuildCompat;
+import android.view.DragEvent;
+
+/**
+ * Helper for accessing features in {@link android.view.DragAndDropPermissions}
+ * introduced after API level 13 in a backwards compatible fashion.
+ */
+public final class DragAndDropPermissionsCompat {
+
+ interface DragAndDropPermissionsCompatImpl {
+ Object request(Activity activity, DragEvent dragEvent);
+ void release(Object dragAndDropPermissions);
+ }
+
+ static class BaseDragAndDropPermissionsCompatImpl implements DragAndDropPermissionsCompatImpl {
+ @Override
+ public Object request(Activity activity, DragEvent dragEvent) {
+ return null;
+ }
+
+ @Override
+ public void release(Object dragAndDropPermissions) {
+ // no-op
+ }
+ }
+
+ static class Api24DragAndDropPermissionsCompatImpl
+ extends BaseDragAndDropPermissionsCompatImpl {
+ @Override
+ public Object request(Activity activity, DragEvent dragEvent) {
+ return DragAndDropPermissionsCompatApi24.request(activity, dragEvent);
+ }
+
+ @Override
+ public void release(Object dragAndDropPermissions) {
+ DragAndDropPermissionsCompatApi24.release(dragAndDropPermissions);
+ }
+ }
+
+ private static DragAndDropPermissionsCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24DragAndDropPermissionsCompatImpl();
+ } else {
+ IMPL = new BaseDragAndDropPermissionsCompatImpl();
+ }
+ }
+
+ private Object mDragAndDropPermissions;
+
+ private DragAndDropPermissionsCompat(Object dragAndDropPermissions) {
+ mDragAndDropPermissions = dragAndDropPermissions;
+ }
+
+ /** @hide */
+ public static DragAndDropPermissionsCompat request(Activity activity, DragEvent dragEvent) {
+ Object dragAndDropPermissions = IMPL.request(activity, dragEvent);
+ if (dragAndDropPermissions != null) {
+ return new DragAndDropPermissionsCompat(dragAndDropPermissions);
+ }
+ return null;
+ }
+
+ /*
+ * Revoke the permission grant explicitly.
+ */
+ public void release() {
+ IMPL.release(mDragAndDropPermissions);
+ }
+}
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/v13/java/android/support/v13/view/DragStartHelper.java
new file mode 100644
index 0000000..d3b51cc
--- /dev/null
+++ b/v13/java/android/support/v13/view/DragStartHelper.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v13.view;
+
+
+import android.graphics.Point;
+import android.support.v4.view.InputDeviceCompat;
+import android.support.v4.view.MotionEventCompat;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * DragStartHelper is a utility class for implementing drag and drop support.
+ * <p>
+ * It detects gestures commonly used to start drag (long click for any input source,
+ * click and drag for mouse).
+ * <p>
+ * It also keeps track of the screen location where the drag started, and helps determining
+ * the hot spot position for a drag shadow.
+ * <p>
+ * Implement {@link DragStartHelper.OnDragStartListener} to start the drag operation:
+ * <pre>
+ * DragStartHelper.OnDragStartListener listener = new DragStartHelper.OnDragStartListener {
+ * protected void onDragStart(View view, DragStartHelper helper) {
+ * View.DragShadowBuilder shadowBuilder = new View.DragShadowBuilder(view) {
+ * public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
+ * super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
+ * helper.getTouchPosition(shadowTouchPoint);
+ * }
+ * };
+ * view.startDrag(mClipData, shadowBuilder, mLocalState, mDragFlags);
+ * }
+ * };
+ * mDragStartHelper = new DragStartHelper(mDraggableView, listener);
+ * </pre>
+ * Once created, DragStartHelper can be attached to a view (this will replace existing long click
+ * and touch listeners):
+ * <pre>
+ * mDragStartHelper.attach();
+ * </pre>
+ * It may also be used in combination with existing listeners:
+ * <pre>
+ * public boolean onTouch(View view, MotionEvent event) {
+ * if (mDragStartHelper.onTouch(view, event)) {
+ * return true;
+ * }
+ * return handleTouchEvent(view, event);
+ * }
+ * public boolean onLongClick(View view) {
+ * if (mDragStartHelper.onLongClick(view)) {
+ * return true;
+ * }
+ * return handleLongClickEvent(view);
+ * }
+ * </pre>
+ */
+public class DragStartHelper {
+ final private View mView;
+ final private OnDragStartListener mListener;
+
+ private int mLastTouchX, mLastTouchY;
+
+ /**
+ * Interface definition for a callback to be invoked when a drag start gesture is detected.
+ */
+ public interface OnDragStartListener {
+ /**
+ * Called when a drag start gesture has been detected.
+ *
+ * @param v The view over which the drag start gesture has been detected.
+ * @param helper The DragStartHelper object which detected the gesture.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ boolean onDragStart(View v, DragStartHelper helper);
+ }
+
+ /**
+ * Create a DragStartHelper associated with the specified view.
+ * The newly created helper is not initially attached to the view, {@link #attach} must be
+ * called explicitly.
+ * @param view A View
+ */
+ public DragStartHelper(View view, OnDragStartListener listener) {
+ mView = view;
+ mListener = listener;
+ }
+
+ /**
+ * Attach the helper to the view.
+ * <p>
+ * This will replace previously existing touch and long click listeners.
+ */
+ public void attach() {
+ mView.setOnLongClickListener(mLongClickListener);
+ mView.setOnTouchListener(mTouchListener);
+ }
+
+ /**
+ * Detach the helper from the view.
+ * <p>
+ * This will reset touch and long click listeners to {@code null}.
+ */
+ public void detach() {
+ mView.setOnLongClickListener(null);
+ mView.setOnTouchListener(null);
+ }
+
+ /**
+ * Handle a touch event.
+ * @param v The view the touch event has been dispatched to.
+ * @param event The MotionEvent object containing full information about
+ * the event.
+ * @return True if the listener has consumed the event, false otherwise.
+ */
+ public boolean onTouch(View v, MotionEvent event) {
+ if (event.getAction() == MotionEvent.ACTION_DOWN ||
+ event.getAction() == MotionEvent.ACTION_MOVE) {
+ mLastTouchX = (int) event.getX();
+ mLastTouchY = (int) event.getY();
+ }
+ if (event.getAction() == MotionEvent.ACTION_MOVE &&
+ MotionEventCompat.isFromSource(event, InputDeviceCompat.SOURCE_MOUSE) &&
+ (MotionEventCompat.getButtonState(event) & MotionEventCompat.BUTTON_PRIMARY) != 0) {
+ return mListener.onDragStart(v, this);
+ }
+ return false;
+ }
+
+ /**
+ * Handle a long click event.
+ * @param v The view that was clicked and held.
+ * @return true if the callback consumed the long click, false otherwise.
+ */
+ public boolean onLongClick(View v) {
+ return mListener.onDragStart(v, this);
+ }
+
+ /**
+ * Compute the position of the touch event that started the drag operation.
+ * @param point The position of the touch event that started the drag operation.
+ */
+ public void getTouchPosition(Point point) {
+ point.set(mLastTouchX, mLastTouchY);
+ }
+
+ private final View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
+ @Override
+ public boolean onLongClick(View v) {
+ return DragStartHelper.this.onLongClick(v);
+ }
+ };
+
+ private final View.OnTouchListener mTouchListener = new View.OnTouchListener() {
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ return DragStartHelper.this.onTouch(v, event);
+ }
+ };
+}
+
diff --git a/v13/java/android/support/v13/view/ViewCompat.java b/v13/java/android/support/v13/view/ViewCompat.java
new file mode 100644
index 0000000..38db5fa
--- /dev/null
+++ b/v13/java/android/support/v13/view/ViewCompat.java
@@ -0,0 +1,105 @@
+/*
+ * 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.v13.view;
+
+import android.content.ClipData;
+import android.support.v4.os.BuildCompat;
+import android.view.View;
+
+/**
+ * Helper for accessing features in {@link View} introduced after API
+ * level 13 in a backwards compatible fashion.
+ */
+public class ViewCompat extends android.support.v4.view.ViewCompat {
+ interface ViewCompatImpl {
+ boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags);
+ void cancelDragAndDrop(View v);
+ void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder);
+ }
+
+ private static class BaseViewCompatImpl implements ViewCompatImpl {
+ @Override
+ public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags) {
+ return v.startDrag(data, shadowBuilder, localState, flags);
+ }
+
+ @Override
+ public void cancelDragAndDrop(View v) {
+ // no-op
+ }
+
+ @Override
+ public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ // no-op
+ }
+ }
+
+ private static class Api24ViewCompatImpl implements ViewCompatImpl {
+ @Override
+ public boolean startDragAndDrop(View v, ClipData data, View.DragShadowBuilder shadowBuilder,
+ Object localState, int flags) {
+ return ViewCompatApi24.startDragAndDrop(
+ v, data, shadowBuilder, localState, flags);
+ }
+
+ @Override
+ public void cancelDragAndDrop(View v) {
+ ViewCompatApi24.cancelDragAndDrop(v);
+ }
+
+ @Override
+ public void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ ViewCompatApi24.updateDragShadow(v, shadowBuilder);
+ }
+ }
+
+ static ViewCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24ViewCompatImpl();
+ } else {
+ IMPL = new BaseViewCompatImpl();
+ }
+ }
+
+ /**
+ * Start the drag and drop operation.
+ */
+ public static boolean startDragAndDrop(View v, ClipData data,
+ View.DragShadowBuilder shadowBuilder, Object localState, int flags) {
+ return IMPL.startDragAndDrop(v, data, shadowBuilder, localState, flags);
+ }
+
+ /**
+ * Cancel the drag and drop operation.
+ */
+ public static void cancelDragAndDrop(View v) {
+ IMPL.cancelDragAndDrop(v);
+ }
+
+ /**
+ * Update the drag shadow while drag and drop is in progress.
+ */
+ public static void updateDragShadow(View v, View.DragShadowBuilder shadowBuilder) {
+ IMPL.updateDragShadow(v, shadowBuilder);
+ }
+
+ private ViewCompat() {
+ }
+}
diff --git a/v14/preference/Android.mk b/v14/preference/Android.mk
index d5f4e3b..a9ff72c 100644
--- a/v14/preference/Android.mk
+++ b/v14/preference/Android.mk
@@ -14,41 +14,34 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources. The resources library and the R class that it
-# contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v14-preference-res
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := \
- frameworks/support/v7/appcompat/res \
- frameworks/support/v7/preference/res \
- $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v14-preference \
+# android-support-v7-preference \
+# android-support-v7-appcompat \
+# android-support-v7-recyclerview \
+# android-support-v4 \
+# android-support-annotations
+#
# in their makefiles to include the resources in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v14-preference
LOCAL_SDK_VERSION := 14
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
-# LOCAL_STATIC_JAVA_LIBRARIES :=
-LOCAL_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-v7-appcompat \
- android-support-v7-recyclerview \
- android-support-v7-preference \
- android-support-annotations \
- android-support-v14-preference-res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v7-recyclerview \
+ android-support-v4 \
+ android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
@@ -58,4 +51,4 @@
support_module_src_files := $(LOCAL_SRC_FILES)
support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
support_module_java_packages := android.support.v14.preference
-include $(SUPPORT_API_CHECK)
\ No newline at end of file
+include $(SUPPORT_API_CHECK)
diff --git a/v14/preference/api/23.1.1.txt b/v14/preference/api/23.1.1.txt
new file mode 100644
index 0000000..fed4623
--- /dev/null
+++ b/v14/preference/api/23.1.1.txt
@@ -0,0 +1,94 @@
+package android.support.v14.preference {
+
+ public class EditTextPreferenceDialogFragment extends android.support.v14.preference.PreferenceDialogFragment {
+ ctor public EditTextPreferenceDialogFragment();
+ method public static android.support.v14.preference.EditTextPreferenceDialogFragment newInstance(java.lang.String);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class ListPreferenceDialogFragment extends android.support.v14.preference.PreferenceDialogFragment {
+ ctor public ListPreferenceDialogFragment();
+ method public static android.support.v14.preference.ListPreferenceDialogFragment newInstance(java.lang.String);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class MultiSelectListPreference extends android.support.v7.preference.DialogPreference {
+ ctor public MultiSelectListPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public MultiSelectListPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public MultiSelectListPreference(android.content.Context, android.util.AttributeSet);
+ ctor public MultiSelectListPreference(android.content.Context);
+ method public int findIndexOfValue(java.lang.String);
+ method public java.lang.CharSequence[] getEntries();
+ method public java.lang.CharSequence[] getEntryValues();
+ method protected boolean[] getSelectedItems();
+ method public java.util.Set<java.lang.String> getValues();
+ method public void setEntries(java.lang.CharSequence[]);
+ method public void setEntries(int);
+ method public void setEntryValues(java.lang.CharSequence[]);
+ method public void setEntryValues(int);
+ method public void setValues(java.util.Set<java.lang.String>);
+ }
+
+ public class MultiSelectListPreferenceDialogFragment extends android.support.v14.preference.PreferenceDialogFragment {
+ ctor public MultiSelectListPreferenceDialogFragment();
+ method public static android.support.v14.preference.MultiSelectListPreferenceDialogFragment newInstance(java.lang.String);
+ method public void onDialogClosed(boolean);
+ }
+
+ public abstract class PreferenceDialogFragment extends android.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ ctor public PreferenceDialogFragment();
+ method public android.support.v7.preference.DialogPreference getPreference();
+ method protected void onBindDialogView(android.view.View);
+ method public void onClick(android.content.DialogInterface, int);
+ method protected android.view.View onCreateDialogView(android.content.Context);
+ method public abstract void onDialogClosed(boolean);
+ method protected void onPrepareDialogBuilder(android.app.AlertDialog.Builder);
+ field protected static final java.lang.String ARG_KEY = "key";
+ }
+
+ public abstract class PreferenceFragment extends android.app.Fragment implements android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ ctor public PreferenceFragment();
+ method public void addPreferencesFromResource(int);
+ method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+ method public final android.support.v7.widget.RecyclerView getListView();
+ method public android.support.v7.preference.PreferenceManager getPreferenceManager();
+ method public android.support.v7.preference.PreferenceScreen getPreferenceScreen();
+ method protected android.support.v7.widget.RecyclerView.Adapter onCreateAdapter(android.support.v7.preference.PreferenceScreen);
+ method public android.support.v7.widget.RecyclerView.LayoutManager onCreateLayoutManager();
+ method public abstract void onCreatePreferences(android.os.Bundle, java.lang.String);
+ method public android.support.v7.widget.RecyclerView onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public void onDisplayPreferenceDialog(android.support.v7.preference.Preference);
+ method public void onNavigateToScreen(android.support.v7.preference.PreferenceScreen);
+ method public boolean onPreferenceTreeClick(android.support.v7.preference.Preference);
+ method public void setPreferenceScreen(android.support.v7.preference.PreferenceScreen);
+ method public void setPreferencesFromResource(int, java.lang.String);
+ field public static final java.lang.String ARG_PREFERENCE_ROOT = "android.support.v7.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
+ }
+
+ public static abstract interface PreferenceFragment.OnPreferenceDisplayDialogCallback {
+ method public abstract boolean onPreferenceDisplayDialog(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.Preference);
+ }
+
+ public static abstract interface PreferenceFragment.OnPreferenceStartFragmentCallback {
+ method public abstract boolean onPreferenceStartFragment(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.Preference);
+ }
+
+ public static abstract interface PreferenceFragment.OnPreferenceStartScreenCallback {
+ method public abstract boolean onPreferenceStartScreen(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.PreferenceScreen);
+ }
+
+ public class SwitchPreference extends android.support.v7.preference.TwoStatePreference {
+ ctor public SwitchPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public SwitchPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public SwitchPreference(android.content.Context, android.util.AttributeSet);
+ ctor public SwitchPreference(android.content.Context);
+ method public java.lang.CharSequence getSwitchTextOff();
+ method public java.lang.CharSequence getSwitchTextOn();
+ method public void setSwitchTextOff(java.lang.CharSequence);
+ method public void setSwitchTextOff(int);
+ method public void setSwitchTextOn(java.lang.CharSequence);
+ method public void setSwitchTextOn(int);
+ }
+
+}
+
diff --git a/v14/preference/api/current.txt b/v14/preference/api/current.txt
index 5ebd7cf..deb017d 100644
--- a/v14/preference/api/current.txt
+++ b/v14/preference/api/current.txt
@@ -60,6 +60,8 @@
method public void onDisplayPreferenceDialog(android.support.v7.preference.Preference);
method public void onNavigateToScreen(android.support.v7.preference.PreferenceScreen);
method public boolean onPreferenceTreeClick(android.support.v7.preference.Preference);
+ method public void scrollToPreference(java.lang.String);
+ method public void scrollToPreference(android.support.v7.preference.Preference);
method public void setDivider(android.graphics.drawable.Drawable);
method public void setDividerHeight(int);
method public void setPreferenceScreen(android.support.v7.preference.PreferenceScreen);
diff --git a/v14/preference/build.gradle b/v14/preference/build.gradle
index b5129b1..b834dd5 100644
--- a/v14/preference/build.gradle
+++ b/v14/preference/build.gradle
@@ -16,7 +16,7 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'preference-v14'
@@ -28,7 +28,7 @@
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -55,6 +55,39 @@
}
}
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
+
uploadArchives {
repositories {
mavenDeployer {
diff --git a/v14/preference/res/layout/preference_dropdown_material.xml b/v14/preference/res/layout/preference_dropdown_material.xml
new file mode 100644
index 0000000..d1ca64e
--- /dev/null
+++ b/v14/preference/res/layout/preference_dropdown_material.xml
@@ -0,0 +1,30 @@
+<?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
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ <include layout="@layout/preference_material" />
+
+</FrameLayout>
diff --git a/v14/preference/res/layout/preference_widget_switch.xml b/v14/preference/res/layout/preference_widget_switch.xml
index ae83afa..afc4351 100644
--- a/v14/preference/res/layout/preference_widget_switch.xml
+++ b/v14/preference/res/layout/preference_widget_switch.xml
@@ -18,7 +18,7 @@
<!-- Layout used by SwitchPreference for the switch widget style. This is inflated
inside android.R.layout.preference. -->
<Switch xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/switchWidget"
+ android:id="@android:id/switch_widget"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
diff --git a/v14/preference/res/values/attrs.xml b/v14/preference/res/values/attrs.xml
index 16f9699..7aa2245 100644
--- a/v14/preference/res/values/attrs.xml
+++ b/v14/preference/res/values/attrs.xml
@@ -41,18 +41,6 @@
<attr name="android:disableDependentsState" />
</declare-styleable>
- <declare-styleable name="MultiSelectListPreference">
- <!-- The human-readable array to present as a list. Each entry must have a corresponding
- index in entryValues. -->
- <attr name="entries" />
- <attr name="android:entries" />
- <!-- The array to find the value to save for a preference when an entry from
- entries is selected. If a user clicks the second item in entries, the
- second item in this array will be saved to the preference. -->
- <attr name="entryValues" />
- <attr name="android:entryValues" />
- </declare-styleable>
-
<!-- Base attributes available to PreferenceFragment. -->
<declare-styleable name="PreferenceFragment">
<!-- The layout for the PreferenceFragment. This should rarely need to be changed. -->
diff --git a/v14/preference/res/values/styles.xml b/v14/preference/res/values/styles.xml
index c197121..a83417a 100644
--- a/v14/preference/res/values/styles.xml
+++ b/v14/preference/res/values/styles.xml
@@ -60,6 +60,10 @@
<item name="android:layout">@layout/preference_material</item>
</style>
+ <style name="Preference.DropDown.Material">
+ <item name="android:layout">@layout/preference_dropdown_material</item>
+ </style>
+
<style name="Preference_TextAppearanceMaterialBody2">
<item name="android:textSize">14sp</item>
<item name="android:fontFamily">sans-serif</item>
diff --git a/v14/preference/res/values/themes.xml b/v14/preference/res/values/themes.xml
index 64bc91e..026d2d8 100644
--- a/v14/preference/res/values/themes.xml
+++ b/v14/preference/res/values/themes.xml
@@ -33,6 +33,7 @@
<item name="switchPreferenceStyle">@style/Preference.SwitchPreference.Material</item>
<item name="dialogPreferenceStyle">@style/Preference.DialogPreference.Material</item>
<item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference.Material</item>
+ <item name="dropdownPreferenceStyle">@style/Preference.DropDown.Material</item>
<item name="preferenceFragmentListStyle">@style/PreferenceFragmentList.Material</item>
</style>
</resources>
diff --git a/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
index 498b874..4f64876f 100644
--- a/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/EditTextPreferenceDialogFragment.java
@@ -17,14 +17,19 @@
package android.support.v14.preference;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v7.preference.EditTextPreference;
import android.view.View;
import android.widget.EditText;
public class EditTextPreferenceDialogFragment extends PreferenceDialogFragment {
+ private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
private EditText mEditText;
+ private CharSequence mText;
+
public static EditTextPreferenceDialogFragment newInstance(String key) {
final EditTextPreferenceDialogFragment
fragment = new EditTextPreferenceDialogFragment();
@@ -35,6 +40,22 @@
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ mText = getEditTextPreference().getText();
+ } else {
+ mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putCharSequence(SAVE_STATE_TEXT, mText);
+ }
+
+ @Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
@@ -45,7 +66,7 @@
" @android:id/edit");
}
- mEditText.setText(getEditTextPreference().getText());
+ mEditText.setText(mText);
}
private EditTextPreference getEditTextPreference() {
diff --git a/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
index c4e922c..6b385d8 100644
--- a/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/ListPreferenceDialogFragment.java
@@ -19,11 +19,19 @@
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v7.preference.ListPreference;
public class ListPreferenceDialogFragment extends PreferenceDialogFragment {
+ private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
+ private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries";
+ private static final String SAVE_STATE_ENTRY_VALUES =
+ "ListPreferenceDialogFragment.entryValues";
+
private int mClickedDialogEntryIndex;
+ private CharSequence[] mEntries;
+ private CharSequence[] mEntryValues;
public static ListPreferenceDialogFragment newInstance(String key) {
final ListPreferenceDialogFragment fragment = new ListPreferenceDialogFragment();
@@ -33,6 +41,35 @@
return fragment;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ final ListPreference preference = getListPreference();
+
+ if (preference.getEntries() == null || preference.getEntryValues() == null) {
+ throw new IllegalStateException(
+ "ListPreference requires an entries array and an entryValues array.");
+ }
+
+ mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+ mEntries = preference.getEntries();
+ mEntryValues = preference.getEntryValues();
+ } else {
+ mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+ mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+ mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+ outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+ outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+ }
+
private ListPreference getListPreference() {
return (ListPreference) getPreference();
}
@@ -41,15 +78,7 @@
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
- final ListPreference preference = getListPreference();
-
- if (preference.getEntries() == null || preference.getEntryValues() == null) {
- throw new IllegalStateException(
- "ListPreference requires an entries array and an entryValues array.");
- }
-
- mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
- builder.setSingleChoiceItems(preference.getEntries(), mClickedDialogEntryIndex,
+ builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mClickedDialogEntryIndex = which;
@@ -75,9 +104,8 @@
@Override
public void onDialogClosed(boolean positiveResult) {
final ListPreference preference = getListPreference();
- if (positiveResult && mClickedDialogEntryIndex >= 0 &&
- preference.getEntryValues() != null) {
- String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+ if (positiveResult && mClickedDialogEntryIndex >= 0) {
+ String value = mEntryValues[mClickedDialogEntryIndex].toString();
if (preference.callChangeListener(value)) {
preference.setValue(value);
}
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 2a636fe..82b3ad9 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -40,8 +40,8 @@
* This set will contain one or more values from the
* {@link #setEntryValues(CharSequence[])} array.
*
- * @attr ref android.R.styleable#MultiSelectListPreference_entries
- * @attr ref android.R.styleable#MultiSelectListPreference_entryValues
+ * @attr name android:entries
+ * @attr name android:entryValues
*/
public class MultiSelectListPreference extends DialogPreference {
private CharSequence[] mEntries;
@@ -53,16 +53,16 @@
super(context, attrs, defStyleAttr, defStyleRes);
final TypedArray a = context.obtainStyledAttributes(attrs,
- R.styleable.MultiSelectListPreference, defStyleAttr,
+ android.support.v7.preference.R.styleable.MultiSelectListPreference, defStyleAttr,
defStyleRes);
mEntries = TypedArrayUtils.getTextArray(a,
- R.styleable.MultiSelectListPreference_entries,
- R.styleable.MultiSelectListPreference_android_entries);
+ android.support.v7.preference.R.styleable.MultiSelectListPreference_entries,
+ android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entries);
mEntryValues = TypedArrayUtils.getTextArray(a,
- R.styleable.MultiSelectListPreference_entryValues,
- R.styleable.MultiSelectListPreference_android_entryValues);
+ android.support.v7.preference.R.styleable.MultiSelectListPreference_entryValues,
+ android.support.v7.preference.R.styleable.MultiSelectListPreference_android_entryValues);
a.recycle();
}
@@ -72,7 +72,9 @@
}
public MultiSelectListPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ android.support.v7.preference.R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public MultiSelectListPreference(Context context) {
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
index ec46aac..5b585bc 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreferenceDialogFragment.java
@@ -19,14 +19,27 @@
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import java.util.ArrayList;
import java.util.HashSet;
import java.util.Set;
public class MultiSelectListPreferenceDialogFragment extends PreferenceDialogFragment {
+ private static final String SAVE_STATE_VALUES =
+ "MultiSelectListPreferenceDialogFragment.values";
+ private static final String SAVE_STATE_CHANGED =
+ "MultiSelectListPreferenceDialogFragment.changed";
+ private static final String SAVE_STATE_ENTRIES =
+ "MultiSelectListPreferenceDialogFragment.entries";
+ private static final String SAVE_STATE_ENTRY_VALUES =
+ "MultiSelectListPreferenceDialogFragment.entryValues";
+
private Set<String> mNewValues = new HashSet<>();
private boolean mPreferenceChanged;
+ private CharSequence[] mEntries;
+ private CharSequence[] mEntryValues;
public static MultiSelectListPreferenceDialogFragment newInstance(String key) {
final MultiSelectListPreferenceDialogFragment fragment =
@@ -37,6 +50,42 @@
return fragment;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState == null) {
+ final MultiSelectListPreference preference = getListPreference();
+
+ if (preference.getEntries() == null || preference.getEntryValues() == null) {
+ throw new IllegalStateException(
+ "MultiSelectListPreference requires an entries array and " +
+ "an entryValues array.");
+ }
+
+ mNewValues.clear();
+ mNewValues.addAll(preference.getValues());
+ mPreferenceChanged = false;
+ mEntries = preference.getEntries();
+ mEntryValues = preference.getEntryValues();
+ } else {
+ mNewValues.clear();
+ mNewValues.addAll(savedInstanceState.getStringArrayList(SAVE_STATE_VALUES));
+ mPreferenceChanged = savedInstanceState.getBoolean(SAVE_STATE_CHANGED, false);
+ mEntries = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRIES);
+ mEntryValues = savedInstanceState.getCharSequenceArray(SAVE_STATE_ENTRY_VALUES);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putStringArrayList(SAVE_STATE_VALUES, new ArrayList<>(mNewValues));
+ outState.putBoolean(SAVE_STATE_CHANGED, mPreferenceChanged);
+ outState.putCharSequenceArray(SAVE_STATE_ENTRIES, mEntries);
+ outState.putCharSequenceArray(SAVE_STATE_ENTRY_VALUES, mEntryValues);
+ }
+
private MultiSelectListPreference getListPreference() {
return (MultiSelectListPreference) getPreference();
}
@@ -45,29 +94,23 @@
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
- final MultiSelectListPreference preference = getListPreference();
-
- if (preference.getEntries() == null || preference.getEntryValues() == null) {
- throw new IllegalStateException(
- "MultiSelectListPreference requires an entries array and " +
- "an entryValues array.");
+ final int entryCount = mEntryValues.length;
+ final boolean[] checkedItems = new boolean[entryCount];
+ for (int i = 0; i < entryCount; i++) {
+ checkedItems[i] = mNewValues.contains(mEntryValues[i].toString());
}
-
- boolean[] checkedItems = preference.getSelectedItems();
- builder.setMultiChoiceItems(preference.getEntries(), checkedItems,
+ builder.setMultiChoiceItems(mEntries, checkedItems,
new DialogInterface.OnMultiChoiceClickListener() {
public void onClick(DialogInterface dialog, int which, boolean isChecked) {
if (isChecked) {
mPreferenceChanged |= mNewValues.add(
- preference.getEntryValues()[which].toString());
+ mEntryValues[which].toString());
} else {
mPreferenceChanged |= mNewValues.remove(
- preference.getEntryValues()[which].toString());
+ mEntryValues[which].toString());
}
}
});
- mNewValues.clear();
- mNewValues.addAll(preference.getValues());
}
@Override
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
index b9027a4..48b404e 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceDialogFragment.java
@@ -22,7 +22,12 @@
import android.app.Fragment;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v7.preference.DialogPreference;
import android.text.TextUtils;
@@ -32,13 +37,35 @@
import android.view.WindowManager;
import android.widget.TextView;
+/**
+ * Abstract base class which presents a dialog associated with a
+ * {@link android.support.v7.preference.DialogPreference}. Since the preference object may
+ * not be available during fragment re-creation, the necessary information for displaying the dialog
+ * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
+ * instance state. Custom subclasses should also follow this pattern.
+ */
public abstract class PreferenceDialogFragment extends DialogFragment implements
DialogInterface.OnClickListener {
protected static final String ARG_KEY = "key";
+ private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
+ private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
+ private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
+ private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
+ private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
+ private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
+
private DialogPreference mPreference;
+ private CharSequence mDialogTitle;
+ private CharSequence mPositiveButtonText;
+ private CharSequence mNegativeButtonText;
+ private CharSequence mDialogMessage;
+ private @LayoutRes int mDialogLayoutRes;
+
+ private BitmapDrawable mDialogIcon;
+
/** Which button was clicked. */
private int mWhichButtonClicked;
@@ -56,7 +83,50 @@
(DialogPreference.TargetFragment) rawFragment;
final String key = getArguments().getString(ARG_KEY);
- mPreference = (DialogPreference) fragment.findPreference(key);
+ if (savedInstanceState == null) {
+ mPreference = (DialogPreference) fragment.findPreference(key);
+ mDialogTitle = mPreference.getDialogTitle();
+ mPositiveButtonText = mPreference.getPositiveButtonText();
+ mNegativeButtonText = mPreference.getNegativeButtonText();
+ mDialogMessage = mPreference.getDialogMessage();
+ mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+ final Drawable icon = mPreference.getDialogIcon();
+ if (icon == null || icon instanceof BitmapDrawable) {
+ mDialogIcon = (BitmapDrawable) icon;
+ } else {
+ final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ icon.draw(canvas);
+ mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+ }
+ } else {
+ mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+ mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+ mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+ mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+ mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+ final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+ if (bitmap != null) {
+ mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+ outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+ outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+ outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+ outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+ if (mDialogIcon != null) {
+ outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+ }
}
@Override
@@ -66,17 +136,17 @@
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
- .setTitle(mPreference.getDialogTitle())
- .setIcon(mPreference.getDialogIcon())
- .setPositiveButton(mPreference.getPositiveButtonText(), this)
- .setNegativeButton(mPreference.getNegativeButtonText(), this);
+ .setTitle(mDialogTitle)
+ .setIcon(mDialogIcon)
+ .setPositiveButton(mPositiveButtonText, this)
+ .setNegativeButton(mNegativeButtonText, this);
View contentView = onCreateDialogView(context);
if (contentView != null) {
onBindDialogView(contentView);
builder.setView(contentView);
} else {
- builder.setMessage(mPreference.getDialogMessage());
+ builder.setMessage(mDialogMessage);
}
onPrepareDialogBuilder(builder);
@@ -87,18 +157,23 @@
requestInputMethod(dialog);
}
-
- return builder.create();
+ return dialog;
}
/**
* Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
- * been called.
+ * been called on the {@link PreferenceFragment} which launched this dialog.
*
* @return The {@link DialogPreference} associated with this
* dialog.
*/
public DialogPreference getPreference() {
+ if (mPreference == null) {
+ final String key = getArguments().getString(ARG_KEY);
+ final DialogPreference.TargetFragment fragment =
+ (DialogPreference.TargetFragment) getTargetFragment();
+ mPreference = (DialogPreference) fragment.findPreference(key);
+ }
return mPreference;
}
@@ -138,7 +213,7 @@
* @see DialogPreference#setLayoutResource(int)
*/
protected View onCreateDialogView(Context context) {
- final int resId = mPreference.getDialogLayoutResource();
+ final int resId = mDialogLayoutRes;
if (resId == 0) {
return null;
}
@@ -158,7 +233,7 @@
View dialogMessageView = view.findViewById(android.R.id.message);
if (dialogMessageView != null) {
- final CharSequence message = mPreference.getDialogMessage();
+ final CharSequence message = mDialogMessage;
int newVisibility = View.GONE;
if (!TextUtils.isEmpty(message)) {
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
index b9fc935..ab4125d 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
@@ -28,13 +28,17 @@
import android.os.Message;
import android.support.annotation.Nullable;
import android.support.annotation.XmlRes;
+import android.support.v4.content.res.TypedArrayUtils;
import android.support.v4.view.ViewCompat;
+import android.support.v7.preference.AndroidResources;
import android.support.v7.preference.DialogPreference;
import android.support.v7.preference.EditTextPreference;
import android.support.v7.preference.ListPreference;
import android.support.v7.preference.Preference;
+import android.support.v7.preference.PreferenceGroup;
import android.support.v7.preference.PreferenceGroupAdapter;
import android.support.v7.preference.PreferenceManager;
+import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate;
import android.support.v7.preference.PreferenceScreen;
import android.support.v7.preference.PreferenceViewHolder;
import android.support.v7.widget.LinearLayoutManager;
@@ -133,7 +137,7 @@
private Context mStyledContext;
- private int mLayoutResId = R.layout.preference_list_fragment;
+ private int mLayoutResId = android.support.v7.preference.R.layout.preference_list_fragment;
private final DividerDecoration mDividerDecoration = new DividerDecoration();
@@ -156,6 +160,8 @@
}
};
+ private Runnable mSelectPreferenceRunnable;
+
/**
* Interface that PreferenceFragment's containing activity should
* implement to be able to process preference items that wish to
@@ -205,7 +211,8 @@
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
final TypedValue tv = new TypedValue();
- getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
+ getActivity().getTheme().resolveAttribute(
+ android.support.v7.preference.R.attr.preferenceTheme, tv, true);
final int theme = tv.resourceId;
if (theme <= 0) {
throw new IllegalStateException("Must specify preferenceTheme in theme");
@@ -242,20 +249,23 @@
TypedArray a = mStyledContext.obtainStyledAttributes(null,
R.styleable.PreferenceFragment,
- R.attr.preferenceFragmentStyle,
+ TypedArrayUtils.getAttr(mStyledContext,
+ android.support.v7.preference.R.attr.preferenceFragmentStyle,
+ AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
0);
mLayoutResId = a.getResourceId(R.styleable.PreferenceFragment_android_layout, mLayoutResId);
final Drawable divider = a.getDrawable(R.styleable.PreferenceFragment_android_divider);
- final int dividerHeight = a.getInt(R.styleable.PreferenceFragment_android_dividerHeight,
- -1);
+ final int dividerHeight = a.getDimensionPixelSize(
+ R.styleable.PreferenceFragment_android_dividerHeight, -1);
a.recycle();
// Need to theme the inflater to pick up the preferenceFragmentListStyle
final TypedValue tv = new TypedValue();
- getActivity().getTheme().resolveAttribute(R.attr.preferenceTheme, tv, true);
+ getActivity().getTheme().resolveAttribute(
+ android.support.v7.preference.R.attr.preferenceTheme, tv, true);
final int theme = tv.resourceId;
final Context themedContext = new ContextThemeWrapper(inflater.getContext(), theme);
@@ -263,10 +273,10 @@
final View view = themedInflater.inflate(mLayoutResId, container, false);
- final View rawListContainer = view.findViewById(R.id.list_container);
+ final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
- throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
- + "that is not a ViewGroup class");
+ throw new RuntimeException("Content has view with id attribute "
+ + "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -287,6 +297,7 @@
listContainer.addView(mList);
mHandler.post(mRequestFocus);
+
return view;
}
@@ -315,14 +326,23 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
if (mHavePrefs) {
bindPreferences();
+ if (mSelectPreferenceRunnable != null) {
+ mSelectPreferenceRunnable.run();
+ mSelectPreferenceRunnable = null;
+ }
}
mInitDone = true;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -351,9 +371,12 @@
@Override
public void onDestroyView() {
- mList = null;
mHandler.removeCallbacks(mRequestFocus);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
+ if (mHavePrefs) {
+ unbindPreferences();
+ }
+ mList = null;
super.onDestroyView();
}
@@ -520,6 +543,14 @@
onBindPreferences();
}
+ private void unbindPreferences() {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.onDetached();
+ }
+ onUnbindPreferences();
+ }
+
/** @hide */
protected void onBindPreferences() {
}
@@ -548,9 +579,12 @@
public RecyclerView onCreateRecyclerView(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
RecyclerView recyclerView = (RecyclerView) inflater
- .inflate(R.layout.preference_recyclerview, parent, false);
+ .inflate(android.support.v7.preference.R.layout.preference_recyclerview,
+ parent, false);
recyclerView.setLayoutManager(onCreateLayoutManager());
+ recyclerView.setAccessibilityDelegateCompat(
+ new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
return recyclerView;
}
@@ -628,6 +662,113 @@
return null;
}
+ public void scrollToPreference(final String key) {
+ scrollToPreferenceInternal(null, key);
+ }
+
+ public void scrollToPreference(final Preference preference) {
+ scrollToPreferenceInternal(preference, null);
+ }
+
+ private void scrollToPreferenceInternal(final Preference preference, final String key) {
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ final RecyclerView.Adapter adapter = mList.getAdapter();
+ if (!(adapter instanceof
+ PreferenceGroup.PreferencePositionCallback)) {
+ if (adapter != null) {
+ throw new IllegalStateException("Adapter must implement "
+ + "PreferencePositionCallback");
+ } else {
+ // Adapter was set to null, so don't scroll I guess?
+ return;
+ }
+ }
+ final int position;
+ if (preference != null) {
+ position = ((PreferenceGroup.PreferencePositionCallback) adapter)
+ .getPreferenceAdapterPosition(preference);
+ } else {
+ position = ((PreferenceGroup.PreferencePositionCallback) adapter)
+ .getPreferenceAdapterPosition(key);
+ }
+ if (position != RecyclerView.NO_POSITION) {
+ mList.scrollToPosition(position);
+ } else {
+ // Item not found, wait for an update and try again
+ adapter.registerAdapterDataObserver(
+ new ScrollToPreferenceObserver(adapter, mList, preference, key));
+ }
+ }
+ };
+ if (mList == null) {
+ mSelectPreferenceRunnable = r;
+ } else {
+ r.run();
+ }
+ }
+
+ private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
+ private final RecyclerView.Adapter mAdapter;
+ private final RecyclerView mList;
+ private final Preference mPreference;
+ private final String mKey;
+
+ public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list,
+ Preference preference, String key) {
+ mAdapter = adapter;
+ mList = list;
+ mPreference = preference;
+ mKey = key;
+ }
+
+ private void scrollToPreference() {
+ mAdapter.unregisterAdapterDataObserver(this);
+ final int position;
+ if (mPreference != null) {
+ position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
+ .getPreferenceAdapterPosition(mPreference);
+ } else {
+ position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
+ .getPreferenceAdapterPosition(mKey);
+ }
+ if (position != RecyclerView.NO_POSITION) {
+ mList.scrollToPosition(position);
+ }
+ }
+
+ @Override
+ public void onChanged() {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ scrollToPreference();
+ }
+ }
+
private class DividerDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
@@ -642,11 +783,6 @@
final int width = parent.getWidth();
for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
final View view = parent.getChildAt(childViewIndex);
- if (shouldDrawDividerAbove(view, parent)) {
- int top = (int) ViewCompat.getY(view);
- mDivider.setBounds(0, top, width, top + mDividerHeight);
- mDivider.draw(c);
- }
if (shouldDrawDividerBelow(view, parent)) {
int top = (int) ViewCompat.getY(view) + view.getHeight();
mDivider.setBounds(0, top, width, top + mDividerHeight);
@@ -658,32 +794,27 @@
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
- if (shouldDrawDividerAbove(view, parent)) {
- outRect.top = mDividerHeight;
- }
if (shouldDrawDividerBelow(view, parent)) {
outRect.bottom = mDividerHeight;
}
}
- private boolean shouldDrawDividerAbove(View view, RecyclerView parent) {
- final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
- return holder.getAdapterPosition() == 0 &&
- ((PreferenceViewHolder) holder).isDividerAllowedAbove();
- }
-
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
- final PreferenceViewHolder holder =
- (PreferenceViewHolder) parent.getChildViewHolder(view);
+ final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
+ final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
+ if (!dividerAllowedBelow) {
+ return false;
+ }
boolean nextAllowed = true;
int index = parent.indexOfChild(view);
if (index < parent.getChildCount() - 1) {
final View nextView = parent.getChildAt(index + 1);
- final PreferenceViewHolder nextHolder =
- (PreferenceViewHolder) parent.getChildViewHolder(nextView);
- nextAllowed = nextHolder.isDividerAllowedAbove();
+ final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
+ nextAllowed = nextHolder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
}
- return nextAllowed && holder.isDividerAllowedBelow();
+ return nextAllowed;
}
public void setDivider(Drawable divider) {
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index 1a46cc4..3f95d1a 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v7.preference.AndroidResources;
import android.support.v7.preference.PreferenceViewHolder;
import android.support.v7.preference.TwoStatePreference;
import android.util.AttributeSet;
@@ -33,11 +34,11 @@
* <p>
* This preference will store a boolean into the SharedPreferences.
*
- * @attr ref android.R.styleable#SwitchPreference_summaryOff
- * @attr ref android.R.styleable#SwitchPreference_summaryOn
- * @attr ref android.R.styleable#SwitchPreference_switchTextOff
- * @attr ref android.R.styleable#SwitchPreference_switchTextOn
- * @attr ref android.R.styleable#SwitchPreference_disableDependentsState
+ * @attr name android:summaryOff
+ * @attr name android:summaryOn
+ * @attr name android:switchTextOff
+ * @attr name android:switchTextOn
+ * @attr name android:disableDependentsState
*/
public class SwitchPreference extends TwoStatePreference {
private final Listener mListener = new Listener();
@@ -121,7 +122,9 @@
* @param attrs Style attributes that differ from the default
*/
public SwitchPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.switchPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ android.support.v7.preference.R.attr.switchPreferenceStyle,
+ android.R.attr.switchPreferenceStyle));
}
/**
@@ -136,7 +139,7 @@
@Override
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- View switchView = holder.findViewById(R.id.switchWidget);
+ View switchView = holder.findViewById(AndroidResources.ANDROID_R_SWITCH_WIDGET);
syncSwitchView(switchView);
syncSummaryView(holder);
}
@@ -214,7 +217,7 @@
return;
}
- View switchView = view.findViewById(R.id.switchWidget);
+ View switchView = view.findViewById(android.support.v7.preference.R.id.switchWidget);
syncSwitchView(switchView);
View summaryView = view.findViewById(android.R.id.summary);
diff --git a/v17/leanback/Android.mk b/v17/leanback/Android.mk
index d6658fb..459d54e 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -14,17 +14,16 @@
LOCAL_PATH:= $(call my-dir)
-# Build the resources using the current SDK version.
+# Build the resources using the latest applicable SDK version.
# We do this here because the final static library must be compiled with an older
# SDK version than the resources. The resources library and the R class that it
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v17-leanback-res
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -49,7 +48,7 @@
# A helper sub-library that makes direct use of API 23.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v17-leanback-api23
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 23
LOCAL_SRC_FILES := $(call all-java-files-under, api23)
LOCAL_JAVA_LIBRARIES := android-support-v17-leanback-res android-support-v17-leanback-common
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
@@ -99,21 +98,34 @@
# -----------------------------------------------------------------------
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v17-leanback \
+# android-support-v7-recyclerview \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v17-leanback
LOCAL_SDK_VERSION := 17
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v17-leanback-kitkat android-support-v17-leanback-jbmr2 \
- android-support-v17-leanback-api23 \
- android-support-v17-leanback-api21 android-support-v17-leanback-common
-LOCAL_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-v7-recyclerview \
- android-support-v17-leanback-res
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-v17-leanback-kitkat \
+ android-support-v17-leanback-jbmr2 \
+ android-support-v17-leanback-api23 \
+ android-support-v17-leanback-api21 \
+ android-support-v17-leanback-common
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-v17-leanback-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-v7-recyclerview \
+ android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
@@ -124,7 +136,6 @@
$(call all-java-files-under, src) \
$(call all-html-files-under, src)
leanback.docs.java_libraries := \
- framework \
android-support-v4 \
android-support-v7-recyclerview \
android-support-v17-leanback-res \
@@ -143,7 +154,7 @@
LOCAL_SRC_FILES := $(leanback.docs.src_files)
LOCAL_ADDITIONAL_JAVA_DIR := $(gen_res_src_dirs)
-LOCAL_SDK_VERSION := 19
+LOCAL_SDK_VERSION := 21
LOCAL_IS_HOST_MODULE := false
LOCAL_DROIDDOC_CUSTOM_TEMPLATE_DIR := build/tools/droiddoc/templates-sdk
diff --git a/v17/leanback/api/23.1.1.txt b/v17/leanback/api/23.1.1.txt
new file mode 100644
index 0000000..962367d
--- /dev/null
+++ b/v17/leanback/api/23.1.1.txt
@@ -0,0 +1,1796 @@
+package android.support.v17.leanback.app {
+
+ public final class BackgroundManager {
+ method public void attach(android.view.Window);
+ method public final int getColor();
+ method public android.graphics.drawable.Drawable getDefaultDimLayer();
+ method public android.graphics.drawable.Drawable getDimLayer();
+ method public android.graphics.drawable.Drawable getDrawable();
+ method public static android.support.v17.leanback.app.BackgroundManager getInstance(android.app.Activity);
+ method public boolean isAttached();
+ method public void release();
+ method public void setBitmap(android.graphics.Bitmap);
+ method public void setColor(int);
+ method public void setDimLayer(android.graphics.drawable.Drawable);
+ method public void setDrawable(android.graphics.drawable.Drawable);
+ method public void setThemeDrawableResourceId(int);
+ }
+
+ abstract class BaseRowFragment extends android.app.Fragment {
+ method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+ method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ }
+
+ abstract class BaseRowSupportFragment extends android.support.v4.app.Fragment {
+ method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+ method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ }
+
+ public class BrowseFragment extends android.support.v17.leanback.app.BrandedFragment {
+ ctor public BrowseFragment();
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
+ method protected java.lang.Object createEntranceTransition();
+ method public void enableRowScaling(boolean);
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public int getBrandColor();
+ method public int getHeadersState();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public final boolean isHeadersTransitionOnBackEnabled();
+ method public boolean isInHeadersTransition();
+ method public boolean isShowingHeaders();
+ method protected void onEntranceTransitionEnd();
+ method protected void onEntranceTransitionPrepare();
+ method protected void onEntranceTransitionStart();
+ method public void onSaveInstanceState(android.os.Bundle);
+ method public void onStart();
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setBrandColor(int);
+ method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseFragment.BrowseTransitionListener);
+ method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public void setHeadersState(int);
+ method public final void setHeadersTransitionOnBackEnabled(boolean);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ method public void startHeadersTransition(boolean);
+ field public static final int HEADERS_DISABLED = 3; // 0x3
+ field public static final int HEADERS_ENABLED = 1; // 0x1
+ field public static final int HEADERS_HIDDEN = 2; // 0x2
+ }
+
+ public static class BrowseFragment.BrowseTransitionListener {
+ ctor public BrowseFragment.BrowseTransitionListener();
+ method public void onHeadersTransitionStart(boolean);
+ method public void onHeadersTransitionStop(boolean);
+ }
+
+ public class BrowseSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
+ ctor public BrowseSupportFragment();
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
+ method protected java.lang.Object createEntranceTransition();
+ method public void enableRowScaling(boolean);
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public int getBrandColor();
+ method public int getHeadersState();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public final boolean isHeadersTransitionOnBackEnabled();
+ method public boolean isInHeadersTransition();
+ method public boolean isShowingHeaders();
+ method protected void onEntranceTransitionEnd();
+ method protected void onEntranceTransitionPrepare();
+ method protected void onEntranceTransitionStart();
+ method public void onSaveInstanceState(android.os.Bundle);
+ method public void onStart();
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setBrandColor(int);
+ method public void setBrowseTransitionListener(android.support.v17.leanback.app.BrowseSupportFragment.BrowseTransitionListener);
+ method public void setHeaderPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public void setHeadersState(int);
+ method public final void setHeadersTransitionOnBackEnabled(boolean);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ method public void startHeadersTransition(boolean);
+ field public static final int HEADERS_DISABLED = 3; // 0x3
+ field public static final int HEADERS_ENABLED = 1; // 0x1
+ field public static final int HEADERS_HIDDEN = 2; // 0x2
+ }
+
+ public static class BrowseSupportFragment.BrowseTransitionListener {
+ ctor public BrowseSupportFragment.BrowseTransitionListener();
+ method public void onHeadersTransitionStart(boolean);
+ method public void onHeadersTransitionStop(boolean);
+ }
+
+ public class DetailsFragment extends android.support.v17.leanback.app.BrandedFragment {
+ ctor public DetailsFragment();
+ method protected java.lang.Object createEntranceTransition();
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method protected void onEntranceTransitionEnd();
+ method protected void onEntranceTransitionPrepare();
+ method protected void onEntranceTransitionStart();
+ method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
+ method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
+ method public void onStart();
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+ method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
+ }
+
+ public class DetailsSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
+ ctor public DetailsSupportFragment();
+ method protected java.lang.Object createEntranceTransition();
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method protected void onEntranceTransitionEnd();
+ method protected void onEntranceTransitionPrepare();
+ method protected void onEntranceTransitionStart();
+ method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
+ method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
+ method public void onStart();
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ method public void setSelectedPosition(int, boolean);
+ method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+ method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
+ }
+
+ public class ErrorFragment extends android.app.Fragment {
+ ctor public ErrorFragment();
+ method public android.graphics.drawable.Drawable getBackgroundDrawable();
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.view.View.OnClickListener getButtonClickListener();
+ method public java.lang.String getButtonText();
+ method public android.graphics.drawable.Drawable getImageDrawable();
+ method public java.lang.CharSequence getMessage();
+ method public java.lang.String getTitle();
+ method public boolean isBackgroundTranslucent();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setButtonClickListener(android.view.View.OnClickListener);
+ method public void setButtonText(java.lang.String);
+ method public void setDefaultBackground(boolean);
+ method public void setImageDrawable(android.graphics.drawable.Drawable);
+ method public void setMessage(java.lang.CharSequence);
+ method public void setTitle(java.lang.String);
+ }
+
+ public class ErrorSupportFragment extends android.support.v4.app.Fragment {
+ ctor public ErrorSupportFragment();
+ method public android.graphics.drawable.Drawable getBackgroundDrawable();
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.view.View.OnClickListener getButtonClickListener();
+ method public java.lang.String getButtonText();
+ method public android.graphics.drawable.Drawable getImageDrawable();
+ method public java.lang.CharSequence getMessage();
+ method public java.lang.String getTitle();
+ method public boolean isBackgroundTranslucent();
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setButtonClickListener(android.view.View.OnClickListener);
+ method public void setButtonText(java.lang.String);
+ method public void setDefaultBackground(boolean);
+ method public void setImageDrawable(android.graphics.drawable.Drawable);
+ method public void setMessage(java.lang.CharSequence);
+ method public void setTitle(java.lang.String);
+ }
+
+ public class GuidedStepFragment extends android.app.Fragment {
+ ctor public GuidedStepFragment();
+ method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment);
+ method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment, int);
+ method public static int addAsRoot(android.app.Activity, android.support.v17.leanback.app.GuidedStepFragment, int);
+ method public android.view.View getActionItemView(int);
+ method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
+ method protected int getContainerIdForBackground();
+ method public static android.support.v17.leanback.app.GuidedStepFragment getCurrentGuidedStepFragment(android.app.FragmentManager);
+ method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
+ method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+ method public int getSelectedActionPosition();
+ method public int getUiStyle();
+ method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+ method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
+ method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
+ method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
+ method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
+ method protected android.app.Fragment onProvideBackgroundFragment();
+ method protected void onProvideFragmentTransitions();
+ method public int onProvideTheme();
+ method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+ method public void setSelectedActionPosition(int);
+ method public void setUiStyle(int);
+ field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
+ field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+ field public static final int UI_STYLE_DEFAULT = 0; // 0x0
+ field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
+ }
+
+ public static class GuidedStepFragment.GuidedStepBackgroundFragment extends android.app.Fragment {
+ ctor public GuidedStepFragment.GuidedStepBackgroundFragment();
+ method protected void onProvideFragmentTransitions();
+ }
+
+ public class GuidedStepSupportFragment extends android.support.v4.app.Fragment {
+ ctor public GuidedStepSupportFragment();
+ method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment);
+ method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
+ method public static int addAsRoot(android.support.v4.app.FragmentActivity, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
+ method public android.view.View getActionItemView(int);
+ method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
+ method protected int getContainerIdForBackground();
+ method public static android.support.v17.leanback.app.GuidedStepSupportFragment getCurrentGuidedStepSupportFragment(android.support.v4.app.FragmentManager);
+ method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
+ method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
+ method public int getSelectedActionPosition();
+ method public int getUiStyle();
+ method public void onCreateActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>, android.os.Bundle);
+ method public android.support.v17.leanback.widget.GuidedActionsStylist onCreateActionsStylist();
+ method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
+ method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
+ method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
+ method protected android.support.v4.app.Fragment onProvideBackgroundSupportFragment();
+ method protected void onProvideFragmentTransitions();
+ method public int onProvideTheme();
+ method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+ method public void setSelectedActionPosition(int);
+ method public void setUiStyle(int);
+ field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
+ field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+ field public static final int UI_STYLE_DEFAULT = 0; // 0x0
+ field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
+ }
+
+ public static class GuidedStepSupportFragment.GuidedStepBackgroundSupportFragment extends android.support.v4.app.Fragment {
+ ctor public GuidedStepSupportFragment.GuidedStepBackgroundSupportFragment();
+ method protected void onProvideFragmentTransitions();
+ }
+
+ public class HeadersFragment extends android.support.v17.leanback.app.BaseRowFragment {
+ ctor public HeadersFragment();
+ method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener);
+ method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
+ }
+
+ static abstract interface HeadersFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked();
+ }
+
+ static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
+ method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ }
+
+ public class HeadersSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
+ ctor public HeadersSupportFragment();
+ method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener);
+ method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
+ }
+
+ static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked();
+ }
+
+ static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
+ method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ }
+
+ public abstract class MediaControllerGlue extends android.support.v17.leanback.app.PlaybackControlGlue {
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public MediaControllerGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
+ method public void attachToMediaController(android.support.v4.media.session.MediaControllerCompat);
+ method public void detach();
+ method public int getCurrentPosition();
+ method public int getCurrentSpeedId();
+ method public android.graphics.drawable.Drawable getMediaArt();
+ method public final android.support.v4.media.session.MediaControllerCompat getMediaController();
+ method public int getMediaDuration();
+ method public java.lang.CharSequence getMediaSubtitle();
+ method public java.lang.CharSequence getMediaTitle();
+ method public long getSupportedActions();
+ method public boolean hasValidMedia();
+ method public boolean isMediaPlaying();
+ method protected void pausePlayback();
+ method protected void skipToNext();
+ method protected void skipToPrevious();
+ method protected void startPlayback(int);
+ }
+
+ public abstract class PlaybackControlGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ ctor public PlaybackControlGlue(android.content.Context, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[]);
+ ctor public PlaybackControlGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlayFragment, int[], int[]);
+ method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
+ method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ method public void enableProgressUpdating(boolean);
+ method public android.content.Context getContext();
+ method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+ method public abstract int getCurrentPosition();
+ method public abstract int getCurrentSpeedId();
+ method public int[] getFastForwardSpeeds();
+ method public android.support.v17.leanback.app.PlaybackOverlayFragment getFragment();
+ method public abstract android.graphics.drawable.Drawable getMediaArt();
+ method public abstract int getMediaDuration();
+ method public abstract java.lang.CharSequence getMediaSubtitle();
+ method public abstract java.lang.CharSequence getMediaTitle();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public int[] getRewindSpeeds();
+ method public abstract long getSupportedActions();
+ method public int getUpdatePeriod();
+ method public abstract boolean hasValidMedia();
+ method public boolean isFadingEnabled();
+ method public abstract boolean isMediaPlaying();
+ method public void onActionClicked(android.support.v17.leanback.widget.Action);
+ method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+ method protected void onMetadataChanged();
+ method protected abstract void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method protected void onStateChanged();
+ method protected abstract void pausePlayback();
+ method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public void setFadingEnabled(boolean);
+ method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method protected abstract void skipToNext();
+ method protected abstract void skipToPrevious();
+ method protected abstract void startPlayback(int);
+ method public void updateProgress();
+ field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+ field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+ field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+ field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+ field public static final int ACTION_REWIND = 32; // 0x20
+ field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+ field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+ field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+ field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+ field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+ field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+ field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+ field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+ field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+ field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+ }
+
+ public abstract class PlaybackControlSupportGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
+ ctor public PlaybackControlSupportGlue(android.content.Context, int[]);
+ ctor public PlaybackControlSupportGlue(android.content.Context, int[], int[]);
+ ctor public PlaybackControlSupportGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlaySupportFragment, int[]);
+ ctor public PlaybackControlSupportGlue(android.content.Context, android.support.v17.leanback.app.PlaybackOverlaySupportFragment, int[], int[]);
+ method public android.support.v17.leanback.widget.PlaybackControlsRowPresenter createControlsRowAndPresenter();
+ method protected android.support.v17.leanback.widget.SparseArrayObjectAdapter createPrimaryActionsAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ method public void enableProgressUpdating(boolean);
+ method public android.content.Context getContext();
+ method public android.support.v17.leanback.widget.PlaybackControlsRow getControlsRow();
+ method public abstract int getCurrentPosition();
+ method public abstract int getCurrentSpeedId();
+ method public int[] getFastForwardSpeeds();
+ method public android.support.v17.leanback.app.PlaybackOverlaySupportFragment getFragment();
+ method public abstract android.graphics.drawable.Drawable getMediaArt();
+ method public abstract int getMediaDuration();
+ method public abstract java.lang.CharSequence getMediaSubtitle();
+ method public abstract java.lang.CharSequence getMediaTitle();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public int[] getRewindSpeeds();
+ method public abstract long getSupportedActions();
+ method public int getUpdatePeriod();
+ method public abstract boolean hasValidMedia();
+ method public boolean isFadingEnabled();
+ method public abstract boolean isMediaPlaying();
+ method public void onActionClicked(android.support.v17.leanback.widget.Action);
+ method public boolean onKey(android.view.View, int, android.view.KeyEvent);
+ method protected void onMetadataChanged();
+ method protected abstract void onRowChanged(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method protected void onStateChanged();
+ method protected abstract void pausePlayback();
+ method public void setControlsRow(android.support.v17.leanback.widget.PlaybackControlsRow);
+ method public void setFadingEnabled(boolean);
+ method public deprecated void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method protected abstract void skipToNext();
+ method protected abstract void skipToPrevious();
+ method protected abstract void startPlayback(int);
+ method public void updateProgress();
+ field public static final int ACTION_CUSTOM_LEFT_FIRST = 1; // 0x1
+ field public static final int ACTION_CUSTOM_RIGHT_FIRST = 4096; // 0x1000
+ field public static final int ACTION_FAST_FORWARD = 128; // 0x80
+ field public static final int ACTION_PLAY_PAUSE = 64; // 0x40
+ field public static final int ACTION_REWIND = 32; // 0x20
+ field public static final int ACTION_SKIP_TO_NEXT = 256; // 0x100
+ field public static final int ACTION_SKIP_TO_PREVIOUS = 16; // 0x10
+ field public static final int PLAYBACK_SPEED_FAST_L0 = 10; // 0xa
+ field public static final int PLAYBACK_SPEED_FAST_L1 = 11; // 0xb
+ field public static final int PLAYBACK_SPEED_FAST_L2 = 12; // 0xc
+ field public static final int PLAYBACK_SPEED_FAST_L3 = 13; // 0xd
+ field public static final int PLAYBACK_SPEED_FAST_L4 = 14; // 0xe
+ field public static final int PLAYBACK_SPEED_INVALID = -1; // 0xffffffff
+ field public static final int PLAYBACK_SPEED_NORMAL = 1; // 0x1
+ field public static final int PLAYBACK_SPEED_PAUSED = 0; // 0x0
+ }
+
+ public class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
+ ctor public PlaybackOverlayFragment();
+ method public int getBackgroundType();
+ method public android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener getFadeCompleteListener();
+ method public final android.support.v17.leanback.app.PlaybackOverlayFragment.InputEventHandler getInputEventHandler();
+ method public boolean isFadingEnabled();
+ method public void onDestroyView();
+ method public void onResume();
+ method public void setBackgroundType(int);
+ method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener);
+ method public void setFadingEnabled(boolean);
+ method public final void setInputEventHandler(android.support.v17.leanback.app.PlaybackOverlayFragment.InputEventHandler);
+ method public void tickle();
+ field public static final int BG_DARK = 1; // 0x1
+ field public static final int BG_LIGHT = 2; // 0x2
+ field public static final int BG_NONE = 0; // 0x0
+ }
+
+ public static abstract interface PlaybackOverlayFragment.InputEventHandler {
+ method public abstract boolean handleInputEvent(android.view.InputEvent);
+ }
+
+ public static class PlaybackOverlayFragment.OnFadeCompleteListener {
+ ctor public PlaybackOverlayFragment.OnFadeCompleteListener();
+ method public void onFadeInComplete();
+ method public void onFadeOutComplete();
+ }
+
+ public class PlaybackOverlaySupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
+ ctor public PlaybackOverlaySupportFragment();
+ method public int getBackgroundType();
+ method public android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener getFadeCompleteListener();
+ method public final android.support.v17.leanback.app.PlaybackOverlaySupportFragment.InputEventHandler getInputEventHandler();
+ method public boolean isFadingEnabled();
+ method public void onDestroyView();
+ method public void onResume();
+ method public void setBackgroundType(int);
+ method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener);
+ method public void setFadingEnabled(boolean);
+ method public final void setInputEventHandler(android.support.v17.leanback.app.PlaybackOverlaySupportFragment.InputEventHandler);
+ method public void tickle();
+ field public static final int BG_DARK = 1; // 0x1
+ field public static final int BG_LIGHT = 2; // 0x2
+ field public static final int BG_NONE = 0; // 0x0
+ }
+
+ public static abstract interface PlaybackOverlaySupportFragment.InputEventHandler {
+ method public abstract boolean handleInputEvent(android.view.InputEvent);
+ }
+
+ public static class PlaybackOverlaySupportFragment.OnFadeCompleteListener {
+ ctor public PlaybackOverlaySupportFragment.OnFadeCompleteListener();
+ method public void onFadeInComplete();
+ method public void onFadeOutComplete();
+ }
+
+ public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment {
+ ctor public RowsFragment();
+ method public void enableRowScaling(boolean);
+ method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public void setExpand(boolean);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ }
+
+ public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
+ ctor public RowsSupportFragment();
+ method public void enableRowScaling(boolean);
+ method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public void setExpand(boolean);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ }
+
+ public class SearchFragment extends android.app.Fragment {
+ ctor public SearchFragment();
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
+ method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.content.Intent getRecognizerIntent();
+ method public java.lang.String getTitle();
+ method public static android.support.v17.leanback.app.SearchFragment newInstance(java.lang.String);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSearchQuery(java.lang.String, boolean);
+ method public void setSearchQuery(android.content.Intent, boolean);
+ method public void setSearchResultProvider(android.support.v17.leanback.app.SearchFragment.SearchResultProvider);
+ method public void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+ method public void setTitle(java.lang.String);
+ method public void startRecognition();
+ }
+
+ public static abstract interface SearchFragment.SearchResultProvider {
+ method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
+ method public abstract boolean onQueryTextChange(java.lang.String);
+ method public abstract boolean onQueryTextSubmit(java.lang.String);
+ }
+
+ public class SearchSupportFragment extends android.support.v4.app.Fragment {
+ ctor public SearchSupportFragment();
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String);
+ method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, java.lang.String);
+ method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.content.Intent getRecognizerIntent();
+ method public java.lang.String getTitle();
+ method public static android.support.v17.leanback.app.SearchSupportFragment newInstance(java.lang.String);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSearchQuery(java.lang.String, boolean);
+ method public void setSearchQuery(android.content.Intent, boolean);
+ method public void setSearchResultProvider(android.support.v17.leanback.app.SearchSupportFragment.SearchResultProvider);
+ method public void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+ method public void setTitle(java.lang.String);
+ method public void startRecognition();
+ }
+
+ public static abstract interface SearchSupportFragment.SearchResultProvider {
+ method public abstract android.support.v17.leanback.widget.ObjectAdapter getResultsAdapter();
+ method public abstract boolean onQueryTextChange(java.lang.String);
+ method public abstract boolean onQueryTextSubmit(java.lang.String);
+ }
+
+ public class VerticalGridFragment extends android.support.v17.leanback.app.BrandedFragment {
+ ctor public VerticalGridFragment();
+ method protected java.lang.Object createEntranceTransition();
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public void onDestroyView();
+ method public void onStart();
+ method public void onViewCreated(android.view.View, android.os.Bundle);
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ }
+
+ public class VerticalGridSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
+ ctor public VerticalGridSupportFragment();
+ method protected java.lang.Object createEntranceTransition();
+ method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
+ method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public void onDestroyView();
+ method public void onStart();
+ method public void onViewCreated(android.view.View, android.os.Bundle);
+ method protected void runEntranceTransition(java.lang.Object);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int);
+ }
+
+}
+
+package android.support.v17.leanback.database {
+
+ public abstract class CursorMapper {
+ ctor public CursorMapper();
+ method protected abstract java.lang.Object bind(android.database.Cursor);
+ method protected abstract void bindColumns(android.database.Cursor);
+ method public java.lang.Object convert(android.database.Cursor);
+ }
+
+}
+
+package android.support.v17.leanback.graphics {
+
+ public final class ColorFilterCache {
+ method public static android.support.v17.leanback.graphics.ColorFilterCache getColorFilterCache(int);
+ method public android.graphics.ColorFilter getFilterForLevel(float);
+ }
+
+ public final class ColorFilterDimmer {
+ method public void applyFilterToView(android.view.View);
+ method public static android.support.v17.leanback.graphics.ColorFilterDimmer create(android.support.v17.leanback.graphics.ColorFilterCache, float, float);
+ method public static android.support.v17.leanback.graphics.ColorFilterDimmer createDefault(android.content.Context);
+ method public android.graphics.ColorFilter getColorFilter();
+ method public android.graphics.Paint getPaint();
+ method public void setActiveLevel(float);
+ }
+
+ public final class ColorOverlayDimmer {
+ method public int applyToColor(int);
+ method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createColorOverlayDimmer(int, float, float);
+ method public static android.support.v17.leanback.graphics.ColorOverlayDimmer createDefault(android.content.Context);
+ method public void drawColorOverlay(android.graphics.Canvas, android.view.View, boolean);
+ method public int getAlpha();
+ method public float getAlphaFloat();
+ method public android.graphics.Paint getPaint();
+ method public boolean needsDraw();
+ method public void setActiveLevel(float);
+ }
+
+}
+
+package android.support.v17.leanback.system {
+
+ public class Settings {
+ method public boolean getBoolean(java.lang.String);
+ method public static android.support.v17.leanback.system.Settings getInstance(android.content.Context);
+ method public void setBoolean(java.lang.String, boolean);
+ field public static final java.lang.String PREFER_STATIC_SHADOWS = "PREFER_STATIC_SHADOWS";
+ }
+
+}
+
+package android.support.v17.leanback.widget {
+
+ public abstract class AbstractDetailsDescriptionPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public AbstractDetailsDescriptionPresenter();
+ method protected abstract void onBindDescription(android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder, java.lang.Object);
+ method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public final android.support.v17.leanback.widget.AbstractDetailsDescriptionPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ }
+
+ public static class AbstractDetailsDescriptionPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+ ctor public AbstractDetailsDescriptionPresenter.ViewHolder(android.view.View);
+ method public android.widget.TextView getBody();
+ method public android.widget.TextView getSubtitle();
+ method public android.widget.TextView getTitle();
+ }
+
+ public class Action {
+ ctor public Action(long);
+ ctor public Action(long, java.lang.CharSequence);
+ ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence);
+ ctor public Action(long, java.lang.CharSequence, java.lang.CharSequence, android.graphics.drawable.Drawable);
+ method public final void addKeyCode(int);
+ method public final android.graphics.drawable.Drawable getIcon();
+ method public final long getId();
+ method public final java.lang.CharSequence getLabel1();
+ method public final java.lang.CharSequence getLabel2();
+ method public final void removeKeyCode(int);
+ method public final boolean respondsToKeyCode(int);
+ method public final void setIcon(android.graphics.drawable.Drawable);
+ method public final void setId(long);
+ method public final void setLabel1(java.lang.CharSequence);
+ method public final void setLabel2(java.lang.CharSequence);
+ }
+
+ public class ArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+ ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ ctor public ArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
+ ctor public ArrayObjectAdapter();
+ method public void add(java.lang.Object);
+ method public void add(int, java.lang.Object);
+ method public void addAll(int, java.util.Collection);
+ method public void clear();
+ method public java.lang.Object get(int);
+ method public int indexOf(java.lang.Object);
+ method public void notifyArrayItemRangeChanged(int, int);
+ method public boolean remove(java.lang.Object);
+ method public int removeItems(int, int);
+ method public void replace(int, java.lang.Object);
+ method public int size();
+ method public java.util.List<E> unmodifiableList();
+ }
+
+ public class BaseCardView extends android.widget.FrameLayout {
+ ctor public BaseCardView(android.content.Context);
+ ctor public BaseCardView(android.content.Context, android.util.AttributeSet);
+ ctor public BaseCardView(android.content.Context, android.util.AttributeSet, int);
+ method public int getCardType();
+ method public int getExtraVisibility();
+ method public int getInfoVisibility();
+ method public boolean isSelectedAnimationDelayed();
+ method public void setCardType(int);
+ method public void setExtraVisibility(int);
+ method public void setInfoVisibility(int);
+ method public void setSelectedAnimationDelayed(boolean);
+ field public static final int CARD_REGION_VISIBLE_ACTIVATED = 1; // 0x1
+ field public static final int CARD_REGION_VISIBLE_ALWAYS = 0; // 0x0
+ field public static final int CARD_REGION_VISIBLE_SELECTED = 2; // 0x2
+ field public static final int CARD_TYPE_INFO_OVER = 1; // 0x1
+ field public static final int CARD_TYPE_INFO_UNDER = 2; // 0x2
+ field public static final int CARD_TYPE_INFO_UNDER_WITH_EXTRA = 3; // 0x3
+ field public static final int CARD_TYPE_MAIN_ONLY = 0; // 0x0
+ }
+
+ public static class BaseCardView.LayoutParams extends android.widget.FrameLayout.LayoutParams {
+ ctor public BaseCardView.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public BaseCardView.LayoutParams(int, int);
+ ctor public BaseCardView.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public BaseCardView.LayoutParams(android.support.v17.leanback.widget.BaseCardView.LayoutParams);
+ field public static final int VIEW_TYPE_EXTRA = 2; // 0x2
+ field public static final int VIEW_TYPE_INFO = 1; // 0x1
+ field public static final int VIEW_TYPE_MAIN = 0; // 0x0
+ field public int viewType;
+ }
+
+ public class BrowseFrameLayout extends android.widget.FrameLayout {
+ ctor public BrowseFrameLayout(android.content.Context);
+ ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet);
+ ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet, int);
+ method public android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener getOnChildFocusListener();
+ method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
+ method public void setOnChildFocusListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnChildFocusListener);
+ method public void setOnFocusSearchListener(android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener);
+ }
+
+ public static abstract interface BrowseFrameLayout.OnChildFocusListener {
+ method public abstract void onRequestChildFocus(android.view.View, android.view.View);
+ method public abstract boolean onRequestFocusInDescendants(int, android.graphics.Rect);
+ }
+
+ public static abstract interface BrowseFrameLayout.OnFocusSearchListener {
+ method public abstract android.view.View onFocusSearch(android.view.View, int);
+ }
+
+ public final class ClassPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+ ctor public ClassPresenterSelector();
+ method public void addClassPresenter(java.lang.Class<?>, android.support.v17.leanback.widget.Presenter);
+ method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+ }
+
+ public class ControlButtonPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+ ctor public ControlButtonPresenterSelector();
+ method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+ method public android.support.v17.leanback.widget.Presenter getPrimaryPresenter();
+ method public android.support.v17.leanback.widget.Presenter getSecondaryPresenter();
+ }
+
+ public class CursorObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+ ctor public CursorObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ ctor public CursorObjectAdapter(android.support.v17.leanback.widget.Presenter);
+ ctor public CursorObjectAdapter();
+ method public void changeCursor(android.database.Cursor);
+ method public void close();
+ method public java.lang.Object get(int);
+ method public final android.database.Cursor getCursor();
+ method public final android.support.v17.leanback.database.CursorMapper getMapper();
+ method protected final void invalidateCache(int);
+ method protected final void invalidateCache(int, int);
+ method public boolean isClosed();
+ method protected void onCursorChanged();
+ method protected void onMapperChanged();
+ method public final void setMapper(android.support.v17.leanback.database.CursorMapper);
+ method public int size();
+ method public android.database.Cursor swapCursor(android.database.Cursor);
+ }
+
+ public class DetailsOverviewLogoPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public DetailsOverviewLogoPresenter();
+ method public boolean isBoundToImage(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.DetailsOverviewRow);
+ method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void setContext(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
+ }
+
+ public static class DetailsOverviewLogoPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+ ctor public DetailsOverviewLogoPresenter.ViewHolder(android.view.View);
+ field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter mParentPresenter;
+ field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder mParentViewHolder;
+ }
+
+ public class DetailsOverviewRow extends android.support.v17.leanback.widget.Row {
+ ctor public DetailsOverviewRow(java.lang.Object);
+ method public final deprecated void addAction(android.support.v17.leanback.widget.Action);
+ method public final deprecated void addAction(int, android.support.v17.leanback.widget.Action);
+ method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
+ method public final deprecated java.util.List<android.support.v17.leanback.widget.Action> getActions();
+ method public final android.support.v17.leanback.widget.ObjectAdapter getActionsAdapter();
+ method public final android.graphics.drawable.Drawable getImageDrawable();
+ method public final java.lang.Object getItem();
+ method public boolean isImageScaleUpAllowed();
+ method public final deprecated boolean removeAction(android.support.v17.leanback.widget.Action);
+ method public final void setActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
+ method public final void setImageDrawable(android.graphics.drawable.Drawable);
+ method public void setImageScaleUpAllowed(boolean);
+ method public final void setItem(java.lang.Object);
+ }
+
+ public static class DetailsOverviewRow.Listener {
+ ctor public DetailsOverviewRow.Listener();
+ method public void onActionsAdapterChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+ method public void onImageDrawableChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+ method public void onItemChanged(android.support.v17.leanback.widget.DetailsOverviewRow);
+ }
+
+ public deprecated class DetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public DetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method public int getBackgroundColor();
+ method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+ method public boolean isStyleLarge();
+ method public final boolean isUsingDefaultSelectEffect();
+ method public void setBackgroundColor(int);
+ method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+ method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
+ method public final void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
+ method public void setStyleLarge(boolean);
+ }
+
+ public final class DetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public DetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter);
+ field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDetailsDescriptionViewHolder;
+ }
+
+ public abstract interface FacetProvider {
+ method public abstract java.lang.Object getFacet(java.lang.Class<?>);
+ }
+
+ public abstract interface FacetProviderAdapter {
+ method public abstract android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
+ }
+
+ public abstract interface FocusHighlight {
+ field public static final int ZOOM_FACTOR_LARGE = 3; // 0x3
+ field public static final int ZOOM_FACTOR_MEDIUM = 2; // 0x2
+ field public static final int ZOOM_FACTOR_NONE = 0; // 0x0
+ field public static final int ZOOM_FACTOR_SMALL = 1; // 0x1
+ field public static final int ZOOM_FACTOR_XSMALL = 4; // 0x4
+ }
+
+ public class FocusHighlightHelper {
+ ctor public FocusHighlightHelper();
+ method public static void setupBrowseItemFocusHighlight(android.support.v17.leanback.widget.ItemBridgeAdapter, int, boolean);
+ method public static void setupHeaderItemFocusHighlight(android.support.v17.leanback.widget.VerticalGridView);
+ }
+
+ public abstract interface FragmentAnimationProvider {
+ method public abstract void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public abstract void onImeDisappearing(java.util.List<android.animation.Animator>);
+ }
+
+ public class FullWidthDetailsOverviewRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter);
+ ctor public FullWidthDetailsOverviewRowPresenter(android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method public final int getActionsBackgroundColor();
+ method public final int getAlignmentMode();
+ method public final int getBackgroundColor();
+ method public final int getInitialState();
+ method protected int getLayoutResourceId();
+ method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+ method public final boolean isParticipatingEntranceTransition();
+ method public final boolean isUsingDefaultSelectEffect();
+ method public final void notifyOnBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
+ method protected void onLayoutLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
+ method protected void onLayoutOverviewFrame(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, boolean);
+ method protected void onStateChanged(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
+ method public final void setActionsBackgroundColor(int);
+ method public final void setAlignmentMode(int);
+ method public final void setBackgroundColor(int);
+ method public final void setInitialState(int);
+ method public final void setListener(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener);
+ method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+ method public final void setParticipatingEntranceTransition(boolean);
+ method public final void setState(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int);
+ field public static final int ALIGN_MODE_MIDDLE = 1; // 0x1
+ field public static final int ALIGN_MODE_START = 0; // 0x0
+ field public static final int STATE_FULL = 1; // 0x1
+ field public static final int STATE_HALF = 0; // 0x0
+ field public static final int STATE_SMALL = 2; // 0x2
+ field protected int mInitialState;
+ }
+
+ public static abstract class FullWidthDetailsOverviewRowPresenter.Listener {
+ ctor public FullWidthDetailsOverviewRowPresenter.Listener();
+ method public void onBindLogo(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder);
+ }
+
+ public class FullWidthDetailsOverviewRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.Presenter, android.support.v17.leanback.widget.DetailsOverviewLogoPresenter);
+ method protected android.support.v17.leanback.widget.DetailsOverviewRow.Listener createRowListener();
+ method public final android.view.ViewGroup getActionsRow();
+ method public final android.view.ViewGroup getDetailsDescriptionFrame();
+ method public final android.support.v17.leanback.widget.Presenter.ViewHolder getDetailsDescriptionViewHolder();
+ method public final android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder getLogoViewHolder();
+ method public final android.view.ViewGroup getOverviewView();
+ method public final int getState();
+ field protected final android.support.v17.leanback.widget.DetailsOverviewRow.Listener mRowListener;
+ }
+
+ public class FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener extends android.support.v17.leanback.widget.DetailsOverviewRow.Listener {
+ ctor public FullWidthDetailsOverviewRowPresenter.ViewHolder.DetailsOverviewRowListener();
+ }
+
+ public class FullWidthDetailsOverviewSharedElementHelper extends android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.Listener {
+ ctor public FullWidthDetailsOverviewSharedElementHelper();
+ method public boolean getAutoStartSharedElementTransition();
+ method public void setAutoStartSharedElementTransition(boolean);
+ method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String);
+ method public void setSharedElementEnterTransition(android.app.Activity, java.lang.String, long);
+ method public void startPostponedEnterTransition();
+ }
+
+ public class GuidanceStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
+ ctor public GuidanceStylist();
+ method public android.widget.TextView getBreadcrumbView();
+ method public android.widget.TextView getDescriptionView();
+ method public android.widget.ImageView getIconView();
+ method public android.widget.TextView getTitleView();
+ method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.support.v17.leanback.widget.GuidanceStylist.Guidance);
+ method public void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public void onImeDisappearing(java.util.List<android.animation.Animator>);
+ method public int onProvideLayoutId();
+ }
+
+ public static class GuidanceStylist.Guidance {
+ ctor public GuidanceStylist.Guidance(java.lang.String, java.lang.String, java.lang.String, android.graphics.drawable.Drawable);
+ method public java.lang.String getBreadcrumb();
+ method public java.lang.String getDescription();
+ method public android.graphics.drawable.Drawable getIconDrawable();
+ method public java.lang.String getTitle();
+ }
+
+ public class GuidedAction extends android.support.v17.leanback.widget.Action {
+ method public int getCheckSetId();
+ method public java.lang.CharSequence getDescription();
+ method public java.lang.CharSequence getEditTitle();
+ method public android.content.Intent getIntent();
+ method public java.lang.CharSequence getTitle();
+ method public boolean hasMultilineDescription();
+ method public boolean hasNext();
+ method public boolean infoOnly();
+ method public boolean isChecked();
+ method public boolean isEditTitleUsed();
+ method public boolean isEditable();
+ method public boolean isEnabled();
+ method public void setChecked(boolean);
+ method public void setDescription(java.lang.CharSequence);
+ method public void setEditTitle(java.lang.CharSequence);
+ method public void setEnabled(boolean);
+ method public void setTitle(java.lang.CharSequence);
+ field public static final int DEFAULT_CHECK_SET_ID = 1; // 0x1
+ field public static final int NO_CHECK_SET = 0; // 0x0
+ field public static final int NO_DRAWABLE = 0; // 0x0
+ }
+
+ public static class GuidedAction.Builder {
+ ctor public GuidedAction.Builder();
+ method public android.support.v17.leanback.widget.GuidedAction build();
+ method public android.support.v17.leanback.widget.GuidedAction.Builder checkSetId(int);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder checked(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder description(java.lang.String);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder editTitle(java.lang.String);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder editable(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder enabled(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder hasNext(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder icon(android.graphics.drawable.Drawable);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder iconResourceId(int, android.content.Context);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder id(long);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder infoOnly(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder intent(android.content.Intent);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder multilineDescription(boolean);
+ method public android.support.v17.leanback.widget.GuidedAction.Builder title(java.lang.String);
+ }
+
+ public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
+ ctor public GuidedActionEditText(android.content.Context);
+ ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet);
+ ctor public GuidedActionEditText(android.content.Context, android.util.AttributeSet, int);
+ method public void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+ }
+
+ public class GuidedActionsStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
+ ctor public GuidedActionsStylist();
+ method public android.support.v17.leanback.widget.VerticalGridView getActionsGridView();
+ method public void onAnimateItemChecked(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+ method public void onAnimateItemFocused(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+ method public void onAnimateItemPressed(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
+ method public void onBindViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+ method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup);
+ method public android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public void onImeAppearing(java.util.List<android.animation.Animator>);
+ method public void onImeDisappearing(java.util.List<android.animation.Animator>);
+ method public int onProvideItemLayoutId();
+ method public int onProvideLayoutId();
+ field protected android.support.v17.leanback.widget.VerticalGridView mActionsGridView;
+ field protected android.view.View mMainView;
+ field protected android.view.View mSelectorView;
+ }
+
+ public static class GuidedActionsStylist.ViewHolder {
+ ctor public GuidedActionsStylist.ViewHolder(android.view.View);
+ method public android.widget.ImageView getCheckmarkView();
+ method public android.widget.ImageView getChevronView();
+ method public android.view.View getContentView();
+ method public android.widget.TextView getDescriptionView();
+ method public android.widget.EditText getEditableTitleView();
+ method public android.widget.ImageView getIconView();
+ method public android.widget.TextView getTitleView();
+ field public final android.view.View view;
+ }
+
+ public class HeaderItem {
+ ctor public HeaderItem(long, java.lang.String);
+ ctor public HeaderItem(java.lang.String);
+ method public final long getId();
+ method public final java.lang.String getName();
+ }
+
+ public class HorizontalGridView extends android.support.v7.widget.RecyclerView {
+ ctor public HorizontalGridView(android.content.Context);
+ ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet);
+ ctor public HorizontalGridView(android.content.Context, android.util.AttributeSet, int);
+ method public final boolean getFadingLeftEdge();
+ method public final int getFadingLeftEdgeLength();
+ method public final int getFadingLeftEdgeOffset();
+ method public final boolean getFadingRightEdge();
+ method public final int getFadingRightEdgeLength();
+ method public final int getFadingRightEdgeOffset();
+ method protected void initAttributes(android.content.Context, android.util.AttributeSet);
+ method public final void setFadingLeftEdge(boolean);
+ method public final void setFadingLeftEdgeLength(int);
+ method public final void setFadingLeftEdgeOffset(int);
+ method public final void setFadingRightEdge(boolean);
+ method public final void setFadingRightEdgeLength(int);
+ method public final void setFadingRightEdgeOffset(int);
+ method public void setNumRows(int);
+ method public void setRowHeight(int);
+ }
+
+ public final class HorizontalHoverCardSwitcher extends android.support.v17.leanback.widget.PresenterSwitcher {
+ ctor public HorizontalHoverCardSwitcher();
+ method protected void insertView(android.view.View);
+ method public void select(android.support.v17.leanback.widget.HorizontalGridView, android.view.View, java.lang.Object);
+ }
+
+ public class ImageCardView extends android.support.v17.leanback.widget.BaseCardView {
+ ctor public ImageCardView(android.content.Context, int);
+ ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
+ ctor public ImageCardView(android.content.Context);
+ ctor public ImageCardView(android.content.Context, android.util.AttributeSet);
+ method public android.graphics.drawable.Drawable getBadgeImage();
+ method public java.lang.CharSequence getContentText();
+ method public android.graphics.drawable.Drawable getInfoAreaBackground();
+ method public android.graphics.drawable.Drawable getMainImage();
+ method public final android.widget.ImageView getMainImageView();
+ method public java.lang.CharSequence getTitleText();
+ method public void setBadgeImage(android.graphics.drawable.Drawable);
+ method public void setContentText(java.lang.CharSequence);
+ method public void setInfoAreaBackground(android.graphics.drawable.Drawable);
+ method public void setInfoAreaBackgroundColor(int);
+ method public void setMainImage(android.graphics.drawable.Drawable);
+ method public void setMainImage(android.graphics.drawable.Drawable, boolean);
+ method public void setMainImageAdjustViewBounds(boolean);
+ method public void setMainImageDimensions(int, int);
+ method public void setMainImageScaleType(android.widget.ImageView.ScaleType);
+ method public void setTitleText(java.lang.CharSequence);
+ field public static final int CARD_TYPE_FLAG_CONTENT = 2; // 0x2
+ field public static final int CARD_TYPE_FLAG_ICON_LEFT = 8; // 0x8
+ field public static final int CARD_TYPE_FLAG_ICON_RIGHT = 4; // 0x4
+ field public static final int CARD_TYPE_FLAG_IMAGE_ONLY = 0; // 0x0
+ field public static final int CARD_TYPE_FLAG_TITLE = 1; // 0x1
+ }
+
+ public abstract interface ImeKeyMonitor {
+ method public abstract void setImeKeyListener(android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener);
+ }
+
+ public static abstract interface ImeKeyMonitor.ImeKeyListener {
+ method public abstract boolean onKeyPreIme(android.widget.EditText, int, android.view.KeyEvent);
+ }
+
+ public final class ItemAlignmentFacet {
+ ctor public ItemAlignmentFacet();
+ method public android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[] getAlignmentDefs();
+ method public boolean isMultiAlignment();
+ method public void setAlignmentDefs(android.support.v17.leanback.widget.ItemAlignmentFacet.ItemAlignmentDef[]);
+ field public static final float ITEM_ALIGN_OFFSET_PERCENT_DISABLED = -1.0f;
+ }
+
+ public static class ItemAlignmentFacet.ItemAlignmentDef {
+ ctor public ItemAlignmentFacet.ItemAlignmentDef();
+ method public final int getItemAlignmentFocusViewId();
+ method public final int getItemAlignmentOffset();
+ method public final float getItemAlignmentOffsetPercent();
+ method public final int getItemAlignmentViewId();
+ method public final boolean isItemAlignmentOffsetWithPadding();
+ method public final void setItemAlignmentFocusViewId(int);
+ method public final void setItemAlignmentOffset(int);
+ method public final void setItemAlignmentOffsetPercent(float);
+ method public final void setItemAlignmentOffsetWithPadding(boolean);
+ method public final void setItemAlignmentViewId(int);
+ }
+
+ public class ItemBridgeAdapter extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.leanback.widget.FacetProviderAdapter {
+ ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter, android.support.v17.leanback.widget.PresenterSelector);
+ ctor public ItemBridgeAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ ctor public ItemBridgeAdapter();
+ method public void clear();
+ method public android.support.v17.leanback.widget.FacetProvider getFacetProvider(int);
+ method public int getItemCount();
+ method public java.util.ArrayList<android.support.v17.leanback.widget.Presenter> getPresenterMapper();
+ method public android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper getWrapper();
+ method protected void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
+ method protected void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method protected void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public final void onBindViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, int);
+ method protected void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public final android.support.v7.widget.RecyclerView.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method protected void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method protected void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public final void onViewAttachedToWindow(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void onViewDetachedFromWindow(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void onViewRecycled(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setAdapterListener(android.support.v17.leanback.widget.ItemBridgeAdapter.AdapterListener);
+ method public void setPresenterMapper(java.util.ArrayList<android.support.v17.leanback.widget.Presenter>);
+ method public void setWrapper(android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper);
+ }
+
+ public static class ItemBridgeAdapter.AdapterListener {
+ ctor public ItemBridgeAdapter.AdapterListener();
+ method public void onAddPresenter(android.support.v17.leanback.widget.Presenter, int);
+ method public void onAttachedToWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public void onBind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public void onCreate(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public void onDetachedFromWindow(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ method public void onUnbind(android.support.v17.leanback.widget.ItemBridgeAdapter.ViewHolder);
+ }
+
+ public class ItemBridgeAdapter.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
+ method public final java.lang.Object getExtraObject();
+ method public java.lang.Object getFacet(java.lang.Class<?>);
+ method public final java.lang.Object getItem();
+ method public final android.support.v17.leanback.widget.Presenter getPresenter();
+ method public final android.support.v17.leanback.widget.Presenter.ViewHolder getViewHolder();
+ method public void setExtraObject(java.lang.Object);
+ }
+
+ public static abstract class ItemBridgeAdapter.Wrapper {
+ ctor public ItemBridgeAdapter.Wrapper();
+ method public abstract android.view.View createWrapper(android.view.View);
+ method public abstract void wrap(android.view.View, android.view.View);
+ }
+
+ public class ItemBridgeAdapterShadowOverlayWrapper extends android.support.v17.leanback.widget.ItemBridgeAdapter.Wrapper {
+ ctor public ItemBridgeAdapterShadowOverlayWrapper(android.support.v17.leanback.widget.ShadowOverlayHelper);
+ method public android.view.View createWrapper(android.view.View);
+ method public void wrap(android.view.View, android.view.View);
+ }
+
+ public class ListRow extends android.support.v17.leanback.widget.Row {
+ ctor public ListRow(android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
+ ctor public ListRow(long, android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
+ ctor public ListRow(android.support.v17.leanback.widget.ObjectAdapter);
+ method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ }
+
+ public final class ListRowHoverCardView extends android.widget.LinearLayout {
+ ctor public ListRowHoverCardView(android.content.Context);
+ ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet);
+ ctor public ListRowHoverCardView(android.content.Context, android.util.AttributeSet, int);
+ method public final java.lang.CharSequence getDescription();
+ method public final java.lang.CharSequence getTitle();
+ method public final void setDescription(java.lang.CharSequence);
+ method public final void setTitle(java.lang.CharSequence);
+ }
+
+ public class ListRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public ListRowPresenter();
+ ctor public ListRowPresenter(int);
+ ctor public ListRowPresenter(int, boolean);
+ method public final boolean areChildRoundedCornersEnabled();
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
+ method public final void enableChildRoundedCorners(boolean);
+ method public int getExpandedRowHeight();
+ method public final int getFocusZoomFactor();
+ method public final android.support.v17.leanback.widget.PresenterSelector getHoverCardPresenterSelector();
+ method public int getRecycledPoolSize(android.support.v17.leanback.widget.Presenter);
+ method public int getRowHeight();
+ method public final boolean getShadowEnabled();
+ method public final deprecated int getZoomFactor();
+ method public final boolean isFocusDimmerUsed();
+ method public final boolean isKeepChildForeground();
+ method public boolean isUsingDefaultListSelectEffect();
+ method public final boolean isUsingDefaultSelectEffect();
+ method public boolean isUsingDefaultShadow();
+ method public boolean isUsingZOrder(android.content.Context);
+ method public void setExpandedRowHeight(int);
+ method public final void setHoverCardPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public final void setKeepChildForeground(boolean);
+ method public void setRecycledPoolSize(android.support.v17.leanback.widget.Presenter, int);
+ method public void setRowHeight(int);
+ method public final void setShadowEnabled(boolean);
+ }
+
+ public static class ListRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public ListRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.HorizontalGridView, android.support.v17.leanback.widget.ListRowPresenter);
+ method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
+ method public final android.support.v17.leanback.widget.HorizontalGridView getGridView();
+ method public final android.support.v17.leanback.widget.ListRowPresenter getListRowPresenter();
+ }
+
+ public final class ListRowView extends android.widget.LinearLayout {
+ ctor public ListRowView(android.content.Context);
+ ctor public ListRowView(android.content.Context, android.util.AttributeSet);
+ ctor public ListRowView(android.content.Context, android.util.AttributeSet, int);
+ method public android.support.v17.leanback.widget.HorizontalGridView getGridView();
+ }
+
+ public abstract class ObjectAdapter {
+ ctor public ObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ ctor public ObjectAdapter(android.support.v17.leanback.widget.Presenter);
+ ctor public ObjectAdapter();
+ method public abstract java.lang.Object get(int);
+ method public long getId(int);
+ method public final android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+ method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+ method public final boolean hasStableIds();
+ method protected final void notifyChanged();
+ method protected final void notifyItemRangeChanged(int, int);
+ method protected final void notifyItemRangeInserted(int, int);
+ method protected final void notifyItemRangeRemoved(int, int);
+ method protected void onHasStableIdsChanged();
+ method protected void onPresenterSelectorChanged();
+ method public final void registerObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
+ method public final void setHasStableIds(boolean);
+ method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
+ method public abstract int size();
+ method public final void unregisterAllObservers();
+ method public final void unregisterObserver(android.support.v17.leanback.widget.ObjectAdapter.DataObserver);
+ field public static final int NO_ID = -1; // 0xffffffff
+ }
+
+ public static abstract class ObjectAdapter.DataObserver {
+ ctor public ObjectAdapter.DataObserver();
+ method public void onChanged();
+ method public void onItemRangeChanged(int, int);
+ method public void onItemRangeInserted(int, int);
+ method public void onItemRangeRemoved(int, int);
+ }
+
+ public abstract interface OnActionClickedListener {
+ method public abstract void onActionClicked(android.support.v17.leanback.widget.Action);
+ }
+
+ public abstract interface OnChildLaidOutListener {
+ method public abstract void onChildLaidOut(android.view.ViewGroup, android.view.View, int, long);
+ }
+
+ public abstract deprecated interface OnChildSelectedListener {
+ method public abstract void onChildSelected(android.view.ViewGroup, android.view.View, int, long);
+ }
+
+ public abstract class OnChildViewHolderSelectedListener {
+ ctor public OnChildViewHolderSelectedListener();
+ method public void onChildViewHolderSelected(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
+ }
+
+ public abstract interface OnItemViewClickedListener {
+ method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ }
+
+ public abstract interface OnItemViewSelectedListener {
+ method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ }
+
+ public class PlaybackControlsRow extends android.support.v17.leanback.widget.Row {
+ ctor public PlaybackControlsRow(java.lang.Object);
+ ctor public PlaybackControlsRow();
+ method public android.support.v17.leanback.widget.Action getActionForKeyCode(int);
+ method public android.support.v17.leanback.widget.Action getActionForKeyCode(android.support.v17.leanback.widget.ObjectAdapter, int);
+ method public int getBufferedProgress();
+ method public int getCurrentTime();
+ method public final android.graphics.drawable.Drawable getImageDrawable();
+ method public final java.lang.Object getItem();
+ method public final android.support.v17.leanback.widget.ObjectAdapter getPrimaryActionsAdapter();
+ method public final android.support.v17.leanback.widget.ObjectAdapter getSecondaryActionsAdapter();
+ method public int getTotalTime();
+ method public void setBufferedProgress(int);
+ method public void setCurrentTime(int);
+ method public final void setImageBitmap(android.content.Context, android.graphics.Bitmap);
+ method public final void setImageDrawable(android.graphics.drawable.Drawable);
+ method public final void setPrimaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public final void setSecondaryActionsAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setTotalTime(int);
+ }
+
+ public static class PlaybackControlsRow.ClosedCaptioningAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context);
+ ctor public PlaybackControlsRow.ClosedCaptioningAction(android.content.Context, int);
+ field public static int OFF;
+ field public static int ON;
+ }
+
+ public static class PlaybackControlsRow.FastForwardAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.FastForwardAction(android.content.Context);
+ ctor public PlaybackControlsRow.FastForwardAction(android.content.Context, int);
+ }
+
+ public static class PlaybackControlsRow.HighQualityAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.HighQualityAction(android.content.Context);
+ ctor public PlaybackControlsRow.HighQualityAction(android.content.Context, int);
+ field public static int OFF;
+ field public static int ON;
+ }
+
+ public static class PlaybackControlsRow.MoreActions extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.MoreActions(android.content.Context);
+ }
+
+ public static abstract class PlaybackControlsRow.MultiAction extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.MultiAction(int);
+ method public int getActionCount();
+ method public android.graphics.drawable.Drawable getDrawable(int);
+ method public int getIndex();
+ method public java.lang.String getLabel(int);
+ method public java.lang.String getSecondaryLabel(int);
+ method public void nextIndex();
+ method public void setDrawables(android.graphics.drawable.Drawable[]);
+ method public void setIndex(int);
+ method public void setLabels(java.lang.String[]);
+ method public void setSecondaryLabels(java.lang.String[]);
+ }
+
+ public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
+ field public static int PAUSE;
+ field public static int PLAY;
+ }
+
+ public static class PlaybackControlsRow.RepeatAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.RepeatAction(android.content.Context);
+ ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int);
+ ctor public PlaybackControlsRow.RepeatAction(android.content.Context, int, int);
+ field public static int ALL;
+ field public static int NONE;
+ field public static int ONE;
+ }
+
+ public static class PlaybackControlsRow.RewindAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.RewindAction(android.content.Context);
+ ctor public PlaybackControlsRow.RewindAction(android.content.Context, int);
+ }
+
+ public static class PlaybackControlsRow.ShuffleAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.ShuffleAction(android.content.Context);
+ ctor public PlaybackControlsRow.ShuffleAction(android.content.Context, int);
+ field public static int OFF;
+ field public static int ON;
+ }
+
+ public static class PlaybackControlsRow.SkipNextAction extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.SkipNextAction(android.content.Context);
+ }
+
+ public static class PlaybackControlsRow.SkipPreviousAction extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.SkipPreviousAction(android.content.Context);
+ }
+
+ public static abstract class PlaybackControlsRow.ThumbsAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
+ ctor public PlaybackControlsRow.ThumbsAction(int, android.content.Context, int, int);
+ field public static int OUTLINE;
+ field public static int SOLID;
+ }
+
+ public static class PlaybackControlsRow.ThumbsDownAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
+ ctor public PlaybackControlsRow.ThumbsDownAction(android.content.Context);
+ }
+
+ public static class PlaybackControlsRow.ThumbsUpAction extends android.support.v17.leanback.widget.PlaybackControlsRow.ThumbsAction {
+ ctor public PlaybackControlsRow.ThumbsUpAction(android.content.Context);
+ }
+
+ public class PlaybackControlsRowPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public PlaybackControlsRowPresenter(android.support.v17.leanback.widget.Presenter);
+ ctor public PlaybackControlsRowPresenter();
+ method public boolean areSecondaryActionsHidden();
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method public int getBackgroundColor();
+ method public android.support.v17.leanback.widget.OnActionClickedListener getOnActionClickedListener();
+ method public int getProgressColor();
+ method public void setBackgroundColor(int);
+ method public void setOnActionClickedListener(android.support.v17.leanback.widget.OnActionClickedListener);
+ method public void setProgressColor(int);
+ method public void setSecondaryActionsHidden(boolean);
+ method public void showBottomSpace(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder, boolean);
+ method public void showPrimaryActions(android.support.v17.leanback.widget.PlaybackControlsRowPresenter.ViewHolder);
+ }
+
+ public class PlaybackControlsRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDescriptionViewHolder;
+ }
+
+ public abstract class Presenter implements android.support.v17.leanback.widget.FacetProvider {
+ ctor public Presenter();
+ method protected static void cancelAnimationsRecursive(android.view.View);
+ method public final java.lang.Object getFacet(java.lang.Class<?>);
+ method public abstract void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public abstract android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public abstract void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public final void setFacet(java.lang.Class<?>, java.lang.Object);
+ method public void setOnClickListener(android.support.v17.leanback.widget.Presenter.ViewHolder, android.view.View.OnClickListener);
+ }
+
+ public static class Presenter.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
+ ctor public Presenter.ViewHolder(android.view.View);
+ method public final java.lang.Object getFacet(java.lang.Class<?>);
+ method public final void setFacet(java.lang.Class<?>, java.lang.Object);
+ field public final android.view.View view;
+ }
+
+ public abstract class PresenterSelector {
+ ctor public PresenterSelector();
+ method public abstract android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+ method public android.support.v17.leanback.widget.Presenter[] getPresenters();
+ }
+
+ public abstract class PresenterSwitcher {
+ ctor public PresenterSwitcher();
+ method public void clear();
+ method public final android.view.ViewGroup getParentViewGroup();
+ method public void init(android.view.ViewGroup, android.support.v17.leanback.widget.PresenterSelector);
+ method protected abstract void insertView(android.view.View);
+ method protected void onViewSelected(android.view.View);
+ method public void select(java.lang.Object);
+ method protected void showView(android.view.View, boolean);
+ method public void unselect();
+ }
+
+ public class Row {
+ ctor public Row(long, android.support.v17.leanback.widget.HeaderItem);
+ ctor public Row(android.support.v17.leanback.widget.HeaderItem);
+ ctor public Row();
+ method public final android.support.v17.leanback.widget.HeaderItem getHeaderItem();
+ method public final long getId();
+ method public final void setHeaderItem(android.support.v17.leanback.widget.HeaderItem);
+ method public final void setId(long);
+ }
+
+ public class RowHeaderPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public RowHeaderPresenter();
+ method protected static float getFontDescent(android.widget.TextView, android.graphics.Paint);
+ method public int getSpaceUnderBaseline(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
+ method public boolean isNullItemVisibilityGone();
+ method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder);
+ method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void setNullItemVisibilityGone(boolean);
+ method public final void setSelectLevel(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, float);
+ }
+
+ public static class RowHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+ ctor public RowHeaderPresenter.ViewHolder(android.view.View);
+ method public final float getSelectLevel();
+ }
+
+ public final class RowHeaderView extends android.widget.TextView {
+ ctor public RowHeaderView(android.content.Context);
+ ctor public RowHeaderView(android.content.Context, android.util.AttributeSet);
+ ctor public RowHeaderView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public abstract class RowPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public RowPresenter();
+ method protected abstract android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method protected void dispatchItemSelectedListener(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+ method public void freeze(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+ method public final android.support.v17.leanback.widget.RowHeaderPresenter getHeaderPresenter();
+ method public final android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public final boolean getSelectEffectEnabled();
+ method public final float getSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public final int getSyncActivatePolicy();
+ method protected void initializeRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+ method protected boolean isClippingChildren();
+ method public boolean isUsingDefaultSelectEffect();
+ method protected void onBindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder, java.lang.Object);
+ method public final void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public final android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method protected void onRowViewAttachedToWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+ method protected void onRowViewDetachedFromWindow(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+ method protected void onRowViewExpanded(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+ method protected void onRowViewSelected(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+ method protected void onSelectLevelChanged(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+ method protected void onUnbindRowViewHolder(android.support.v17.leanback.widget.RowPresenter.ViewHolder);
+ method public final void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public final void onViewAttachedToWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public final void onViewDetachedFromWindow(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void setEntranceTransitionState(android.support.v17.leanback.widget.RowPresenter.ViewHolder, boolean);
+ method public final void setHeaderPresenter(android.support.v17.leanback.widget.RowHeaderPresenter);
+ method public final void setRowViewExpanded(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
+ method public final void setRowViewSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, boolean);
+ method public final void setSelectEffectEnabled(boolean);
+ method public final void setSelectLevel(android.support.v17.leanback.widget.Presenter.ViewHolder, float);
+ method public final void setSyncActivatePolicy(int);
+ field public static final int SYNC_ACTIVATED_CUSTOM = 0; // 0x0
+ field public static final int SYNC_ACTIVATED_TO_EXPANDED = 1; // 0x1
+ field public static final int SYNC_ACTIVATED_TO_EXPANDED_AND_SELECTED = 3; // 0x3
+ field public static final int SYNC_ACTIVATED_TO_SELECTED = 2; // 0x2
+ }
+
+ public static class RowPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+ ctor public RowPresenter.ViewHolder(android.view.View);
+ method public final android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder getHeaderViewHolder();
+ method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.view.View.OnKeyListener getOnKeyListener();
+ method public final android.support.v17.leanback.widget.Row getRow();
+ method public final float getSelectLevel();
+ method public final boolean isExpanded();
+ method public final boolean isSelected();
+ method public final void setActivated(boolean);
+ method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setOnKeyListener(android.view.View.OnKeyListener);
+ method public final void syncActivatedStatus(android.view.View);
+ field protected final android.support.v17.leanback.graphics.ColorOverlayDimmer mColorDimmer;
+ }
+
+ public class SearchBar extends android.widget.RelativeLayout {
+ ctor public SearchBar(android.content.Context);
+ ctor public SearchBar(android.content.Context, android.util.AttributeSet);
+ ctor public SearchBar(android.content.Context, android.util.AttributeSet, int);
+ method public void displayCompletions(java.util.List<java.lang.String>);
+ method public void displayCompletions(android.view.inputmethod.CompletionInfo[]);
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public java.lang.CharSequence getHint();
+ method public java.lang.String getTitle();
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setSearchBarListener(android.support.v17.leanback.widget.SearchBar.SearchBarListener);
+ method public void setSearchQuery(java.lang.String);
+ method public void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
+ method public void setSpeechRecognizer(android.speech.SpeechRecognizer);
+ method public void setTitle(java.lang.String);
+ method public void startRecognition();
+ method public void stopRecognition();
+ }
+
+ public static abstract interface SearchBar.SearchBarListener {
+ method public abstract void onKeyboardDismiss(java.lang.String);
+ method public abstract void onSearchQueryChange(java.lang.String);
+ method public abstract void onSearchQuerySubmit(java.lang.String);
+ }
+
+ public class SearchEditText extends android.support.v17.leanback.widget.StreamingTextView {
+ ctor public SearchEditText(android.content.Context);
+ ctor public SearchEditText(android.content.Context, android.util.AttributeSet);
+ ctor public SearchEditText(android.content.Context, android.util.AttributeSet, int);
+ method public void setOnKeyboardDismissListener(android.support.v17.leanback.widget.SearchEditText.OnKeyboardDismissListener);
+ }
+
+ public static abstract interface SearchEditText.OnKeyboardDismissListener {
+ method public abstract void onKeyboardDismiss();
+ }
+
+ public class SearchOrbView extends android.widget.FrameLayout implements android.view.View.OnClickListener {
+ ctor public SearchOrbView(android.content.Context);
+ ctor public SearchOrbView(android.content.Context, android.util.AttributeSet);
+ ctor public SearchOrbView(android.content.Context, android.util.AttributeSet, int);
+ method public void enableOrbColorAnimation(boolean);
+ method public int getOrbColor();
+ method public android.support.v17.leanback.widget.SearchOrbView.Colors getOrbColors();
+ method public android.graphics.drawable.Drawable getOrbIcon();
+ method public void onClick(android.view.View);
+ method public void setOnOrbClickedListener(android.view.View.OnClickListener);
+ method public void setOrbColor(int);
+ method public deprecated void setOrbColor(int, int);
+ method public void setOrbColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+ method public void setOrbIcon(android.graphics.drawable.Drawable);
+ }
+
+ public static class SearchOrbView.Colors {
+ ctor public SearchOrbView.Colors(int);
+ ctor public SearchOrbView.Colors(int, int);
+ ctor public SearchOrbView.Colors(int, int, int);
+ method public static int getBrightColor(int);
+ field public int brightColor;
+ field public int color;
+ field public int iconColor;
+ }
+
+ public class ShadowOverlayContainer extends android.widget.FrameLayout {
+ ctor public ShadowOverlayContainer(android.content.Context);
+ ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
+ ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet, int);
+ method public int getShadowType();
+ method public android.view.View getWrappedView();
+ method public deprecated void initialize(boolean, boolean);
+ method public deprecated void initialize(boolean, boolean, boolean);
+ method public static void prepareParentForShadow(android.view.ViewGroup);
+ method public void setOverlayColor(int);
+ method public void setShadowFocusLevel(float);
+ method public static boolean supportsDynamicShadow();
+ method public static boolean supportsShadow();
+ method public void useDynamicShadow();
+ method public void useDynamicShadow(float, float);
+ method public void useStaticShadow();
+ method public void wrap(android.view.View);
+ field public static final int SHADOW_DYNAMIC = 3; // 0x3
+ field public static final int SHADOW_NONE = 1; // 0x1
+ field public static final int SHADOW_STATIC = 2; // 0x2
+ }
+
+ public final class ShadowOverlayHelper {
+ method public android.support.v17.leanback.widget.ShadowOverlayContainer createShadowOverlayContainer(android.content.Context);
+ method public int getShadowType();
+ method public boolean needsOverlay();
+ method public boolean needsRoundedCorner();
+ method public boolean needsWrapper();
+ method public void onViewCreated(android.view.View);
+ method public void prepareParentForShadow(android.view.ViewGroup);
+ method public static void setNoneWrapperOverlayColor(android.view.View, int);
+ method public static void setNoneWrapperShadowFocusLevel(android.view.View, float);
+ method public void setOverlayColor(android.view.View, int);
+ method public void setShadowFocusLevel(android.view.View, float);
+ method public static boolean supportsDynamicShadow();
+ method public static boolean supportsForeground();
+ method public static boolean supportsRoundedCorner();
+ method public static boolean supportsShadow();
+ field public static final int SHADOW_DYNAMIC = 3; // 0x3
+ field public static final int SHADOW_NONE = 1; // 0x1
+ field public static final int SHADOW_STATIC = 2; // 0x2
+ }
+
+ public static final class ShadowOverlayHelper.Builder {
+ ctor public ShadowOverlayHelper.Builder();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper build(android.content.Context);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder keepForegroundDrawable(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsOverlay(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsRoundedCorner(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder needsShadow(boolean);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder options(android.support.v17.leanback.widget.ShadowOverlayHelper.Options);
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Builder preferZOrder(boolean);
+ }
+
+ public static final class ShadowOverlayHelper.Options {
+ ctor public ShadowOverlayHelper.Options();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options dynamicShadowZ(float, float);
+ method public final float getDynamicShadowFocusedZ();
+ method public final float getDynamicShadowUnfocusedZ();
+ method public final int getRoundedCornerRadius();
+ method public android.support.v17.leanback.widget.ShadowOverlayHelper.Options roundedCornerRadius(int);
+ field public static final android.support.v17.leanback.widget.ShadowOverlayHelper.Options DEFAULT;
+ }
+
+ public final class SinglePresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
+ ctor public SinglePresenterSelector(android.support.v17.leanback.widget.Presenter);
+ method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
+ }
+
+ public class SparseArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
+ ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
+ ctor public SparseArrayObjectAdapter(android.support.v17.leanback.widget.Presenter);
+ ctor public SparseArrayObjectAdapter();
+ method public void clear(int);
+ method public void clear();
+ method public java.lang.Object get(int);
+ method public int indexOf(java.lang.Object);
+ method public int indexOf(int);
+ method public java.lang.Object lookup(int);
+ method public void notifyArrayItemRangeChanged(int, int);
+ method public void set(int, java.lang.Object);
+ method public int size();
+ }
+
+ public class SpeechOrbView extends android.support.v17.leanback.widget.SearchOrbView {
+ ctor public SpeechOrbView(android.content.Context);
+ ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet);
+ ctor public SpeechOrbView(android.content.Context, android.util.AttributeSet, int);
+ method public void setSoundLevel(int);
+ method public void showListening();
+ method public void showNotListening();
+ }
+
+ public abstract interface SpeechRecognitionCallback {
+ method public abstract void recognizeSpeech();
+ }
+
+ class StreamingTextView extends android.widget.EditText {
+ ctor public StreamingTextView(android.content.Context, android.util.AttributeSet);
+ ctor public StreamingTextView(android.content.Context, android.util.AttributeSet, int);
+ method public static boolean isLayoutRtl(android.view.View);
+ method public void reset();
+ method public void setFinalRecognizedText(java.lang.CharSequence);
+ method public void updateRecognizedText(java.lang.String, java.lang.String);
+ method public void updateRecognizedText(java.lang.String, java.util.List<java.lang.Float>);
+ }
+
+ public class TitleHelper {
+ ctor public TitleHelper(android.view.ViewGroup, android.support.v17.leanback.widget.TitleView);
+ method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
+ method public android.view.ViewGroup getSceneRoot();
+ method public android.support.v17.leanback.widget.TitleView getTitleView();
+ method public void showTitle(boolean);
+ }
+
+ public class TitleView extends android.widget.FrameLayout {
+ ctor public TitleView(android.content.Context);
+ ctor public TitleView(android.content.Context, android.util.AttributeSet);
+ ctor public TitleView(android.content.Context, android.util.AttributeSet, int);
+ method public void enableAnimation(boolean);
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+ method public android.view.View getSearchAffordanceView();
+ method public java.lang.CharSequence getTitle();
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+ method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+ method public void setTitle(java.lang.String);
+ }
+
+ public class VerticalGridPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public VerticalGridPresenter();
+ ctor public VerticalGridPresenter(int);
+ ctor public VerticalGridPresenter(int, boolean);
+ method public final boolean areChildRoundedCornersEnabled();
+ method protected android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder createGridViewHolder(android.view.ViewGroup);
+ method protected android.support.v17.leanback.widget.ShadowOverlayHelper.Options createShadowOverlayOptions();
+ method public final void enableChildRoundedCorners(boolean);
+ method public final int getFocusZoomFactor();
+ method public final boolean getKeepChildForeground();
+ method public int getNumberOfColumns();
+ method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
+ method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public final boolean getShadowEnabled();
+ method protected void initializeGridViewHolder(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder);
+ method public final boolean isFocusDimmerUsed();
+ method public boolean isUsingDefaultShadow();
+ method public boolean isUsingZOrder(android.content.Context);
+ method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public final android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ method public void setEntranceTransitionState(android.support.v17.leanback.widget.VerticalGridPresenter.ViewHolder, boolean);
+ method public final void setKeepChildForeground(boolean);
+ method public void setNumberOfColumns(int);
+ method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public final void setShadowEnabled(boolean);
+ }
+
+ public static class VerticalGridPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
+ ctor public VerticalGridPresenter.ViewHolder(android.support.v17.leanback.widget.VerticalGridView);
+ method public android.support.v17.leanback.widget.VerticalGridView getGridView();
+ }
+
+ public class VerticalGridView extends android.support.v7.widget.RecyclerView {
+ ctor public VerticalGridView(android.content.Context);
+ ctor public VerticalGridView(android.content.Context, android.util.AttributeSet);
+ ctor public VerticalGridView(android.content.Context, android.util.AttributeSet, int);
+ method protected void initAttributes(android.content.Context, android.util.AttributeSet);
+ method public void setColumnWidth(int);
+ method public void setNumColumns(int);
+ }
+
+}
+
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index a4decd8..a7e2851 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -19,7 +19,12 @@
abstract class BaseRowFragment extends android.app.Fragment {
method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+ method public int getSelectedPosition();
+ method public void onTransitionEnd();
+ method public boolean onTransitionPrepare();
+ method public void onTransitionStart();
method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setAlignment(int);
method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
@@ -28,30 +33,80 @@
abstract class BaseRowSupportFragment extends android.support.v4.app.Fragment {
method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public final android.support.v17.leanback.widget.PresenterSelector getPresenterSelector();
+ method public int getSelectedPosition();
+ method public void onTransitionEnd();
+ method public boolean onTransitionPrepare();
+ method public void onTransitionStart();
method public final void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setAlignment(int);
method public final void setPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
}
+ public class BrandedFragment extends android.app.Fragment {
+ ctor public BrandedFragment();
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public int getSearchAffordanceColor();
+ method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+ method public java.lang.CharSequence getTitle();
+ method public android.view.View getTitleView();
+ method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+ method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public final boolean isShowingTitle();
+ method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+ method public void setSearchAffordanceColor(int);
+ method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+ method public void setTitle(java.lang.CharSequence);
+ method public void setTitleView(android.view.View);
+ method public void showTitle(boolean);
+ method public void showTitle(int);
+ }
+
+ public class BrandedSupportFragment extends android.support.v4.app.Fragment {
+ ctor public BrandedSupportFragment();
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public int getSearchAffordanceColor();
+ method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+ method public java.lang.CharSequence getTitle();
+ method public android.view.View getTitleView();
+ method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
+ method public void installTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public final boolean isShowingTitle();
+ method public android.view.View onInflateTitleView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+ method public void setSearchAffordanceColor(int);
+ method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+ method public void setTitle(java.lang.CharSequence);
+ method public void setTitleView(android.view.View);
+ method public void showTitle(boolean);
+ method public void showTitle(int);
+ }
+
public class BrowseFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public BrowseFragment();
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
method protected java.lang.Object createEntranceTransition();
- method public void enableRowScaling(boolean);
+ method public void enableMainFragmentScaling(boolean);
+ method public deprecated void enableRowScaling(boolean);
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBrandColor();
+ method public android.support.v17.leanback.app.HeadersFragment getHeadersFragment();
method public int getHeadersState();
+ method public final android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
+ method public int getSelectedPosition();
method public final boolean isHeadersTransitionOnBackEnabled();
method public boolean isInHeadersTransition();
method public boolean isShowingHeaders();
method protected void onEntranceTransitionEnd();
method protected void onEntranceTransitionPrepare();
method protected void onEntranceTransitionStart();
- method public void onSaveInstanceState(android.os.Bundle);
- method public void onStart();
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBrandColor(int);
@@ -63,6 +118,7 @@
method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
method public void startHeadersTransition(boolean);
field public static final int HEADERS_DISABLED = 3; // 0x3
field public static final int HEADERS_ENABLED = 1; // 0x1
@@ -75,24 +131,83 @@
method public void onHeadersTransitionStop(boolean);
}
+ public static abstract class BrowseFragment.FragmentFactory {
+ ctor public BrowseFragment.FragmentFactory();
+ method public abstract T createFragment(java.lang.Object);
+ }
+
+ public static abstract interface BrowseFragment.FragmentHost {
+ method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
+ method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter);
+ method public abstract void showTitleView(boolean);
+ }
+
+ public static class BrowseFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseFragment.FragmentFactory {
+ ctor public BrowseFragment.ListRowFragmentFactory();
+ method public android.support.v17.leanback.app.RowsFragment createFragment(java.lang.Object);
+ }
+
+ public static class BrowseFragment.MainFragmentAdapter {
+ ctor public BrowseFragment.MainFragmentAdapter(T);
+ method public final T getFragment();
+ method public final android.support.v17.leanback.app.BrowseFragment.FragmentHost getFragmentHost();
+ method public boolean isScalingEnabled();
+ method public boolean isScrolling();
+ method public void onTransitionEnd();
+ method public boolean onTransitionPrepare();
+ method public void onTransitionStart();
+ method public void setAlignment(int);
+ method public void setEntranceTransitionState(boolean);
+ method public void setExpand(boolean);
+ method public void setScalingEnabled(boolean);
+ }
+
+ public static abstract interface BrowseFragment.MainFragmentAdapterProvider {
+ method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
+ }
+
+ public static final class BrowseFragment.MainFragmentAdapterRegistry {
+ ctor public BrowseFragment.MainFragmentAdapterRegistry();
+ method public android.app.Fragment createFragment(java.lang.Object);
+ method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseFragment.FragmentFactory);
+ }
+
+ public static class BrowseFragment.MainFragmentRowsAdapter {
+ ctor public BrowseFragment.MainFragmentRowsAdapter(T);
+ method public final T getFragment();
+ method public int getSelectedPosition();
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+ method public void setSelectedPosition(int, boolean);
+ }
+
+ public static abstract interface BrowseFragment.MainFragmentRowsAdapterProvider {
+ method public abstract android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ }
+
public class BrowseSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
ctor public BrowseSupportFragment();
method public static android.os.Bundle createArgs(android.os.Bundle, java.lang.String, int);
method protected java.lang.Object createEntranceTransition();
- method public void enableRowScaling(boolean);
+ method public void enableMainFragmentScaling(boolean);
+ method public deprecated void enableRowScaling(boolean);
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public int getBrandColor();
method public int getHeadersState();
+ method public android.support.v17.leanback.app.HeadersSupportFragment getHeadersSupportFragment();
+ method public final android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterRegistry getMainFragmentRegistry();
method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
+ method public int getSelectedPosition();
method public final boolean isHeadersTransitionOnBackEnabled();
method public boolean isInHeadersTransition();
method public boolean isShowingHeaders();
method protected void onEntranceTransitionEnd();
method protected void onEntranceTransitionPrepare();
method protected void onEntranceTransitionStart();
- method public void onSaveInstanceState(android.os.Bundle);
- method public void onStart();
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setBrandColor(int);
@@ -104,6 +219,7 @@
method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
method public void startHeadersTransition(boolean);
field public static final int HEADERS_DISABLED = 3; // 0x3
field public static final int HEADERS_ENABLED = 1; // 0x1
@@ -116,22 +232,78 @@
method public void onHeadersTransitionStop(boolean);
}
+ public static abstract class BrowseSupportFragment.FragmentFactory {
+ ctor public BrowseSupportFragment.FragmentFactory();
+ method public abstract T createFragment(java.lang.Object);
+ }
+
+ public static abstract interface BrowseSupportFragment.FragmentHost {
+ method public abstract void notifyDataReady(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
+ method public abstract void notifyViewCreated(android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter);
+ method public abstract void showTitleView(boolean);
+ }
+
+ public static class BrowseSupportFragment.ListRowFragmentFactory extends android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory {
+ ctor public BrowseSupportFragment.ListRowFragmentFactory();
+ method public android.support.v17.leanback.app.RowsSupportFragment createFragment(java.lang.Object);
+ }
+
+ public static class BrowseSupportFragment.MainFragmentAdapter {
+ ctor public BrowseSupportFragment.MainFragmentAdapter(T);
+ method public final T getFragment();
+ method public final android.support.v17.leanback.app.BrowseSupportFragment.FragmentHost getFragmentHost();
+ method public boolean isScalingEnabled();
+ method public boolean isScrolling();
+ method public void onTransitionEnd();
+ method public boolean onTransitionPrepare();
+ method public void onTransitionStart();
+ method public void setAlignment(int);
+ method public void setEntranceTransitionState(boolean);
+ method public void setExpand(boolean);
+ method public void setScalingEnabled(boolean);
+ }
+
+ public static abstract interface BrowseSupportFragment.MainFragmentAdapterProvider {
+ method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
+ }
+
+ public static final class BrowseSupportFragment.MainFragmentAdapterRegistry {
+ ctor public BrowseSupportFragment.MainFragmentAdapterRegistry();
+ method public android.support.v4.app.Fragment createFragment(java.lang.Object);
+ method public void registerFragment(java.lang.Class, android.support.v17.leanback.app.BrowseSupportFragment.FragmentFactory);
+ }
+
+ public static class BrowseSupportFragment.MainFragmentRowsAdapter {
+ ctor public BrowseSupportFragment.MainFragmentRowsAdapter(T);
+ method public final T getFragment();
+ method public int getSelectedPosition();
+ method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+ method public void setSelectedPosition(int, boolean);
+ }
+
+ public static abstract interface BrowseSupportFragment.MainFragmentRowsAdapterProvider {
+ method public abstract android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ }
+
public class DetailsFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public DetailsFragment();
method protected java.lang.Object createEntranceTransition();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
- method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.app.RowsFragment getRowsFragment();
+ method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
method protected void onEntranceTransitionEnd();
method protected void onEntranceTransitionPrepare();
method protected void onEntranceTransitionStart();
method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
- method public void onStart();
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
- method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
- method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
@@ -142,62 +314,54 @@
ctor public DetailsSupportFragment();
method protected java.lang.Object createEntranceTransition();
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
- method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method protected android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.app.RowsSupportFragment getRowsSupportFragment();
+ method protected deprecated android.view.View inflateTitle(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
method protected void onEntranceTransitionEnd();
method protected void onEntranceTransitionPrepare();
method protected void onEntranceTransitionStart();
method protected void onSetDetailsOverviewRowStatus(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, int, int, int);
method protected void onSetRowStatus(android.support.v17.leanback.widget.RowPresenter, android.support.v17.leanback.widget.RowPresenter.ViewHolder, int, int, int);
- method public void onStart();
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
- method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
- method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
method public void setSelectedPosition(int);
method public void setSelectedPosition(int, boolean);
method protected void setupDetailsOverviewRowPresenter(android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
method protected void setupPresenter(android.support.v17.leanback.widget.Presenter);
}
- public class ErrorFragment extends android.app.Fragment {
+ public class ErrorFragment extends android.support.v17.leanback.app.BrandedFragment {
ctor public ErrorFragment();
method public android.graphics.drawable.Drawable getBackgroundDrawable();
- method public android.graphics.drawable.Drawable getBadgeDrawable();
method public android.view.View.OnClickListener getButtonClickListener();
method public java.lang.String getButtonText();
method public android.graphics.drawable.Drawable getImageDrawable();
method public java.lang.CharSequence getMessage();
- method public java.lang.String getTitle();
method public boolean isBackgroundTranslucent();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
- method public void setBadgeDrawable(android.graphics.drawable.Drawable);
method public void setButtonClickListener(android.view.View.OnClickListener);
method public void setButtonText(java.lang.String);
method public void setDefaultBackground(boolean);
method public void setImageDrawable(android.graphics.drawable.Drawable);
method public void setMessage(java.lang.CharSequence);
- method public void setTitle(java.lang.String);
}
- public class ErrorSupportFragment extends android.support.v4.app.Fragment {
+ public class ErrorSupportFragment extends android.support.v17.leanback.app.BrandedSupportFragment {
ctor public ErrorSupportFragment();
method public android.graphics.drawable.Drawable getBackgroundDrawable();
- method public android.graphics.drawable.Drawable getBadgeDrawable();
method public android.view.View.OnClickListener getButtonClickListener();
method public java.lang.String getButtonText();
method public android.graphics.drawable.Drawable getImageDrawable();
method public java.lang.CharSequence getMessage();
- method public java.lang.String getTitle();
method public boolean isBackgroundTranslucent();
method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
- method public void setBadgeDrawable(android.graphics.drawable.Drawable);
method public void setButtonClickListener(android.view.View.OnClickListener);
method public void setButtonText(java.lang.String);
method public void setDefaultBackground(boolean);
method public void setImageDrawable(android.graphics.drawable.Drawable);
method public void setMessage(java.lang.CharSequence);
- method public void setTitle(java.lang.String);
}
public class GuidedStepFragment extends android.app.Fragment {
@@ -205,13 +369,13 @@
method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment);
method public static int add(android.app.FragmentManager, android.support.v17.leanback.app.GuidedStepFragment, int);
method public static int addAsRoot(android.app.Activity, android.support.v17.leanback.app.GuidedStepFragment, int);
+ method public void collapseSubActions();
+ method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
method public int findActionPositionById(long);
method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
method public int findButtonActionPositionById(long);
method public void finishGuidedStepFragments();
- method public java.lang.String generateStackEntryName();
- method public static java.lang.String generateStackEntryName(int, java.lang.Class);
method public android.view.View getActionItemView(int);
method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
method public android.view.View getButtonActionItemView(int);
@@ -220,12 +384,12 @@
method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
- method public static java.lang.String getGuidedStepFragmentClassName(java.lang.String);
method public int getSelectedActionPosition();
method public int getSelectedButtonActionPosition();
method public int getUiStyle();
- method public static boolean isUiStyleDefault(java.lang.String);
- method public static boolean isUiStyleEntrance(java.lang.String);
+ method public boolean isFocusOutEndAllowed();
+ method public boolean isFocusOutStartAllowed();
+ method public boolean isSubActionsExpanded();
method public void notifyActionChanged(int);
method public void notifyButtonActionChanged(int);
method protected void onAddSharedElementTransition(android.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepFragment);
@@ -237,11 +401,13 @@
method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
- method public void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
+ method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
method protected void onProvideFragmentTransitions();
method public int onProvideTheme();
+ method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
method public void popBackStackToGuidedStepFragment(java.lang.Class, int);
method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
@@ -250,6 +416,7 @@
method public void setUiStyle(int);
field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+ field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
field public static final int UI_STYLE_REPLACE = 0; // 0x0
}
@@ -259,13 +426,13 @@
method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment);
method public static int add(android.support.v4.app.FragmentManager, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
method public static int addAsRoot(android.support.v4.app.FragmentActivity, android.support.v17.leanback.app.GuidedStepSupportFragment, int);
+ method public void collapseSubActions();
+ method public void expandSubActions(android.support.v17.leanback.widget.GuidedAction);
method public android.support.v17.leanback.widget.GuidedAction findActionById(long);
method public int findActionPositionById(long);
method public android.support.v17.leanback.widget.GuidedAction findButtonActionById(long);
method public int findButtonActionPositionById(long);
method public void finishGuidedStepSupportFragments();
- method public java.lang.String generateStackEntryName();
- method public static java.lang.String generateStackEntryName(int, java.lang.Class);
method public android.view.View getActionItemView(int);
method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getActions();
method public android.view.View getButtonActionItemView(int);
@@ -274,12 +441,12 @@
method public android.support.v17.leanback.widget.GuidanceStylist getGuidanceStylist();
method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedActionsStylist();
method public android.support.v17.leanback.widget.GuidedActionsStylist getGuidedButtonActionsStylist();
- method public static java.lang.String getGuidedStepSupportFragmentClassName(java.lang.String);
method public int getSelectedActionPosition();
method public int getSelectedButtonActionPosition();
method public int getUiStyle();
- method public static boolean isUiStyleDefault(java.lang.String);
- method public static boolean isUiStyleEntrance(java.lang.String);
+ method public boolean isFocusOutEndAllowed();
+ method public boolean isFocusOutStartAllowed();
+ method public boolean isSubActionsExpanded();
method public void notifyActionChanged(int);
method public void notifyButtonActionChanged(int);
method protected void onAddSharedElementTransition(android.support.v4.app.FragmentTransaction, android.support.v17.leanback.app.GuidedStepSupportFragment);
@@ -291,11 +458,13 @@
method public android.support.v17.leanback.widget.GuidanceStylist.Guidance onCreateGuidance(android.os.Bundle);
method public android.support.v17.leanback.widget.GuidanceStylist onCreateGuidanceStylist();
method public void onGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
- method public void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
+ method public void onGuidedActionEditCanceled(android.support.v17.leanback.widget.GuidedAction);
+ method public deprecated void onGuidedActionEdited(android.support.v17.leanback.widget.GuidedAction);
method public long onGuidedActionEditedAndProceed(android.support.v17.leanback.widget.GuidedAction);
method public void onGuidedActionFocused(android.support.v17.leanback.widget.GuidedAction);
method protected void onProvideFragmentTransitions();
method public int onProvideTheme();
+ method public boolean onSubGuidedActionClicked(android.support.v17.leanback.widget.GuidedAction);
method public void popBackStackToGuidedStepSupportFragment(java.lang.Class, int);
method public void setActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
method public void setButtonActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
@@ -304,35 +473,38 @@
method public void setUiStyle(int);
field public static final java.lang.String EXTRA_UI_STYLE = "uiStyle";
field public static final int UI_STYLE_ACTIVITY_ROOT = 2; // 0x2
+ field public static final deprecated int UI_STYLE_DEFAULT = 0; // 0x0
field public static final int UI_STYLE_ENTRANCE = 1; // 0x1
field public static final int UI_STYLE_REPLACE = 0; // 0x0
}
public class HeadersFragment extends android.support.v17.leanback.app.BaseRowFragment {
ctor public HeadersFragment();
+ method public boolean isScrolling();
method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderClickedListener);
method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
}
- static abstract interface HeadersFragment.OnHeaderClickedListener {
- method public abstract void onHeaderClicked();
+ public static abstract interface HeadersFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
+ public static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
public class HeadersSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
ctor public HeadersSupportFragment();
+ method public boolean isScrolling();
method public void setOnHeaderClickedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderClickedListener);
method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
}
- static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
- method public abstract void onHeaderClicked();
+ public static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
+ public static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
@@ -357,6 +529,42 @@
method protected void startPlayback(int);
}
+ public abstract class OnboardingFragment extends android.app.Fragment {
+ ctor public OnboardingFragment();
+ method protected final int getCurrentPageIndex();
+ method public final int getLogoResourceId();
+ method protected abstract int getPageCount();
+ method protected abstract java.lang.CharSequence getPageDescription(int);
+ method protected abstract java.lang.CharSequence getPageTitle(int);
+ method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected android.animation.Animator onCreateEnterAnimation();
+ method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected android.animation.Animator onCreateLogoAnimation();
+ method protected void onFinishFragment();
+ method protected void onPageChanged(int, int);
+ method public int onProvideTheme();
+ method public final void setLogoResourceId(int);
+ }
+
+ public abstract class OnboardingSupportFragment extends android.support.v4.app.Fragment {
+ ctor public OnboardingSupportFragment();
+ method protected final int getCurrentPageIndex();
+ method public final int getLogoResourceId();
+ method protected abstract int getPageCount();
+ method protected abstract java.lang.CharSequence getPageDescription(int);
+ method protected abstract java.lang.CharSequence getPageTitle(int);
+ method protected abstract android.view.View onCreateBackgroundView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected abstract android.view.View onCreateContentView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected android.animation.Animator onCreateEnterAnimation();
+ method protected abstract android.view.View onCreateForegroundView(android.view.LayoutInflater, android.view.ViewGroup);
+ method protected android.animation.Animator onCreateLogoAnimation();
+ method protected void onFinishFragment();
+ method protected void onPageChanged(int, int);
+ method public int onProvideTheme();
+ method public final void setLogoResourceId(int);
+ }
+
public abstract class PlaybackControlGlue implements android.support.v17.leanback.widget.OnActionClickedListener android.view.View.OnKeyListener {
ctor public PlaybackControlGlue(android.content.Context, int[]);
ctor public PlaybackControlGlue(android.content.Context, int[], int[]);
@@ -469,12 +677,11 @@
public class PlaybackOverlayFragment extends android.support.v17.leanback.app.DetailsFragment {
ctor public PlaybackOverlayFragment();
+ method public void fadeOut();
method public int getBackgroundType();
method public android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener getFadeCompleteListener();
method public final android.support.v17.leanback.app.PlaybackOverlayFragment.InputEventHandler getInputEventHandler();
method public boolean isFadingEnabled();
- method public void onDestroyView();
- method public void onResume();
method public void setBackgroundType(int);
method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackOverlayFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
@@ -497,12 +704,11 @@
public class PlaybackOverlaySupportFragment extends android.support.v17.leanback.app.DetailsSupportFragment {
ctor public PlaybackOverlaySupportFragment();
+ method public void fadeOut();
method public int getBackgroundType();
method public android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener getFadeCompleteListener();
method public final android.support.v17.leanback.app.PlaybackOverlaySupportFragment.InputEventHandler getInputEventHandler();
method public boolean isFadingEnabled();
- method public void onDestroyView();
- method public void onResume();
method public void setBackgroundType(int);
method public void setFadeCompleteListener(android.support.v17.leanback.app.PlaybackOverlaySupportFragment.OnFadeCompleteListener);
method public void setFadingEnabled(boolean);
@@ -523,26 +729,66 @@
method public void onFadeOutComplete();
}
- public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment {
- ctor public RowsFragment();
- method public void enableRowScaling(boolean);
- method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
- method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
- method public void setExpand(boolean);
- method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
- method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ public final class ProgressBarManager {
+ ctor public ProgressBarManager();
+ method public void disableProgressBar();
+ method public void enableProgressBar();
+ method public long getInitialDelay();
+ method public void hide();
+ method public void setInitialDelay(long);
+ method public void setProgressBarView(android.view.View);
+ method public void setRootView(android.view.ViewGroup);
+ method public void show();
}
- public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment {
- ctor public RowsSupportFragment();
- method public void enableRowScaling(boolean);
+ public class RowsFragment extends android.support.v17.leanback.app.BaseRowFragment implements android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapterProvider {
+ ctor public RowsFragment();
+ method public deprecated void enableRowScaling(boolean);
method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
- method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter getMainFragmentAdapter();
+ method public android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+ method public boolean isScrolling();
+ method public void setEntranceTransitionState(boolean);
method public void setExpand(boolean);
- method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
- method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+ }
+
+ public static class RowsFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentAdapter {
+ ctor public RowsFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsFragment);
+ }
+
+ public static class RowsFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseFragment.MainFragmentRowsAdapter {
+ ctor public RowsFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsFragment);
+ }
+
+ public class RowsSupportFragment extends android.support.v17.leanback.app.BaseRowSupportFragment implements android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapterProvider android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapterProvider {
+ ctor public RowsSupportFragment();
+ method public deprecated void enableRowScaling(boolean);
+ method protected android.support.v17.leanback.widget.VerticalGridView findGridViewFromRoot(android.view.View);
+ method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter();
+ method public android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ method public android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+ method public android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public android.support.v17.leanback.widget.RowPresenter.ViewHolder getRowViewHolder(int);
+ method public boolean isScrolling();
+ method public void setEntranceTransitionState(boolean);
+ method public void setExpand(boolean);
+ method public void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+ method public void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
+ method public void setSelectedPosition(int, boolean, android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+ }
+
+ public static class RowsSupportFragment.MainFragmentAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentAdapter {
+ ctor public RowsSupportFragment.MainFragmentAdapter(android.support.v17.leanback.app.RowsSupportFragment);
+ }
+
+ public static class RowsSupportFragment.MainFragmentRowsAdapter extends android.support.v17.leanback.app.BrowseSupportFragment.MainFragmentRowsAdapter {
+ ctor public RowsSupportFragment.MainFragmentRowsAdapter(android.support.v17.leanback.app.RowsSupportFragment);
}
public class SearchFragment extends android.app.Fragment {
@@ -605,9 +851,6 @@
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public void onDestroyView();
- method public void onStart();
- method public void onViewCreated(android.view.View, android.os.Bundle);
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
@@ -622,9 +865,6 @@
method public android.support.v17.leanback.widget.ObjectAdapter getAdapter();
method public android.support.v17.leanback.widget.VerticalGridPresenter getGridPresenter();
method public android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public void onDestroyView();
- method public void onStart();
- method public void onViewCreated(android.view.View, android.os.Bundle);
method protected void runEntranceTransition(java.lang.Object);
method public void setAdapter(android.support.v17.leanback.widget.ObjectAdapter);
method public void setGridPresenter(android.support.v17.leanback.widget.VerticalGridPresenter);
@@ -704,6 +944,50 @@
method public android.widget.TextView getTitle();
}
+ public abstract class AbstractMediaItemPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public AbstractMediaItemPresenter();
+ ctor public AbstractMediaItemPresenter(int);
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method public android.support.v17.leanback.widget.Presenter getActionPresenter();
+ method public int getThemeId();
+ method public boolean hasMediaRowSeparator();
+ method protected abstract void onBindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
+ method protected void onBindRowActions(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+ method protected void onUnbindMediaDetails(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder);
+ method public void setActionPresenter(android.support.v17.leanback.widget.Presenter);
+ method public void setBackgroundColor(int);
+ method public void setHasMediaRowSeparator(boolean);
+ method public void setThemeId(int);
+ }
+
+ public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public AbstractMediaItemPresenter.ViewHolder(android.view.View);
+ method public android.view.ViewGroup getMediaItemActionsContainer();
+ method public android.view.View getMediaItemDetailsView();
+ method public android.widget.TextView getMediaItemDurationView();
+ method public android.widget.TextView getMediaItemNameView();
+ method public android.widget.TextView getMediaItemNumberView();
+ method public android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getMediaItemRowActions();
+ method public android.view.View getMediaItemRowSeparator();
+ method public android.view.View getSelectorView();
+ method public void notifyActionChanged(android.support.v17.leanback.widget.MultiActionsProvider.MultiAction);
+ method public void notifyDetailsChanged();
+ method public void onBindRowActions();
+ }
+
+ public abstract class AbstractMediaListHeaderPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public AbstractMediaListHeaderPresenter(android.content.Context, int);
+ ctor public AbstractMediaListHeaderPresenter();
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method protected abstract void onBindMediaListHeaderViewHolder(android.support.v17.leanback.widget.AbstractMediaListHeaderPresenter.ViewHolder, java.lang.Object);
+ method public void setBackgroundColor(int);
+ }
+
+ public static class AbstractMediaListHeaderPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public AbstractMediaListHeaderPresenter.ViewHolder(android.view.View);
+ method public android.widget.TextView getHeaderView();
+ }
+
public class Action {
ctor public Action(long);
ctor public Action(long, java.lang.CharSequence);
@@ -720,6 +1004,7 @@
method public final void setId(long);
method public final void setLabel1(java.lang.CharSequence);
method public final void setLabel2(java.lang.CharSequence);
+ field public static final long NO_ID = -1L; // 0xffffffffffffffffL
}
public class ArrayObjectAdapter extends android.support.v17.leanback.widget.ObjectAdapter {
@@ -772,6 +1057,14 @@
field public int viewType;
}
+ public abstract interface BaseOnItemViewClickedListener {
+ method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+ }
+
+ public abstract interface BaseOnItemViewSelectedListener {
+ method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, T);
+ }
+
public class BrowseFrameLayout extends android.widget.FrameLayout {
ctor public BrowseFrameLayout(android.content.Context);
ctor public BrowseFrameLayout(android.content.Context, android.util.AttributeSet);
@@ -793,7 +1086,8 @@
public final class ClassPresenterSelector extends android.support.v17.leanback.widget.PresenterSelector {
ctor public ClassPresenterSelector();
- method public void addClassPresenter(java.lang.Class<?>, android.support.v17.leanback.widget.Presenter);
+ method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenter(java.lang.Class<?>, android.support.v17.leanback.widget.Presenter);
+ method public android.support.v17.leanback.widget.ClassPresenterSelector addClassPresenterSelector(java.lang.Class<?>, android.support.v17.leanback.widget.PresenterSelector);
method public android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
}
@@ -827,6 +1121,7 @@
ctor public DetailsOverviewLogoPresenter();
method public boolean isBoundToImage(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.DetailsOverviewRow);
method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public android.view.View onCreateView(android.view.ViewGroup);
method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
method public void setContext(android.support.v17.leanback.widget.DetailsOverviewLogoPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder, android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter);
@@ -834,6 +1129,10 @@
public static class DetailsOverviewLogoPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
ctor public DetailsOverviewLogoPresenter.ViewHolder(android.view.View);
+ method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter getParentPresenter();
+ method public android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder getParentViewHolder();
+ method public boolean isSizeFromDrawableIntrinsic();
+ method public void setSizeFromDrawableIntrinsic(boolean);
field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter mParentPresenter;
field protected android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter.ViewHolder mParentViewHolder;
}
@@ -882,6 +1181,18 @@
field public final android.support.v17.leanback.widget.Presenter.ViewHolder mDetailsDescriptionViewHolder;
}
+ public class DividerPresenter extends android.support.v17.leanback.widget.Presenter {
+ ctor public DividerPresenter();
+ method public void onBindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object);
+ method public android.support.v17.leanback.widget.Presenter.ViewHolder onCreateViewHolder(android.view.ViewGroup);
+ method public void onUnbindViewHolder(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ }
+
+ public class DividerRow extends android.support.v17.leanback.widget.Row {
+ ctor public DividerRow();
+ method public final boolean isRenderedAsRowView();
+ }
+
public abstract interface FacetProvider {
method public abstract java.lang.Object getFacet(java.lang.Class<?>);
}
@@ -1003,22 +1314,31 @@
method public java.lang.CharSequence getEditTitle();
method public int getInputType();
method public android.content.Intent getIntent();
+ method public java.util.List<android.support.v17.leanback.widget.GuidedAction> getSubActions();
method public java.lang.CharSequence getTitle();
+ method public boolean hasEditableActivatorView();
method public boolean hasMultilineDescription();
method public boolean hasNext();
+ method public boolean hasSubActions();
+ method public boolean hasTextEditable();
method public boolean infoOnly();
+ method public final boolean isAutoSaveRestoreEnabled();
method public boolean isChecked();
method public boolean isDescriptionEditable();
method public boolean isEditTitleUsed();
method public boolean isEditable();
method public boolean isEnabled();
method public boolean isFocusable();
+ method public void onRestoreInstanceState(android.os.Bundle, java.lang.String);
+ method public void onSaveInstanceState(android.os.Bundle, java.lang.String);
method public void setChecked(boolean);
method public void setDescription(java.lang.CharSequence);
method public void setEditDescription(java.lang.CharSequence);
method public void setEditTitle(java.lang.CharSequence);
method public void setEnabled(boolean);
method public void setFocusable(boolean);
+ method public void setIntent(android.content.Intent);
+ method public void setSubActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
method public void setTitle(java.lang.CharSequence);
field public static final long ACTION_ID_CANCEL = -5L; // 0xfffffffffffffffbL
field public static final long ACTION_ID_CONTINUE = -7L; // 0xfffffffffffffff9L
@@ -1033,37 +1353,46 @@
field public static final int NO_CHECK_SET = 0; // 0x0
}
- public static class GuidedAction.Builder {
- ctor public GuidedAction.Builder();
+ public static class GuidedAction.Builder extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
+ ctor public deprecated GuidedAction.Builder();
+ ctor public GuidedAction.Builder(android.content.Context);
+ method public android.support.v17.leanback.widget.GuidedAction build();
+ }
+
+ public static abstract class GuidedAction.BuilderBase {
+ ctor public GuidedAction.BuilderBase(android.content.Context);
method protected final void applyValues(android.support.v17.leanback.widget.GuidedAction);
- method public final android.support.v17.leanback.widget.GuidedAction build();
- method public android.support.v17.leanback.widget.GuidedAction.Builder checkSetId(int);
- method public android.support.v17.leanback.widget.GuidedAction.Builder checked(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructCancel(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructContinue(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructFinish(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructNo(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructOK(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder constructYes(android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder description(java.lang.CharSequence);
- method public android.support.v17.leanback.widget.GuidedAction.Builder descriptionEditInputType(int);
- method public android.support.v17.leanback.widget.GuidedAction.Builder descriptionEditable(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder descriptionInputType(int);
- method public android.support.v17.leanback.widget.GuidedAction.Builder editDescription(java.lang.CharSequence);
- method public android.support.v17.leanback.widget.GuidedAction.Builder editInputType(int);
- method public android.support.v17.leanback.widget.GuidedAction.Builder editTitle(java.lang.CharSequence);
- method public android.support.v17.leanback.widget.GuidedAction.Builder editable(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder enabled(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder focusable(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder hasNext(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder icon(android.graphics.drawable.Drawable);
- method public android.support.v17.leanback.widget.GuidedAction.Builder iconResourceId(int, android.content.Context);
- method public android.support.v17.leanback.widget.GuidedAction.Builder id(long);
- method public android.support.v17.leanback.widget.GuidedAction.Builder infoOnly(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder inputType(int);
- method public android.support.v17.leanback.widget.GuidedAction.Builder intent(android.content.Intent);
- method public android.support.v17.leanback.widget.GuidedAction.Builder multilineDescription(boolean);
- method public android.support.v17.leanback.widget.GuidedAction.Builder title(java.lang.CharSequence);
+ method public B autoSaveRestoreEnabled(boolean);
+ method public B checkSetId(int);
+ method public B checked(boolean);
+ method public B clickAction(long);
+ method public B description(java.lang.CharSequence);
+ method public B description(int);
+ method public B descriptionEditInputType(int);
+ method public B descriptionEditable(boolean);
+ method public B descriptionInputType(int);
+ method public B editDescription(java.lang.CharSequence);
+ method public B editDescription(int);
+ method public B editInputType(int);
+ method public B editTitle(java.lang.CharSequence);
+ method public B editTitle(int);
+ method public B editable(boolean);
+ method public B enabled(boolean);
+ method public B focusable(boolean);
+ method public android.content.Context getContext();
+ method public B hasEditableActivatorView(boolean);
+ method public B hasNext(boolean);
+ method public B icon(android.graphics.drawable.Drawable);
+ method public B icon(int);
+ method public deprecated B iconResourceId(int, android.content.Context);
+ method public B id(long);
+ method public B infoOnly(boolean);
+ method public B inputType(int);
+ method public B intent(android.content.Intent);
+ method public B multilineDescription(boolean);
+ method public B subActions(java.util.List<android.support.v17.leanback.widget.GuidedAction>);
+ method public B title(java.lang.CharSequence);
+ method public B title(int);
}
public class GuidedActionEditText extends android.widget.EditText implements android.support.v17.leanback.widget.ImeKeyMonitor {
@@ -1076,12 +1405,18 @@
public class GuidedActionsStylist implements android.support.v17.leanback.widget.FragmentAnimationProvider {
ctor public GuidedActionsStylist();
method public android.support.v17.leanback.widget.VerticalGridView getActionsGridView();
+ method public android.support.v17.leanback.widget.GuidedAction getExpandedAction();
method public int getItemViewType(android.support.v17.leanback.widget.GuidedAction);
+ method public android.support.v17.leanback.widget.VerticalGridView getSubActionsGridView();
method public boolean isButtonActions();
+ method public boolean isExpandTransitionSupported();
+ method public boolean isInExpandTransition();
+ method public boolean isSubActionsExpanded();
method public void onAnimateItemChecked(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
method public void onAnimateItemFocused(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
method public void onAnimateItemPressed(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, boolean);
method public void onAnimateItemPressedCancelled(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+ method public void onBindActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
method public void onBindCheckMarkView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
method public void onBindChevronView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
method public void onBindViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
@@ -1095,14 +1430,21 @@
method public int onProvideItemLayoutId();
method public int onProvideItemLayoutId(int);
method public int onProvideLayoutId();
+ method public boolean onUpdateActivatorView(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+ method public void onUpdateExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
method public void setAsButtonActions();
method public void setEditingMode(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction, boolean);
+ method public void setExpandedViewHolder(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
method protected void setupImeOptions(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder, android.support.v17.leanback.widget.GuidedAction);
+ method public void startExpandedTransition(android.support.v17.leanback.widget.GuidedActionsStylist.ViewHolder);
+ field public static final int VIEW_TYPE_DATE_PICKER = 1; // 0x1
field public static final int VIEW_TYPE_DEFAULT = 0; // 0x0
}
- public static class GuidedActionsStylist.ViewHolder {
+ public static class GuidedActionsStylist.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.support.v17.leanback.widget.FacetProvider {
ctor public GuidedActionsStylist.ViewHolder(android.view.View);
+ ctor public GuidedActionsStylist.ViewHolder(android.view.View, boolean);
+ method public android.support.v17.leanback.widget.GuidedAction getAction();
method public android.widget.ImageView getCheckmarkView();
method public android.widget.ImageView getChevronView();
method public android.view.View getContentView();
@@ -1110,18 +1452,47 @@
method public android.widget.EditText getEditableDescriptionView();
method public android.widget.EditText getEditableTitleView();
method public android.view.View getEditingView();
+ method public java.lang.Object getFacet(java.lang.Class<?>);
method public android.widget.ImageView getIconView();
method public android.widget.TextView getTitleView();
method public boolean isInEditing();
+ method public boolean isInEditingActivatorView();
method public boolean isInEditingDescription();
- field public final android.view.View view;
+ method public boolean isInEditingText();
+ method public boolean isInEditingTitle();
+ method public boolean isSubAction();
+ }
+
+ public class GuidedDatePickerAction extends android.support.v17.leanback.widget.GuidedAction {
+ ctor public GuidedDatePickerAction();
+ method public long getDate();
+ method public java.lang.String getDatePickerFormat();
+ method public long getMaxDate();
+ method public long getMinDate();
+ method public void setDate(long);
+ }
+
+ public static final class GuidedDatePickerAction.Builder extends android.support.v17.leanback.widget.GuidedDatePickerAction.BuilderBase {
+ ctor public GuidedDatePickerAction.Builder(android.content.Context);
+ method public android.support.v17.leanback.widget.GuidedDatePickerAction build();
+ }
+
+ public static abstract class GuidedDatePickerAction.BuilderBase extends android.support.v17.leanback.widget.GuidedAction.BuilderBase {
+ ctor public GuidedDatePickerAction.BuilderBase(android.content.Context);
+ method protected final void applyDatePickerValues(android.support.v17.leanback.widget.GuidedDatePickerAction);
+ method public B date(long);
+ method public B datePickerFormat(java.lang.String);
+ method public B maxDate(long);
+ method public B minDate(long);
}
public class HeaderItem {
ctor public HeaderItem(long, java.lang.String);
ctor public HeaderItem(java.lang.String);
+ method public java.lang.CharSequence getContentDescription();
method public final long getId();
method public final java.lang.String getName();
+ method public void setContentDescription(java.lang.CharSequence);
}
public class HorizontalGridView extends android.support.v7.widget.RecyclerView {
@@ -1152,7 +1523,7 @@
}
public class ImageCardView extends android.support.v17.leanback.widget.BaseCardView {
- ctor public ImageCardView(android.content.Context, int);
+ ctor public deprecated ImageCardView(android.content.Context, int);
ctor public ImageCardView(android.content.Context, android.util.AttributeSet, int);
ctor public ImageCardView(android.content.Context);
ctor public ImageCardView(android.content.Context, android.util.AttributeSet);
@@ -1201,7 +1572,9 @@
method public final int getItemAlignmentOffset();
method public final float getItemAlignmentOffsetPercent();
method public final int getItemAlignmentViewId();
+ method public boolean isAlignedToTextViewBaseLine();
method public final boolean isItemAlignmentOffsetWithPadding();
+ method public final void setAlignedToTextViewBaseline(boolean);
method public final void setItemAlignmentFocusViewId(int);
method public final void setItemAlignmentOffset(int);
method public final void setItemAlignmentOffsetPercent(float);
@@ -1271,6 +1644,8 @@
ctor public ListRow(long, android.support.v17.leanback.widget.HeaderItem, android.support.v17.leanback.widget.ObjectAdapter);
ctor public ListRow(android.support.v17.leanback.widget.ObjectAdapter);
method public final android.support.v17.leanback.widget.ObjectAdapter getAdapter();
+ method public java.lang.CharSequence getContentDescription();
+ method public void setContentDescription(java.lang.CharSequence);
}
public final class ListRowHoverCardView extends android.widget.LinearLayout {
@@ -1307,16 +1682,29 @@
method public void setExpandedRowHeight(int);
method public final void setHoverCardPresenterSelector(android.support.v17.leanback.widget.PresenterSelector);
method public final void setKeepChildForeground(boolean);
+ method public void setNumRows(int);
method public void setRecycledPoolSize(android.support.v17.leanback.widget.Presenter, int);
method public void setRowHeight(int);
method public final void setShadowEnabled(boolean);
}
+ public static class ListRowPresenter.SelectItemViewHolderTask extends android.support.v17.leanback.widget.Presenter.ViewHolderTask {
+ ctor public ListRowPresenter.SelectItemViewHolderTask(int);
+ method public int getItemPosition();
+ method public android.support.v17.leanback.widget.Presenter.ViewHolderTask getItemTask();
+ method public boolean isSmoothScroll();
+ method public void setItemPosition(int);
+ method public void setItemTask(android.support.v17.leanback.widget.Presenter.ViewHolderTask);
+ method public void setSmoothScroll(boolean);
+ }
+
public static class ListRowPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
ctor public ListRowPresenter.ViewHolder(android.view.View, android.support.v17.leanback.widget.HorizontalGridView, android.support.v17.leanback.widget.ListRowPresenter);
method public final android.support.v17.leanback.widget.ItemBridgeAdapter getBridgeAdapter();
method public final android.support.v17.leanback.widget.HorizontalGridView getGridView();
+ method public android.support.v17.leanback.widget.Presenter.ViewHolder getItemViewHolder(int);
method public final android.support.v17.leanback.widget.ListRowPresenter getListRowPresenter();
+ method public int getSelectedPosition();
}
public final class ListRowView extends android.widget.LinearLayout {
@@ -1326,6 +1714,21 @@
method public android.support.v17.leanback.widget.HorizontalGridView getGridView();
}
+ public abstract interface MultiActionsProvider {
+ method public abstract android.support.v17.leanback.widget.MultiActionsProvider.MultiAction[] getActions();
+ }
+
+ public static class MultiActionsProvider.MultiAction {
+ ctor public MultiActionsProvider.MultiAction(long);
+ method public android.graphics.drawable.Drawable getCurrentDrawable();
+ method public android.graphics.drawable.Drawable[] getDrawables();
+ method public long getId();
+ method public int getIndex();
+ method public void incrementIndex();
+ method public void setDrawables(android.graphics.drawable.Drawable[]);
+ method public void setIndex(int);
+ }
+
public abstract class ObjectAdapter {
ctor public ObjectAdapter(android.support.v17.leanback.widget.PresenterSelector);
ctor public ObjectAdapter(android.support.v17.leanback.widget.Presenter);
@@ -1375,12 +1778,15 @@
method public void onChildViewHolderSelected(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, int);
}
- public abstract interface OnItemViewClickedListener {
- method public abstract void onItemClicked(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ public abstract interface OnItemViewClickedListener implements android.support.v17.leanback.widget.BaseOnItemViewClickedListener {
}
- public abstract interface OnItemViewSelectedListener {
- method public abstract void onItemSelected(android.support.v17.leanback.widget.Presenter.ViewHolder, java.lang.Object, android.support.v17.leanback.widget.RowPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
+ public abstract interface OnItemViewSelectedListener implements android.support.v17.leanback.widget.BaseOnItemViewSelectedListener {
+ }
+
+ public class PageRow extends android.support.v17.leanback.widget.Row {
+ ctor public PageRow(android.support.v17.leanback.widget.HeaderItem);
+ method public final boolean isRenderedAsRowView();
}
public class PlaybackControlsRow extends android.support.v17.leanback.widget.Row {
@@ -1441,6 +1847,10 @@
method public void setSecondaryLabels(java.lang.String[]);
}
+ public static class PlaybackControlsRow.PictureInPictureAction extends android.support.v17.leanback.widget.Action {
+ ctor public PlaybackControlsRow.PictureInPictureAction(android.content.Context);
+ }
+
public static class PlaybackControlsRow.PlayPauseAction extends android.support.v17.leanback.widget.PlaybackControlsRow.MultiAction {
ctor public PlaybackControlsRow.PlayPauseAction(android.content.Context);
field public static int PAUSE;
@@ -1530,6 +1940,11 @@
field public final android.view.View view;
}
+ public static abstract class Presenter.ViewHolderTask {
+ ctor public Presenter.ViewHolderTask();
+ method public void run(android.support.v17.leanback.widget.Presenter.ViewHolder);
+ }
+
public abstract class PresenterSelector {
ctor public PresenterSelector();
method public abstract android.support.v17.leanback.widget.Presenter getPresenter(java.lang.Object);
@@ -1554,6 +1969,7 @@
ctor public Row();
method public final android.support.v17.leanback.widget.HeaderItem getHeaderItem();
method public final long getId();
+ method public boolean isRenderedAsRowView();
method public final void setHeaderItem(android.support.v17.leanback.widget.HeaderItem);
method public final void setId(long);
}
@@ -1623,16 +2039,17 @@
public static class RowPresenter.ViewHolder extends android.support.v17.leanback.widget.Presenter.ViewHolder {
ctor public RowPresenter.ViewHolder(android.view.View);
method public final android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder getHeaderViewHolder();
- method public final android.support.v17.leanback.widget.OnItemViewClickedListener getOnItemViewClickedListener();
- method public final android.support.v17.leanback.widget.OnItemViewSelectedListener getOnItemViewSelectedListener();
+ method public final android.support.v17.leanback.widget.BaseOnItemViewClickedListener getOnItemViewClickedListener();
+ method public final android.support.v17.leanback.widget.BaseOnItemViewSelectedListener getOnItemViewSelectedListener();
method public android.view.View.OnKeyListener getOnKeyListener();
method public final android.support.v17.leanback.widget.Row getRow();
+ method public final java.lang.Object getRowObject();
method public final float getSelectLevel();
method public final boolean isExpanded();
method public final boolean isSelected();
method public final void setActivated(boolean);
- method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.OnItemViewClickedListener);
- method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.OnItemViewSelectedListener);
+ method public final void setOnItemViewClickedListener(android.support.v17.leanback.widget.BaseOnItemViewClickedListener);
+ method public final void setOnItemViewSelectedListener(android.support.v17.leanback.widget.BaseOnItemViewSelectedListener);
method public void setOnKeyListener(android.view.View.OnKeyListener);
method public final void syncActivatedStatus(android.view.View);
field protected final android.support.v17.leanback.graphics.ColorOverlayDimmer mColorDimmer;
@@ -1647,7 +2064,9 @@
method public android.graphics.drawable.Drawable getBadgeDrawable();
method public java.lang.CharSequence getHint();
method public java.lang.String getTitle();
+ method public boolean isRecognizing();
method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setPermissionListener(android.support.v17.leanback.widget.SearchBar.SearchBarPermissionListener);
method public void setSearchBarListener(android.support.v17.leanback.widget.SearchBar.SearchBarListener);
method public void setSearchQuery(java.lang.String);
method public void setSpeechRecognitionCallback(android.support.v17.leanback.widget.SpeechRecognitionCallback);
@@ -1663,6 +2082,10 @@
method public abstract void onSearchQuerySubmit(java.lang.String);
}
+ public static abstract interface SearchBar.SearchBarPermissionListener {
+ method public abstract void requestAudioPermission();
+ }
+
public class SearchEditText extends android.support.v17.leanback.widget.StreamingTextView {
ctor public SearchEditText(android.content.Context);
ctor public SearchEditText(android.content.Context, android.util.AttributeSet);
@@ -1700,6 +2123,13 @@
field public int iconColor;
}
+ public class SectionRow extends android.support.v17.leanback.widget.Row {
+ ctor public SectionRow(android.support.v17.leanback.widget.HeaderItem);
+ ctor public SectionRow(long, java.lang.String);
+ ctor public SectionRow(java.lang.String);
+ method public final boolean isRenderedAsRowView();
+ }
+
public class ShadowOverlayContainer extends android.widget.FrameLayout {
ctor public ShadowOverlayContainer(android.content.Context);
ctor public ShadowOverlayContainer(android.content.Context, android.util.AttributeSet);
@@ -1808,14 +2238,14 @@
}
public class TitleHelper {
- ctor public TitleHelper(android.view.ViewGroup, android.support.v17.leanback.widget.TitleView);
+ ctor public TitleHelper(android.view.ViewGroup, android.view.View);
method public android.support.v17.leanback.widget.BrowseFrameLayout.OnFocusSearchListener getOnFocusSearchListener();
method public android.view.ViewGroup getSceneRoot();
- method public android.support.v17.leanback.widget.TitleView getTitleView();
+ method public android.view.View getTitleView();
method public void showTitle(boolean);
}
- public class TitleView extends android.widget.FrameLayout {
+ public class TitleView extends android.widget.FrameLayout implements android.support.v17.leanback.widget.TitleViewAdapter.Provider {
ctor public TitleView(android.content.Context);
ctor public TitleView(android.content.Context, android.util.AttributeSet);
ctor public TitleView(android.content.Context, android.util.AttributeSet, int);
@@ -1824,10 +2254,33 @@
method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
method public android.view.View getSearchAffordanceView();
method public java.lang.CharSequence getTitle();
+ method public android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
method public void setBadgeDrawable(android.graphics.drawable.Drawable);
method public void setOnSearchClickedListener(android.view.View.OnClickListener);
method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
- method public void setTitle(java.lang.String);
+ method public void setTitle(java.lang.CharSequence);
+ method public void updateComponentsVisibility(int);
+ }
+
+ public abstract class TitleViewAdapter {
+ ctor public TitleViewAdapter();
+ method public android.graphics.drawable.Drawable getBadgeDrawable();
+ method public android.support.v17.leanback.widget.SearchOrbView.Colors getSearchAffordanceColors();
+ method public abstract android.view.View getSearchAffordanceView();
+ method public java.lang.CharSequence getTitle();
+ method public void setAnimationEnabled(boolean);
+ method public void setBadgeDrawable(android.graphics.drawable.Drawable);
+ method public void setOnSearchClickedListener(android.view.View.OnClickListener);
+ method public void setSearchAffordanceColors(android.support.v17.leanback.widget.SearchOrbView.Colors);
+ method public void setTitle(java.lang.CharSequence);
+ method public void updateComponentsVisibility(int);
+ field public static final int BRANDING_VIEW_VISIBLE = 2; // 0x2
+ field public static final int FULL_VIEW_VISIBLE = 6; // 0x6
+ field public static final int SEARCH_VIEW_VISIBLE = 4; // 0x4
+ }
+
+ public static abstract interface TitleViewAdapter.Provider {
+ method public abstract android.support.v17.leanback.widget.TitleViewAdapter getTitleViewAdapter();
}
public class VerticalGridPresenter extends android.support.v17.leanback.widget.Presenter {
@@ -1873,5 +2326,57 @@
method public void setNumColumns(int);
}
+ public abstract interface ViewHolderTask {
+ method public abstract void run(android.support.v7.widget.RecyclerView.ViewHolder);
+ }
+
+}
+
+package android.support.v17.leanback.widget.picker {
+
+ public class Picker extends android.widget.FrameLayout {
+ ctor public Picker(android.content.Context, android.util.AttributeSet, int);
+ method public void addOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+ method public float getActivatedVisibleItemCount();
+ method public android.support.v17.leanback.widget.picker.PickerColumn getColumnAt(int);
+ method public int getColumnsCount();
+ method protected int getPickerItemHeightPixels();
+ method public final int getPickerItemLayoutId();
+ method public final int getPickerItemTextViewId();
+ method public int getSelectedColumn();
+ method public final java.lang.CharSequence getSeparator();
+ method public float getVisibleItemCount();
+ method public void onColumnValueChanged(int, int);
+ method public void removeOnValueChangedListener(android.support.v17.leanback.widget.picker.Picker.PickerValueListener);
+ method public void setActivatedVisibleItemCount(float);
+ method public void setColumnAt(int, android.support.v17.leanback.widget.picker.PickerColumn);
+ method public void setColumnValue(int, int, boolean);
+ method public void setColumns(java.util.List<android.support.v17.leanback.widget.picker.PickerColumn>);
+ method public final void setPickerItemTextViewId(int);
+ method public void setSelectedColumn(int);
+ method public final void setSeparator(java.lang.CharSequence);
+ method public void setVisibleItemCount(float);
+ }
+
+ public static abstract interface Picker.PickerValueListener {
+ method public abstract void onValueChanged(android.support.v17.leanback.widget.picker.Picker, int);
+ }
+
+ public class PickerColumn {
+ ctor public PickerColumn();
+ method public int getCount();
+ method public int getCurrentValue();
+ method public java.lang.CharSequence getLabelFor(int);
+ method public java.lang.String getLabelFormat();
+ method public int getMaxValue();
+ method public int getMinValue();
+ method public java.lang.CharSequence[] getStaticLabels();
+ method public void setCurrentValue(int);
+ method public void setLabelFormat(java.lang.String);
+ method public void setMaxValue(int);
+ method public void setMinValue(int);
+ method public void setStaticLabels(java.lang.CharSequence[]);
+ }
+
}
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java b/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
index 1762c15..224d062 100644
--- a/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
@@ -18,10 +18,15 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Rect;
+import android.support.v17.leanback.R;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.transition.Visibility;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -38,54 +43,101 @@
private static final String PROPNAME_SCREEN_POSITION =
"android:fadeAndShortSlideTransition:screenPosition";
- private CalculateSlide mSlideCalculator = sCalculateEnd;
+ private CalculateSlide mSlideCalculator;
private Visibility mFade = new Fade();
+ private float mDistance = -1;
- private interface CalculateSlide {
+ private static abstract class CalculateSlide {
- /** Returns the translation value for view when it goes out of the scene */
- float getGoneX(ViewGroup sceneRoot, View view, int[] position);
+ /** Returns the translation X value for view when it goes out of the scene */
+ float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
+ return view.getTranslationX();
+ }
+
+ /** Returns the translation Y value for view when it goes out of the scene */
+ float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
+ return view.getTranslationY();
+ }
}
- private static final CalculateSlide sCalculateStart = new CalculateSlide() {
+ float getHorizontalDistance(ViewGroup sceneRoot) {
+ return mDistance >= 0 ? mDistance : (sceneRoot.getWidth() / 4);
+ }
+
+ float getVerticalDistance(ViewGroup sceneRoot) {
+ return mDistance >= 0 ? mDistance : (sceneRoot.getHeight() / 4);
+ }
+
+ final static CalculateSlide sCalculateStart = new CalculateSlide() {
@Override
- public float getGoneX(ViewGroup sceneRoot, View view, int[] position) {
+ public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final float x;
if (isRtl) {
- x = view.getTranslationX() + sceneRoot.getWidth() / 4;
+ x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
} else {
- x = view.getTranslationX() - sceneRoot.getWidth() / 4;
+ x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
}
return x;
}
};
- private static final CalculateSlide sCalculateEnd = new CalculateSlide() {
+ final static CalculateSlide sCalculateEnd = new CalculateSlide() {
@Override
- public float getGoneX(ViewGroup sceneRoot, View view, int[] position) {
+ public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final boolean isRtl = sceneRoot.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
final float x;
if (isRtl) {
- x = view.getTranslationX() - sceneRoot.getWidth() / 4;
+ x = view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
} else {
- x = view.getTranslationX() + sceneRoot.getWidth() / 4;
+ x = view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
}
return x;
}
};
- private static final CalculateSlide sCalculateBoth = new CalculateSlide() {
-
+ final static CalculateSlide sCalculateStartEnd = new CalculateSlide() {
@Override
- public float getGoneX(ViewGroup sceneRoot, View view, int[] position) {
+ public float getGoneX(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
final int viewCenter = position[0] + view.getWidth() / 2;
sceneRoot.getLocationOnScreen(position);
- final int sceneRootCenter = position[0] + sceneRoot.getWidth() / 2;
+ Rect center = t.getEpicenter();
+ final int sceneRootCenter = center == null ? (position[0] + sceneRoot.getWidth() / 2)
+ : center.centerX();
if (viewCenter < sceneRootCenter) {
- return view.getTranslationX() - sceneRoot.getWidth() / 2;
+ return view.getTranslationX() - t.getHorizontalDistance(sceneRoot);
} else {
- return view.getTranslationX() + sceneRoot.getWidth() / 2;
+ return view.getTranslationX() + t.getHorizontalDistance(sceneRoot);
+ }
+ }
+ };
+
+ final static CalculateSlide sCalculateBottom = new CalculateSlide() {
+ @Override
+ public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
+ return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
+ }
+ };
+
+ final static CalculateSlide sCalculateTop = new CalculateSlide() {
+ @Override
+ public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
+ return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
+ }
+ };
+
+ final CalculateSlide sCalculateTopBottom = new CalculateSlide() {
+ @Override
+ public float getGoneY(FadeAndShortSlide t, ViewGroup sceneRoot, View view, int[] position) {
+ final int viewCenter = position[1] + view.getHeight() / 2;
+ sceneRoot.getLocationOnScreen(position);
+ Rect center = getEpicenter();
+ final int sceneRootCenter = center == null ? (position[1] + sceneRoot.getHeight() / 2)
+ : center.centerY();
+ if (viewCenter < sceneRootCenter) {
+ return view.getTranslationY() - t.getVerticalDistance(sceneRoot);
+ } else {
+ return view.getTranslationY() + t.getVerticalDistance(sceneRoot);
}
}
};
@@ -98,10 +150,18 @@
setSlideEdge(slideEdge);
}
+ public FadeAndShortSlide(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
+ int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START);
+ setSlideEdge(edge);
+ a.recycle();
+ }
+
@Override
public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
- super.setEpicenterCallback(epicenterCallback);
mFade.setEpicenterCallback(epicenterCallback);
+ super.setEpicenterCallback(epicenterCallback);
}
private void captureValues(TransitionValues transitionValues) {
@@ -113,15 +173,15 @@
@Override
public void captureStartValues(TransitionValues transitionValues) {
- super.captureStartValues(transitionValues);
mFade.captureStartValues(transitionValues);
+ super.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
- super.captureEndValues(transitionValues);
mFade.captureEndValues(transitionValues);
+ super.captureEndValues(transitionValues);
captureValues(transitionValues);
}
@@ -134,14 +194,20 @@
mSlideCalculator = sCalculateEnd;
break;
case Gravity.START | Gravity.END:
- mSlideCalculator = sCalculateBoth;
+ mSlideCalculator = sCalculateStartEnd;
+ break;
+ case Gravity.TOP:
+ mSlideCalculator = sCalculateTop;
+ break;
+ case Gravity.BOTTOM:
+ mSlideCalculator = sCalculateBottom;
+ break;
+ case Gravity.TOP | Gravity.BOTTOM:
+ mSlideCalculator = sCalculateTopBottom;
break;
default:
throw new IllegalArgumentException("Invalid slide direction");
}
- // SidePropagation propagation = new SidePropagation();
- // propagation.setSide(slideEdge);
- // setPropagation(propagation);
}
@Override
@@ -156,12 +222,21 @@
}
int[] position = (int[]) endValues.values.get(PROPNAME_SCREEN_POSITION);
int left = position[0];
+ int top = position[1];
float endX = view.getTranslationX();
- float startX = mSlideCalculator.getGoneX(sceneRoot, view, position);
+ float startX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
+ float endY = view.getTranslationY();
+ float startY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
- left, startX, endX, sDecelerate, this);
+ left, top, startX, startY, endX, endY, sDecelerate, this);
+ final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues);
+ if (slideAnimator == null) {
+ return fadeAnimator;
+ } else if (fadeAnimator == null) {
+ return slideAnimator;
+ }
final AnimatorSet set = new AnimatorSet();
- set.play(slideAnimator).with(mFade.onAppear(sceneRoot, view, startValues, endValues));
+ set.play(slideAnimator).with(fadeAnimator);
return set;
}
@@ -178,12 +253,22 @@
}
int[] position = (int[]) startValues.values.get(PROPNAME_SCREEN_POSITION);
int left = position[0];
+ int top = position[1];
float startX = view.getTranslationX();
- float endX = mSlideCalculator.getGoneX(sceneRoot, view, position);
+ float endX = mSlideCalculator.getGoneX(this, sceneRoot, view, position);
+ float startY = view.getTranslationY();
+ float endY = mSlideCalculator.getGoneY(this, sceneRoot, view, position);
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view,
- startValues, left, startX, endX, sDecelerate /* sAccelerate */, this);
+ startValues, left, top, startX, startY, endX, endY, sDecelerate /* sAccelerate */,
+ this);
+ final Animator fadeAnimator = mFade.onDisappear(sceneRoot, view, startValues, endValues);
+ if (slideAnimator == null) {
+ return fadeAnimator;
+ } else if (fadeAnimator == null) {
+ return slideAnimator;
+ }
final AnimatorSet set = new AnimatorSet();
- set.play(slideAnimator).with(mFade.onDisappear(sceneRoot, view, startValues, endValues));
+ set.play(slideAnimator).with(fadeAnimator);
return set;
}
@@ -200,6 +285,23 @@
return super.removeListener(listener);
}
+ /**
+ * Returns distance to slide. When negative value is returned, it will use 1/4 of
+ * sceneRoot dimension.
+ */
+ public float getDistance() {
+ return mDistance;
+ }
+
+ /**
+ * Set distance to slide, default value is -1. when negative value is set, it will use 1/4 of
+ * sceneRoot dimension.
+ * @param distance Pixels to slide.
+ */
+ public void setDistance(float distance) {
+ mDistance = distance;
+ }
+
@Override
public Transition clone() {
FadeAndShortSlide clone = null;
@@ -208,4 +310,3 @@
return clone;
}
}
-
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java b/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
index c5a33cb..1206ea8 100644
--- a/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/TransitionHelperApi21.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.transition.ChangeTransform;
import android.transition.Transition;
+import android.transition.TransitionManager;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
@@ -86,10 +87,25 @@
return AnimationUtils.loadInterpolator(context, R.interpolator.fast_out_linear_in);
}
+ public static Object createChangeTransform() {
+ return new ChangeTransform();
+ }
+
public static Object createFadeAndShortSlide(int edge) {
return new FadeAndShortSlide(edge);
}
+ public static Object createFadeAndShortSlide(int edge, float distance) {
+ FadeAndShortSlide slide = new FadeAndShortSlide(edge);
+ slide.setDistance(distance);
+ return slide;
+ }
+
+ public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) {
+ Transition transition = (Transition) transitionObject;
+ TransitionManager.beginDelayedTransition(sceneRoot, transition);
+ }
+
public static void setTransitionGroup(ViewGroup viewGroup, boolean transitionGroup) {
viewGroup.setTransitionGroup(transitionGroup);
}
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java b/v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
index 2cc35452..46068da 100644
--- a/v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/TranslationAnimationCreator.java
@@ -9,54 +9,60 @@
import android.graphics.Path;
import android.transition.Transition;
import android.transition.TransitionValues;
+import android.transition.Transition.TransitionListener;
import android.view.View;
/**
- * This class is used by Slide and Explode to create an animator that goes from the start position
- * to the end position. It takes into account the canceled position so that it will not blink out or
- * shift suddenly when the transition is interrupted.
+ * This class is used by Slide and Explode to create an animator that goes from the start
+ * position to the end position. It takes into account the canceled position so that it
+ * will not blink out or shift suddenly when the transition is interrupted.
* @hide
*/
class TranslationAnimationCreator {
/**
- * Creates an animator that can be used for x and/or y translations. When interrupted, it sets a
- * tag to keep track of the position so that it may be continued from position.
+ * Creates an animator that can be used for x and/or y translations. When interrupted,
+ * it sets a tag to keep track of the position so that it may be continued from position.
*
* @param view The view being moved. This may be in the overlay for onDisappear.
* @param values The values containing the view in the view hierarchy.
* @param viewPosX The x screen coordinate of view
+ * @param viewPosY The y screen coordinate of view
* @param startX The start translation x of view
+ * @param startY The start translation y of view
* @param endX The end translation x of view
+ * @param endY The end translation y of view
* @param interpolator The interpolator to use with this animator.
- * @return An animator that moves from (startX, startY) to (endX, endY) unless there was a
- * previous interruption, in which case it moves from the current position to (endX,
- * endY).
+ * @return An animator that moves from (startX, startY) to (endX, endY) unless there was
+ * a previous interruption, in which case it moves from the current position to (endX, endY).
*/
- static Animator createAnimation(View view, TransitionValues values, int viewPosX, float startX,
- float endX, TimeInterpolator interpolator, Transition transition) {
+ static Animator createAnimation(View view, TransitionValues values, int viewPosX, int viewPosY,
+ float startX, float startY, float endX, float endY, TimeInterpolator interpolator,
+ Transition transition) {
float terminalX = view.getTranslationX();
- Integer startPosition = (Integer) values.view.getTag(R.id.transitionPosition);
+ float terminalY = view.getTranslationY();
+ int[] startPosition = (int[]) values.view.getTag(R.id.transitionPosition);
if (startPosition != null) {
- startX = startPosition - viewPosX + terminalX;
+ startX = startPosition[0] - viewPosX + terminalX;
+ startY = startPosition[1] - viewPosY + terminalY;
}
- // Initial position is at translation startX, startY, so position is offset by that
- // amount
+ // Initial position is at translation startX, startY, so position is offset by that amount
int startPosX = viewPosX + Math.round(startX - terminalX);
+ int startPosY = viewPosY + Math.round(startY - terminalY);
view.setTranslationX(startX);
- if (startX == endX) {
+ view.setTranslationY(startY);
+ if (startX == endX && startY == endY) {
return null;
}
- float y = view.getTranslationY();
Path path = new Path();
- path.moveTo(startX, y);
- path.lineTo(endX, y);
- ObjectAnimator anim =
- ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y, path);
+ path.moveTo(startX, startY);
+ path.lineTo(endX, endY);
+ ObjectAnimator anim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, View.TRANSLATION_Y,
+ path);
- TransitionPositionListener listener =
- new TransitionPositionListener(view, values.view, startPosX, terminalX);
+ TransitionPositionListener listener = new TransitionPositionListener(view, values.view,
+ startPosX, startPosY, terminalX, terminalY);
transition.addListener(listener);
anim.addListener(listener);
anim.addPauseListener(listener);
@@ -64,23 +70,28 @@
return anim;
}
- private static class TransitionPositionListener extends AnimatorListenerAdapter
- implements Transition.TransitionListener {
+ private static class TransitionPositionListener extends AnimatorListenerAdapter implements
+ TransitionListener {
private final View mViewInHierarchy;
private final View mMovingView;
private final int mStartX;
- private Integer mTransitionPosition;
+ private final int mStartY;
+ private int[] mTransitionPosition;
private float mPausedX;
+ private float mPausedY;
private final float mTerminalX;
+ private final float mTerminalY;
- private TransitionPositionListener(View movingView, View viewInHierarchy, int startX,
- float terminalX) {
+ private TransitionPositionListener(View movingView, View viewInHierarchy,
+ int startX, int startY, float terminalX, float terminalY) {
mMovingView = movingView;
mViewInHierarchy = viewInHierarchy;
mStartX = startX - Math.round(mMovingView.getTranslationX());
+ mStartY = startY - Math.round(mMovingView.getTranslationY());
mTerminalX = terminalX;
- mTransitionPosition = (Integer) mViewInHierarchy.getTag(R.id.transitionPosition);
+ mTerminalY = terminalY;
+ mTransitionPosition = (int[]) mViewInHierarchy.getTag(R.id.transitionPosition);
if (mTransitionPosition != null) {
mViewInHierarchy.setTag(R.id.transitionPosition, null);
}
@@ -88,41 +99,53 @@
@Override
public void onAnimationCancel(Animator animation) {
- mTransitionPosition = Math.round(mStartX + mMovingView.getTranslationX());
+ if (mTransitionPosition == null) {
+ mTransitionPosition = new int[2];
+ }
+ mTransitionPosition[0] = Math.round(mStartX + mMovingView.getTranslationX());
+ mTransitionPosition[1] = Math.round(mStartY + mMovingView.getTranslationY());
mViewInHierarchy.setTag(R.id.transitionPosition, mTransitionPosition);
}
@Override
- public void onAnimationEnd(Animator animator) {}
+ public void onAnimationEnd(Animator animator) {
+ }
@Override
public void onAnimationPause(Animator animator) {
mPausedX = mMovingView.getTranslationX();
+ mPausedY = mMovingView.getTranslationY();
mMovingView.setTranslationX(mTerminalX);
+ mMovingView.setTranslationY(mTerminalY);
}
@Override
public void onAnimationResume(Animator animator) {
mMovingView.setTranslationX(mPausedX);
+ mMovingView.setTranslationY(mPausedY);
}
@Override
- public void onTransitionStart(Transition transition) {}
+ public void onTransitionStart(Transition transition) {
+ }
@Override
public void onTransitionEnd(Transition transition) {
mMovingView.setTranslationX(mTerminalX);
+ mMovingView.setTranslationY(mTerminalY);
}
@Override
- public void onTransitionCancel(Transition transition) {}
+ public void onTransitionCancel(Transition transition) {
+ }
@Override
- public void onTransitionPause(Transition transition) {}
+ public void onTransitionPause(Transition transition) {
+ }
@Override
- public void onTransitionResume(Transition transition) {}
+ public void onTransitionResume(Transition transition) {
+ }
}
}
-
diff --git a/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java b/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java
new file mode 100644
index 0000000..19cf5d6
--- /dev/null
+++ b/v17/leanback/api23/android/support/v17/leanback/app/PermissionHelper23.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+class PermissionHelper23 {
+
+ public static void requestPermissions(android.app.Fragment fragment, String[] permissions,
+ int requestCode) {
+ fragment.requestPermissions(permissions, requestCode);
+ }
+
+}
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 401a5c4..eed7d6b 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -1,4 +1,4 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'leanback-v17'
@@ -9,7 +9,7 @@
android {
// WARNING: should be 17
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
defaultConfig {
minSdkVersion 17
diff --git a/v17/leanback/generatev4.py b/v17/leanback/generatev4.py
index 1b60b09e..fb23876 100755
--- a/v17/leanback/generatev4.py
+++ b/v17/leanback/generatev4.py
@@ -20,7 +20,7 @@
print "Generate v4 fragment related code for leanback"
cls = ['Background', 'Base', 'BaseRow', 'Browse', 'Details', 'Error', 'Headers',
- 'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded', 'GuidedStep']
+ 'PlaybackOverlay', 'Rows', 'Search', 'VerticalGrid', 'Branded', 'GuidedStep', 'Onboarding']
for w in cls:
print "copy {}Fragment to {}SupportFragment".format(w, w)
@@ -52,3 +52,4 @@
outfile.write(line)
file.close()
outfile.close()
+
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml
deleted file mode 100644
index df3aca2..0000000
--- a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_end.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:ordering="together" >
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="translationX"
- android:valueFrom="@dimen/lb_guidedstep_slide_end_distance"
- android:valueTo="0.0"
- android:valueType="floatType" />
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="alpha"
- android:valueFrom="0.0"
- android:valueTo="1.0"
- android:valueType="floatType" />
-
-</set>
\ No newline at end of file
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml
deleted file mode 100644
index 49ddc12..0000000
--- a/v17/leanback/res/animator-v21/lb_guidedstep_slide_in_from_start.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:ordering="together" >
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="translationX"
- android:valueFrom="@dimen/lb_guidedstep_slide_start_distance"
- android:valueTo="0.0"
- android:valueType="floatType" />
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="alpha"
- android:valueFrom="0.0"
- android:valueTo="1.0"
- android:valueType="floatType" />
-
-</set>
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml
deleted file mode 100644
index d481273..0000000
--- a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_end.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:ordering="together" >
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="translationX"
- android:valueFrom="0.0"
- android:valueTo="@dimen/lb_guidedstep_slide_end_distance"
- android:valueType="floatType" />
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.0"
- android:valueType="floatType" />
-
-</set>
diff --git a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml b/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml
deleted file mode 100644
index b172e86..0000000
--- a/v17/leanback/res/animator-v21/lb_guidedstep_slide_out_to_start.xml
+++ /dev/null
@@ -1,36 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:ordering="together" >
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="translationX"
- android:valueFrom="0.0"
- android:valueTo="@dimen/lb_guidedstep_slide_start_distance"
- android:valueType="floatType" />
-
- <objectAnimator
- android:duration="@android:integer/config_longAnimTime"
- android:interpolator="@android:interpolator/fast_out_slow_in"
- android:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.0"
- android:valueType="floatType" />
-
-</set>
diff --git a/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml b/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml
deleted file mode 100644
index e5dafb0..0000000
--- a/v17/leanback/res/animator/lb_guidedactions_selector_hide.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?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.
--->
-<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
- android:duration="@integer/lb_guidedactions_animation_duration"
- android:propertyName="alpha"
- android:valueTo="0.0"
- android:interpolator="@animator/lb_decelerator_2"
- android:valueType="floatType" />
diff --git a/v17/leanback/res/animator/lb_guidedactions_selector_show.xml b/v17/leanback/res/animator/lb_guidedactions_selector_show.xml
deleted file mode 100644
index fcfd9fa..0000000
--- a/v17/leanback/res/animator/lb_guidedactions_selector_show.xml
+++ /dev/null
@@ -1,32 +0,0 @@
-<?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.
--->
-<set xmlns:android="http://schemas.android.com/apk/res/android"
- android:ordering="together">
-
- <objectAnimator
- android:duration="@integer/lb_guidedactions_animation_duration"
- android:propertyName="alpha"
- android:valueTo="1.0"
- android:interpolator="@animator/lb_decelerator_2"
- android:valueType="floatType" />
-
- <objectAnimator
- android:duration="@integer/lb_guidedactions_animation_duration"
- android:propertyName="scaleY"
- android:interpolator="@animator/lb_decelerator_2"
- android:valueType="floatType" />
-</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_description_enter.xml b/v17/leanback/res/animator/lb_onboarding_description_enter.xml
new file mode 100644
index 0000000..5f26cdd
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_description_enter.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="533"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="60dp"
+ android:valueTo="0dp"
+ android:duration="533"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_enter.xml b/v17/leanback/res/animator/lb_onboarding_logo_enter.xml
new file mode 100644
index 0000000..76a4609a
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_logo_enter.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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="333"
+ android:interpolator="@android:interpolator/linear_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_logo_exit.xml b/v17/leanback/res/animator/lb_onboarding_logo_exit.xml
new file mode 100644
index 0000000..40b618e
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_logo_exit.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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="666"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml b/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.xml
new file mode 100644
index 0000000..e9fc46e
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_page_indicator_enter.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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="500"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml b/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_in.xml
new file mode 100644
index 0000000..f21fc23
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_in.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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml b/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_out.xml
new file mode 100644
index 0000000..4c69c5d
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_page_indicator_fade_out.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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_start_button_fade_in.xml b/v17/leanback/res/animator/lb_onboarding_start_button_fade_in.xml
new file mode 100644
index 0000000..c6bbedb
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_start_button_fade_in.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="@dimen/lb_onboarding_start_button_translation_offset"
+ android:valueTo="0dp"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_start_button_fade_out.xml b/v17/leanback/res/animator/lb_onboarding_start_button_fade_out.xml
new file mode 100644
index 0000000..d22164a
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_start_button_fade_out.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="1.0"
+ android:valueTo="0.0"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+ <objectAnimator
+ android:propertyName="translationX"
+ android:valueFrom="0dp"
+ android:valueTo="@dimen/lb_onboarding_start_button_translation_offset"
+ android:duration="417"
+ android:interpolator="@android:anim/decelerate_interpolator" />
+</set>
diff --git a/v17/leanback/res/animator/lb_onboarding_title_enter.xml b/v17/leanback/res/animator/lb_onboarding_title_enter.xml
new file mode 100644
index 0000000..5f26cdd
--- /dev/null
+++ b/v17/leanback/res/animator/lb_onboarding_title_enter.xml
@@ -0,0 +1,31 @@
+<?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.
+ -->
+
+<set xmlns:android="http://schemas.android.com/apk/res/android">
+ <objectAnimator
+ android:propertyName="alpha"
+ android:valueFrom="0.0"
+ android:valueTo="1.0"
+ android:duration="533"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+ <objectAnimator
+ android:propertyName="translationY"
+ android:valueFrom="60dp"
+ android:valueTo="0dp"
+ android:duration="533"
+ android:interpolator="@android:interpolator/fast_out_slow_in" />
+</set>
diff --git a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
index 3058076..071ec4d 100644
--- a/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-hdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
index b4c0abe..332abad 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png
index f06c02d..733cb3a 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
index 283b4d8..38d221c 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
index f45f1fd..d3d128e01 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
index 25617f5..53d3b00 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
index 2eaecbd..fad401e 100644
--- a/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-hdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
index 823c69c8..3726a55 100644
--- a/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-mdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
index 8c2c3b9..596be5e 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png
index 149e214..b191f52 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
index b0bed22..1bd71b8 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_sad_cloud.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
index 75eb962..60b63ad 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
index 1682a46..eae8331 100644
--- a/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-mdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
index a63d6b1..6b63574 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
index 0080b8e6..86187e8 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_card_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
index d0ca2e2..d6d6f48 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
index d4616cf..fa09957 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_cc.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
index 0dfefcc..7cbce5b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_forward.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
index 09e8a3b..5ef4976 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_fast_rewind.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png b/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
index 6a65ccf..f7a2ba2 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_guidedactions_item_chevron.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
index 5aefe6d..dc2b818 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_hq.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
index 8ef325b..2a665cf 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
index e10f5d3..ca1ec77 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_loop_one.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
index 662e03c..a9b5e2d 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_more.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png b/v17/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png
new file mode 100644
index 0000000..04578a7
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_nav_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
index e55f78d..d3af0a0 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pause.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
new file mode 100644
index 0000000..1a59e47
--- /dev/null
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_pip.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
index fbd792b..270187b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_play.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
index c5a0294..4c9f629 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_replay.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
index 0785c8b..1abb1bb 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
index 7bbfa23..1733d26e 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
index 5aa850b..a77ebee 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_shuffle.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
index 7349a07..fa29b30 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_skip_next.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
index 586c4bd..1fdc0d8 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_stop.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
index 6e9d472..db5958b 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
index 6000fa3..3c36132 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_down_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
index 54b9ad4..b5175c3 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
index 7a9706e..49a4aa9 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_ic_thumb_up_outline.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
index 5204234..baa6b3d 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
index 65f522c..b1644ed 100644
--- a/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
+++ b/v17/leanback/res/drawable-xhdpi/lb_text_dot_two_small.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
index 1bef6f2..845435f 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_action_bg_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
index 42b7c77..c95d7d7 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_actions_right_arrow.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
index b45deb6..3497133 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_in_app_search.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
index a36a912..d093e0b 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
index 8c251e1..640ed3f 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_ic_search_mic_out.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
index fef8b07..d16d2d1 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_bg.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
index ceb6a40..3be90bc 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_focused.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
index 18d2fcb..f71df89 100644
--- a/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
+++ b/v17/leanback/res/drawable-xxhdpi/lb_in_app_search_shadow_normal.9.png
Binary files differ
diff --git a/v17/leanback/res/drawable/lb_headers_right_fading.xml b/v17/leanback/res/drawable/lb_headers_right_fading.xml
index b20c4e8..8aea204 100644
--- a/v17/leanback/res/drawable/lb_headers_right_fading.xml
+++ b/v17/leanback/res/drawable/lb_headers_right_fading.xml
@@ -20,6 +20,6 @@
<gradient
android:angle="0"
android:startColor="#00000000"
- android:endColor="?attr/defaultBrandColor"
+ android:endColor="#00000000"
/>
</shape>
diff --git a/v17/leanback/res/drawable/lb_onboarding_start_button_background.xml b/v17/leanback/res/drawable/lb_onboarding_start_button_background.xml
new file mode 100644
index 0000000..57c3138
--- /dev/null
+++ b/v17/leanback/res/drawable/lb_onboarding_start_button_background.xml
@@ -0,0 +1,24 @@
+<?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.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item>
+ <shape>
+ <solid android:color="#EEEEEE"/>
+ <corners android:radius="2dp" />
+ </shape>
+ </item>
+</selector>
diff --git a/v17/leanback/res/layout/lb_browse_fragment.xml b/v17/leanback/res/layout/lb_browse_fragment.xml
index bc8ffc3..dc38f27 100644
--- a/v17/leanback/res/layout/lb_browse_fragment.xml
+++ b/v17/leanback/res/layout/lb_browse_fragment.xml
@@ -27,12 +27,19 @@
android:descendantFocusability="afterDescendants"
android:id="@+id/browse_frame"
android:layout_width="match_parent"
- android:layout_height="match_parent"
- >
+ android:layout_height="match_parent">
+
<android.support.v17.leanback.widget.BrowseRowsFrameLayout
android:id="@+id/browse_container_dock"
android:layout_width="match_parent"
- android:layout_height="match_parent" />
+ android:layout_height="match_parent">
+
+ <android.support.v17.leanback.widget.ScaleFrameLayout
+ android:id="@+id/scale_frame"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ </android.support.v17.leanback.widget.BrowseRowsFrameLayout>
<!-- Padding needed for shadow -->
<FrameLayout
@@ -41,6 +48,5 @@
android:layout_height="match_parent"
android:clipToPadding="false"
android:paddingEnd="50dp" />
- <include layout="@layout/lb_browse_title" />
</android.support.v17.leanback.widget.BrowseFrameLayout>
</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_divider.xml b/v17/leanback/res/layout/lb_divider.xml
new file mode 100644
index 0000000..389b3ab
--- /dev/null
+++ b/v17/leanback/res/layout/lb_divider.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<View
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/row_header"
+ android:layout_width="match_parent"
+ android:layout_marginEnd="12dp"
+ android:layout_height="1dp"
+ android:focusable="false"
+ android:focusableInTouchMode="false"
+ android:background="?android:attr/listDivider"
+ />
diff --git a/v17/leanback/res/layout/lb_error_fragment.xml b/v17/leanback/res/layout/lb_error_fragment.xml
index 2815560..af6b957 100644
--- a/v17/leanback/res/layout/lb_error_fragment.xml
+++ b/v17/leanback/res/layout/lb_error_fragment.xml
@@ -59,6 +59,4 @@
style="?android:attr/buttonStyle"/>
</LinearLayout>
- <include layout="@layout/lb_browse_title" />
-
</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_guidance.xml b/v17/leanback/res/layout/lb_guidance.xml
index 28c0220..4d051b8 100644
--- a/v17/leanback/res/layout/lb_guidance.xml
+++ b/v17/leanback/res/layout/lb_guidance.xml
@@ -17,28 +17,29 @@
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="match_parent">
- <RelativeLayout
+ <android.support.v17.leanback.widget.GuidanceStylingRelativeLayout
+ android:id="@+id/guidance_container"
style="?attr/guidanceContainerStyle" >
<ImageView
android:id="@+id/guidance_icon"
style="?attr/guidanceIconStyle"
- tools:ignore="ContentDescription" />
-
- <TextView
- android:id="@+id/guidance_title"
- style="?attr/guidanceTitleStyle" />
+ tools:ignore="ContentDescription"/>
<TextView
android:id="@+id/guidance_breadcrumb"
- style="?attr/guidanceBreadcrumbStyle" />
+ style="?attr/guidanceBreadcrumbStyle"/>
+
+ <TextView
+ android:id="@+id/guidance_title"
+ style="?attr/guidanceTitleStyle"/>
<TextView
android:id="@+id/guidance_description"
- style="?attr/guidanceDescriptionStyle" />
+ style="?attr/guidanceDescriptionStyle"/>
- </RelativeLayout>
+ </android.support.v17.leanback.widget.GuidanceStylingRelativeLayout>
</FrameLayout>
diff --git a/v17/leanback/res/layout/lb_guidedactions.xml b/v17/leanback/res/layout/lb_guidedactions.xml
index f2926d3..2ba9075 100644
--- a/v17/leanback/res/layout/lb_guidedactions.xml
+++ b/v17/leanback/res/layout/lb_guidedactions.xml
@@ -41,11 +41,13 @@
android:transitionGroup="true"
android:id="@+id/guidedactions_list"
style="?attr/guidedActionsListStyle" />
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:transitionGroup="true"
+ android:id="@+id/guidedactions_sub_list"
+ style="?attr/guidedSubActionsListStyle"
+ android:visibility="invisible"
+ android:background="?attr/guidedActionsBackgroundDark" />
- <android.support.v17.leanback.widget.NonOverlappingView
- android:id="@+id/guidedactions_selector"
- android:transitionName="guidedactions_selector"
- style="?attr/guidedActionsSelectorStyle" />
</android.support.v17.leanback.widget.NonOverlappingFrameLayout>
</RelativeLayout>
diff --git a/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml b/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml
new file mode 100644
index 0000000..a947c25
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedactions_datepicker_item.xml
@@ -0,0 +1,40 @@
+<?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.
+-->
+<!-- Layout for an action item displayed in the 2 pane actions fragment. -->
+<android.support.v17.leanback.widget.GuidedActionItemContainer
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ style="?attr/guidedActionItemContainerStyle" >
+
+ <ImageView
+ android:id="@+id/guidedactions_item_icon"
+ style="?attr/guidedActionItemIconStyle"
+ tools:ignore="ContentDescription" />
+
+ <TextView
+ android:id="@+id/guidedactions_item_title"
+ android:layout_width="wrap_content"
+ android:layout_gravity="center_vertical"
+ style="?attr/guidedActionItemTitleStyle" />
+
+ <android.support.v17.leanback.widget.picker.DatePicker
+ android:id="@+id/guidedactions_activator_item"
+ android:importantForAccessibility="yes"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+
+</android.support.v17.leanback.widget.GuidedActionItemContainer>
diff --git a/v17/leanback/res/layout/lb_guidedactions_item.xml b/v17/leanback/res/layout/lb_guidedactions_item.xml
index 831c355..a498b26 100644
--- a/v17/leanback/res/layout/lb_guidedactions_item.xml
+++ b/v17/leanback/res/layout/lb_guidedactions_item.xml
@@ -15,7 +15,7 @@
limitations under the License.
-->
<!-- Layout for an action item displayed in the 2 pane actions fragment. -->
-<android.support.v17.leanback.widget.NonOverlappingLinearLayout
+<android.support.v17.leanback.widget.GuidedActionItemContainer
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
style="?attr/guidedActionItemContainerStyle" >
@@ -49,4 +49,4 @@
style="?attr/guidedActionItemChevronStyle"
tools:ignore="ContentDescription" />
-</android.support.v17.leanback.widget.NonOverlappingLinearLayout>
+</android.support.v17.leanback.widget.GuidedActionItemContainer>
diff --git a/v17/leanback/res/layout/lb_guidedbuttonactions.xml b/v17/leanback/res/layout/lb_guidedbuttonactions.xml
new file mode 100644
index 0000000..7631c91
--- /dev/null
+++ b/v17/leanback/res/layout/lb_guidedbuttonactions.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.
+-->
+<!-- Layout for the settings list fragment -->
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/guidedactions_root2"
+ android:transitionName="guidedactions_root2"
+ android:transitionGroup="false"
+ android:layout_width="0dp"
+ android:layout_weight="1"
+ android:layout_height="match_parent">
+
+ <android.support.v17.leanback.widget.NonOverlappingView
+ android:id="@+id/guidedactions_list_background2"
+ android:transitionName="guidedactions_list_background2"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/guidedActionsBackgroundDark" />
+
+ <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+ android:id="@+id/guidedactions_content2"
+ android:transitionName="guidedactions_content2"
+ android:transitionGroup="false"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.support.v17.leanback.widget.VerticalGridView
+ android:transitionGroup="true"
+ android:id="@+id/guidedactions_list2"
+ style="?attr/guidedButtonActionsListStyle" />
+
+ </android.support.v17.leanback.widget.NonOverlappingFrameLayout>
+
+</RelativeLayout>
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
index a41d47d..adf9d4f 100644
--- a/v17/leanback/res/layout/lb_guidedstep_fragment.xml
+++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
@@ -15,57 +15,71 @@
limitations under the License.
-->
<!-- Layout for the frame of a 2 pane actions fragment. -->
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v17.leanback.app.GuidedStepRootLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/guidedstep_root"
- android:orientation="horizontal"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
- <LinearLayout
- android:id="@+id/content_frame"
- android:orientation="horizontal"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+ android:gravity="bottom"
+ android:weightSum="2">
+
+ <FrameLayout
+ android:id="@+id/guidedstep_background_view_root"
android:layout_width="match_parent"
- android:layout_height="match_parent" >
+ android:layout_height="0dp"
+ android:layout_weight="?attr/guidedStepHeightWeight">
- <android.support.v17.leanback.widget.NonOverlappingFrameLayout
- android:id="@+id/content_fragment"
- android:layout_toStartOf="@+id/action_fragment"
- android:layout_width="0dp"
- android:layout_weight="1"
- android:layout_height="match_parent"
- android:layout_alignParentStart="true" />
-
- <android.support.v17.leanback.widget.NonOverlappingFrameLayout
- android:id="@+id/action_fragment_root"
- android:transitionName="action_fragment_root"
- android:transitionGroup="false"
+ <LinearLayout
+ android:id="@+id/content_frame"
android:orientation="horizontal"
- android:clipToPadding="false"
- android:clipChildren="false"
- android:paddingStart="@dimen/lb_guidedactions_section_shadow_width"
- android:layout_width="0dp"
- android:layout_weight="?attr/guidedActionContentWidthWeight"
- android:layout_height="match_parent"
- android:layout_alignParentEnd="true">
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
- <android.support.v17.leanback.widget.NonOverlappingView
- android:id="@+id/action_fragment_background"
- android:transitionName="action_fragment_background"
- android:orientation="horizontal"
- android:outlineProvider="paddedBounds"
- android:layout_width="match_parent"
+ <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+ android:id="@+id/content_fragment"
+ android:layout_width="0dp"
+ android:layout_weight="1"
android:layout_height="match_parent"
- android:background="?attr/guidedActionsBackground"
- android:elevation="?attr/guidedActionsElevation" />
+ android:layout_alignParentStart="true" />
- <android.support.v17.leanback.widget.NonOverlappingLinearLayout
- android:id="@+id/action_fragment"
- android:transitionName="action_fragment"
+ <android.support.v17.leanback.widget.NonOverlappingFrameLayout
+ android:id="@+id/action_fragment_root"
+ android:transitionName="action_fragment_root"
android:transitionGroup="false"
android:orientation="horizontal"
- android:layout_width="match_parent"
+ android:clipToPadding="false"
+ android:clipChildren="false"
+ android:paddingStart="@dimen/lb_guidedactions_section_shadow_width"
+ android:layout_width="0dp"
+ android:layout_weight="?attr/guidedActionContentWidthWeight"
android:layout_height="match_parent"
- android:elevation="?attr/guidedActionsElevation" />
- </android.support.v17.leanback.widget.NonOverlappingFrameLayout>
+ android:layout_alignParentEnd="true">
- </LinearLayout>
-</FrameLayout>
\ No newline at end of file
+ <android.support.v17.leanback.widget.NonOverlappingView
+ android:id="@+id/action_fragment_background"
+ android:transitionName="action_fragment_background"
+ android:orientation="horizontal"
+ android:outlineProvider="paddedBounds"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:background="?attr/guidedActionsBackground"
+ android:elevation="?attr/guidedActionsElevation" />
+
+ <android.support.v17.leanback.widget.NonOverlappingLinearLayout
+ android:id="@+id/action_fragment"
+ android:focusable="true"
+ android:descendantFocusability="afterDescendants"
+ android:transitionName="action_fragment"
+ android:transitionGroup="false"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:elevation="?attr/guidedActionsElevation" />
+ </android.support.v17.leanback.widget.NonOverlappingFrameLayout>
+
+ </LinearLayout>
+
+ </FrameLayout>
+
+</android.support.v17.leanback.app.GuidedStepRootLayout>
diff --git a/v17/leanback/res/layout/lb_header.xml b/v17/leanback/res/layout/lb_header.xml
index 7437cf3..d86245c 100644
--- a/v17/leanback/res/layout/lb_header.xml
+++ b/v17/leanback/res/layout/lb_header.xml
@@ -20,5 +20,6 @@
android:id="@+id/row_header"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
+ android:focusable="true"
style="?headerStyle"
/>
diff --git a/v17/leanback/res/layout/lb_image_card_view.xml b/v17/leanback/res/layout/lb_image_card_view.xml
index 1bc23f8..5b37dcf 100644
--- a/v17/leanback/res/layout/lb_image_card_view.xml
+++ b/v17/leanback/res/layout/lb_image_card_view.xml
@@ -20,11 +20,11 @@
<ImageView
android:id="@+id/main_image"
- style="?attr/lbImageCardViewImageStyle" />
+ style="?attr/imageCardViewImageStyle" />
<android.support.v17.leanback.widget.NonOverlappingRelativeLayout
android:id="@+id/info_field"
- style="?attr/lbImageCardViewInfoAreaStyle">
+ style="?attr/imageCardViewInfoAreaStyle">
</android.support.v17.leanback.widget.NonOverlappingRelativeLayout>
</merge>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml b/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
index 35d2da6..3683e64 100644
--- a/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_badge_left.xml
@@ -20,4 +20,4 @@
android:layout_alignBottom="@+id/content_text"
android:layout_alignParentStart="true"
android:layout_marginEnd="@dimen/lb_basic_card_info_badge_margin"
- style="?attr/lbImageCardViewBadgeStyle" />
\ No newline at end of file
+ style="?attr/imageCardViewBadgeStyle" />
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml b/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
index 02dd917..481e0d1 100644
--- a/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_badge_right.xml
@@ -20,4 +20,4 @@
android:layout_alignBottom="@+id/content_text"
android:layout_alignParentEnd="true"
android:layout_marginStart="@dimen/lb_basic_card_info_badge_margin"
- style="?attr/lbImageCardViewBadgeStyle" />
\ No newline at end of file
+ style="?attr/imageCardViewBadgeStyle" />
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_content.xml b/v17/leanback/res/layout/lb_image_card_view_themed_content.xml
index 5592371..08332f4 100644
--- a/v17/leanback/res/layout/lb_image_card_view_themed_content.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_content.xml
@@ -17,4 +17,4 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/content_text"
- style="?attr/lbImageCardViewContentStyle" />
+ style="?attr/imageCardViewContentStyle" />
diff --git a/v17/leanback/res/layout/lb_image_card_view_themed_title.xml b/v17/leanback/res/layout/lb_image_card_view_themed_title.xml
index 67e2493..8f2ca82 100644
--- a/v17/leanback/res/layout/lb_image_card_view_themed_title.xml
+++ b/v17/leanback/res/layout/lb_image_card_view_themed_title.xml
@@ -17,4 +17,4 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/title_text"
- style="?attr/lbImageCardViewTitleStyle" />
+ style="?attr/imageCardViewTitleStyle" />
diff --git a/v17/leanback/res/layout/lb_media_list_header.xml b/v17/leanback/res/layout/lb_media_list_header.xml
new file mode 100644
index 0000000..f6fcd1c
--- /dev/null
+++ b/v17/leanback/res/layout/lb_media_list_header.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ style="?attr/playbackMediaListHeaderStyle">
+
+ <TextView
+ android:id="@+id/mediaListHeader"
+ style="?attr/playbackMediaListHeaderTitleStyle"
+ />
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_onboarding_fragment.xml b/v17/leanback/res/layout/lb_onboarding_fragment.xml
new file mode 100644
index 0000000..04bd0ea
--- /dev/null
+++ b/v17/leanback/res/layout/lb_onboarding_fragment.xml
@@ -0,0 +1,76 @@
+<?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.
+ -->
+
+<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/onboarding_fragment_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipChildren="false"
+ android:clipToPadding="false">
+
+ <FrameLayout
+ android:id="@+id/background_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <LinearLayout
+ android:id="@+id/page_container"
+ style="?attr/onboardingHeaderStyle"
+ android:visibility="gone">
+ <TextView
+ android:id="@+id/title"
+ style="?attr/onboardingTitleStyle"/>
+ <TextView
+ android:id="@+id/description"
+ style="?attr/onboardingDescriptionStyle"/>
+ </LinearLayout>
+
+ <FrameLayout
+ android:id="@+id/content_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_above="@+id/navigator_container"
+ android:layout_below="@id/page_container"
+ android:layout_centerHorizontal="true"
+ android:visibility="gone" />
+
+ <FrameLayout
+ android:id="@id/navigator_container"
+ style="?attr/onboardingNavigatorContainerStyle">
+ <android.support.v17.leanback.widget.PagingIndicator
+ android:id="@+id/page_indicator"
+ style="?attr/onboardingPageIndicatorStyle"
+ android:visibility="gone" />
+
+ <Button
+ android:id="@+id/button_start"
+ style="?attr/onboardingStartButtonStyle"
+ android:visibility="gone"/>
+ </FrameLayout>
+
+ <FrameLayout
+ android:id="@+id/foreground_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:visibility="gone" />
+
+ <ImageView
+ android:id="@+id/logo"
+ style="?attr/onboardingLogoStyle"/>
+
+</RelativeLayout>
diff --git a/v17/leanback/res/layout/lb_picker.xml b/v17/leanback/res/layout/lb_picker.xml
new file mode 100644
index 0000000..b11c051
--- /dev/null
+++ b/v17/leanback/res/layout/lb_picker.xml
@@ -0,0 +1,24 @@
+<?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"
+ android:id="@+id/picker"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent"
+ android:layout_gravity="center"
+ android:gravity="center_vertical"
+ android:orientation="horizontal" />
diff --git a/v17/leanback/res/layout/lb_picker_column.xml b/v17/leanback/res/layout/lb_picker_column.xml
new file mode 100644
index 0000000..1f218a6
--- /dev/null
+++ b/v17/leanback/res/layout/lb_picker_column.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.
+-->
+<android.support.v17.leanback.widget.VerticalGridView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/column"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/picker_item_height"
+ android:importantForAccessibility="no"
+ lb:columnWidth="wrap_content"
+ android:clipToPadding="false"
+ android:paddingStart="@dimen/picker_column_horizontal_padding"
+ android:paddingEnd="@dimen/picker_column_horizontal_padding" />
diff --git a/v17/leanback/res/layout/lb_picker_item.xml b/v17/leanback/res/layout/lb_picker_item.xml
new file mode 100644
index 0000000..3523e78
--- /dev/null
+++ b/v17/leanback/res/layout/lb_picker_item.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/list_item"
+ android:focusable="true"
+ android:layout_width="wrap_content"
+ android:layout_height="@dimen/picker_item_height"
+ android:layout_centerHorizontal="true"
+ android:gravity="center" />
diff --git a/v17/leanback/res/layout/lb_picker_separator.xml b/v17/leanback/res/layout/lb_picker_separator.xml
new file mode 100644
index 0000000..4c239a3
--- /dev/null
+++ b/v17/leanback/res/layout/lb_picker_separator.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+<TextView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/separator"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ android:paddingStart="@dimen/picker_separator_horizontal_padding"
+ android:paddingEnd="@dimen/picker_separator_horizontal_padding" />
diff --git a/v17/leanback/res/layout/lb_playback_controls_row.xml b/v17/leanback/res/layout/lb_playback_controls_row.xml
index a58840d..e3893b3 100644
--- a/v17/leanback/res/layout/lb_playback_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_controls_row.xml
@@ -18,14 +18,15 @@
<!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
views with no padding of their own. Also to allow for negative margin on description. -->
-<android.support.v17.leanback.widget.PlaybackControlsRowView xmlns:android="http://schemas.android.com/apk/res/android"
+<android.support.v17.leanback.widget.PlaybackControlsRowView
+ xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:clipChildren="false"
android:clipToPadding="false"
- android:paddingStart="@dimen/lb_playback_controls_margin_start"
- android:paddingEnd="@dimen/lb_playback_controls_margin_end" >
+ android:paddingStart="?attr/playbackPaddingStart"
+ android:paddingEnd="?attr/playbackPaddingEnd" >
<LinearLayout
android:id="@+id/controls_card"
@@ -88,4 +89,4 @@
android:layout_width="match_parent"
android:layout_height="@dimen/lb_playback_controls_margin_bottom" />
-</android.support.v17.leanback.widget.PlaybackControlsRowView>
\ No newline at end of file
+</android.support.v17.leanback.widget.PlaybackControlsRowView>
diff --git a/v17/leanback/res/layout/lb_row_header.xml b/v17/leanback/res/layout/lb_row_header.xml
index 69fac46..67da907 100644
--- a/v17/leanback/res/layout/lb_row_header.xml
+++ b/v17/leanback/res/layout/lb_row_header.xml
@@ -18,6 +18,7 @@
<android.support.v17.leanback.widget.RowHeaderView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/row_header"
+ android:importantForAccessibility="no"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="?rowHeaderStyle"
diff --git a/v17/leanback/res/layout/lb_row_media_item.xml b/v17/leanback/res/layout/lb_row_media_item.xml
new file mode 100644
index 0000000..72efb09
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_media_item.xml
@@ -0,0 +1,72 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <ImageView
+ android:id="@+id/mediaRowSeparator"
+ style="?attr/playbackMediaItemSeparatorStyle"/>
+
+ <FrameLayout
+ android:id="@+id/background"
+ style="?attr/playbackMediaItemRowStyle">
+
+ <android.support.v17.leanback.widget.MediaRowFocusView
+ android:id="@+id/mediaRowSelector"
+ android:layout_width="0dp"
+ android:layout_height="match_parent"
+ android:alpha="0" />
+
+ <LinearLayout
+ android:id="@+id/mediaItemRow"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+
+ <LinearLayout
+ android:id="@+id/mediaItemDetails"
+ style="?attr/playbackMediaItemDetailsStyle"
+ >
+
+ <TextView
+ android:id="@+id/mediaItemNumber"
+ style="?attr/playbackMediaItemNumberStyle"/>
+
+
+ <TextView
+ android:id="@+id/mediaItemName"
+ style="?attr/playbackMediaItemNameStyle"/>
+
+ <TextView
+ android:id="@+id/mediaItemDuration"
+ style="?attr/playbackMediaItemDurationStyle"/>
+ </LinearLayout>
+
+ <LinearLayout
+ android:id="@+id/mediaItemActionsContainer"
+ android:layout_width="wrap_content"
+ android:layout_height="match_parent"
+ android:paddingStart="16dip" />
+
+ </LinearLayout>
+
+ </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_media_item_action.xml b/v17/leanback/res/layout/lb_row_media_item_action.xml
new file mode 100644
index 0000000..29bb27e
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_media_item_action.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ImageView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/actionIcon"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_vertical"
+ android:paddingStart="16dp"
+ android:paddingEnd="16dp"
+ android:focusable="true"
+ />
diff --git a/v17/leanback/res/layout/lb_rows_fragment.xml b/v17/leanback/res/layout/lb_rows_fragment.xml
index 0a5112d..fc5e706 100644
--- a/v17/leanback/res/layout/lb_rows_fragment.xml
+++ b/v17/leanback/res/layout/lb_rows_fragment.xml
@@ -14,17 +14,10 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
-<android.support.v17.leanback.widget.ScaleFrameLayout
+<android.support.v17.leanback.widget.VerticalGridView
xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/scale_frame"
+ android:id="@+id/container_list"
android:layout_width="match_parent"
- android:layout_height="match_parent">
-
- <android.support.v17.leanback.widget.VerticalGridView
- android:id="@+id/container_list"
- android:layout_width="match_parent"
- android:layout_height="match_parent"
- android:clipToPadding="false"
- style="?attr/rowsVerticalGridStyle" />
-
-</android.support.v17.leanback.widget.ScaleFrameLayout>
\ No newline at end of file
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ style="?attr/rowsVerticalGridStyle" />
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_search_bar.xml b/v17/leanback/res/layout/lb_search_bar.xml
index 6123ea7..37cdfb3 100644
--- a/v17/leanback/res/layout/lb_search_bar.xml
+++ b/v17/leanback/res/layout/lb_search_bar.xml
@@ -67,6 +67,7 @@
android:imeOptions="normal|flagNoExtractUi|actionSearch"
android:inputType="text|textAutoComplete"
android:singleLine="true"
+ android:textAlignment="viewStart"
android:textColor="@color/lb_search_bar_text"
android:textColorHint="@color/lb_search_bar_hint"
android:textCursorDrawable="@null"
diff --git a/v17/leanback/res/layout/lb_section_header.xml b/v17/leanback/res/layout/lb_section_header.xml
new file mode 100644
index 0000000..78bd6f8
--- /dev/null
+++ b/v17/leanback/res/layout/lb_section_header.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.v17.leanback.widget.RowHeaderView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/row_header"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:focusable="false"
+ style="?sectionHeaderStyle"
+ />
diff --git a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
index 118b0a9..aac498d 100644
--- a/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
+++ b/v17/leanback/res/layout/lb_vertical_grid_fragment.xml
@@ -32,7 +32,5 @@
android:layout_width="match_parent"
android:layout_height="match_parent" />
- <include layout="@layout/lb_browse_title" />
-
</android.support.v17.leanback.widget.BrowseFrameLayout>
</FrameLayout>
diff --git a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
index 5ee74ee..d92890d 100644
--- a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
+++ b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter.xml
@@ -43,11 +43,10 @@
<target android:targetId="@id/guidedactions_list_background" />
<target android:targetId="@id/guidedactions_content" />
<target android:targetId="@id/guidedactions_list" />
- <target android:targetId="@id/guidedactions_selector" />
+ <target android:targetId="@id/guidedactions_sub_list" />
<target android:targetId="@id/guidedactions_list_background2" />
<target android:targetId="@id/guidedactions_content2" />
<target android:targetId="@id/guidedactions_list2" />
- <target android:targetId="@id/guidedactions_selector2" />
</targets>
</slide>
</transitionSet>
\ No newline at end of file
diff --git a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
new file mode 100644
index 0000000..ac00e9c
--- /dev/null
+++ b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
@@ -0,0 +1,23 @@
+<?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.
+-->
+
+<transition class="android.support.v17.leanback.transition.FadeAndShortSlide"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:interpolator="@android:interpolator/fast_out_linear_in"
+ android:duration="@integer/lb_guidedstep_activity_background_fade_duration_ms"
+ app:lb_slideEdge="bottom" />
diff --git a/v17/leanback/res/transition-v21/lb_title_out.xml b/v17/leanback/res/transition-v21/lb_title_out.xml
index 50fb67e..69735ab 100644
--- a/v17/leanback/res/transition-v21/lb_title_out.xml
+++ b/v17/leanback/res/transition-v21/lb_title_out.xml
@@ -18,7 +18,7 @@
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:lb="http://schemas.android.com/apk/res-auto"
class="android.support.v17.leanback.transition.SlideKitkat"
- android:interpolator="@animator/lb_decelerator_4"
+ android:interpolator="@animator/lb_decelerator_2"
lb:lb_slideEdge="top" >
<targets>
<target android:targetId="@id/browse_title_group" />
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
index 0310403..2f2ca18 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigasiekieslys"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Soekhandeling"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Soek"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Praat om te soek"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiveer hoë gehalte"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktiveer onderskrifte"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiveer onderskrifte"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Voer prent in prentmodus in"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooi"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Gaan voort"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BEGIN HIER"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Volgende"</string>
</resources>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index 77d2993..c4b461a 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"የዳሰሳ ምናሌ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"እርምጃ ይፈልጉ"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ይፈልጉ"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ለመፈለግ ይናገሩ"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ከፍተኛ ጥራትን አሰናክል"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ዝግ የምስል ስር ጽሑፍ አጻጻፍን አንቃ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ዝግ የምስል ስር ጽሑፍ አጻጻፍን አሰናክል"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ስዕሉን በስዕል ሁነታ ውስጥ ያክሉ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ጨርስ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ቀጥል"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"፦"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ይጀምሩ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ቀጣይ"</string>
</resources>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index c52be7f..cda830c 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"قائمة التنقل"</string>
<string name="orb_search_action" msgid="5651268540267663887">"إجراء البحث"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"بحث"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"التحدث للبحث"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"تعطيل الجودة العالية"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"تمكين الترجمة المصاحبة"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"تعطيل الترجمة المصاحبة"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"إدخال صورة في وضع الصورة"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"إنهاء"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"متابعة"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"البدء"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"التالية"</string>
</resources>
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az-rAZ/strings.xml
index cb558a0..f9f0277 100644
--- a/v17/leanback/res/values-az-rAZ/strings.xml
+++ b/v17/leanback/res/values-az-rAZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Naviqasiya menyusu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Axtarış Fəaliyyəti"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Axtarış"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Axtarış üçün danışın"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksək keyfiyyəti deaktiv edin"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Qapalı çəkilişi aktiv edin"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Qapalı çəkilişi deaktiv edin"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Şəkil içində Şəkil Rejiminə daxil olun"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Bitir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davam edin"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BAŞLAYIN"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Növbəti"</string>
</resources>
diff --git a/v17/leanback/res/values-b+sr+Latn/strings.xml b/v17/leanback/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..9406bb6
--- /dev/null
+++ b/v17/leanback/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="orb_search_action" msgid="5651268540267663887">"Radnja pretrage"</string>
+ <string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
+ <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Govorite da biste pretraživali"</string>
+ <string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Pretražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite da biste pretražili <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
+ <string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
+ <string name="lb_playback_controls_play" msgid="731953341987346903">"Pusti"</string>
+ <string name="lb_playback_controls_pause" msgid="6189521112079849518">"Pauziraj"</string>
+ <string name="lb_playback_controls_fast_forward" msgid="8569951318244687220">"Premotaj unapred"</string>
+ <string name="lb_playback_controls_fast_forward_multiplier" msgid="1058753672110224526">"Premotaj unapred %1$dX"</string>
+ <string name="lb_playback_controls_rewind" msgid="2227196334132350684">"Premotaj unazad"</string>
+ <string name="lb_playback_controls_rewind_multiplier" msgid="1640629531440849942">"Premotaj unazad %1$dX"</string>
+ <string name="lb_playback_controls_skip_next" msgid="2946499493161095772">"Preskoči sledeću"</string>
+ <string name="lb_playback_controls_skip_previous" msgid="2326801832933178348">"Preskoči prethodnu"</string>
+ <string name="lb_playback_controls_more_actions" msgid="2330770008796987655">"Još radnji"</string>
+ <string name="lb_playback_controls_thumb_up" msgid="6530420347129222601">"Opozovi izbor palca nagore"</string>
+ <string name="lb_playback_controls_thumb_up_outline" msgid="1577637924003500946">"Izaberi palac nagore"</string>
+ <string name="lb_playback_controls_thumb_down" msgid="4498041193172964797">"Opozovi izbor palca nadole"</string>
+ <string name="lb_playback_controls_thumb_down_outline" msgid="2936020280629424365">"Izaberi palac nadole"</string>
+ <string name="lb_playback_controls_repeat_none" msgid="87476947476529036">"Ne ponavljaj nijednu"</string>
+ <string name="lb_playback_controls_repeat_all" msgid="6730354406289599000">"Ponovi sve"</string>
+ <string name="lb_playback_controls_repeat_one" msgid="3285202316452203619">"Ponovi jednu"</string>
+ <string name="lb_playback_controls_shuffle_enable" msgid="1099874107835264529">"Omogući nasumičnu reprodukciju"</string>
+ <string name="lb_playback_controls_shuffle_disable" msgid="8388150597335115226">"Onemogući nasumičnu reprodukciju"</string>
+ <string name="lb_playback_controls_high_quality_enable" msgid="202415780019335254">"Omogući visok kvalitet"</string>
+ <string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visok kvalitet"</string>
+ <string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
+ <string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u režim Slika u slici"</string>
+ <string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dovrši"</string>
+ <string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
+</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index 6b16775..642abc5 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Меню за навигация"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Действие за търсене"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Търсете"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорете, за да търсите"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Деактивиране на високото качество"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Активиране на субтитрите"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Деактивиране на субтитрите"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Вход в режима „Картина в картина“"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Край"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Напред"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ПЪРВИ СТЪПКИ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Напред"</string>
</resources>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index 04669bd..be98f58 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"নেভিগেশান মেনু"</string>
<string name="orb_search_action" msgid="5651268540267663887">"অনুসন্ধান অ্যাকশন"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"অনুসন্ধান"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"অনুসন্ধান করতে বলুন"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"উচ্চ গুণমান অক্ষম করুন"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"সাবটাইটেল সক্ষম করুন"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"সাবটাইটেল অক্ষম করুন"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ছবি মোডে ছবি লগান"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"শেষ করুন"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"চালিয়ে যান"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"শুরু করা যাক"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"পরবর্তী"</string>
</resources>
diff --git a/v17/leanback/res/values-bs-rBA/strings.xml b/v17/leanback/res/values-bs-rBA/strings.xml
new file mode 100644
index 0000000..ae7df6d
--- /dev/null
+++ b/v17/leanback/res/values-bs-rBA/strings.xml
@@ -0,0 +1,90 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+Copyright (C) 2014 The Android Open Source Project
+
+Licensed under the Apache License, Version 2.0 (the "License");
+you may not use this file except in compliance with the License.
+You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing, software
+distributed under the License is distributed on an "AS IS" BASIS,
+WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+See the License for the specific language governing permissions and
+limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- no translation found for orb_search_action (5651268540267663887) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint (8325490927970116252) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_speech (5511270823320183816) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_with_title (1627103380996590035) -->
+ <skip />
+ <!-- no translation found for lb_search_bar_hint_with_title_speech (2712734639766312034) -->
+ <skip />
+ <!-- no translation found for lb_control_display_fast_forward_multiplier (4541442045214207774) -->
+ <skip />
+ <!-- no translation found for lb_control_display_rewind_multiplier (3097220783222910245) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_play (731953341987346903) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_pause (6189521112079849518) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_fast_forward (8569951318244687220) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_fast_forward_multiplier (1058753672110224526) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_rewind (2227196334132350684) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_rewind_multiplier (1640629531440849942) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_skip_next (2946499493161095772) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_skip_previous (2326801832933178348) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_more_actions (2330770008796987655) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_up (6530420347129222601) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_up_outline (1577637924003500946) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_down (4498041193172964797) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_thumb_down_outline (2936020280629424365) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_none (87476947476529036) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_all (6730354406289599000) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_repeat_one (3285202316452203619) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_shuffle_enable (1099874107835264529) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_shuffle_disable (8388150597335115226) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_high_quality_enable (202415780019335254) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_high_quality_disable (8637371582779057866) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_closed_captioning_enable (2429655367176440226) -->
+ <skip />
+ <!-- no translation found for lb_playback_controls_closed_captioning_disable (6133362019475930048) -->
+ <skip />
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Uđi u način rada Slika u slici"</string>
+ <!-- no translation found for lb_guidedaction_finish_title (4015190340667946245) -->
+ <skip />
+ <!-- no translation found for lb_guidedaction_continue_title (8842094924543063706) -->
+ <skip />
+ <!-- no translation found for lb_date_separator (2440386660906697298) -->
+ <skip />
+ <!-- no translation found for lb_time_separator (2763247350845477227) -->
+ <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAPOČNITE"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
+</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index 6578f3b..5567e23 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menú de navegació"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Acció de cerca"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Cerca."</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per fer una cerca."</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactiva l\'alta qualitat"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activa els subtítols tancats"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactiva els subtítols tancats"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entra al mode d\'imatge en imatge"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalitza"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMENÇA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Següent"</string>
</resources>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index 8ffb4f3..a5fbfb5 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigační nabídka"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Vyhledávání akce"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Vyhledávání"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Vyhledávejte hlasem"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Vypnout vysokou kvalitu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnout titulky"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnout titulky"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Přejít do režimu Obraz v obraze"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončit"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovat"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČÍNÁME"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Další"</string>
</resources>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 87c507b..76062f5 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigationsmenu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Søg handling"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Søg"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tal for at søge"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høj kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér undertekster"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver undertekster"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tilføj billedet i billedtilstand"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Afslut"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsæt"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM GODT I GANG"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Næste"</string>
</resources>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index 9d018c6..53ad13a 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigationsmenü"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Suchvorgang"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Suchen"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Zum Suchen sprechen"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hohe Qualität deaktivieren"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Untertitel aktivieren"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Untertitel deaktivieren"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Bild-in-Bild-Modus aktivieren"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fertigstellen"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Weiter"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"JETZT STARTEN"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Weiter"</string>
</resources>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index 310b6a9..6dc20ef 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Μενού πλοήγησης"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Ενέργεια αναζήτησης"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Αναζήτηση"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Μιλήστε για να κάνετε αναζήτηση"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Απενεργοποίηση Υψηλής ποιότητας"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ενεργοποίηση υποτίτλων"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Απενεργοποίηση υποτίτλων"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Εισαγωγή εικόνας στη Λειτουργία εικόνας"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Τέλος"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Συνέχεια"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ΕΝΑΡΞΗ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Επόμενο"</string>
</resources>
diff --git a/v17/leanback/res/values-en-rAU/strings.xml b/v17/leanback/res/values-en-rAU/strings.xml
index 0097135..8032f7d 100644
--- a/v17/leanback/res/values-en-rAU/strings.xml
+++ b/v17/leanback/res/values-en-rAU/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigation menu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Next"</string>
</resources>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 0097135..8032f7d 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigation menu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Next"</string>
</resources>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 0097135..8032f7d 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigation menu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Search Action"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Search"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Speak to search"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disable High Quality"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Enable Closed Captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disable Closed Captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Enter Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continue"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Next"</string>
</resources>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 8341a4d..8a81040 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menú de navegación"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Acción de búsqueda"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Búsqueda"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar calidad alta"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar el modo Imagen en imagen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMENZAR"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Siguiente"</string>
</resources>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index 9f308c0..59f707b 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menú de navegación"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Buscar..."</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Buscar"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Habla para buscar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inhabilitar alta calidad"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Habilitar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inhabilitar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar modo Imagen en imagen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"EMPEZAR"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Siguiente"</string>
</resources>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index a97c385..6d6bac8 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigeerimismenüü"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Otsimistoiming"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Otsing"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Öelge otsimiseks"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Keela kõrgkvaliteetne taasesitus"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Luba subtiitrid"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Keela subtiitrid"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Sisene režiimi Pilt pildis"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Lõpeta"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jätka"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ALUSTAGE"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Järgmine"</string>
</resources>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index c22f172..98787d4 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Nabigazio-menua"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Bilaketa"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Bilatu"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Esan bilatu nahi duzuna"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desgaitu kalitate handiko erreprodukzioa"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Gaitu azpitituluak"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desgaitu azpitituluak"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktibatu \"Argazkia argazkian\" modua"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Amaitu"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jarraitu"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"LEHEN URRATSAK"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Hurrengoa"</string>
</resources>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index 8e6f444..3774bd2 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"منوی پیمایش"</string>
<string name="orb_search_action" msgid="5651268540267663887">"عملکرد جستجو"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"جستجو"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"برای جستجو صحبت کنید"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"غیرفعال کردن کیفیت بالا"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"فعال کردن زیرنویس"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"غیرفعال کردن زیرنویس"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"وارد حالت تصویر در تصویر شوید"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"پایان"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ادامه"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"شروع به کار"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"بعدی"</string>
</resources>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index 0c55e6f..3444642 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigointivalikko"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Hakutoiminto"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Haku"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tee haku puhumalla"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Poista korkea laatu käytöstä"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ota tekstitys käyttöön"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Poista tekstitys käytöstä"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vaihda kuva kuvassa ‑tilaan"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Valmis"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jatka"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"."</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ALOITA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Seuraava"</string>
</resources>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index b28beae..4c8cc19 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu de navigation"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Action de recherche"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncez votre recherche"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la lecture haute qualité"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer le sous-titrage"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver le sous-titrage"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode Incrustation d\'image"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMMENCER"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Suivant"</string>
</resources>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index bb065e8..9086fa1 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu de navigation"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Commande de recherche"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Rechercher"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Énoncer la recherche"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Désactiver la haute qualité"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activer les sous-titres"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Désactiver les sous-titres"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activer le mode PIP"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Terminer"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMMENCER"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Suivant"</string>
</resources>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index 62b3a2b..2d2579b 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menú de navegación"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Acción de busca"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Buscar"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fala para efectuar a busca"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desactivar alta calidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activar subtítulos"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desactivar subtítulos"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activar o modo Imaxe superposta"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizar"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"INTRODUCIÓN"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Seguinte"</string>
</resources>
diff --git a/v17/leanback/res/values-gu-rIN/strings.xml b/v17/leanback/res/values-gu-rIN/strings.xml
index afec9da9..569da29 100644
--- a/v17/leanback/res/values-gu-rIN/strings.xml
+++ b/v17/leanback/res/values-gu-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"નૅવિગેશન મેનૂ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"શોધ ક્રિયા"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"શોધો"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"શોધવા માટે બોલો"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ઉચ્ચ ગુણવત્તા અક્ષમ કરો"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ઉપશીર્ષક સક્ષમ કરો"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"વિગતવાર ઉપશીર્ષકોને અક્ષમ કરો"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ચિત્ર મોડમાં ચિત્ર દાખલ કરો"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"સમાપ્ત કરો"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ચાલુ રાખો"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"પ્રારંભ કરો"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"આગલું"</string>
</resources>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index c243c17..1b73165 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"मार्गदर्शक मेनू"</string>
<string name="orb_search_action" msgid="5651268540267663887">"खोज कार्रवाई"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"खोजें"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजने के लिए बोलें"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षक सक्षम करें"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"उपशीर्षक अक्षम करें"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोड में चित्र डालें"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त करें"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी रखें"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"प्रारंभ करें"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"अगला"</string>
</resources>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index bb6ebcc..73c8dc6 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -17,11 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigacijski izbornik"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Radnja pretraživanja"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Pretražite"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite upit za pretraživanje"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Tražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit da pretražite <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite upit da pretražite uslugu <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Reproduciraj"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogući visoku kvalitetu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogući titlove"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogući titlove"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Unos slike u načinu slike"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Završi"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"."</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"POČETAK"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Dalje"</string>
</resources>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index cfeb2ad..f30c4f6 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigációs menü"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Keresési művelet"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Keresés"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Beszéljen a keresés indításához"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Jó minőségű lejátszás letiltása"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Feliratok engedélyezése"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Feliratok letiltása"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Kép a képben mód indítása"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Befejezés"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Folytatás"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KEZDŐ LÉPÉSEK"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Következő"</string>
</resources>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 1ac5dd8..edc1f47 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Նավարկման ընտրացանկ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Որոնման հրամանը"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Որոնում"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Խոսեք՝ որոնելու համար"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Անջատել բարձր որակը"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Միացնել խորագրերը"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Անջատել խորագրերը"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Մուտք «Նկար նկարի մեջ» ռեժիմ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Վերջ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Շարունակել"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ՍԿՍԵԼ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Հաջորդը"</string>
</resources>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index 6569825..05e9dc9 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu navigasi"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Tindakan Penelusuran"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Telusuri"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ucapkan untuk menelusuri"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Nonaktifkan Kualitas Tinggi"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktifkan Pembuatan Teks"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Nonaktifkan Pembuatan Teks"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Foto Dalam Mode Foto"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Lanjutkan"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"."</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MULAI"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Berikutnya"</string>
</resources>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index 830b11e..84b6430 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -17,8 +17,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Yfirlitsvalmynd"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Leitaraðgerð"</string>
- <string name="lb_search_bar_hint" msgid="8325490927970116252">"Leitaðu"</string>
+ <string name="lb_search_bar_hint" msgid="8325490927970116252">"Leita"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Talaðu til að leita"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Talaðu til að leita í <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Slökkva á miklum gæðum"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Kveikja á skjátextum"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Slökkva á skjátextum"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Skoða mynd í myndsniði"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Ljúka"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Halda áfram"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"HEFJAST HANDA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Áfram"</string>
</resources>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 2f0ca47..b0814d0 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu di navigazione"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Azione di ricerca"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Ricerca"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Parla per cercare"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Disattiva alta qualità"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Attiva sottotitoli"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Disattiva sottotitoli"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Attiva modalità Picture-in-picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fine"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"INIZIA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Avanti"</string>
</resources>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index f2bda58..b94c431 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -17,11 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"תפריט ניווט"</string>
<string name="orb_search_action" msgid="5651268540267663887">"פעולת חיפוש"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"חפש"</string>
- <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר כדי לחפש"</string>
+ <string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"דבר בקול כדי לחפש"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"חפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"דבר כדי לחפש את <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"דבר בקול כדי לחפש ב-<xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"הפעל"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"השבת איכות גבוהה"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"הפעל כתוביות"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"השבת כתוביות"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"הזן את התמונה במצב תמונה"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"סיום"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"המשך"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"התחל"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"הבא"</string>
</resources>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 09faa8b..650f4fd 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ナビゲーション メニュー"</string>
<string name="orb_search_action" msgid="5651268540267663887">"検索操作"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"検索"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"音声検索"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"高品質を無効にする"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"字幕を有効にする"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"字幕を無効にする"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"PIP モードに移動"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完了"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"続行"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"使ってみる"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"次へ"</string>
</resources>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index ac9f4cd..9962645 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ნავიგაციის მენიუ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ძიების მოქმედება"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ძიება"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"თქვით საძიებო ფრაზა"</string>
@@ -50,6 +51,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"მაღალი ხარისხის გამორთვა"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"დახურული წარწერების ჩართვა"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"დახურული წარწერების გაუქმება"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"რეჟიმზე „სურათი სურათში“ გადასვლა"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"დასრულება"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"გაგრძელება"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"დაწყება"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"შემდეგი"</string>
</resources>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index 380695b..6f7faf2 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Навигация мәзірі"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Іздеу әрекеті"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Іздеу"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Іздеу үшін сөйлеу"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Жоғары сапаны өшіру"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Жасырын титрлерді қосу"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Жасырын титрлерді өшіру"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Сурет ішіндегі сурет режиміне кіру"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Аяқтау"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Жалғастыру"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ІСКЕ КІРІСУ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Келесі"</string>
</resources>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index 60f90e5..25abb45 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ម៉ឺនុយរុករក"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ស្វែងរកសកម្មភាព"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ស្វែងរក"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"និយាយដើម្បីស្វែងរក"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"បិទគុណភាពខ្ពស់"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"បើកការដាក់ចំណងដែលបានបិទ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"បិទការដាក់ចំណងដែលបានបិទ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"បញ្ចូលរូបភាពនៅក្នុងរបៀបរូបភាព"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"បញ្ចប់"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"បន្ត"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"៖"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ចាប់ផ្ដើម"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"បន្ទាប់"</string>
</resources>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 18206ed..92fdddd 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ನ್ಯಾವಿಗೇಶನ್ ಮೆನು"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ಹುಡುಕಾಟ ಕ್ರಿಯೆ"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ಹುಡುಕಿ"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ಹುಡುಕಲು ಮಾತನಾಡಿ"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ಹೆಚ್ಚು ಗುಣಮಟ್ಟವನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಯನ್ನು ಸಕ್ರಿಯಗೊಳಿಸಿ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ಮುಚ್ಚಿದ ಶೀರ್ಷಿಕೆಯನ್ನು ನಿಷ್ಕ್ರಿಯಗೊಳಿಸಿ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ಚಿತ್ರವನ್ನು ಚಿತ್ರ ಮೋಡ್ನಲ್ಲಿ ಪ್ರವೇಶಿಸಿ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ಪೂರ್ಣಗೊಳಿಸು"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ಮುಂದುವರಿಸು"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ಪ್ರಾರಂಭಿಸಿ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ಮುಂದೆ"</string>
</resources>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index e262b1c..1696bca 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"탐색 메뉴"</string>
<string name="orb_search_action" msgid="5651268540267663887">"검색 작업"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"검색"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"음성 검색"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"고화질 사용 중지"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"자막 사용 설정"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"자막 사용 중지"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"사진 모드에서 사진 입력"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"완료"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"계속"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"시작하기"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"다음"</string>
</resources>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index 74cc841..e2dc200 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Чабыттоо менюсу"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Издөө аракети"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Издөө"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Издөө үчүн сүйлөңүз"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Жогорку сапатты өчүрүү"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Жабык субтитрлерди иштетүү"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Жабык субтитрлерди өчүрүү"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Сүрөт режиминде сүрөт киргизүү"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Бүтүрүү"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Улантуу"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"БАШТАДЫК"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Кийинки"</string>
</resources>
diff --git a/v17/leanback/res/values-ldrtl/dimens.xml b/v17/leanback/res/values-ldrtl/dimens.xml
index 9f54273a..45dfe33 100644
--- a/v17/leanback/res/values-ldrtl/dimens.xml
+++ b/v17/leanback/res/values-ldrtl/dimens.xml
@@ -15,11 +15,7 @@
limitations under the License.
-->
<resources>
- <!-- GuidedStepFragment -->
- <dimen name="lb_guidedstep_slide_start_distance">200dp</dimen>
- <dimen name="lb_guidedstep_slide_end_distance">-200dp</dimen>
- <dimen name="lb_guidance_entry_translationX">120dp</dimen>
- <dimen name="lb_guidedactions_entry_translationX">-384dp</dimen>
- <!-- end GuidedStepFragment -->
-
+ <!-- OnboardingFragment -->
+ <dimen name="lb_onboarding_start_button_translation_offset">-16dp</dimen>
+ <!-- end OnboardingFragment -->
</resources>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index d919e93..389b1d0 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ເມນູນຳທາງ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ຊອກຫາຄຳສັ່ງ"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ຊອກຫາ"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ເວົ້າເພື່ອຊອກຫາ"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ປິດນຳໃຊ້ການຫຼິ້ນດ້ວຍຄຸນນະພາບສູງ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ເປີດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ປິດນຳໃຊ້ຄຳບັນຍາຍແບບປິດ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ປ້ອນຮູບພາບໃນໂໝດຮູບພາບ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ສໍາເລັດ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ສືບຕໍ່"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ເລີ່ມຕົ້ນນຳໃຊ້"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ຕໍ່ໄປ"</string>
</resources>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index 415fc25..43eebb8 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Naršymo meniu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Paieškos veiksmas"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Paieška"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Pasakykite, kad ieškotumėte"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Išjungti aukštą kokybę"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Įgalinti subtitrus"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Išjungti subtitrus"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Įjungti vaizdo vaizde režimą"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Baigti"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tęsti"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRADĖTI"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Kitas"</string>
</resources>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index 3979e43..644d990 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigācijas izvēlne"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Meklēšanas darbība"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Meklēt"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Runāt, lai meklētu"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Atspējot augstas kvalitātes vienumu atskaņošanu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Iespējot slēgtos parakstus"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Atspējot slēgtos parakstus"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Aktivizēt režīmu Attēls attēlā"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Pabeigt"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Turpināt"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"SĀKT DARBU"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Nākamā"</string>
</resources>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index ccf6d62..6e506fe 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Мени за навигација"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Акција на пребарување"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Пребарување"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Зборувајте за да пребарувате"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Оневозможи висок квалитет"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Овозможи затворено објаснување"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Оневозможи затворено објаснување"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Влези во режимот „Слика во слика“"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Заврши"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продолжи"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЗАПОЧНИ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Следно"</string>
</resources>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index 356b0e9..0306a6a 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"നാവിഗേഷൻ മെനു"</string>
<string name="orb_search_action" msgid="5651268540267663887">"തിരയൽ പ്രവർത്തനം"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"തിരയുക"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ശബ്ദം ഉപയോഗിച്ച് തിരയുക"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ഉയർന്ന നിലവാരം പ്രവർത്തനരഹിതമാക്കുക"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"അടച്ച അടിക്കുറിപ്പ് നൽകൽ പ്രവർത്തനക്ഷമമാക്കുക"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"അടച്ച അടിക്കുറിപ്പ് നൽകൽ പ്രവർത്തനരഹിതമാക്കുക"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"\'ചിത്രത്തിനുള്ളിൽ ചിത്രം\' മോഡിലേക്ക് പ്രവേശിക്കുക"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"പൂര്ത്തിയാക്കുക"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"തുടരുക"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ആരംഭിക്കുക"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"അടുത്തത്"</string>
</resources>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index a7a640f..f21b649 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Шилжүүлэх цэс"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Хайлтын үйлдэл"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Хайлт"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Ярьж хайх"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Өндөр чанарыг идэвхгүйжүүлэх"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Текст тайлбарыг идэвхжүүлэх"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Текст тайлбарыг идэвхгүйжүүлэх"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Зургийн горимд зураг оруулна уу"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Дуусгах"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Үргэлжлүүлэх"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЭХЭЛЦГЭЭЕ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Дараах"</string>
</resources>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index b9b568a..ef6a6a6 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"नेव्हिगेशन मेनू"</string>
<string name="orb_search_action" msgid="5651268540267663887">"शोध क्रिया"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"शोधा"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"शोधण्यासाठी बोला"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणवत्ता अक्षम करा"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"उपशीर्षके सक्षम करा"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"उपशीर्षके अक्षम करा"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्र मोडमध्ये चित्र प्रविष्ट करा"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"सुरू ठेवा"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"प्रारंभ करा"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"पुढील"</string>
</resources>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index 0a5f8bd..1764e52 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu navigasi"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Tindakan Carian"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Carian"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tutur untuk membuat carian"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Lumpuhkan Kualiti Tinggi"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Dayakan Kapsyen Tertutup"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Lumpuhkan Kapsyen Tertutup"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Masukkan Gambar Dalam Mod Gambar"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Selesai"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Teruskan"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MULAKAN"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Seterusnya"</string>
</resources>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index 1b3cdee..645f169 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"လမ်းညွှန် မီနူး"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ရှာဖွေရန် လုပ်ဆောင်ချက်"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ရှာဖွေရန်"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ရှာဖွေရန် ပြောပါ"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"အရည်အသွေးကောင်းအား ပိတ်ထားရန်"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"စာတမ်းထိုး ဖွင့်ရန်"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"စာတမ်းထိုးအား ပိတ်ထားရန်"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ဓာတ်ပုံမုဒ်တွင် ဓာတ်ပုံထည့်ပါ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ပြီးပြီ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ဆက်လုပ်ရန်"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"−"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"စတင်ပါ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ရှေ့သို့"</string>
</resources>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index c58544d..ae3394b 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigasjonsmeny"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Søkehandling"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Søk"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Snakk for å søke"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Deaktiver høy kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivér teksting"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Deaktiver teksting"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Slå på modusen Bilde-i-bilde"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Fullfør"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsett"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">"."</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM I GANG"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Neste"</string>
</resources>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 54c92856..e8baffb 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"नेभिगेसन मेनु"</string>
<string name="orb_search_action" msgid="5651268540267663887">"कार्य खोजी गर्नुहोस्"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"खोजी गर्नुहोस्"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"खोजी गर्न बोल्नुहोस्"</string>
@@ -48,6 +49,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"उच्च गुणस्तर असक्षम"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"बन्द क्याप्सनहरु सक्षम"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"बन्द क्याप्सनहरु असक्षम"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"चित्रलाई चित्र मोडमा प्रविष्ट गर्नुहोस्"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"समाप्त गर्नुहोस्"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी राख्नुहोस्"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"सुरु गरौँ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"अर्को"</string>
</resources>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 941aa79..0a5bdce 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigatiemenu"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Actie zoeken"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Zoeken"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Spreek om te zoeken"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Hoge kwaliteit uitschakelen"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ondertiteling inschakelen"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Ondertiteling uitschakelen"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Beeld-in-beeld-modus openen"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Voltooien"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Doorgaan"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"-"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"AAN DE SLAG"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Volgende"</string>
</resources>
diff --git a/v17/leanback/res/values-pa-rIN/strings.xml b/v17/leanback/res/values-pa-rIN/strings.xml
index f197657..b8c7591 100644
--- a/v17/leanback/res/values-pa-rIN/strings.xml
+++ b/v17/leanback/res/values-pa-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"ਆਵਾਗੌਣ ਮੀਨੂ"</string>
<string name="orb_search_action" msgid="5651268540267663887">"ਖੋਜ ਕਿਰਿਆ"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ਖੋਜੋ"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"ਖੋਜਣ ਲਈ ਬੋਲੋ"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ਉੱਚ ਗੁਣਵੱਤਾ ਨੂੰ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"ਬੰਦ ਕੈਪਸ਼ਨਿੰਗ ਸਮਰੱਥ ਬਣਾਓ"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ਬੰਦ ਕੈਪਸ਼ਨਿੰਗ ਅਸਮਰੱਥ ਬਣਾਓ"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"ਤਸਵੀਰ ਮੋਡ ਵਿੱਚ ਤਸਵੀਰ ਦਾਖਲ ਕਰੋ"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ਖ਼ਤਮ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ਜਾਰੀ ਰੱਖੋ"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ਸ਼ੁਰੂਆਤ ਕਰੋ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ਅੱਗੇ"</string>
</resources>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index 7598c73..15de203 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -17,11 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu nawigacyjne"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Wyszukaj czynność"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Szukaj"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Powiedz, aby wyszukać"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Szukaj <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, by wyszukać <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Powiedz, co chcesz wyszukać w usłudze <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$dX"</string>
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$dX"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Odtwórz"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Wyłącz wysoką jakość"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Włącz napisy"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Wyłącz napisy"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Włącz tryb obrazu w obrazie"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Zakończ"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Dalej"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ROZPOCZNIJ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Dalej"</string>
</resources>
diff --git a/v17/leanback/res/values-pt-rBR/strings.xml b/v17/leanback/res/values-pt-rBR/strings.xml
index 12c9533..4621809 100644
--- a/v17/leanback/res/values-pt-rBR/strings.xml
+++ b/v17/leanback/res/values-pt-rBR/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu de navegação"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRIMEIROS PASSOS"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Próximo"</string>
</resources>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index 7b12f5e..0df9c00 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu de navegação"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar legendas"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar legendas"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Imagem na imagem"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"INICIAR"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Seguinte"</string>
</resources>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 12c9533..4621809 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu de navegação"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Ação de pesquisa"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Pesquisar"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fale para pesquisar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Desativar alta qualidade"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Ativar closed captioning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Desativar closed captioning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Entrar no modo Picture in Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Concluir"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRIMEIROS PASSOS"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Próximo"</string>
</resources>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index c81b259f..5fa64ef 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Meniu de navigare"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Acțiunea de căutare"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Căutați"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Rostiți pentru a căuta"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Dezactivează calitatea înaltă"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Activează subtitrările"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Dezactivează subtitrările"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Activați modul Picture-in-Picture"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Finalizați"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuați"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ÎNCEPEȚI"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Înainte"</string>
</resources>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index 864054a..c7a146f 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Меню навигации"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Поиск"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Поиск"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Произнесите запрос"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Отключить высокое качество."</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Включить субтитры."</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Отключить субтитры."</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Включить режим \"Картинка в картинке\"."</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Готово"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Далее"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"НАЧАТЬ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Далее"</string>
</resources>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index 3db1293..44e4f31 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"සංචාලන මෙනුව"</string>
<string name="orb_search_action" msgid="5651268540267663887">"සෙවීමේ ක්රියාව"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"සොයන්න"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"සෙවීමට කථා කරන්න"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"උපරිම ගුණත්වය අබල කරන ලදි"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"වැසුණු ශිර්ෂ කිරීම සබල කරන ලදි"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"වැසුණු ශිර්ෂ කිරීම අබල කරන ලදි"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"පින්තූරය-තුළ-පින්තූරය ප්රකාරයට ඇතුළු වන්න"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"අවසානය"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"දිගටම කර ගෙන යන්න"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ආරම්භ කරන්න"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ඊළඟ"</string>
</resources>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index a317d18..bfd972a 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigačná ponuka"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Akcia vyhľadávania"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Hľadať"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Hovorením spustíte vyhľadávanie"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zakázať médiá vo vysokej kvalite"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Zapnúť skryté titulky"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Vypnúť skryté titulky"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vložiť obrázok v režime obrázka"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončiť"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovať"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČÍNAME"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Ďalej"</string>
</resources>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index 7b16952..1919b29 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -17,11 +17,12 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Meni za krmarjenje"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Dejanje iskanja"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Iskanje"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Izgovorite iskalno poizvedbo"</string>
<string name="lb_search_bar_hint_with_title" msgid="1627103380996590035">"Iskanje: <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
- <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite poizvedbo za iskanje v <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
+ <string name="lb_search_bar_hint_with_title_speech" msgid="2712734639766312034">"Izgovorite poizvedbo za iskanje v storitvi <xliff:g id="SEARCH_CONTEXT">%1$s</xliff:g>"</string>
<string name="lb_control_display_fast_forward_multiplier" msgid="4541442045214207774">"%1$d-kratno"</string>
<string name="lb_control_display_rewind_multiplier" msgid="3097220783222910245">"%1$d-kratno"</string>
<string name="lb_playback_controls_play" msgid="731953341987346903">"Predvajaj"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Onemogoči visoko kakovost"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Omogoči podnapise"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Onemogoči podnapise"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vklop načina za sliko v sliki"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Dokončaj"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Naprej"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČNITE"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Naprej"</string>
</resources>
diff --git a/v17/leanback/res/values-sq-rAL/strings.xml b/v17/leanback/res/values-sq-rAL/strings.xml
index 723b90c..36cfdf3 100644
--- a/v17/leanback/res/values-sq-rAL/strings.xml
+++ b/v17/leanback/res/values-sq-rAL/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menyja e navigimit"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Veprim i kërkimit"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Kërko"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Fol për të kërkuar"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Çaktivizo \"Cilësinë e lartë\""</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivizo titrat"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Çaktivizo titrat me sekuencë kohore"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Fut një fotografi në modalitetin e fotografisë"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Përfundo"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Vazhdo"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"FILLO"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Përpara"</string>
</resources>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index df6c6b1..1a9a0c4 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Мени за навигацију"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Радња претраге"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Претражите"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Говорите да бисте претраживали"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Онемогући висок квалитет"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Омогући титлове"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Онемогући титлове"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Уђи у режим Слика у слици"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Доврши"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Настави"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЗАПОЧНИТЕ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Даље"</string>
</resources>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index 9b874ca..5bdd293 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigationsmeny"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Sökåtgärd"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Sök"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Säg det du söker efter"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Inaktivera hög kvalitet"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Aktivera textning"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Inaktivera textning"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ange läget Bild-i-bild"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Slutför"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsätt"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM IGÅNG"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Nästa"</string>
</resources>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index 53ef95a..6683e46 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menyu ya kusogeza"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Kitendo cha Kutafuta"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Utafutaji"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Tamka ili utafute"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Zima Ubora wa Juu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Washa manukuu"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Zima manukuu"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Weka Picha Katika Hali ya Picha"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Kamilisha"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Endelea"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ANZA KUTUMIA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Inayofuata"</string>
</resources>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index 1cc2eea..5afde83 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"வழிசெலுத்தல் மெனு"</string>
<string name="orb_search_action" msgid="5651268540267663887">"செயலைத் தேடுக"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"தேடு"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"தேட, பேசவும்"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"உயர் தரத்தை முடக்கு"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"விரிவான வசனங்களை இயக்கு"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"விரிவான வசனங்களை முடக்கு"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"பிக்ச்சர் இன் பிக்ச்சர் பயன்முறைக்குச் செல்"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"முடி"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"தொடர்க"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"தொடங்குக"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"அடுத்து"</string>
</resources>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index 32d311d..f74c525 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"నావిగేషన్ మెను"</string>
<string name="orb_search_action" msgid="5651268540267663887">"శోధన చర్య"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"శోధించండి"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"శోధించడానికి చదివి వినిపించండి"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"అధిక నాణ్యతను నిలిపివేయి"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"సంవృత శీర్షికలను ప్రారంభించు"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"సంవృత శీర్షికలను నిలిపివేయి"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"చిత్రంలో చిత్రం మోడ్లోకి ప్రవేశించండి"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"ముగించు"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"కొనసాగించు"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ప్రారంభించు"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"తదుపరి"</string>
</resources>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index d3eb2a3..8440dd9d9 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"เมนูการนำทาง"</string>
<string name="orb_search_action" msgid="5651268540267663887">"การดำเนินการค้นหา"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"ค้นหา"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"พูดเพื่อค้นหา"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"ปิดใช้คุณภาพสูง"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"เปิดใช้คำบรรยาย"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"ปิดใช้คำบรรยาย"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"เข้าสู่โหมดการแสดงผลหลายแหล่งพร้อมกัน"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"เสร็จสิ้น"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ต่อไป"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"เริ่มต้นใช้งาน"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ถัดไป"</string>
</resources>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index f50b4d1..c2f52a4 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu ng navigation"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Pagkilos sa Paghahanap"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Maghanap"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Magsalita upang maghanap"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"I-disable ang Mataas na Kalidad"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"I-enable ang Paglalagay ng Subtitle"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"I-disable ang Paglalagay ng Subtitle"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Pumasok sa Picture In Picture Mode"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tapusin"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Magpatuloy"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MAGSIMULA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Susunod"</string>
</resources>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index 814cb29..0c6431e 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Gezinme menüsü"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Arama İşlemi"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Ara"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Arama yapmak için konuşun"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yüksek Kalitede Oynatmayı Devre Dışı Bırak"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Altyazıları Etkinleştir"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Altyazıları Devre Dışı Bırak"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Resim İçinde Resim Moduna Geç"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Son"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Devam"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BAŞLA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Sonraki"</string>
</resources>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index a38db30..bb6bde7 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Навігаційне меню"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Команда пошуку"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Пошук"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Продиктуйте пошуковий запит"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Вимкнути високу якість"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Увімкнути субтитри"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Вимкнути субтитри"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Перейти в режим \"Картинка в картинці\""</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Закінчити"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продовжити"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"."</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ПОЧАТИ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Далі"</string>
</resources>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index 666cf71..f387aa3 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"نیویگیشن مینو"</string>
<string name="orb_search_action" msgid="5651268540267663887">"تلاش کی کارروائی"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"تلاش کریں"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"تلاش کرنے کیلئے بولیں"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"اعلی معیار کو غیر فعال کریں"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"سب ٹائٹلز کو فعال کریں"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"سب ٹائٹلز کو غیر فعال کریں"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"\'تصویر میں تصویر موڈ\' میں داخل ہوں"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"مکمل کریں"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"جاری رکھیں"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"شروع کریں"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"اگلا"</string>
</resources>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index d81d8de..48dc067 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Navigatsiya menyusi"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Qidiruv amali"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Qidirish"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Qidirish uchun gapiring"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Yuqori sifatni o‘chirib qo‘yish"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Taglavhalarni yoqish"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Taglavhalarni o‘chirib qo‘yish"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Tasvir ichida tasvir rejimiga kirish"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Tugatish"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davom etish"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BOSHLADIK"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Keyingisi"</string>
</resources>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index 881734b..8d8c282 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Menu điều hướng"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Tác vụ tìm kiếm"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Tìm kiếm"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Nói để tìm kiếm"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Tắt chế độ chất lượng cao"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Bật phụ đề"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Tắt phụ đề"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Vào ảnh ở chế độ ảnh"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Hoàn tất"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tiếp tục"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BẮT ĐẦU"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Tiếp theo"</string>
</resources>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index fc3fa103..611bff1 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"导航菜单"</string>
<string name="orb_search_action" msgid="5651268540267663887">"搜索操作"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"搜索"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"说话即可开始搜索"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"关闭高画质模式"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"开启字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"关闭字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"进入画中画模式"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"继续"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"开始使用"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"下一页"</string>
</resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index 7cba4b5..c18032e 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"導覽選單"</string>
<string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高畫質"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"啟用字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"停用字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"進入「畫中畫模式」"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"開始使用"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"下一頁"</string>
</resources>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index dcca2db..1e14d0a 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"導覽選單"</string>
<string name="orb_search_action" msgid="5651268540267663887">"搜尋動作"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"搜尋"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"使用語音搜尋"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"停用高品質播放"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"啟用字幕"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"停用字幕"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"進入子母畫面模式"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"完成"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"開始使用"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"繼續"</string>
</resources>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index f4c589d..7fb290f 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -17,6 +17,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="lb_navigation_menu_contentDescription" msgid="6215811486591629025">"Imenyu yokuzulazula"</string>
<string name="orb_search_action" msgid="5651268540267663887">"Isenzo sokusesha"</string>
<string name="lb_search_bar_hint" msgid="8325490927970116252">"Sesha"</string>
<string name="lb_search_bar_hint_speech" msgid="5511270823320183816">"Khuluma ukuze useshe"</string>
@@ -46,6 +47,11 @@
<string name="lb_playback_controls_high_quality_disable" msgid="8637371582779057866">"Khubaza ikhwalithi ephezulu"</string>
<string name="lb_playback_controls_closed_captioning_enable" msgid="2429655367176440226">"Nika amandla imibhalo engezansi"</string>
<string name="lb_playback_controls_closed_captioning_disable" msgid="6133362019475930048">"Khubaza imihbalo engezansi"</string>
+ <string name="lb_playback_controls_picture_in_picture" msgid="3040035547765350690">"Ngena isithombe kumodi yesithombe"</string>
<string name="lb_guidedaction_finish_title" msgid="4015190340667946245">"Qeda"</string>
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Qhubeka"</string>
+ <string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
+ <string name="lb_time_separator" msgid="2763247350845477227">":"</string>
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"QALISA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Okulandelayo"</string>
</resources>
diff --git a/v17/leanback/res/values/attrs.xml b/v17/leanback/res/values/attrs.xml
index 90f010a..3a0ca19 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -22,6 +22,12 @@
<attr name="focusOutFront" format="boolean" />
<!-- Allow DPAD key to navigate out at the end of the view, default is false -->
<attr name="focusOutEnd" format="boolean" />
+ <!-- Allow DPAD key to navigate out of first row, for HorizontalGridView, it's the
+ top edge, for VerticalGridView it's the "start" edge. Default value is true. -->
+ <attr name="focusOutSideStart" format="boolean" />
+ <!-- Allow DPAD key to navigate out of last row, for HorizontalGridView, it's the
+ bottom edge, for VerticalGridView it's the "end" edge. Default value is true. -->
+ <attr name="focusOutSideEnd" format="boolean" />
<!-- Defining margin between two items horizontally -->
<attr name="horizontalMargin" format="dimension" />
<!-- Defining margin between two items vertically -->
@@ -120,14 +126,8 @@
</declare-styleable>
<declare-styleable name="lbImageCardView">
- <!-- Deprecated. Use 'lbImageCardViewInfoAreaStyle' instead. -->
+ <!-- Deprecated. Use 'imageCardViewInfoAreaStyle' instead. -->
<attr name="infoAreaBackground" format="reference|color"/>
- <!-- Use these attributes to override a ImageCardView's component style. -->
- <attr name="lbImageCardViewImageStyle" format="reference" />
- <attr name="lbImageCardViewTitleStyle" format="reference" />
- <attr name="lbImageCardViewContentStyle" format="reference" />
- <attr name="lbImageCardViewBadgeStyle" format="reference" />
- <attr name="lbImageCardViewInfoAreaStyle" format="reference" />
<!-- Defines what components the ImageCardView will use. -->
<attr name="lbImageCardViewType">
<flag name="Title" value="1" />
@@ -166,6 +166,7 @@
<attr name="shuffle" format="reference"/>
<attr name="high_quality" format="reference"/>
<attr name="closed_captioning" format="reference"/>
+ <attr name="picture_in_picture" format="reference"/>
</declare-styleable>
<declare-styleable name="lbSlide">
@@ -222,19 +223,24 @@
<!-- fading edge length of start of browse row when HeadersFragment is visible -->
<attr name="browseRowsFadingEdgeLength" format="dimension" />
- <!-- BrowseFragment Title text style -->
+ <!-- fragment title text style -->
<attr name="browseTitleTextStyle" format="reference" />
- <!-- BrowseFragment Title icon style -->
+ <!-- fragment title icon style -->
<attr name="browseTitleIconStyle" format="reference" />
- <!-- BrowseFragment TitleView style -->
+ <!-- fragment title view style -->
<attr name="browseTitleViewStyle" format="reference" />
+ <!-- customize title view layout file, it must have title view with id browse_title_group -->
+ <attr name="browseTitleViewLayout" format="reference" />
+
<!-- vertical grid style inside HeadersFragment -->
<attr name="headersVerticalGridStyle" format="reference" />
<!-- header style inside HeadersFragment -->
<attr name="headerStyle" format="reference" />
+ <!-- Non selectable header style inside HeadersFragment -->
+ <attr name="sectionHeaderStyle" format="reference" />
<!-- vertical grid style inside RowsFragment -->
<attr name="rowsVerticalGridStyle" format="reference" />
@@ -254,6 +260,11 @@
<!-- CardView styles -->
<attr name="baseCardViewStyle" format="reference" />
<attr name="imageCardViewStyle" format="reference" />
+ <attr name="imageCardViewImageStyle" format="reference" />
+ <attr name="imageCardViewTitleStyle" format="reference" />
+ <attr name="imageCardViewContentStyle" format="reference" />
+ <attr name="imageCardViewBadgeStyle" format="reference" />
+ <attr name="imageCardViewInfoAreaStyle" format="reference" />
<!-- for details overviews -->
<attr name="detailsDescriptionTitleStyle" format="reference" />
@@ -261,7 +272,21 @@
<attr name="detailsDescriptionBodyStyle" format="reference" />
<attr name="detailsActionButtonStyle" format="reference" />
- <!-- for playback controls -->
+ <!-- for playlist and playback controls styling -->
+ <attr name="playbackPaddingStart" format="dimension"/>
+ <attr name="playbackPaddingEnd" format="dimension"/>
+ <attr name="playbackMediaItemPaddingStart" format="dimension"/>
+
+ <attr name="playbackMediaListHeaderStyle" format="reference"/>
+ <attr name="playbackMediaItemRowStyle" format="reference"/>
+ <attr name="playbackMediaItemSeparatorStyle" format="reference"/>
+
+ <attr name="playbackMediaListHeaderTitleStyle" format="reference"/>
+ <attr name="playbackMediaItemDetailsStyle" format="reference"/>
+ <attr name="playbackMediaItemNumberStyle" format="reference"/>
+ <attr name="playbackMediaItemNameStyle" format="reference"/>
+ <attr name="playbackMediaItemDurationStyle" format="reference"/>
+
<attr name="playbackControlsButtonStyle" format="reference" />
<attr name="playbackControlButtonLabelStyle" format="reference" />
<attr name="playbackControlsTimeStyle" format="reference" />
@@ -285,10 +310,14 @@
-->
<attr name="defaultBrandColorDark" format="reference|color" />
- <!-- Default colors -->
+ <!-- Default background color for Search Icon -->
<attr name="defaultSearchColor" format="reference|color" />
+ <!-- Default icon color for Search Icon -->
+ <attr name="defaultSearchIconColor" format="reference|color" />
<!-- Default color that search orb pulses to. If not set, this color is determined programatically based on the defaultSearchColor -->
<attr name="defaultSearchBrightColor" format="reference|color" />
+ <!-- Default color for SectionHeader, by default same as defaultSearchColor -->
+ <attr name="defaultSectionHeaderColor" format="reference|color" />
<!-- Style for searchOrb -->
<attr name="searchOrbViewStyle" format="reference"/>
@@ -313,6 +342,15 @@
can set this to <code>@style/Theme.Leanback.GuidedStep</code> in order to specify the
default GuidedStepFragment styles. -->
<attr name="guidedStepTheme" format="reference" />
+ <!-- Used to control the height of the fragment. By default this fragment will take
+ up the full height of it's parent. The height of this fragment is governed by
+ this property. Default weight is set to 2.0, so inorder to render the fragment
+ in half screen mode, this attribute should be set to 1.0 -->
+ <attr name="guidedStepHeightWeight" format="float" />
+
+ <!-- Y offset to the bottom of the TitleView(font baseline) used to align the
+ first action text on the right. -->
+ <attr name="guidedStepKeyline" format="float" />
<!-- @hide
Theme attribute used to inspect theme inheritance. -->
@@ -346,17 +384,9 @@
{@link android.support.v17.leanback.R.style#Widget_Leanback_GuidanceIconStyle}. -->
<attr name="guidanceIconStyle" format="reference" />
- <!-- Theme attribute for the animation used in a GuidedActionsPresenter when the action
- selector is animated in at activity start. Default is {@link
- android.support.v17.leanback.R.animator#lb_guidedactions_selector_show}. -->
- <attr name="guidedActionsSelectorShowAnimation" format="reference" />
- <!-- Theme attribute for the animation used in a GuidedActionsPresenter when the action
- selector is animated in at activity start. Default is {@link
- android.support.v17.leanback.R.animator#lb_guidedactions_selector_hide}. -->
- <attr name="guidedActionsSelectorHideAnimation" format="reference" />
<!-- Theme attribute for the style of the item selector in a GuidedActionsPresenter. Default is
- {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionsSelectorStyle}. -->
- <attr name="guidedActionsSelectorStyle" format="reference" />
+ ?android:attr/selectableItemBackground. -->
+ <attr name="guidedActionsSelectorDrawable" format="reference" />
<!-- Theme attribute for the shadow elevation of GuidedActions. Default is
{@link android.support.v17.leanback.R.dimen#lb_guidedactions_elevation}.-->
@@ -374,6 +404,14 @@
{@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionsListStyle}.-->
<attr name="guidedActionsListStyle" format="reference" />
+ <!-- Theme attribute for the style of the sub actions list in a GuidedActionsPresenter. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedSubActionsListStyle}.-->
+ <attr name="guidedSubActionsListStyle" format="reference" />
+
+ <!-- Theme attribute for the style of the list in a GuidedActionsPresenter. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_GuidedButtonActionsListStyle}.-->
+ <attr name="guidedButtonActionsListStyle" format="reference" />
+
<!-- Theme attribute for the style of the container of a single action in a
GuidedActionsPresenter. Default is {@link
android.support.v17.leanback.R.style#Widget_Leanback_GuidedActionItemContainerStyle}. -->
@@ -442,6 +480,87 @@
android.support.v17.leanback.R.dimen#lb_guidedactions_vertical_padding}. -->
<attr name="guidedActionVerticalPadding" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionsContainerStyle" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionsSelectorStyle" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedStepEntryAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedStepExitAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedStepReentryAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedStepReturnAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidanceEntryAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionsEntryAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionsSelectorShowAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionsSelectorHideAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionCheckedAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionUncheckedAnimation" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionContentWidth" format="reference" />
+ <!-- Deprecated theme attribute, do not use -->
+ <attr name="guidedActionContentWidthNoIcon" format="reference" />
</declare-styleable>
-</resources>
\ No newline at end of file
+ <declare-styleable name="lbDatePicker">
+ <attr name="android:minDate" />
+ <attr name="android:maxDate" />
+ <!-- e.g. "MDY", "MY" -->
+ <attr name="datePickerFormat" format="string"/>
+ </declare-styleable>
+
+ <declare-styleable name="LeanbackOnboardingTheme">
+ <!-- Theme attribute for the overall theme used in the onboarding. The Leanback themes set
+ the default for this, but a custom theme that does not derive from a leanback theme can
+ set this to <code>@style/Theme.Leanback.Onboarding</code> in order to specify the
+ default OnboardingFragment styles. -->
+ <attr name="onboardingTheme" format="reference" />
+
+ <!-- Theme attribute for the style of the header in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingHeaderStyle}.-->
+ <attr name="onboardingHeaderStyle" format="reference" />
+ <!-- Theme attribute for the style of the title text in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingTitleStyle}.-->
+ <attr name="onboardingTitleStyle" format="reference" />
+ <!-- Theme attribute for the style of the description text in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingDescriptionStyle}.-->
+ <attr name="onboardingDescriptionStyle" format="reference" />
+
+ <!-- Theme attribute for the style of the navigator container in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingNavigatorContainerStyle}.-->
+ <attr name="onboardingNavigatorContainerStyle" format="reference" />
+ <!-- Theme attribute for the style of the page indicator in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingPageIndicatorStyle}.-->
+ <attr name="onboardingPageIndicatorStyle" format="reference" />
+ <!-- Theme attribute for the style of the start button in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingStartButtonStyle}.-->
+ <attr name="onboardingStartButtonStyle" format="reference" />
+
+ <!-- Theme attribute for the style of the logo in onboarding screen. Default is
+ {@link android.support.v17.leanback.R.style#Widget_Leanback_OnboardingLogoStyle}.-->
+ <attr name="onboardingLogoStyle" format="reference" />
+ </declare-styleable>
+
+ <declare-styleable name="PagingIndicator">
+ <!-- Attributes for the radius of the dot. -->
+ <attr name="dotRadius" format="reference" />
+ <!-- Attributes for the radius of the arrow. -->
+ <attr name="arrowRadius" format="reference" />
+ <!-- Attributes for the distance between the centers of the adjacent dots. -->
+ <attr name="dotToDotGap" format="reference" />
+ <!-- Attributes for the distance between the centers of the arrow circle and the adjacent dot. -->
+ <attr name="dotToArrowGap" format="reference" />
+ <!-- Attribute for background color of the dots in PagingIndicator. -->
+ <attr name="dotBgColor" format="reference" />
+ <!-- Attribute for background color of the arrow in PagingIndicator. -->
+ <attr name="arrowBgColor" format="reference" />
+ </declare-styleable>
+</resources>
diff --git a/v17/leanback/res/values/colors.xml b/v17/leanback/res/values/colors.xml
index 858ace5..bbae9ad 100644
--- a/v17/leanback/res/values/colors.xml
+++ b/v17/leanback/res/values/colors.xml
@@ -51,10 +51,12 @@
<color name="lb_basic_card_title_text_color">#FFEEEEEE</color>
<color name="lb_basic_card_content_text_color">#B3EEEEEE</color>
- <color name="lb_default_brand_color">#FF455A64</color>
- <color name="lb_default_brand_color_dark">#FF222D32</color>
- <color name="lb_default_search_color">#FFFFAA3F</color>
+ <color name="lb_default_brand_color">#FF37474F</color>
+ <color name="lb_default_brand_color_dark">#FF263238</color>
+ <color name="lb_default_search_color">#FF86C739</color>
+ <color name="lb_default_search_icon_color">#FFFFFFFF</color>
+ <color name="lb_media_background_color">#FF384248</color>
<color name="lb_control_button_color">#66EEEEEE</color>
<color name="lb_control_button_text">#EEEEEE</color>
<color name="lb_playback_progress_color_no_theme">#ff40c4ff</color>
@@ -64,6 +66,8 @@
<color name="lb_playback_controls_background_light">#80000000</color>
<color name="lb_playback_controls_background_dark">#c0000000</color>
<color name="lb_playback_controls_time_text_color">#B2EEEEEE</color>
+ <color name="lb_playback_media_row_highlight_color">#1AFFFFFF</color>
+ <color name="lb_playback_media_row_separator_highlight_color">#1AFFFFFF</color>
<color name="lb_search_plate_hint_text_color">#FFCCCCCC</color>
@@ -73,8 +77,12 @@
<!-- refactor naming here -->
<color name="lb_guidedactions_background">#FF111111</color>
<color name="lb_guidedactions_background_dark">#FF080808</color>
- <color name="lb_guidedactions_selector_color">#26FFFFFF</color>
<color name="lb_guidedactions_item_unselected_text_color">#FFF1F1F1</color>
<!-- end refactor naming -->
+ <!-- Onboarding screen -->
+ <color name="lb_page_indicator_dot">#014269</color>
+ <color name="lb_page_indicator_arrow_background">#EEEEEE</color>
+ <color name="lb_page_indicator_arrow_shadow">#4C000000</color>
+
</resources>
diff --git a/v17/leanback/res/values/dimens.xml b/v17/leanback/res/values/dimens.xml
index 053c7e0..c62c281 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -42,6 +42,7 @@
<dimen name="lb_browse_headers_vertical_margin">21dp</dimen>
<dimen name="lb_browse_header_text_size">20sp</dimen>
<dimen name="lb_browse_header_height">24dp</dimen>
+ <dimen name="lb_browse_section_header_text_size">16sp</dimen>
<dimen name="lb_browse_header_fading_length">12dp</dimen>
<dimen name="lb_browse_header_padding_end">8dp</dimen>
@@ -123,6 +124,7 @@
<dimen name="lb_action_button_corner_radius">2dp</dimen>
<dimen name="lb_playback_controls_align_bottom">58dp</dimen>
+ <dimen name="lb_playback_controls_padding_top">216dp</dimen>
<dimen name="lb_playback_controls_padding_bottom">28dp</dimen>
<dimen name="lb_playback_major_fade_translate_y">200dp</dimen>
<dimen name="lb_playback_minor_fade_translate_y">16dp</dimen>
@@ -140,6 +142,13 @@
<dimen name="lb_playback_controls_child_margin_default">48dp</dimen>
<dimen name="lb_playback_controls_child_margin_bigger">64dp</dimen>
<dimen name="lb_playback_controls_child_margin_biggest">88dp</dimen>
+ <dimen name="lb_playback_media_radio_width_with_padding">88dp</dimen>
+ <dimen name="lb_playback_media_item_radio_icon_size">24dp</dimen>
+ <dimen name="lb_playback_media_row_horizontal_padding">32dp</dimen>
+ <dimen name="lb_playback_media_row_details_selector_width">668dp</dimen>
+ <dimen name="lb_playback_media_row_radio_selector_width">72dp</dimen>
+ <dimen name="lb_playback_media_row_selector_round_rect_radius">36dp</dimen>
+ <dimen name="lb_playback_media_row_separator_height">1dp</dimen>
<dimen name="lb_control_button_diameter">90dp</dimen>
<dimen name="lb_control_button_height">64dp</dimen>
@@ -224,26 +233,23 @@
<dimen name="lb_rounded_rect_corner_radius">2dp</dimen>
<!-- GuidedStepFragment -->
- <dimen name="lb_guidedstep_slide_start_distance">-200dp</dimen>
- <dimen name="lb_guidedstep_slide_end_distance">200dp</dimen>
<dimen name="lb_guidedstep_slide_ime_distance">-100dp</dimen>
- <dimen name="lb_guidance_entry_translationX">-120dp</dimen>
-
- <dimen name="lb_guidedactions_entry_translationX">384dp</dimen>
<item name="lb_guidedactions_width_weight" format="float" type="string">0.666666667</item>
<item name="lb_guidedactions_width_weight_two_panels" format="float" type="string">1</item>
<dimen name="lb_guidedactions_section_shadow_width">16dp</dimen>
<dimen name="lb_guidedactions_elevation">12dp</dimen>
- <dimen name="lb_guidedactions_selector_min_height">8dp</dimen>
<dimen name="lb_guidedactions_vertical_padding">12dp</dimen>
+ <item name="lb_guidedstep_height_weight" format="float" type="string">2.0</item>
+ <item name="lb_guidedstep_height_weight_translucent" format="float" type="string">1.0</item>
<item name="lb_guidedactions_item_disabled_text_alpha" format="float" type="string">0.25</item>
<item name="lb_guidedactions_item_disabled_description_text_alpha" format="float" type="string">0.25</item>
<item name="lb_guidedactions_item_unselected_text_alpha" format="float" type="string">1.00</item>
<item name="lb_guidedactions_item_unselected_description_text_alpha" format="float" type="string">0.50</item>
<item name="lb_guidedactions_item_enabled_chevron_alpha" format="float" type="string">1.00</item>
<item name="lb_guidedactions_item_disabled_chevron_alpha" format="float" type="string">0.50</item>
+ <item name="lb_guidedstep_keyline" format="float" type="string">40.0</item>
<dimen name="lb_guidedactions_item_text_width">248dp</dimen>
<dimen name="lb_guidedactions_item_text_width_no_icon">284dp</dimen>
@@ -256,15 +262,39 @@
<dimen name="lb_guidedactions_item_icon_height">32dp</dimen>
<dimen name="lb_guidedactions_item_title_font_size">18sp</dimen>
<dimen name="lb_guidedactions_item_description_font_size">12sp</dimen>
-
- <integer name="lb_guidedstep_entry_animation_delay">550</integer>
- <integer name="lb_guidedstep_entry_animation_duration">250</integer>
+ <dimen name="lb_guidedactions_sublist_bottom_margin">28dp</dimen>
<integer name="lb_guidedactions_item_animation_duration">100</integer>
- <integer name="lb_guidedactions_animation_duration">150</integer>
<integer name="lb_guidedactions_item_title_min_lines">1</integer>
<integer name="lb_guidedactions_item_title_max_lines">3</integer>
<integer name="lb_guidedactions_item_description_min_lines">2</integer>
<!-- end GuidedStepFragment -->
+ <!-- height for picker item. -->
+ <dimen name="picker_item_height">64dp</dimen>
+ <!-- picker column horizontal padding-->
+ <dimen name="picker_column_horizontal_padding">8dp</dimen>
+ <!-- picker separator horizontal padding -->
+ <dimen name="picker_separator_horizontal_padding">4dp</dimen>
+
+ <!-- Onboarding screen -->
+ <dimen name="lb_onboarding_content_width">536dp</dimen>
+ <dimen name="lb_onboarding_header_height">100dp</dimen>
+ <dimen name="lb_onboarding_header_margin_top">64dp</dimen>
+ <dimen name="lb_onboarding_start_button_height">36dp</dimen>
+ <dimen name="lb_onboarding_start_button_margin_bottom">62dp</dimen>
+ <!-- This value should be lb_onboarding_header_margin_top + lb_onboarding_header_height -->
+ <dimen name="lb_onboarding_content_margin_top">164dp</dimen>
+ <!-- This value should be lb_onboarding_start_button_height + lb_onboarding_start_button_margin_bottom -->
+ <dimen name="lb_onboarding_content_margin_bottom">98dp</dimen>
+ <!-- This value should be 2 * lb_page_indicator_arrow_radius + 2 * lb_page_indicator_arrow_shadow_radius -->
+ <dimen name="lb_onboarding_navigation_height">40dp</dimen>
+ <dimen name="lb_page_indicator_arrow_radius">18dp</dimen>
+ <dimen name="lb_page_indicator_arrow_shadow_radius">2dp</dimen>
+ <dimen name="lb_page_indicator_arrow_shadow_offset">1dp</dimen>
+ <dimen name="lb_page_indicator_arrow_gap">32dp</dimen>
+ <dimen name="lb_page_indicator_dot_radius">5dp</dimen>
+ <dimen name="lb_page_indicator_dot_gap">16dp</dimen>
+ <dimen name="lb_onboarding_start_button_translation_offset">16dp</dimen>
+
</resources>
diff --git a/v17/leanback/res/values/ids.xml b/v17/leanback/res/values/ids.xml
index ca84efc8..ab7f568 100644
--- a/v17/leanback/res/values/ids.xml
+++ b/v17/leanback/res/values/ids.xml
@@ -34,11 +34,6 @@
<item type="id" name="lb_control_shuffle" />
<item type="id" name="lb_control_high_quality" />
<item type="id" name="lb_control_closed_captioning" />
-
- <item type="id" name="guidedactions_root2" />
- <item type="id" name="guidedactions_list_background2" />
- <item type="id" name="guidedactions_list2" />
- <item type="id" name="guidedactions_selector2" />
- <item type="id" name="guidedactions_content2" />
+ <item type="id" name="lb_control_picture_in_picture" />
</resources>
diff --git a/v17/leanback/res/values/strings.xml b/v17/leanback/res/values/strings.xml
index 3d93db2..1ca09dd 100644
--- a/v17/leanback/res/values/strings.xml
+++ b/v17/leanback/res/values/strings.xml
@@ -15,6 +15,10 @@
limitations under the License.
-->
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+
+ <!-- contentDescription that will be announced when go inside navigation menu of BrowseFragment-->
+ <string name="lb_navigation_menu_contentDescription">Navigation menu</string>
+
<!-- Image description for the call to search action visible when browsing content [CHAR LIMIT=40] -->
<string name="orb_search_action">Search Action</string>
<!-- Hint showing in the empty search bar [CHAR LIMIT=40] -->
@@ -74,9 +78,24 @@
<string name="lb_playback_controls_closed_captioning_enable">Enable Closed Captioning</string>
<!-- Talkback label for the control button to disable closed captioning -->
<string name="lb_playback_controls_closed_captioning_disable">Disable Closed Captioning</string>
+ <!-- Talkback label for the control button to enter picture in picture mode -->
+ <string name="lb_playback_controls_picture_in_picture">Enter Picture In Picture Mode</string>
<!-- Title of standard Finish action for GuidedStepFragment -->
<string name="lb_guidedaction_finish_title">Finish</string>
<!-- Title of standard Continue action for GuidedStepFragment -->
<string name="lb_guidedaction_continue_title">Continue</string>
+
+ <!-- Separator for date picker [CHAR LIMIT=2] -->
+ <string name="lb_date_separator">/</string>
+ <!-- Separator for time picker [CHAR LIMIT=2] -->
+ <string name="lb_time_separator">:</string>
+
+ <!-- Onboarding screen -->
+ <eat-comment />
+ <!-- Text for "GET STARTED" button. This text should be in ALL CAPS. -->
+ <string name="lb_onboarding_get_started">GET STARTED</string>
+ <!-- Content description for page navigator. -->
+ <string name="lb_onboarding_accessibility_next">Next</string>
+
</resources>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 3904467..39e1413 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -34,6 +34,11 @@
<item name="android:textColor">@color/lb_browse_header_color</item>
</style>
+ <style name="TextAppearance.Leanback.Header.Section">
+ <item name="android:textColor">?defaultSectionHeaderColor</item>
+ <item name="android:textSize">@dimen/lb_browse_section_header_text_size</item>
+ </style>
+
<style name="TextAppearance.Leanback.Row.Header" parent="TextAppearance.Leanback.Header">
</style>
@@ -103,15 +108,13 @@
<item name="infoVisibility">activated</item>
<!-- In order to keep backward compatibility we have to create an icon on right. -->
<item name="lbImageCardViewType">Title|Content|IconOnRight</item>
- <item name="lbImageCardViewImageStyle">@style/Widget.Leanback.ImageCardView.ImageStyle</item>
- <item name="lbImageCardViewTitleStyle">@style/Widget.Leanback.ImageCardView.TitleStyle</item>
- <item name="lbImageCardViewContentStyle">@style/Widget.Leanback.ImageCardView.ContentStyle</item>
- <item name="lbImageCardViewBadgeStyle">@style/Widget.Leanback.ImageCardView.BadgeStyle</item>
- <item name="lbImageCardViewInfoAreaStyle">@style/Widget.Leanback.ImageCardView.InfoAreaStyle</item>
<!-- Deprecated. Use 'Widget.Leanback.ImageCardView.InfoAreaStyle' instead. -->
<item name="infoAreaBackground">@null</item>
</style>
+ <style name="TextAppearance.Leanback.ImageCardView">
+ </style>
+
<style name="Widget.Leanback.ImageCardView" />
<style name="Widget.Leanback.ImageCardView.ImageStyle">
@@ -134,17 +137,26 @@
<item name="android:paddingTop">@dimen/lb_basic_card_info_padding_top</item>
<item name="android:background">@color/lb_basic_card_info_bg_color</item>
</style>
-
+
+ <style name="TextAppearance.Leanback.ImageCardView.Title">
+ <item name="android:textColor">@color/lb_basic_card_title_text_color</item>
+ <item name="android:textSize">@dimen/lb_basic_card_title_text_size</item>
+ </style>
+
<style name="Widget.Leanback.ImageCardView.TitleStyle">
<item name="android:id">@id/title_text</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:maxLines">1</item>
<item name="android:layout_marginBottom">@dimen/lb_basic_card_info_text_margin</item>
- <item name="android:fontFamily">sans-serif-condensed</item>
- <item name="android:textColor">@color/lb_basic_card_title_text_color</item>
- <item name="android:textSize">@dimen/lb_basic_card_title_text_size</item>
+ <item name="android:textAlignment">viewStart</item>
<item name="android:ellipsize">end</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.ImageCardView.Title</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.ImageCardView.Content">
+ <item name="android:textColor">@color/lb_basic_card_content_text_color</item>
+ <item name="android:textSize">@dimen/lb_basic_card_content_text_size</item>
</style>
<style name="Widget.Leanback.ImageCardView.ContentStyle">
@@ -155,10 +167,9 @@
<item name="android:layout_below">@+id/title_text</item>
<item name="android:layout_toStartOf">@+id/extra_badge</item>
<item name="android:maxLines">1</item>
- <item name="android:fontFamily">sans-serif-condensed</item>
- <item name="android:textColor">@color/lb_basic_card_content_text_color</item>
- <item name="android:textSize">@dimen/lb_basic_card_content_text_size</item>
+ <item name="android:textAlignment">viewStart</item>
<item name="android:ellipsize">none</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.ImageCardView.Content</item>
</style>
<style name="Widget.Leanback.ImageCardView.BadgeStyle">
@@ -175,6 +186,7 @@
<item name="android:singleLine">true</item>
<item name="android:gravity">end</item>
<item name="android:ellipsize">end</item>
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.Title</item>
</style>
@@ -202,16 +214,24 @@
<item name="verticalMargin">@dimen/lb_browse_headers_vertical_margin</item>
<item name="android:focusable">true</item>
<item name="android:focusableInTouchMode">true</item>
+ <item name="android:contentDescription">@string/lb_navigation_menu_contentDescription</item>
</style>
<style name="Widget.Leanback.Header" >
<item name="android:minHeight">@dimen/lb_browse_header_height</item>
<item name="android:minWidth">1dp</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.Header</item>
- <item name="android:singleLine">true</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:singleLine">false</item>
+ <item name="android:maxLines">2</item>
<item name="android:ellipsize">none</item>
</style>
+ <style name="Widget.Leanback.Header.Section" >
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.Header.Section</item>
+ <item name="android:singleLine">true</item>
+ </style>
+
<style name="Widget.Leanback.Rows.VerticalGridView" >
<item name="android:paddingBottom">?attr/browsePaddingBottom</item>
<item name="focusOutFront">true</item>
@@ -262,6 +282,7 @@
</style>
<style name="Widget.Leanback.Row.HoverCardTitle" >
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardTitle</item>
<item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
<item name="android:singleLine">true</item>
@@ -269,13 +290,115 @@
</style>
<style name="Widget.Leanback.Row.HoverCardDescription" >
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.Row.HoverCardDescription</item>
<item name="android:maxWidth">@dimen/lb_browse_row_hovercard_max_width</item>
<item name="android:ellipsize">end</item>
<item name="android:maxLines">4</item>
</style>
+
+ <!-- Styles for playback control, playlist header, and playlist content in a default media player layout. -->
+ <style name="Widget.Leanback.PlaybackRow">
+ <item name="android:layout_marginStart">?attr/playbackPaddingStart</item>
+ <item name="android:layout_marginEnd">?attr/playbackPaddingEnd</item>
+ <item name="android:clipChildren">true</item>
+ <item name="android:clipToPadding">true</item>
+ <item name="android:foreground">@null</item>
+ <item name="android:background">#384248</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">48dp</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemRowStyle" parent="Widget.Leanback.PlaybackRow">
+ <item name="android:focusable">false</item>
+ <item name="android:focusableInTouchMode">false</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemSeparatorStyle" parent="Widget.Leanback.PlaybackRow">
+ <item name="android:background">@color/lb_media_background_color</item>
+ <item name="android:src">@color/lb_playback_media_row_separator_highlight_color</item>
+ <item name="android:layout_height">@dimen/lb_playback_media_row_separator_height</item>"
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaListHeaderStyle"
+ parent="Widget.Leanback.PlaybackRow">
+ <item name="android:background">#263238</item>
+ <item name="android:focusable">false</item>
+ <item name="android:focusableInTouchMode">false</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemDetailsStyle">
+ <item name="android:paddingStart">?attr/playbackMediaItemPaddingStart</item>
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:focusable">true</item>
+ <item name="android:focusableInTouchMode">true</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaListHeaderTitle">
+ <item name="android:textColor">#80EEEEEE</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaListHeaderTitleStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_alignParentStart">true</item>
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:gravity">center_vertical</item>"
+ <item name="android:paddingLeft">?attr/playbackMediaItemPaddingStart</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaListHeaderTitle</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemNumber">
+ <item name="android:textColor">#FFFFFF</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemNumberStyle">
+ <item name="android:layout_width">56dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:visibility">gone</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemNumber</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemName">
+ <item name="android:textColor">#FFFFFF</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemNameStyle">
+ <item name="android:layout_width">0dp</item>
+ <item name="android:layout_weight">1</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:gravity">center_vertical</item>"
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemName</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemDuration">
+ <item name="android:textColor">#80FFFFFF</item>
+ <item name="android:textSize">18sp</item>
+ <item name="android:fontFamily">sans-serif-regular</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemDurationStyle">
+ <item name="android:layout_width">56dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:gravity">center_vertical|right</item>
+ <item name="android:visibility">gone</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemDuration</item>
+ </style>
+
<style name="Widget.Leanback.DetailsDescriptionTitleStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionTitle</item>
<item name="android:maxLines">@integer/lb_details_description_title_max_lines</item>
<item name="android:includeFontPadding">false</item>
@@ -287,6 +410,7 @@
</style>
<style name="Widget.Leanback.DetailsDescriptionSubtitleStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionSubtitle</item>
<item name="android:maxLines">@integer/lb_details_description_subtitle_max_lines</item>
<item name="android:includeFontPadding">false</item>
@@ -294,6 +418,7 @@
</style>
<style name="Widget.Leanback.DetailsDescriptionBodyStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionBody</item>
<item name="android:includeFontPadding">false</item>
<item name="android:ellipsize">end</item>
@@ -303,6 +428,7 @@
</style>
<style name="Widget.Leanback.DetailsActionButtonStyle" parent="Widget.Leanback.DetailsActionButtonStyleBase">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsActionButton</item>
<item name="android:includeFontPadding">false</item>
<item name="android:drawablePadding">@dimen/lb_action_icon_margin</item>
@@ -318,14 +444,17 @@
</style>
<style name="Widget.Leanback.PlaybackControlLabelStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlLabel</item>
</style>
<style name="Widget.Leanback.PlaybackControlsTimeStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackControlsTime</item>
</style>
<style name="Widget.Leanback.ErrorMessageStyle">
+ <item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.ErrorMessage</item>
<item name="android:includeFontPadding">false</item>
<item name="android:maxLines">@integer/lb_error_message_max_lines</item>
@@ -335,6 +464,7 @@
<style name="Widget.Leanback.SearchOrbViewStyle">
<item name="searchOrbIcon">?attr/defaultSearchIcon</item>
<item name="searchOrbColor">?attr/defaultSearchColor</item>
+ <item name="searchOrbIconColor">?attr/defaultSearchIconColor</item>
<item name="searchOrbBrightColor">?attr/defaultSearchBrightColor</item>
</style>
@@ -354,96 +484,110 @@
<item name="shuffle">@drawable/lb_ic_shuffle</item>
<item name="high_quality">@drawable/lb_ic_hq</item>
<item name="closed_captioning">@drawable/lb_ic_cc</item>
+ <item name="picture_in_picture">@drawable/lb_ic_pip</item>
</style>
<!-- Style for the main container view in a GuidanceStylist's default layout. -->
<style name="Widget.Leanback.GuidanceContainerStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
- <item name="android:paddingStart">48dp</item>
- <item name="android:paddingEnd">16dp</item>
+ <item name="android:paddingStart">56dp</item>
+ <item name="android:paddingEnd">32dp</item>
<item name="android:clipToPadding">false</item>
</style>
<!-- Style for the title view in a GuidanceStylist's default layout. -->
<style name="Widget.Leanback.GuidanceTitleStyle">
- <item name="android:layout_toStartOf">@id/guidance_icon</item>
+ <item name="android:importantForAccessibility">no</item>
+ <item name="android:layout_below">@id/guidance_breadcrumb</item>
+ <item name="android:layout_toEndOf">@id/guidance_icon</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:layout_alignWithParentIfMissing">true</item>
<item name="android:layout_centerVertical">true</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif-light</item>
- <item name="android:gravity">end</item>
+ <item name="android:gravity">start</item>
<item name="android:maxLines">2</item>
- <item name="android:paddingBottom">4dp</item>
- <item name="android:paddingTop">2dp</item>
<item name="android:textColor">#FFF1F1F1</item>
<item name="android:textSize">36sp</item>
+ <item name="android:textAlignment">viewStart</item>
+ <item name="android:paddingBottom">4dp</item>
+ <item name="android:paddingTop">2dp</item>
</style>
<!-- Style for the description view in a GuidanceStylist's default layout. -->
<style name="Widget.Leanback.GuidanceDescriptionStyle">
+ <item name="android:importantForAccessibility">no</item>
<item name="android:layout_below">@id/guidance_title</item>
- <item name="android:layout_toStartOf">@id/guidance_icon</item>
+ <item name="android:layout_toEndOf">@id/guidance_icon</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
<item name="android:layout_alignWithParentIfMissing">true</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif</item>
- <item name="android:gravity">end</item>
+ <item name="android:gravity">start</item>
<item name="android:maxLines">6</item>
<item name="android:textColor">#88F1F1F1</item>
<item name="android:textSize">14sp</item>
<item name="android:lineSpacingExtra">3dp</item>
+ <item name="android:textAlignment">viewStart</item>
</style>
<!-- Style for the breadcrumb view in a GuidanceStylist's default layout. -->
<style name="Widget.Leanback.GuidanceBreadcrumbStyle">
- <item name="android:layout_above">@id/guidance_title</item>
- <item name="android:layout_toStartOf">@id/guidance_icon</item>
+ <item name="android:importantForAccessibility">no</item>
<item name="android:layout_width">wrap_content</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:layout_alignWithParentIfMissing">true</item>
+ <item name="android:layout_toEndOf">@id/guidance_icon</item>
<item name="android:ellipsize">end</item>
<item name="android:fontFamily">sans-serif-condensed</item>
<item name="android:singleLine">true</item>
<item name="android:textColor">#88F1F1F1</item>
<item name="android:textSize">18sp</item>
+ <item name="android:gravity">start</item>
+ <item name="android:textAlignment">viewStart</item>
</style>
<!-- Style for the icon view in a GuidanceStylist's default layout. -->
<style name="Widget.Leanback.GuidanceIconStyle">
<item name="android:layout_width">140dp</item>
<item name="android:layout_height">wrap_content</item>
- <item name="android:layout_alignParentEnd">true</item>
- <item name="android:layout_centerVertical">true</item>
- <item name="android:layout_marginStart">16dp</item>
+ <item name="android:layout_alignParentStart">true</item>
+ <item name="android:layout_marginEnd">24dp</item>
<item name="android:maxHeight">280dp</item>
<item name="android:scaleType">fitCenter</item>
</style>
- <!-- Style for the selector view in a GuidedActionsStylist's default layout. -->
- <style name="Widget.Leanback.GuidedActionsSelectorStyle">
- <item name="android:layout_width">match_parent</item>
- <item name="android:layout_height">@dimen/lb_guidedactions_selector_min_height</item>
- <item name="android:layout_centerVertical">true</item>
- <item name="android:background">@color/lb_guidedactions_selector_color</item>
- </style>
-
<!-- Style for the vertical grid of actions in a GuidedActionsStylist's default layout. -->
<style name="Widget.Leanback.GuidedActionsListStyle">
+ <item name="android:focusable">false</item>
+ <item name="android:focusableInTouchMode">false</item>
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">match_parent</item>
- <item name="android:focusable">true</item>
+ <item name="focusOutEnd">false</item>
+ <item name="focusOutFront">false</item>
</style>
+ <!-- Style for the vertical grid of sub actions in a GuidedActionsStylist's default layout. -->
+ <style name="Widget.Leanback.GuidedSubActionsListStyle" parent="Widget.Leanback.GuidedActionsListStyle">
+ <item name="android:focusable">true</item>
+ <item name="android:focusableInTouchMode">true</item>
+ <item name="focusOutSideStart">false</item>
+ <item name="focusOutSideEnd">false</item>
+ <item name="android:layout_marginBottom">@dimen/lb_guidedactions_sublist_bottom_margin</item>
+ </style>
+
+ <!-- Style for the vertical grid of button actions in a GuidedActionsStylist's default layout. -->
+ <style name="Widget.Leanback.GuidedButtonActionsListStyle" parent="Widget.Leanback.GuidedActionsListStyle">
+ </style>
<!-- Style for an action's container in a GuidedActionsStylist's default item layout. -->
<style name="Widget.Leanback.GuidedActionItemContainerStyle">
<item name="android:layout_width">match_parent</item>
<item name="android:layout_height">wrap_content</item>
+ <item name="android:foreground">?attr/guidedActionsSelectorDrawable</item>
<item name="android:focusable">true</item>
+ <item name="android:focusableInTouchMode">true</item>
<item name="android:minHeight">@dimen/lb_guidedactions_item_min_height</item>
<item name="android:paddingBottom">@dimen/lb_guidedactions_vertical_padding</item>
<item name="android:paddingStart">@dimen/lb_guidedactions_item_start_padding</item>
@@ -490,7 +634,7 @@
<item name="android:maxLines">@integer/lb_guidedactions_item_title_min_lines</item>
<item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
<item name="android:textSize">@dimen/lb_guidedactions_item_title_font_size</item>
- <item name="android:background">@null</item>
+ <item name="android:textAlignment">viewStart</item>
</style>
<!-- Style for an action's description in a GuidedActionsStylist's default item layout. -->
@@ -503,8 +647,8 @@
<item name="android:maxLines">@integer/lb_guidedactions_item_description_min_lines</item>
<item name="android:textColor">@color/lb_guidedactions_item_unselected_text_color</item>
<item name="android:textSize">@dimen/lb_guidedactions_item_description_font_size</item>
+ <item name="android:textAlignment">viewStart</item>
<item name="android:visibility">gone</item>
- <item name="android:background">@null</item>
</style>
<!-- Style for an action's chevron in a GuidedActionsStylist's default item layout. -->
@@ -518,4 +662,98 @@
<item name="android:visibility">gone</item>
</style>
+ <!-- deprecated style, do not use -->
+ <style name="Widget.Leanback.GuidedActionsContainerStyle"></style>
+ <!-- deprecated style, do not use -->
+ <style name="Widget.Leanback.GuidedActionsSelectorStyle"></style>
+
+ <!-- Style for the header in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingHeaderStyle">
+ <item name="android:layout_width">@dimen/lb_onboarding_content_width</item>
+ <item name="android:layout_height">@dimen/lb_onboarding_header_height</item>
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:layout_centerHorizontal">true</item>
+ <item name="android:layout_marginTop">@dimen/lb_onboarding_header_margin_top</item>
+ <item name="android:clipChildren">false</item>
+ <item name="android:clipToPadding">false</item>
+ <item name="android:orientation">vertical</item>
+ </style>
+
+ <!-- Style for the header title in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingTitleStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">0dp</item>
+ <item name="android:layout_weight">0.5</item>
+ <item name="android:layout_marginBottom">3dp</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">#EEEEEE</item>
+ <item name="android:textSize">34sp</item>
+ <item name="android:lineSpacingExtra">14sp</item>
+ </style>
+
+ <!-- Style for the header description in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingDescriptionStyle">
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">0dp</item>
+ <item name="android:layout_weight">0.5</item>
+ <item name="android:layout_marginTop">3dp</item>
+ <item name="android:fontFamily">sans-serif-light</item>
+ <item name="android:gravity">center</item>
+ <item name="android:textColor">#B3EEEEEE</item>
+ <item name="android:textSize">14sp</item>
+ <item name="android:lineSpacingExtra">10sp</item>
+ </style>
+
+ <!-- Style for the container of page indicator and start button in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingNavigatorContainerStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_marginBottom">58dp</item>
+ <item name="android:layout_centerHorizontal">true</item>
+ <item name="android:layout_alignParentBottom">true</item>
+ </style>
+
+ <!-- Style for the page indicator in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingPageIndicatorStyle">
+ <item name="android:layout_width">@dimen/lb_onboarding_content_width</item>
+ <item name="android:layout_height">@dimen/lb_onboarding_navigation_height</item>
+ <item name="android:layout_gravity">center_horizontal</item>
+ <item name="android:focusable">true</item>
+ <item name="android:contentDescription">@string/lb_onboarding_accessibility_next</item>
+ <item name="dotRadius">@dimen/lb_page_indicator_dot_radius</item>
+ <item name="arrowRadius">@dimen/lb_page_indicator_arrow_radius</item>
+ <item name="dotToDotGap">@dimen/lb_page_indicator_dot_gap</item>
+ <item name="dotToArrowGap">@dimen/lb_page_indicator_arrow_gap</item>
+ <item name="dotBgColor">@color/lb_page_indicator_dot</item>
+ <item name="arrowBgColor">@color/lb_page_indicator_arrow_background</item>
+ </style>
+
+ <!-- Style for the start button in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingStartButtonStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">36dp</item>
+ <item name="android:layout_gravity">center_horizontal</item>
+ <item name="android:layout_marginBottom">4dp</item>
+ <item name="android:background">@drawable/lb_onboarding_start_button_background</item>
+ <item name="android:elevation">1.5dp</item>
+ <item name="android:fontFamily">sans-serif</item>
+ <item name="android:gravity">center_vertical</item>
+ <item name="android:paddingEnd">24dp</item>
+ <item name="android:paddingStart">24dp</item>
+ <item name="android:stateListAnimator">@null</item>
+ <item name="android:text">@string/lb_onboarding_get_started</item>
+ <item name="android:textAllCaps">true</item>
+ <item name="android:textColor">#014269</item>
+ <item name="android:textSize">16sp</item>
+ </style>
+
+ <!-- Style for the logo splash image in OnboardingFragment. -->
+ <style name="Widget.Leanback.OnboardingLogoStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">wrap_content</item>
+ <item name="android:layout_centerInParent">true</item>
+ <item name="android:contentDescription">@null</item>
+ </style>
+
</resources>
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index 36362dd..fd93ea8 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -32,6 +32,11 @@
<item name="baseCardViewStyle">@style/Widget.Leanback.BaseCardViewStyle</item>
<item name="imageCardViewStyle">@style/Widget.Leanback.ImageCardViewStyle</item>
+ <item name="imageCardViewImageStyle">@style/Widget.Leanback.ImageCardView.ImageStyle</item>
+ <item name="imageCardViewTitleStyle">@style/Widget.Leanback.ImageCardView.TitleStyle</item>
+ <item name="imageCardViewContentStyle">@style/Widget.Leanback.ImageCardView.ContentStyle</item>
+ <item name="imageCardViewBadgeStyle">@style/Widget.Leanback.ImageCardView.BadgeStyle</item>
+ <item name="imageCardViewInfoAreaStyle">@style/Widget.Leanback.ImageCardView.InfoAreaStyle</item>
<item name="browsePaddingStart">@dimen/lb_browse_padding_start</item>
<item name="browsePaddingEnd">@dimen/lb_browse_padding_end</item>
@@ -43,11 +48,13 @@
<item name="headersVerticalGridStyle">@style/Widget.Leanback.Headers.VerticalGridView</item>
<item name="headerStyle">@style/Widget.Leanback.Header</item>
+ <item name="sectionHeaderStyle">@style/Widget.Leanback.Header.Section</item>
<item name="rowsVerticalGridStyle">@style/Widget.Leanback.Rows.VerticalGridView</item>
<item name="rowHorizontalGridStyle">@style/Widget.Leanback.Row.HorizontalGridView</item>
<item name="itemsVerticalGridStyle">@style/Widget.Leanback.GridItems.VerticalGridView</item>
+ <item name="browseTitleViewLayout">@layout/lb_browse_title</item>
<item name="browseTitleTextStyle">@style/Widget.Leanback.Title.Text</item>
<item name="browseTitleIconStyle">@style/Widget.Leanback.Title.Icon</item>
<item name="browseTitleViewStyle">@style/Widget.Leanback.TitleView</item>
@@ -59,21 +66,39 @@
<item name="searchOrbViewStyle">@style/Widget.Leanback.SearchOrbViewStyle</item>
+
<item name="detailsDescriptionTitleStyle">@style/Widget.Leanback.DetailsDescriptionTitleStyle</item>
<item name="detailsDescriptionSubtitleStyle">@style/Widget.Leanback.DetailsDescriptionSubtitleStyle</item>
<item name="detailsDescriptionBodyStyle">@style/Widget.Leanback.DetailsDescriptionBodyStyle</item>
<item name="detailsActionButtonStyle">@style/Widget.Leanback.DetailsActionButtonStyle</item>
+ <!-- Attributes used for styling of a playback -->
+ <item name="playbackPaddingStart">@dimen/lb_playback_controls_margin_start</item>
+ <item name="playbackPaddingEnd">@dimen/lb_playback_controls_margin_end</item>
+ <item name="playbackMediaItemPaddingStart">@dimen/lb_playback_media_row_horizontal_padding</item>
+
+ <item name="playbackMediaListHeaderStyle">@style/Widget.Leanback.PlaybackMediaListHeaderStyle</item>
+ <item name="playbackMediaItemRowStyle">@style/Widget.Leanback.PlaybackMediaItemRowStyle</item>
+ <item name="playbackMediaItemSeparatorStyle">@style/Widget.Leanback.PlaybackMediaItemSeparatorStyle</item>
+ <item name="playbackMediaListHeaderTitleStyle">@style/Widget.Leanback.PlaybackMediaListHeaderTitleStyle</item>
+ <item name="playbackMediaItemDetailsStyle">@style/Widget.Leanback.PlaybackMediaItemDetailsStyle</item>
+ <item name="playbackMediaItemNumberStyle">@style/Widget.Leanback.PlaybackMediaItemNumberStyle</item>
+ <item name="playbackMediaItemNameStyle">@style/Widget.Leanback.PlaybackMediaItemNameStyle</item>
+ <item name="playbackMediaItemDurationStyle">@style/Widget.Leanback.PlaybackMediaItemDurationStyle</item>
+
<item name="playbackControlsButtonStyle">@style/Widget.Leanback.PlaybackControlsButtonStyle</item>
<item name="playbackControlButtonLabelStyle">@style/Widget.Leanback.PlaybackControlLabelStyle</item>
<item name="playbackControlsTimeStyle">@style/Widget.Leanback.PlaybackControlsTimeStyle</item>
<item name="playbackControlsActionIcons">@style/Widget.Leanback.PlaybackControlsActionIconsStyle</item>
+
<item name="errorMessageStyle">@style/Widget.Leanback.ErrorMessageStyle</item>
- <!-- TODO should be null, and set programatically to avoid dependence on leanback theme -->
- <item name="defaultSearchColor">@null</item>
- <item name="defaultSearchBrightColor">@null</item>
- <item name="defaultSearchIcon">@null</item>
+ <item name="defaultSearchColor">@color/lb_default_search_color</item>
+ <item name="defaultSearchIconColor">@color/lb_default_search_icon_color</item>
+ <item name="defaultSearchBrightColor">?attr/defaultSearchColor</item>
+ <item name="defaultSearchIcon">@drawable/lb_ic_in_app_search</item>
+
+ <item name="defaultSectionHeaderColor">?attr/defaultSearchColor</item>
<!-- android:windowSharedElementEnterTransition is kept for backward compatibility for apps still refer
to Theme.Leanback, app should use Theme.Leanback.Details instead -->
@@ -116,6 +141,7 @@
<style name="Theme.Leanback.GuidedStep" parent="Theme.LeanbackBase">
<item name="guidedStepThemeFlag">true</item>
+ <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight</item>
<item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter</item>
@@ -139,10 +165,10 @@
<item name="guidedActionsElevation">@dimen/lb_guidedactions_elevation</item>
<item name="guidedActionsBackground">@color/lb_guidedactions_background</item>
<item name="guidedActionsBackgroundDark">@color/lb_guidedactions_background_dark</item>
- <item name="guidedActionsSelectorStyle">@style/Widget.Leanback.GuidedActionsSelectorStyle</item>
+ <item name="guidedActionsSelectorDrawable">?android:attr/selectableItemBackground</item>
<item name="guidedActionsListStyle">@style/Widget.Leanback.GuidedActionsListStyle</item>
- <item name="guidedActionsSelectorShowAnimation">@animator/lb_guidedactions_selector_show</item>
- <item name="guidedActionsSelectorHideAnimation">@animator/lb_guidedactions_selector_hide</item>
+ <item name="guidedSubActionsListStyle">@style/Widget.Leanback.GuidedSubActionsListStyle</item>
+ <item name="guidedButtonActionsListStyle">@style/Widget.Leanback.GuidedButtonActionsListStyle</item>
<item name="guidedActionItemContainerStyle">@style/Widget.Leanback.GuidedActionItemContainerStyle</item>
<item name="guidedActionItemCheckmarkStyle">@style/Widget.Leanback.GuidedActionItemCheckmarkStyle</item>
@@ -162,6 +188,23 @@
<item name="guidedActionTitleMaxLines">@integer/lb_guidedactions_item_title_max_lines</item>
<item name="guidedActionDescriptionMinLines">@integer/lb_guidedactions_item_description_min_lines</item>
<item name="guidedActionVerticalPadding">@dimen/lb_guidedactions_vertical_padding</item>
+ <item name="guidedStepKeyline">@string/lb_guidedstep_keyline</item>
</style>
+ <style name="Theme.Leanback.GuidedStep.Half" parent="Theme.Leanback.GuidedStep">
+ <item name="android:windowEnterTransition">@transition/lb_guidedstep_activity_enter_bottom</item>
+ <item name="guidedStepHeightWeight">@string/lb_guidedstep_height_weight_translucent</item>
+ <item name="android:windowIsTranslucent">true</item>
+ <item name="android:windowBackground">@android:color/transparent</item>
+ </style>
+
+ <style name="Theme.Leanback.Onboarding" parent="Theme.LeanbackBase">
+ <item name="onboardingHeaderStyle">@style/Widget.Leanback.OnboardingHeaderStyle</item>
+ <item name="onboardingTitleStyle">@style/Widget.Leanback.OnboardingTitleStyle</item>
+ <item name="onboardingDescriptionStyle">@style/Widget.Leanback.OnboardingDescriptionStyle</item>
+ <item name="onboardingNavigatorContainerStyle">@style/Widget.Leanback.OnboardingNavigatorContainerStyle</item>
+ <item name="onboardingPageIndicatorStyle">@style/Widget.Leanback.OnboardingPageIndicatorStyle</item>
+ <item name="onboardingStartButtonStyle">@style/Widget.Leanback.OnboardingStartButtonStyle</item>
+ <item name="onboardingLogoStyle">@style/Widget.Leanback.OnboardingLogoStyle</item>
+ </style>
</resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
index c878a86..d298e20 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseFragment.java
@@ -20,27 +20,85 @@
import android.view.View;
import android.view.ViewTreeObserver;
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.util.StateMachine.State;
+
+import static android.support.v17.leanback.util.StateMachine.*;
+
/**
* @hide
*/
class BaseFragment extends BrandedFragment {
- private boolean mEntranceTransitionEnabled = false;
- private boolean mStartEntranceTransitionPending = false;
- private boolean mEntranceTransitionPreparePending = false;
+ /**
+ * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true
+ * Action: none
+ */
+ private final State STATE_ALLOWED = new State() {
+ @Override
+ public boolean canRun() {
+ return TransitionHelper.systemSupportsEntranceTransitions();
+ }
+
+ @Override
+ public void run() {
+ mProgressBarManager.show();
+ }
+ };
+
+ /**
+ * Condition: {@link #isReadyForPrepareEntranceTransition()} is true
+ * Action: {@link #onEntranceTransitionPrepare()} }
+ */
+ private final State STATE_PREPARE = new State() {
+ @Override
+ public boolean canRun() {
+ return isReadyForPrepareEntranceTransition();
+ }
+
+ @Override
+ public void run() {
+ onEntranceTransitionPrepare();
+ }
+ };
+
+ /**
+ * Condition: {@link #isReadyForStartEntranceTransition()} is true
+ * Action: {@link #onExecuteEntranceTransition()} }
+ */
+ private final State STATE_START = new State() {
+ @Override
+ public boolean canRun() {
+ return isReadyForStartEntranceTransition();
+ }
+
+ @Override
+ public void run() {
+ mProgressBarManager.hide();
+ onExecuteEntranceTransition();
+ }
+ };
+
+ final StateMachine mEnterTransitionStates;
+
private Object mEntranceTransition;
+ private final ProgressBarManager mProgressBarManager = new ProgressBarManager();
+
+ BaseFragment() {
+ mEnterTransitionStates = new StateMachine();
+ mEnterTransitionStates.addState(STATE_ALLOWED);
+ mEnterTransitionStates.addState(STATE_PREPARE);
+ mEnterTransitionStates.addState(STATE_START);
+ }
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- if (mEntranceTransitionPreparePending) {
- mEntranceTransitionPreparePending = false;
- onEntranceTransitionPrepare();
- }
- if (mStartEntranceTransitionPending) {
- mStartEntranceTransitionPending = false;
- startEntranceTransition();
- }
+ performPendingStates();
+ }
+
+ final void performPendingStates() {
+ mEnterTransitionStates.runPendingStates();
}
/**
@@ -70,14 +128,8 @@
* override the default transition that browse and details provides.
*/
public void prepareEntranceTransition() {
- if (TransitionHelper.systemSupportsEntranceTransitions()) {
- mEntranceTransitionEnabled = true;
- if (getView() == null) {
- mEntranceTransitionPreparePending = true;
- return;
- }
- onEntranceTransitionPrepare();
- }
+ mEnterTransitionStates.runState(STATE_ALLOWED);
+ mEnterTransitionStates.runState(STATE_PREPARE);
}
/**
@@ -86,7 +138,8 @@
* is reset to false after entrance transition is started.
*/
boolean isEntranceTransitionEnabled() {
- return mEntranceTransitionEnabled;
+ // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call.
+ return STATE_ALLOWED.getStatus() == STATUS_EXECUTED;
}
/**
@@ -127,6 +180,26 @@
}
/**
+ * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ */
+ boolean isReadyForPrepareEntranceTransition() {
+ return getView() != null;
+ }
+
+ /**
+ * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ */
+ boolean isReadyForStartEntranceTransition() {
+ return getView() != null;
+ }
+
+ /**
* When fragment finishes loading data, it should call startEntranceTransition()
* to execute the entrance transition.
* startEntranceTransition() will start transition only if both two conditions
@@ -138,18 +211,10 @@
* and executed when view is created.
*/
public void startEntranceTransition() {
- if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
- return;
- }
- // if view is not created yet, delay until onViewCreated()
- if (getView() == null) {
- mStartEntranceTransitionPending = true;
- return;
- }
- if (mEntranceTransitionPreparePending) {
- mEntranceTransitionPreparePending = false;
- onEntranceTransitionPrepare();
- }
+ mEnterTransitionStates.runState(STATE_START);
+ }
+
+ void onExecuteEntranceTransition() {
// wait till views get their initial position before start transition
final View view = getView();
view.getViewTreeObserver().addOnPreDrawListener(
@@ -158,7 +223,6 @@
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
internalCreateEntranceTransition();
- mEntranceTransitionEnabled = false;
if (mEntranceTransition != null) {
onEntranceTransitionStart();
runEntranceTransition(mEntranceTransition);
@@ -179,7 +243,15 @@
public void onTransitionEnd(Object transition) {
mEntranceTransition = null;
onEntranceTransitionEnd();
+ mEnterTransitionStates.resetStatus();
}
});
}
+
+ /**
+ * Returns the {@link ProgressBarManager}.
+ */
+ public final ProgressBarManager getProgressBarManager() {
+ return mProgressBarManager;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
index a84f7e7..8a65b0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowFragment.java
@@ -15,13 +15,13 @@
import android.app.Fragment;
import android.os.Bundle;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -31,12 +31,14 @@
* An internal base class for a fragment containing a list of rows.
*/
abstract class BaseRowFragment extends Fragment {
+ private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
private ObjectAdapter mAdapter;
private VerticalGridView mVerticalGridView;
private PresenterSelector mPresenterSelector;
private ItemBridgeAdapter mBridgeAdapter;
private int mSelectedPosition = -1;
private boolean mPendingTransitionPrepare;
+ private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
abstract int getLayoutResourceId();
@@ -45,6 +47,7 @@
@Override
public void onChildViewHolderSelected(RecyclerView parent,
RecyclerView.ViewHolder view, int position, int subposition) {
+ mSelectedPosition = position;
onRowSelected(parent, view, position, subposition);
}
};
@@ -71,21 +74,74 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+ }
if (mBridgeAdapter != null) {
- mVerticalGridView.setAdapter(mBridgeAdapter);
- if (mSelectedPosition != -1) {
+ setAdapterAndSelection();
+ }
+ mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+ }
+
+ /**
+ * This class waits for the adapter to be updated before setting the selected
+ * row.
+ */
+ private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+ boolean mIsLateSelection = false;
+
+ public void onChanged() {
+ performLateSelection();
+ }
+
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ performLateSelection();
+ }
+
+ void startLateSelection() {
+ mIsLateSelection = true;
+ mBridgeAdapter.registerAdapterDataObserver(this);
+ }
+
+ void performLateSelection() {
+ clear();
+ if (mVerticalGridView != null) {
mVerticalGridView.setSelectedPosition(mSelectedPosition);
}
}
- mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+
+ void clear() {
+ if (mIsLateSelection) {
+ mIsLateSelection = false;
+ mBridgeAdapter.unregisterAdapterDataObserver(this);
+ }
+ }
+ }
+
+ void setAdapterAndSelection() {
+ mVerticalGridView.setAdapter(mBridgeAdapter);
+ // We don't set the selected position unless we've data in the adapter.
+ boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+ if (lateSelection) {
+ mLateSelectionObserver.startLateSelection();
+ } else if (mSelectedPosition >= 0) {
+ mVerticalGridView.setSelectedPosition(mSelectedPosition);
+ }
}
@Override
public void onDestroyView() {
super.onDestroyView();
+ mLateSelectionObserver.clear();
mVerticalGridView = null;
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+ }
+
/**
* Set the presenter selector used to create and bind views.
*/
@@ -131,11 +187,25 @@
}
/**
+ * Gets position of currently selected row.
+ * @return Position of currently selected row.
+ */
+ public int getSelectedPosition() {
+ return mSelectedPosition;
+ }
+
+ /**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
+ if (mSelectedPosition == position) {
+ return;
+ }
mSelectedPosition = position;
if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+ if (mLateSelectionObserver.mIsLateSelection) {
+ return;
+ }
if (smooth) {
mVerticalGridView.setSelectedPositionSmooth(position);
} else {
@@ -151,6 +221,7 @@
void updateAdapter() {
if (mBridgeAdapter != null) {
// detach observer from ObjectAdapter
+ mLateSelectionObserver.clear();
mBridgeAdapter.clear();
mBridgeAdapter = null;
}
@@ -160,10 +231,7 @@
mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
}
if (mVerticalGridView != null) {
- mVerticalGridView.setAdapter(mBridgeAdapter);
- if (mBridgeAdapter != null && mSelectedPosition != -1) {
- mVerticalGridView.setSelectedPosition(mSelectedPosition);
- }
+ setAdapterAndSelection();
}
}
@@ -175,7 +243,7 @@
}
}
- boolean onTransitionPrepare() {
+ public boolean onTransitionPrepare() {
if (mVerticalGridView != null) {
mVerticalGridView.setAnimateChildLayout(false);
mVerticalGridView.setScrollEnabled(false);
@@ -185,7 +253,7 @@
return false;
}
- void onTransitionStart() {
+ public void onTransitionStart() {
if (mVerticalGridView != null) {
mVerticalGridView.setPruneChild(false);
mVerticalGridView.setLayoutFrozen(true);
@@ -193,7 +261,7 @@
}
}
- void onTransitionEnd() {
+ public void onTransitionEnd() {
// be careful that fragment might be destroyed before header transition ends.
if (mVerticalGridView != null) {
mVerticalGridView.setLayoutFrozen(false);
@@ -204,19 +272,15 @@
}
}
- void setItemAlignment() {
+ public void setAlignment(int windowAlignOffsetTop) {
if (mVerticalGridView != null) {
// align the top edge of item
mVerticalGridView.setItemAlignmentOffset(0);
mVerticalGridView.setItemAlignmentOffsetPercent(
VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
- }
- }
- void setWindowAlignmentFromTop(int alignedTop) {
- if (mVerticalGridView != null) {
// align to a fixed position from top
- mVerticalGridView.setWindowAlignmentOffset(alignedTop);
+ mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
mVerticalGridView.setWindowAlignmentOffsetPercent(
VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
index 753a01c..a202fb2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseRowSupportFragment.java
@@ -17,13 +17,13 @@
import android.support.v4.app.Fragment;
import android.os.Bundle;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.PresenterSelector;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
@@ -33,12 +33,14 @@
* An internal base class for a fragment containing a list of rows.
*/
abstract class BaseRowSupportFragment extends Fragment {
+ private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
private ObjectAdapter mAdapter;
private VerticalGridView mVerticalGridView;
private PresenterSelector mPresenterSelector;
private ItemBridgeAdapter mBridgeAdapter;
private int mSelectedPosition = -1;
private boolean mPendingTransitionPrepare;
+ private LateSelectionObserver mLateSelectionObserver = new LateSelectionObserver();
abstract int getLayoutResourceId();
@@ -47,6 +49,7 @@
@Override
public void onChildViewHolderSelected(RecyclerView parent,
RecyclerView.ViewHolder view, int position, int subposition) {
+ mSelectedPosition = position;
onRowSelected(parent, view, position, subposition);
}
};
@@ -73,21 +76,74 @@
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ mSelectedPosition = savedInstanceState.getInt(CURRENT_SELECTED_POSITION, -1);
+ }
if (mBridgeAdapter != null) {
- mVerticalGridView.setAdapter(mBridgeAdapter);
- if (mSelectedPosition != -1) {
+ setAdapterAndSelection();
+ }
+ mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+ }
+
+ /**
+ * This class waits for the adapter to be updated before setting the selected
+ * row.
+ */
+ private class LateSelectionObserver extends RecyclerView.AdapterDataObserver {
+ boolean mIsLateSelection = false;
+
+ public void onChanged() {
+ performLateSelection();
+ }
+
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ performLateSelection();
+ }
+
+ void startLateSelection() {
+ mIsLateSelection = true;
+ mBridgeAdapter.registerAdapterDataObserver(this);
+ }
+
+ void performLateSelection() {
+ clear();
+ if (mVerticalGridView != null) {
mVerticalGridView.setSelectedPosition(mSelectedPosition);
}
}
- mVerticalGridView.setOnChildViewHolderSelectedListener(mRowSelectedListener);
+
+ void clear() {
+ if (mIsLateSelection) {
+ mIsLateSelection = false;
+ mBridgeAdapter.unregisterAdapterDataObserver(this);
+ }
+ }
+ }
+
+ void setAdapterAndSelection() {
+ mVerticalGridView.setAdapter(mBridgeAdapter);
+ // We don't set the selected position unless we've data in the adapter.
+ boolean lateSelection = mBridgeAdapter.getItemCount() == 0 && mSelectedPosition >= 0;
+ if (lateSelection) {
+ mLateSelectionObserver.startLateSelection();
+ } else if (mSelectedPosition >= 0) {
+ mVerticalGridView.setSelectedPosition(mSelectedPosition);
+ }
}
@Override
public void onDestroyView() {
super.onDestroyView();
+ mLateSelectionObserver.clear();
mVerticalGridView = null;
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+ }
+
/**
* Set the presenter selector used to create and bind views.
*/
@@ -133,11 +189,25 @@
}
/**
+ * Gets position of currently selected row.
+ * @return Position of currently selected row.
+ */
+ public int getSelectedPosition() {
+ return mSelectedPosition;
+ }
+
+ /**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
+ if (mSelectedPosition == position) {
+ return;
+ }
mSelectedPosition = position;
if(mVerticalGridView != null && mVerticalGridView.getAdapter() != null) {
+ if (mLateSelectionObserver.mIsLateSelection) {
+ return;
+ }
if (smooth) {
mVerticalGridView.setSelectedPositionSmooth(position);
} else {
@@ -153,6 +223,7 @@
void updateAdapter() {
if (mBridgeAdapter != null) {
// detach observer from ObjectAdapter
+ mLateSelectionObserver.clear();
mBridgeAdapter.clear();
mBridgeAdapter = null;
}
@@ -162,10 +233,7 @@
mBridgeAdapter = new ItemBridgeAdapter(mAdapter, mPresenterSelector);
}
if (mVerticalGridView != null) {
- mVerticalGridView.setAdapter(mBridgeAdapter);
- if (mBridgeAdapter != null && mSelectedPosition != -1) {
- mVerticalGridView.setSelectedPosition(mSelectedPosition);
- }
+ setAdapterAndSelection();
}
}
@@ -177,7 +245,7 @@
}
}
- boolean onTransitionPrepare() {
+ public boolean onTransitionPrepare() {
if (mVerticalGridView != null) {
mVerticalGridView.setAnimateChildLayout(false);
mVerticalGridView.setScrollEnabled(false);
@@ -187,7 +255,7 @@
return false;
}
- void onTransitionStart() {
+ public void onTransitionStart() {
if (mVerticalGridView != null) {
mVerticalGridView.setPruneChild(false);
mVerticalGridView.setLayoutFrozen(true);
@@ -195,7 +263,7 @@
}
}
- void onTransitionEnd() {
+ public void onTransitionEnd() {
// be careful that fragment might be destroyed before header transition ends.
if (mVerticalGridView != null) {
mVerticalGridView.setLayoutFrozen(false);
@@ -206,19 +274,15 @@
}
}
- void setItemAlignment() {
+ public void setAlignment(int windowAlignOffsetTop) {
if (mVerticalGridView != null) {
// align the top edge of item
mVerticalGridView.setItemAlignmentOffset(0);
mVerticalGridView.setItemAlignmentOffsetPercent(
VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
- }
- }
- void setWindowAlignmentFromTop(int alignedTop) {
- if (mVerticalGridView != null) {
// align to a fixed position from top
- mVerticalGridView.setWindowAlignmentOffset(alignedTop);
+ mVerticalGridView.setWindowAlignmentOffset(windowAlignOffsetTop);
mVerticalGridView.setWindowAlignmentOffsetPercent(
VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
mVerticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
index b9c7d58..5322195 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BaseSupportFragment.java
@@ -22,27 +22,85 @@
import android.view.View;
import android.view.ViewTreeObserver;
+import android.support.v17.leanback.util.StateMachine;
+import android.support.v17.leanback.util.StateMachine.State;
+
+import static android.support.v17.leanback.util.StateMachine.*;
+
/**
* @hide
*/
class BaseSupportFragment extends BrandedSupportFragment {
- private boolean mEntranceTransitionEnabled = false;
- private boolean mStartEntranceTransitionPending = false;
- private boolean mEntranceTransitionPreparePending = false;
+ /**
+ * Condition: {@link TransitionHelper#systemSupportsEntranceTransitions()} is true
+ * Action: none
+ */
+ private final State STATE_ALLOWED = new State() {
+ @Override
+ public boolean canRun() {
+ return TransitionHelper.systemSupportsEntranceTransitions();
+ }
+
+ @Override
+ public void run() {
+ mProgressBarManager.show();
+ }
+ };
+
+ /**
+ * Condition: {@link #isReadyForPrepareEntranceTransition()} is true
+ * Action: {@link #onEntranceTransitionPrepare()} }
+ */
+ private final State STATE_PREPARE = new State() {
+ @Override
+ public boolean canRun() {
+ return isReadyForPrepareEntranceTransition();
+ }
+
+ @Override
+ public void run() {
+ onEntranceTransitionPrepare();
+ }
+ };
+
+ /**
+ * Condition: {@link #isReadyForStartEntranceTransition()} is true
+ * Action: {@link #onExecuteEntranceTransition()} }
+ */
+ private final State STATE_START = new State() {
+ @Override
+ public boolean canRun() {
+ return isReadyForStartEntranceTransition();
+ }
+
+ @Override
+ public void run() {
+ mProgressBarManager.hide();
+ onExecuteEntranceTransition();
+ }
+ };
+
+ final StateMachine mEnterTransitionStates;
+
private Object mEntranceTransition;
+ private final ProgressBarManager mProgressBarManager = new ProgressBarManager();
+
+ BaseSupportFragment() {
+ mEnterTransitionStates = new StateMachine();
+ mEnterTransitionStates.addState(STATE_ALLOWED);
+ mEnterTransitionStates.addState(STATE_PREPARE);
+ mEnterTransitionStates.addState(STATE_START);
+ }
@Override
public void onViewCreated(View view, Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
- if (mEntranceTransitionPreparePending) {
- mEntranceTransitionPreparePending = false;
- onEntranceTransitionPrepare();
- }
- if (mStartEntranceTransitionPending) {
- mStartEntranceTransitionPending = false;
- startEntranceTransition();
- }
+ performPendingStates();
+ }
+
+ final void performPendingStates() {
+ mEnterTransitionStates.runPendingStates();
}
/**
@@ -72,14 +130,8 @@
* override the default transition that browse and details provides.
*/
public void prepareEntranceTransition() {
- if (TransitionHelper.systemSupportsEntranceTransitions()) {
- mEntranceTransitionEnabled = true;
- if (getView() == null) {
- mEntranceTransitionPreparePending = true;
- return;
- }
- onEntranceTransitionPrepare();
- }
+ mEnterTransitionStates.runState(STATE_ALLOWED);
+ mEnterTransitionStates.runState(STATE_PREPARE);
}
/**
@@ -88,7 +140,8 @@
* is reset to false after entrance transition is started.
*/
boolean isEntranceTransitionEnabled() {
- return mEntranceTransitionEnabled;
+ // Enabled when passed STATE_ALLOWED in prepareEntranceTransition call.
+ return STATE_ALLOWED.getStatus() == STATUS_EXECUTED;
}
/**
@@ -129,6 +182,26 @@
}
/**
+ * Returns true if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ * @return True if it is ready to perform {@link #prepareEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ */
+ boolean isReadyForPrepareEntranceTransition() {
+ return getView() != null;
+ }
+
+ /**
+ * Returns true if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ * @return True if it is ready to perform {@link #startEntranceTransition()}, false otherwise.
+ * Subclass may override and add additional conditions.
+ */
+ boolean isReadyForStartEntranceTransition() {
+ return getView() != null;
+ }
+
+ /**
* When fragment finishes loading data, it should call startEntranceTransition()
* to execute the entrance transition.
* startEntranceTransition() will start transition only if both two conditions
@@ -140,18 +213,10 @@
* and executed when view is created.
*/
public void startEntranceTransition() {
- if (!mEntranceTransitionEnabled || mEntranceTransition != null) {
- return;
- }
- // if view is not created yet, delay until onViewCreated()
- if (getView() == null) {
- mStartEntranceTransitionPending = true;
- return;
- }
- if (mEntranceTransitionPreparePending) {
- mEntranceTransitionPreparePending = false;
- onEntranceTransitionPrepare();
- }
+ mEnterTransitionStates.runState(STATE_START);
+ }
+
+ void onExecuteEntranceTransition() {
// wait till views get their initial position before start transition
final View view = getView();
view.getViewTreeObserver().addOnPreDrawListener(
@@ -160,7 +225,6 @@
public boolean onPreDraw() {
view.getViewTreeObserver().removeOnPreDrawListener(this);
internalCreateEntranceTransition();
- mEntranceTransitionEnabled = false;
if (mEntranceTransition != null) {
onEntranceTransitionStart();
runEntranceTransition(mEntranceTransition);
@@ -181,7 +245,15 @@
public void onTransitionEnd(Object transition) {
mEntranceTransition = null;
onEntranceTransitionEnd();
+ mEnterTransitionStates.resetStatus();
}
});
}
+
+ /**
+ * Returns the {@link ProgressBarManager}.
+ */
+ public final ProgressBarManager getProgressBarManager() {
+ return mProgressBarManager;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
index f454144..f16d569 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
@@ -19,44 +19,93 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
- * Fragment support for managing branding on a
- * {@link android.support.v17.leanback.widget.TitleView}.
- * @hide
+ * Fragment class for managing search and branding using a view that implements
+ * {@link TitleViewAdapter.Provider}.
*/
-class BrandedFragment extends Fragment {
+public class BrandedFragment extends Fragment {
// BUNDLE attribute for title is showing
private static final String TITLE_SHOW = "titleShow";
private boolean mShowingTitle = true;
- private String mTitle;
+ private CharSequence mTitle;
private Drawable mBadgeDrawable;
- private TitleView mTitleView;
+ private View mTitleView;
+ private TitleViewAdapter mTitleViewAdapter;
private SearchOrbView.Colors mSearchAffordanceColors;
private boolean mSearchAffordanceColorSet;
private View.OnClickListener mExternalOnSearchClickedListener;
private TitleHelper mTitleHelper;
/**
- * Sets the {@link TitleView}.
+ * Called by {@link #installTitleView(LayoutInflater, ViewGroup, Bundle)} to inflate
+ * title view. Default implementation uses layout file lb_browse_title.
+ * Subclass may override and use its own layout, the layout must have a descendant with id
+ * browse_title_group that implements {@link TitleViewAdapter.Provider}. Subclass may return
+ * null if no title is needed.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param parent Parent of title view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ * @return Title view which must have a descendant with id browse_title_group that implements
+ * {@link TitleViewAdapter.Provider}, or null for no title view.
*/
- void setTitleView(TitleView titleView) {
+ public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ TypedValue typedValue = new TypedValue();
+ boolean found = parent.getContext().getTheme().resolveAttribute(
+ R.attr.browseTitleViewLayout, typedValue, true);
+ return inflater.inflate(found ? typedValue.resourceId : R.layout.lb_browse_title,
+ parent, false);
+ }
+
+ /**
+ * Inflate title view and add to parent. This method should be called in
+ * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param parent Parent of title view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void installTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ View titleLayoutRoot = onInflateTitleView(inflater, parent, savedInstanceState);
+ if (titleLayoutRoot != null) {
+ parent.addView(titleLayoutRoot);
+ setTitleView(titleLayoutRoot.findViewById(R.id.browse_title_group));
+ } else {
+ setTitleView(null);
+ }
+ }
+
+ /**
+ * Sets the view that implemented {@link TitleViewAdapter}.
+ * @param titleView The view that implemented {@link TitleViewAdapter.Provider}.
+ */
+ public void setTitleView(View titleView) {
mTitleView = titleView;
if (mTitleView == null) {
+ mTitleViewAdapter = null;
mTitleHelper = null;
} else {
- mTitleView.setTitle(mTitle);
- mTitleView.setBadgeDrawable(mBadgeDrawable);
+ mTitleViewAdapter = ((TitleViewAdapter.Provider) mTitleView).getTitleViewAdapter();
+ mTitleViewAdapter.setTitle(mTitle);
+ mTitleViewAdapter.setBadgeDrawable(mBadgeDrawable);
if (mSearchAffordanceColorSet) {
- mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
}
if (mExternalOnSearchClickedListener != null) {
- mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+ setOnSearchClickedListener(mExternalOnSearchClickedListener);
}
if (getView() instanceof ViewGroup) {
mTitleHelper = new TitleHelper((ViewGroup) getView(), mTitleView);
@@ -65,13 +114,22 @@
}
/**
- * Returns the {@link TitleView}.
+ * Returns the view that implements {@link TitleViewAdapter.Provider}.
+ * @return The view that implements {@link TitleViewAdapter.Provider}.
*/
- TitleView getTitleView() {
+ public View getTitleView() {
return mTitleView;
}
/**
+ * Returns the {@link TitleViewAdapter} implemented by title view.
+ * @return The {@link TitleViewAdapter} implemented by title view.
+ */
+ public TitleViewAdapter getTitleViewAdapter() {
+ return mTitleViewAdapter;
+ }
+
+ /**
* Returns the {@link TitleHelper}.
*/
TitleHelper getTitleHelper() {
@@ -102,9 +160,10 @@
}
/**
- * Shows or hides the {@link android.support.v17.leanback.widget.TitleView}.
+ * Shows or hides the title view.
+ * @param show True to show title view, false to hide title view.
*/
- void showTitle(boolean show) {
+ public void showTitle(boolean show) {
// TODO: handle interruptions?
if (show == mShowingTitle) {
return;
@@ -116,42 +175,59 @@
}
/**
- * Sets the drawable displayed in the browse fragment title.
+ * Changes title view's components visibility and shows title.
+ * @param flags Flags representing the visibility of components inside title view.
+ * @see TitleViewAdapter#SEARCH_VIEW_VISIBLE
+ * @see TitleViewAdapter#BRANDING_VIEW_VISIBLE
+ * @see TitleViewAdapter#FULL_VIEW_VISIBLE
+ * @see TitleViewAdapter#updateComponentsVisibility(int)
+ */
+ public void showTitle(int flags) {
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.updateComponentsVisibility(flags);
+ }
+ showTitle(true);
+ }
+
+ /**
+ * Sets the drawable displayed in the fragment title.
*
- * @param drawable The Drawable to display in the browse fragment title.
+ * @param drawable The Drawable to display in the fragment title.
*/
public void setBadgeDrawable(Drawable drawable) {
if (mBadgeDrawable != drawable) {
mBadgeDrawable = drawable;
- if (mTitleView != null) {
- mTitleView.setBadgeDrawable(drawable);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setBadgeDrawable(drawable);
}
}
}
/**
* Returns the badge drawable used in the fragment title.
+ * @return The badge drawable used in the fragment title.
*/
public Drawable getBadgeDrawable() {
return mBadgeDrawable;
}
/**
- * Sets a title for the browse fragment.
+ * Sets title text for the fragment.
*
- * @param title The title of the browse fragment.
+ * @param title The title text of the fragment.
*/
- public void setTitle(String title) {
+ public void setTitle(CharSequence title) {
mTitle = title;
- if (mTitleView != null) {
- mTitleView.setTitle(title);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setTitle(title);
}
}
/**
- * Returns the title for the browse fragment.
+ * Returns the title text for the fragment.
+ * @return Title text for the fragment.
*/
- public String getTitle() {
+ public CharSequence getTitle() {
return mTitle;
}
@@ -169,19 +245,22 @@
*/
public void setOnSearchClickedListener(View.OnClickListener listener) {
mExternalOnSearchClickedListener = listener;
- if (mTitleView != null) {
- mTitleView.setOnSearchClickedListener(listener);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setOnSearchClickedListener(listener);
}
}
/**
- * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the search affordance.
+ * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
+ * search affordance.
+ *
+ * @param colors Colors used to draw search affordance.
*/
public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
mSearchAffordanceColors = colors;
mSearchAffordanceColorSet = true;
- if (mTitleView != null) {
- mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
}
}
@@ -193,10 +272,10 @@
if (mSearchAffordanceColorSet) {
return mSearchAffordanceColors;
}
- if (mTitleView == null) {
+ if (mTitleViewAdapter == null) {
throw new IllegalStateException("Fragment views not yet created");
}
- return mTitleView.getSearchAffordanceColors();
+ return mTitleViewAdapter.getSearchAffordanceColors();
}
/**
@@ -219,15 +298,16 @@
@Override
public void onStart() {
super.onStart();
- if (mTitleView != null) {
- mTitleView.setVisibility(mShowingTitle ? View.VISIBLE : View.INVISIBLE);
+ if (mTitleViewAdapter != null) {
+ showTitle(mShowingTitle);
+ mTitleViewAdapter.setAnimationEnabled(true);
}
}
@Override
public void onPause() {
- if (mTitleView != null) {
- mTitleView.enableAnimation(false);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setAnimationEnabled(false);
}
super.onPause();
}
@@ -235,8 +315,18 @@
@Override
public void onResume() {
super.onResume();
- if (mTitleView != null) {
- mTitleView.enableAnimation(true);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setAnimationEnabled(true);
}
}
+
+ /**
+ * Returns true/false to indicate the visibility of TitleView.
+ *
+ * @return boolean to indicate whether or not it's showing the title.
+ */
+ public final boolean isShowingTitle() {
+ return mShowingTitle;
+ }
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
index 18fae4f..ee5c479 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
@@ -21,44 +21,93 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.SearchOrbView;
import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
/**
- * Fragment support for managing branding on a
- * {@link android.support.v17.leanback.widget.TitleView}.
- * @hide
+ * Fragment class for managing search and branding using a view that implements
+ * {@link TitleViewAdapter.Provider}.
*/
-class BrandedSupportFragment extends Fragment {
+public class BrandedSupportFragment extends Fragment {
// BUNDLE attribute for title is showing
private static final String TITLE_SHOW = "titleShow";
private boolean mShowingTitle = true;
- private String mTitle;
+ private CharSequence mTitle;
private Drawable mBadgeDrawable;
- private TitleView mTitleView;
+ private View mTitleView;
+ private TitleViewAdapter mTitleViewAdapter;
private SearchOrbView.Colors mSearchAffordanceColors;
private boolean mSearchAffordanceColorSet;
private View.OnClickListener mExternalOnSearchClickedListener;
private TitleHelper mTitleHelper;
/**
- * Sets the {@link TitleView}.
+ * Called by {@link #installTitleView(LayoutInflater, ViewGroup, Bundle)} to inflate
+ * title view. Default implementation uses layout file lb_browse_title.
+ * Subclass may override and use its own layout, the layout must have a descendant with id
+ * browse_title_group that implements {@link TitleViewAdapter.Provider}. Subclass may return
+ * null if no title is needed.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param parent Parent of title view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ * @return Title view which must have a descendant with id browse_title_group that implements
+ * {@link TitleViewAdapter.Provider}, or null for no title view.
*/
- void setTitleView(TitleView titleView) {
+ public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ TypedValue typedValue = new TypedValue();
+ boolean found = parent.getContext().getTheme().resolveAttribute(
+ R.attr.browseTitleViewLayout, typedValue, true);
+ return inflater.inflate(found ? typedValue.resourceId : R.layout.lb_browse_title,
+ parent, false);
+ }
+
+ /**
+ * Inflate title view and add to parent. This method should be called in
+ * {@link Fragment#onCreateView(LayoutInflater, ViewGroup, Bundle)}.
+ * @param inflater The LayoutInflater object that can be used to inflate
+ * any views in the fragment,
+ * @param parent Parent of title view.
+ * @param savedInstanceState If non-null, this fragment is being re-constructed
+ * from a previous saved state as given here.
+ */
+ public void installTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ View titleLayoutRoot = onInflateTitleView(inflater, parent, savedInstanceState);
+ if (titleLayoutRoot != null) {
+ parent.addView(titleLayoutRoot);
+ setTitleView(titleLayoutRoot.findViewById(R.id.browse_title_group));
+ } else {
+ setTitleView(null);
+ }
+ }
+
+ /**
+ * Sets the view that implemented {@link TitleViewAdapter}.
+ * @param titleView The view that implemented {@link TitleViewAdapter.Provider}.
+ */
+ public void setTitleView(View titleView) {
mTitleView = titleView;
if (mTitleView == null) {
+ mTitleViewAdapter = null;
mTitleHelper = null;
} else {
- mTitleView.setTitle(mTitle);
- mTitleView.setBadgeDrawable(mBadgeDrawable);
+ mTitleViewAdapter = ((TitleViewAdapter.Provider) mTitleView).getTitleViewAdapter();
+ mTitleViewAdapter.setTitle(mTitle);
+ mTitleViewAdapter.setBadgeDrawable(mBadgeDrawable);
if (mSearchAffordanceColorSet) {
- mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
}
if (mExternalOnSearchClickedListener != null) {
- mTitleView.setOnSearchClickedListener(mExternalOnSearchClickedListener);
+ setOnSearchClickedListener(mExternalOnSearchClickedListener);
}
if (getView() instanceof ViewGroup) {
mTitleHelper = new TitleHelper((ViewGroup) getView(), mTitleView);
@@ -67,13 +116,22 @@
}
/**
- * Returns the {@link TitleView}.
+ * Returns the view that implements {@link TitleViewAdapter.Provider}.
+ * @return The view that implements {@link TitleViewAdapter.Provider}.
*/
- TitleView getTitleView() {
+ public View getTitleView() {
return mTitleView;
}
/**
+ * Returns the {@link TitleViewAdapter} implemented by title view.
+ * @return The {@link TitleViewAdapter} implemented by title view.
+ */
+ public TitleViewAdapter getTitleViewAdapter() {
+ return mTitleViewAdapter;
+ }
+
+ /**
* Returns the {@link TitleHelper}.
*/
TitleHelper getTitleHelper() {
@@ -104,9 +162,10 @@
}
/**
- * Shows or hides the {@link android.support.v17.leanback.widget.TitleView}.
+ * Shows or hides the title view.
+ * @param show True to show title view, false to hide title view.
*/
- void showTitle(boolean show) {
+ public void showTitle(boolean show) {
// TODO: handle interruptions?
if (show == mShowingTitle) {
return;
@@ -118,42 +177,59 @@
}
/**
- * Sets the drawable displayed in the browse fragment title.
+ * Changes title view's components visibility and shows title.
+ * @param flags Flags representing the visibility of components inside title view.
+ * @see TitleViewAdapter#SEARCH_VIEW_VISIBLE
+ * @see TitleViewAdapter#BRANDING_VIEW_VISIBLE
+ * @see TitleViewAdapter#FULL_VIEW_VISIBLE
+ * @see TitleViewAdapter#updateComponentsVisibility(int)
+ */
+ public void showTitle(int flags) {
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.updateComponentsVisibility(flags);
+ }
+ showTitle(true);
+ }
+
+ /**
+ * Sets the drawable displayed in the fragment title.
*
- * @param drawable The Drawable to display in the browse fragment title.
+ * @param drawable The Drawable to display in the fragment title.
*/
public void setBadgeDrawable(Drawable drawable) {
if (mBadgeDrawable != drawable) {
mBadgeDrawable = drawable;
- if (mTitleView != null) {
- mTitleView.setBadgeDrawable(drawable);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setBadgeDrawable(drawable);
}
}
}
/**
* Returns the badge drawable used in the fragment title.
+ * @return The badge drawable used in the fragment title.
*/
public Drawable getBadgeDrawable() {
return mBadgeDrawable;
}
/**
- * Sets a title for the browse fragment.
+ * Sets title text for the fragment.
*
- * @param title The title of the browse fragment.
+ * @param title The title text of the fragment.
*/
- public void setTitle(String title) {
+ public void setTitle(CharSequence title) {
mTitle = title;
- if (mTitleView != null) {
- mTitleView.setTitle(title);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setTitle(title);
}
}
/**
- * Returns the title for the browse fragment.
+ * Returns the title text for the fragment.
+ * @return Title text for the fragment.
*/
- public String getTitle() {
+ public CharSequence getTitle() {
return mTitle;
}
@@ -171,19 +247,22 @@
*/
public void setOnSearchClickedListener(View.OnClickListener listener) {
mExternalOnSearchClickedListener = listener;
- if (mTitleView != null) {
- mTitleView.setOnSearchClickedListener(listener);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setOnSearchClickedListener(listener);
}
}
/**
- * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the search affordance.
+ * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
+ * search affordance.
+ *
+ * @param colors Colors used to draw search affordance.
*/
public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
mSearchAffordanceColors = colors;
mSearchAffordanceColorSet = true;
- if (mTitleView != null) {
- mTitleView.setSearchAffordanceColors(mSearchAffordanceColors);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setSearchAffordanceColors(mSearchAffordanceColors);
}
}
@@ -195,10 +274,10 @@
if (mSearchAffordanceColorSet) {
return mSearchAffordanceColors;
}
- if (mTitleView == null) {
+ if (mTitleViewAdapter == null) {
throw new IllegalStateException("Fragment views not yet created");
}
- return mTitleView.getSearchAffordanceColors();
+ return mTitleViewAdapter.getSearchAffordanceColors();
}
/**
@@ -221,15 +300,16 @@
@Override
public void onStart() {
super.onStart();
- if (mTitleView != null) {
- mTitleView.setVisibility(mShowingTitle ? View.VISIBLE : View.INVISIBLE);
+ if (mTitleViewAdapter != null) {
+ showTitle(mShowingTitle);
+ mTitleViewAdapter.setAnimationEnabled(true);
}
}
@Override
public void onPause() {
- if (mTitleView != null) {
- mTitleView.enableAnimation(false);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setAnimationEnabled(false);
}
super.onPause();
}
@@ -237,8 +317,18 @@
@Override
public void onResume() {
super.onResume();
- if (mTitleView != null) {
- mTitleView.enableAnimation(true);
+ if (mTitleViewAdapter != null) {
+ mTitleViewAdapter.setAnimationEnabled(true);
}
}
+
+ /**
+ * Returns true/false to indicate the visibility of TitleView.
+ *
+ * @return boolean to indicate whether or not it's showing the title.
+ */
+ public final boolean isShowingTitle() {
+ return mShowingTitle;
+ }
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 8c417a4..913a6b3 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -13,44 +13,46 @@
*/
package android.support.v17.leanback.app;
-import android.support.annotation.ColorInt;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.LeanbackTransitionHelper;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.TitleView;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v4.view.ViewCompat;
-import android.util.Log;
-import android.app.Activity;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentManager.BackStackEntry;
-import android.content.Context;
+import android.app.FragmentTransaction;
import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.InvisibleRowPresenter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewTreeObserver;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import java.util.HashMap;
+import java.util.Map;
/**
* A fragment for creating Leanback browse screens. It is composed of a
@@ -80,7 +82,8 @@
static final String HEADER_STACK_INDEX = "headerStackIndex";
// BUNDLE attribute for saving header show/hide status when backstack is not used:
static final String HEADER_SHOW = "headerShow";
-
+ private static final String IS_PAGE_ROW = "isPageRow";
+ private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
int mLastEntryCount;
@@ -125,6 +128,13 @@
} else if (count < mLastEntryCount) {
// if popped "headers" backstack, initiate the show header transition if needed
if (mIndexOfHeadersBackStack >= count) {
+ if (!isHeadersDataReady()) {
+ // if main fragment was restored first before BrowseFragment's adapater gets
+ // restored: dont start header transition, but add the entry back.
+ getFragmentManager().beginTransaction()
+ .addToBackStack(mWithHeadersBackStackName).commit();
+ return;
+ }
mIndexOfHeadersBackStack = -1;
if (!mShowingHeaders) {
startHeadersTransitionInternal(true);
@@ -198,6 +208,390 @@
}
}
+ /**
+ * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
+ * fragments can interact with {@link BrowseFragment} using this interface.
+ */
+ public interface FragmentHost {
+ /**
+ * Fragments are required to invoke this callback once their view is created
+ * inside {@link Fragment#onViewCreated} method. {@link BrowseFragment} starts the entrance
+ * animation only after receiving this callback. Failure to invoke this method
+ * will lead to fragment not showing up.
+ *
+ * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+ */
+ void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+ /**
+ * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
+ * is created for transition, the entrance animation only after receiving this callback.
+ * Failure to invoke this method will lead to fragment not showing up.
+ *
+ * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+ */
+ void notifyDataReady(MainFragmentAdapter fragmentAdapter);
+
+ /**
+ * Show or hide title view in {@link BrowseFragment} for fragments mapped to
+ * {@link PageRow}. Otherwise the request is ignored, in that case BrowseFragment is fully
+ * in control of showing/hiding title view.
+ * <p>
+ * When HeadersFragment is visible, BrowseFragment will hide search affordance view if
+ * there are other focusable rows above currently focused row.
+ *
+ * @param show Boolean indicating whether or not to show the title view.
+ */
+ void showTitleView(boolean show);
+ }
+
+ /**
+ * Default implementation of {@link FragmentHost} that is used only by
+ * {@link BrowseFragment}.
+ */
+ private final class FragmentHostImpl implements FragmentHost {
+ boolean mShowTitleView = true;
+ boolean mDataReady = false;
+
+ @Override
+ public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+ performPendingStates();
+ }
+
+ @Override
+ public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
+ mDataReady = true;
+
+ // If fragment host is not the currently active fragment (in BrowseFragment), then
+ // ignore the request.
+ if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+ return;
+ }
+
+ // We only honor showTitle request for PageRows.
+ if (!mIsPageRow) {
+ return;
+ }
+
+ performPendingStates();
+ }
+
+ @Override
+ public void showTitleView(boolean show) {
+ mShowTitleView = show;
+
+ // If fragment host is not the currently active fragment (in BrowseFragment), then
+ // ignore the request.
+ if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+ return;
+ }
+
+ // We only honor showTitle request for PageRows.
+ if (!mIsPageRow) {
+ return;
+ }
+
+ updateTitleViewVisibility();
+ }
+ }
+
+ /**
+ * Interface that defines the interaction between {@link BrowseFragment} and it's main
+ * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+ * it will be used to get the fragment to be shown in the content section. Clients can
+ * provide any implementation of fragment and customize it's interaction with
+ * {@link BrowseFragment} by overriding the necessary methods.
+ *
+ * <p>
+ * Clients are expected to provide
+ * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+ * implementations of {@link MainFragmentAdapter} for given content types. Currently
+ * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+ * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+ * {@link PageRow} - {@link android.support.v17.leanback.app.RowsFragment.MainFragmentAdapter}.
+ *
+ * <p>
+ * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+ * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+ * and provide that through {@link MainFragmentAdapterRegistry}.
+ * {@link MainFragmentAdapter} implementation can supply any fragment and override
+ * just those interactions that makes sense.
+ */
+ public static class MainFragmentAdapter<T extends Fragment> {
+ private boolean mScalingEnabled;
+ private final T mFragment;
+ private FragmentHostImpl mFragmentHost;
+
+ public MainFragmentAdapter(T fragment) {
+ this.mFragment = fragment;
+ }
+
+ public final T getFragment() {
+ return mFragment;
+ }
+
+ /**
+ * Returns whether its scrolling.
+ */
+ public boolean isScrolling() {
+ return false;
+ }
+
+ /**
+ * Set the visibility of titles/hovercard of browse rows.
+ */
+ public void setExpand(boolean expand) {
+ }
+
+ /**
+ * For rows that willing to participate entrance transition, this function
+ * hide views if afterTransition is true, show views if afterTransition is false.
+ */
+ public void setEntranceTransitionState(boolean state) {
+ }
+
+ /**
+ * Sets the window alignment and also the pivots for scale operation.
+ */
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ }
+
+ /**
+ * Callback indicating transition prepare start.
+ */
+ public boolean onTransitionPrepare() {
+ return false;
+ }
+
+ /**
+ * Callback indicating transition start.
+ */
+ public void onTransitionStart() {
+ }
+
+ /**
+ * Callback indicating transition end.
+ */
+ public void onTransitionEnd() {
+ }
+
+ /**
+ * Returns whether row scaling is enabled.
+ */
+ public boolean isScalingEnabled() {
+ return mScalingEnabled;
+ }
+
+ /**
+ * Sets the row scaling property.
+ */
+ public void setScalingEnabled(boolean scalingEnabled) {
+ this.mScalingEnabled = scalingEnabled;
+ }
+
+ /**
+ * Returns the current host interface so that main fragment can interact with
+ * {@link BrowseFragment}.
+ */
+ public final FragmentHost getFragmentHost() {
+ return mFragmentHost;
+ }
+
+ void setFragmentHost(FragmentHostImpl fragmentHost) {
+ this.mFragmentHost = fragmentHost;
+ }
+ }
+
+ /**
+ * Interface to be implemented by all fragments for providing an instance of
+ * {@link MainFragmentAdapter}. Both {@link RowsFragment} and custom fragment provided
+ * against {@link PageRow} will need to implement this interface.
+ */
+ public interface MainFragmentAdapterProvider {
+ /**
+ * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseFragment}
+ * would use to communicate with the target fragment.
+ */
+ MainFragmentAdapter getMainFragmentAdapter();
+ }
+
+ /**
+ * Interface to be implemented by {@link RowsFragment} and it's subclasses for providing
+ * an instance of {@link MainFragmentRowsAdapter}.
+ */
+ public interface MainFragmentRowsAdapterProvider {
+ /**
+ * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseFragment}
+ * would use to communicate with the target fragment.
+ */
+ MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ }
+
+ /**
+ * This is used to pass information to {@link RowsFragment} or its subclasses.
+ * {@link BrowseFragment} uses this interface to pass row based interaction events to
+ * the target fragment.
+ */
+ public static class MainFragmentRowsAdapter<T extends Fragment> {
+ private final T mFragment;
+
+ public MainFragmentRowsAdapter(T fragment) {
+ if (fragment == null) {
+ throw new IllegalArgumentException("Fragment can't be null");
+ }
+ this.mFragment = fragment;
+ }
+
+ public final T getFragment() {
+ return mFragment;
+ }
+ /**
+ * Set the visibility titles/hover of browse rows.
+ */
+ public void setAdapter(ObjectAdapter adapter) {
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ }
+
+ /**
+ * Selects a Row and perform an optional task on the Row.
+ */
+ public void setSelectedPosition(int rowPosition,
+ boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ }
+
+ /**
+ * Selects a Row.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth) {
+ }
+
+ /**
+ * Returns the selected position.
+ */
+ public int getSelectedPosition() {
+ return 0;
+ }
+ }
+
+ private boolean createMainFragment(ObjectAdapter adapter, int position) {
+ Object item = null;
+ if (adapter == null || adapter.size() == 0) {
+ return false;
+ } else {
+ if (position < 0) {
+ position = 0;
+ } else if (position >= adapter.size()) {
+ throw new IllegalArgumentException(
+ String.format("Invalid position %d requested", position));
+ }
+ item = adapter.get(position);
+ }
+
+ mSelectedPosition = position;
+ boolean oldIsPageRow = mIsPageRow;
+ mIsPageRow = item instanceof PageRow;
+ boolean swap;
+
+ if (mMainFragment == null) {
+ swap = true;
+ } else {
+ if (oldIsPageRow) {
+ swap = true;
+ } else {
+ swap = mIsPageRow;
+ }
+ }
+
+ if (swap) {
+ mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+ if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
+ throw new IllegalArgumentException(
+ "Fragment must implement MainFragmentAdapterProvider");
+ }
+
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
+ .getMainFragmentRowsAdapter();
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ }
+
+ return swap;
+ }
+
+ /**
+ * Factory class responsible for creating fragment given the current item. {@link ListRow}
+ * should returns {@link RowsFragment} or it's subclass whereas {@link PageRow}
+ * can return any fragment class.
+ */
+ public abstract static class FragmentFactory<T extends Fragment> {
+ public abstract T createFragment(Object row);
+ }
+
+ /**
+ * FragmentFactory implementation for {@link ListRow}.
+ */
+ public static class ListRowFragmentFactory extends FragmentFactory<RowsFragment> {
+ @Override
+ public RowsFragment createFragment(Object row) {
+ return new RowsFragment();
+ }
+ }
+
+ /**
+ * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+ * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+ * handling {@link ListRow}. Developers can override that and also if they want to
+ * use custom fragment, they can register a custom {@link FragmentFactory}
+ * against {@link PageRow}.
+ */
+ public final static class MainFragmentAdapterRegistry {
+ private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap();
+ private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+ public MainFragmentAdapterRegistry() {
+ registerFragment(ListRow.class, sDefaultFragmentFactory);
+ }
+
+ public void registerFragment(Class rowClass, FragmentFactory factory) {
+ mItemToFragmentFactoryMapping.put(rowClass, factory);
+ }
+
+ public Fragment createFragment(Object item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Item can't be null");
+ }
+
+ FragmentFactory fragmentFactory = mItemToFragmentFactoryMapping.get(item.getClass());
+ if (fragmentFactory == null && !(item instanceof PageRow)) {
+ fragmentFactory = sDefaultFragmentFactory;
+ }
+
+ return fragmentFactory.createFragment(item);
+ }
+ }
+
private static final String TAG = "BrowseFragment";
private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
@@ -213,26 +607,35 @@
/** The headers fragment is disabled and will never be shown. */
public static final int HEADERS_DISABLED = 3;
- private RowsFragment mRowsFragment;
+ private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry
+ = new MainFragmentAdapterRegistry();
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private Fragment mMainFragment;
private HeadersFragment mHeadersFragment;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
private ObjectAdapter mAdapter;
+ private PresenterSelector mAdapterPresenter;
+ private PresenterSelector mWrappingPresenterSelector;
private int mHeadersState = HEADERS_ENABLED;
private int mBrandColor = Color.TRANSPARENT;
private boolean mBrandColorSet;
private BrowseFrameLayout mBrowseFrame;
+ private ScaleFrameLayout mScaleFrameLayout;
private boolean mHeadersBackStackEnabled = true;
private String mWithHeadersBackStackName;
private boolean mShowingHeaders = true;
private boolean mCanShowHeaders = true;
private int mContainerListMarginStart;
private int mContainerListAlignTop;
- private boolean mRowScaleEnabled = true;
+ private boolean mMainFragmentScaleEnabled = true;
private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
private OnItemViewClickedListener mOnItemViewClickedListener;
private int mSelectedPosition = -1;
+ private float mScaleFactor;
+ private boolean mIsPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -246,7 +649,6 @@
private BrowseTransitionListener mBrowseTransitionListener;
private static final String ARG_TITLE = BrowseFragment.class.getCanonicalName() + ".title";
- private static final String ARG_BADGE_URI = BrowseFragment.class.getCanonicalName() + ".badge";
private static final String ARG_HEADERS_STATE =
BrowseFragment.class.getCanonicalName() + ".headersState";
@@ -296,6 +698,43 @@
}
/**
+ * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
+ * DividerRow and PageRow.
+ */
+ private void createAndSetWrapperPresenter() {
+ final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
+ if (adapterPresenter == null) {
+ throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
+ }
+ if (adapterPresenter == mAdapterPresenter) {
+ return;
+ }
+ mAdapterPresenter = adapterPresenter;
+
+ Presenter[] presenters = adapterPresenter.getPresenters();
+ final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
+ final Presenter[] allPresenters = new Presenter[presenters.length + 1];
+ System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
+ allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
+ mAdapter.setPresenterSelector(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object item) {
+ Row row = (Row) item;
+ if (row.isRenderedAsRowView()) {
+ return adapterPresenter.getPresenter(item);
+ } else {
+ return invisibleRowPresenter;
+ }
+ }
+
+ @Override
+ public Presenter[] getPresenters() {
+ return allPresenters;
+ }
+ });
+ }
+
+ /**
* Sets the adapter containing the rows for the fragment.
*
* <p>The items referenced by the adapter must be be derived from
@@ -307,12 +746,24 @@
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
- if (mRowsFragment != null) {
- mRowsFragment.setAdapter(adapter);
+ createAndSetWrapperPresenter();
+ if (getView() == null) {
+ return;
+ }
+ replaceMainFragment(mSelectedPosition);
+
+ if (adapter != null) {
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(adapter);
+ }
mHeadersFragment.setAdapter(adapter);
}
}
+ public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
+ return mMainFragmentAdapterRegistry;
+ }
+
/**
* Returns the adapter containing the rows for the fragment.
*/
@@ -335,15 +786,37 @@
}
/**
+ * Get RowsFragment if it's bound to BrowseFragment or null if either BrowseFragment has
+ * not been created yet or a different fragment is bound to it.
+ *
+ * @return RowsFragment if it's bound to BrowseFragment or null otherwise.
+ */
+ public RowsFragment getRowsFragment() {
+ if (mMainFragment instanceof RowsFragment) {
+ return (RowsFragment) mMainFragment;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get currently bound HeadersFragment or null if HeadersFragment has not been created yet.
+ * @return Currently bound HeadersFragment or null if HeadersFragment has not been created yet.
+ */
+ public HeadersFragment getHeadersFragment() {
+ return mHeadersFragment;
+ }
+
+ /**
* Sets an item clicked listener on the fragment.
* OnItemViewClickedListener will override {@link View.OnClickListener} that
* item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
- * So in general, developer should choose one of the listeners but not both.
+ * So in general, developer should choose one of the listeners but not both.
*/
public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
mOnItemViewClickedListener = listener;
- if (mRowsFragment != null) {
- mRowsFragment.setOnItemViewClickedListener(listener);
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
}
}
@@ -399,24 +872,37 @@
}
/**
- * Enables scaling of rows when headers are present.
- * By default enabled to increase density.
+ * @deprecated use {@link BrowseFragment#enableMainFragmentScaling(boolean)} instead.
*
* @param enable true to enable row scaling
*/
+ @Deprecated
public void enableRowScaling(boolean enable) {
- mRowScaleEnabled = enable;
- if (mRowsFragment != null) {
- mRowsFragment.enableRowScaling(mRowScaleEnabled);
- }
+ enableMainFragmentScaling(enable);
+ }
+
+ /**
+ * Enables scaling of main fragment when headers are present. For the page/row fragment,
+ * scaling is enabled only when both this method and
+ * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+ *
+ * @param enable true to enable row scaling
+ */
+ public void enableMainFragmentScaling(boolean enable) {
+ mMainFragmentScaleEnabled = enable;
}
private void startHeadersTransitionInternal(final boolean withHeaders) {
if (getFragmentManager().isDestroyed()) {
return;
}
+ if (!isHeadersDataReady()) {
+ return;
+ }
mShowingHeaders = withHeaders;
- mRowsFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+ mMainFragmentAdapter.onTransitionPrepare();
+ mMainFragmentAdapter.onTransitionStart();
+ onExpandTransitionStart(!withHeaders, new Runnable() {
@Override
public void run() {
mHeadersFragment.onTransitionPrepare();
@@ -425,8 +911,8 @@
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
}
- TransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
- mHeadersTransition);
+ TransitionHelper.runTransition(
+ withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
if (mHeadersBackStackEnabled) {
if (!withHeaders) {
getFragmentManager().beginTransaction()
@@ -444,12 +930,9 @@
});
}
- private boolean isVerticalScrolling() {
+ boolean isVerticalScrolling() {
// don't run transition
- return mHeadersFragment.getVerticalGridView().getScrollState()
- != HorizontalGridView.SCROLL_STATE_IDLE
- || mRowsFragment.getVerticalGridView().getScrollState()
- != HorizontalGridView.SCROLL_STATE_IDLE;
+ return mHeadersFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
}
@@ -470,15 +953,14 @@
if (getTitleView() != null && getTitleView().hasFocus() &&
direction == View.FOCUS_DOWN) {
return mCanShowHeaders && mShowingHeaders ?
- mHeadersFragment.getVerticalGridView() :
- mRowsFragment.getVerticalGridView();
+ mHeadersFragment.getVerticalGridView() : mMainFragment.getView();
}
boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
if (mCanShowHeaders && direction == towardStart) {
- if (isVerticalScrolling() || mShowingHeaders) {
+ if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
return focused;
}
return mHeadersFragment.getVerticalGridView();
@@ -486,13 +968,20 @@
if (isVerticalScrolling()) {
return focused;
}
- return mRowsFragment.getVerticalGridView();
+ return mMainFragment.getView();
+ } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
+ // disable focus_down moving into PageFragment.
+ return focused;
} else {
return null;
}
}
};
+ private final boolean isHeadersDataReady() {
+ return mAdapter != null && mAdapter.size() != 0;
+ }
+
private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
new BrowseFrameLayout.OnChildFocusListener() {
@@ -508,8 +997,8 @@
return true;
}
}
- if (mRowsFragment != null && mRowsFragment.getView() != null &&
- mRowsFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ if (mMainFragment != null && mMainFragment.getView() != null &&
+ mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
return true;
}
if (getTitleView() != null &&
@@ -517,7 +1006,7 @@
return true;
}
return false;
- };
+ }
@Override
public void onRequestChildFocus(View child, View focused) {
@@ -537,6 +1026,9 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+ outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
if (mBackStackChangedListener != null) {
mBackStackChangedListener.save(outState);
} else {
@@ -570,6 +1062,17 @@
}
}
}
+
+ mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+ }
+
+ @Override
+ public void onDestroyView() {
+ mMainFragmentRowsAdapter = null;
+ mMainFragmentAdapter = null;
+ mMainFragment = null;
+ mHeadersFragment = null;
+ super.onDestroyView();
}
@Override
@@ -583,41 +1086,77 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
- mRowsFragment = new RowsFragment();
+
+ if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
mHeadersFragment = new HeadersFragment();
- getChildFragmentManager().beginTransaction()
- .replace(R.id.browse_headers_dock, mHeadersFragment)
- .replace(R.id.browse_container_dock, mRowsFragment).commit();
+
+ createMainFragment(mAdapter, mSelectedPosition);
+ FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+ .replace(R.id.browse_headers_dock, mHeadersFragment);
+
+ if (mMainFragment != null) {
+ ft.replace(R.id.scale_frame, mMainFragment);
+ } else {
+ // Empty adapter used to guard against lazy adapter loading. When this
+ // fragment is instantiated, mAdapter might not have the data or might not
+ // have been set. In either of those cases mFragmentAdapter will be null.
+ // This way we can maintain the invariant that mMainFragmentAdapter is never
+ // null and it avoids doing null checks all over the code.
+ mMainFragmentAdapter = new MainFragmentAdapter(null);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ }
+
+ ft.commit();
} else {
mHeadersFragment = (HeadersFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
- mRowsFragment = (RowsFragment) getChildFragmentManager()
- .findFragmentById(R.id.browse_container_dock);
+ mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+
+ mIsPageRow = savedInstanceState != null ?
+ savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
+
+ mSelectedPosition = savedInstanceState != null ?
+ savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
+
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter();
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
}
mHeadersFragment.setHeadersGone(!mCanShowHeaders);
-
- mRowsFragment.setAdapter(mAdapter);
if (mHeaderPresenterSelector != null) {
mHeadersFragment.setPresenterSelector(mHeaderPresenterSelector);
}
mHeadersFragment.setAdapter(mAdapter);
-
- mRowsFragment.enableRowScaling(mRowScaleEnabled);
- mRowsFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
mHeadersFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
mHeadersFragment.setOnHeaderClickedListener(mHeaderClickedListener);
- mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
- setTitleView((TitleView) root.findViewById(R.id.browse_title_group));
+ getProgressBarManager().setRootView((ViewGroup)root);
mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+ installTitleView(inflater, mBrowseFrame, savedInstanceState);
+
+ mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+ mScaleFrameLayout.setPivotX(0);
+ mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+ setupMainFragment();
+
if (mBrandColorSet) {
mHeadersFragment.setBackgroundColor(mBrandColor);
}
@@ -640,9 +1179,30 @@
setEntranceTransitionEndState();
}
});
+
return root;
}
+ private void setupMainFragment() {
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ }
+
+ @Override
+ boolean isReadyForPrepareEntranceTransition() {
+ return mMainFragment != null && mMainFragment.getView() != null;
+ }
+
+ @Override
+ boolean isReadyForStartEntranceTransition() {
+ return mMainFragment != null && mMainFragment.getView() != null
+ && (!mIsPageRow || mMainFragmentAdapter.mFragmentHost.mDataReady);
+ }
+
private void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(getActivity(),
mShowingHeaders ?
@@ -655,19 +1215,28 @@
@Override
public void onTransitionEnd(Object transition) {
mHeadersTransition = null;
- mRowsFragment.onTransitionEnd();
- mHeadersFragment.onTransitionEnd();
- if (mShowingHeaders) {
- VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
- if (headerGridView != null && !headerGridView.hasFocus()) {
- headerGridView.requestFocus();
- }
- } else {
- VerticalGridView rowsGridView = mRowsFragment.getVerticalGridView();
- if (rowsGridView != null && !rowsGridView.hasFocus()) {
- rowsGridView.requestFocus();
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.onTransitionEnd();
+ if (!mShowingHeaders && mMainFragment != null) {
+ View mainFragmentView = mMainFragment.getView();
+ if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
+ mainFragmentView.requestFocus();
+ }
}
}
+ if (mHeadersFragment != null) {
+ mHeadersFragment.onTransitionEnd();
+ if (mShowingHeaders) {
+ VerticalGridView headerGridView = mHeadersFragment.getVerticalGridView();
+ if (headerGridView != null && !headerGridView.hasFocus()) {
+ headerGridView.requestFocus();
+ }
+ }
+ }
+
+ // Animate TitleView once header animation is complete.
+ updateTitleViewVisibility();
+
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
}
@@ -675,6 +1244,68 @@
});
}
+ void updateTitleViewVisibility() {
+ if (!mShowingHeaders) {
+ boolean showTitleView;
+ if (mIsPageRow && mMainFragmentAdapter != null) {
+ // page fragment case:
+ showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+ } else {
+ // regular row view case:
+ showTitleView = isFirstRowWithContent(mSelectedPosition);
+ }
+ if (showTitleView) {
+ showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
+ } else {
+ showTitle(false);
+ }
+ } else {
+ // when HeaderFragment is showing, showBranding and showSearch are slightly different
+ boolean showBranding;
+ boolean showSearch;
+ if (mIsPageRow && mMainFragmentAdapter != null) {
+ showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+ } else {
+ showBranding = isFirstRowWithContent(mSelectedPosition);
+ }
+ showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
+ int flags = 0;
+ if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
+ if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
+ if (flags != 0) {
+ showTitle(flags);
+ } else {
+ showTitle(false);
+ }
+ }
+ }
+
+ boolean isFirstRowWithContentOrPageRow(int rowPosition) {
+ if (mAdapter == null || mAdapter.size() == 0) {
+ return true;
+ }
+ for (int i = 0; i < mAdapter.size(); i++) {
+ final Row row = (Row) mAdapter.get(i);
+ if (row.isRenderedAsRowView() || row instanceof PageRow) {
+ return rowPosition == i;
+ }
+ }
+ return true;
+ }
+
+ boolean isFirstRowWithContent(int rowPosition) {
+ if (mAdapter == null || mAdapter.size() == 0) {
+ return true;
+ }
+ for (int i = 0; i < mAdapter.size(); i++) {
+ final Row row = (Row) mAdapter.get(i);
+ if (row.isRenderedAsRowView()) {
+ return rowPosition == i;
+ }
+ }
+ return true;
+ }
+
/**
* Sets the {@link PresenterSelector} used to render the row headers.
*
@@ -688,15 +1319,6 @@
}
}
- private void setRowsAlignedLeft(boolean alignLeft) {
- MarginLayoutParams lp;
- View containerList;
- containerList = mRowsFragment.getView();
- lp = (MarginLayoutParams) containerList.getLayoutParams();
- lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
- containerList.setLayoutParams(lp);
- }
-
private void setHeadersOnScreen(boolean onScreen) {
MarginLayoutParams lp;
View containerList;
@@ -710,27 +1332,46 @@
if (DEBUG) Log.v(TAG, "showHeaders " + show);
mHeadersFragment.setHeadersEnabled(show);
setHeadersOnScreen(show);
- setRowsAlignedLeft(!show);
- mRowsFragment.setExpand(!show);
+ expandMainFragment(!show);
+ }
+
+ private void expandMainFragment(boolean expand) {
+ MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+ params.setMarginStart(!expand ? mContainerListMarginStart : 0);
+ mScaleFrameLayout.setLayoutParams(params);
+ mMainFragmentAdapter.setExpand(expand);
+
+ setMainFragmentAlignment();
+ final float scaleFactor = !expand
+ && mMainFragmentScaleEnabled
+ && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+ mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+ mScaleFrameLayout.setChildScale(scaleFactor);
}
private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
new HeadersFragment.OnHeaderClickedListener() {
@Override
- public void onHeaderClicked() {
+ public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
return;
}
startHeadersTransitionInternal(false);
- mRowsFragment.getVerticalGridView().requestFocus();
+ mMainFragment.getView().requestFocus();
}
};
- private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
+ class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+ public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
+ mMainFragmentRowsAdapter = fragmentRowsAdapter;
+ }
+
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
- int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
+ int position = mMainFragmentRowsAdapter.getSelectedPosition();
if (DEBUG) Log.v(TAG, "row selected position " + position);
onRowSelected(position);
if (mExternalOnItemViewSelectedListener != null) {
@@ -744,7 +1385,7 @@
new HeadersFragment.OnHeaderViewSelectedListener() {
@Override
public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
- int position = mHeadersFragment.getVerticalGridView().getSelectedPosition();
+ int position = mHeadersFragment.getSelectedPosition();
if (DEBUG) Log.v(TAG, "header selected position " + position);
onRowSelected(position);
}
@@ -754,21 +1395,60 @@
if (position != mSelectedPosition) {
mSetSelectionRunnable.post(
position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-
- if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
- showTitle(true);
- } else {
- showTitle(false);
- }
}
}
private void setSelection(int position, boolean smooth) {
- if (position != NO_POSITION) {
- mRowsFragment.setSelectedPosition(position, smooth);
- mHeadersFragment.setSelectedPosition(position, smooth);
+ if (position == NO_POSITION) {
+ return;
+ }
+
+ mHeadersFragment.setSelectedPosition(position, smooth);
+ replaceMainFragment(position);
+
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
}
mSelectedPosition = position;
+
+ updateTitleViewVisibility();
+ }
+
+ private void replaceMainFragment(int position) {
+ if (createMainFragment(mAdapter, position)) {
+ swapToMainFragment();
+ expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+ setupMainFragment();
+ performPendingStates();
+ }
+ }
+
+ private void swapToMainFragment() {
+ final VerticalGridView gridView = mHeadersFragment.getVerticalGridView();
+ if (isShowingHeaders() && gridView != null
+ && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ // if user is scrolling HeadersFragment, swap to empty fragment and wait scrolling
+ // finishes.
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.scale_frame, new Fragment()).commit();
+ gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ gridView.removeOnScrollListener(this);
+ FragmentManager fm = getChildFragmentManager();
+ Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
+ if (currentFragment != mMainFragment) {
+ fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
+ }
+ }
+ }
+ });
+ } else {
+ // Otherwise swap immediately
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.scale_frame, mMainFragment).commit();
+ }
}
/**
@@ -779,6 +1459,14 @@
}
/**
+ * Gets position of currently selected row.
+ * @return Position of currently selected row.
+ */
+ public int getSelectedPosition() {
+ return mSelectedPosition;
+ }
+
+ /**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
@@ -786,30 +1474,73 @@
position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
}
+ /**
+ * Selects a Row and perform an optional task on the Row. For example
+ * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+ * scrolls to 11th row and selects 6th item on that row. The method will be ignored if
+ * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+ * ViewGroup, Bundle)}).
+ *
+ * @param rowPosition Which row to select.
+ * @param smooth True to scroll to the row, false for no animation.
+ * @param rowHolderTask Optional task to perform on the Row. When the task is not null, headers
+ * fragment will be collapsed.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ if (mMainFragmentAdapterRegistry == null) {
+ return;
+ }
+ if (rowHolderTask != null) {
+ startHeadersTransition(false);
+ }
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
- mHeadersFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
- mHeadersFragment.setItemAlignment();
- mRowsFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
- mRowsFragment.setItemAlignment();
-
- mRowsFragment.setScalePivots(0, mContainerListAlignTop);
+ mHeadersFragment.setAlignment(mContainerListAlignTop);
+ setMainFragmentAlignment();
if (mCanShowHeaders && mShowingHeaders && mHeadersFragment.getView() != null) {
mHeadersFragment.getView().requestFocus();
} else if ((!mCanShowHeaders || !mShowingHeaders)
- && mRowsFragment.getView() != null) {
- mRowsFragment.getView().requestFocus();
+ && mMainFragment.getView() != null) {
+ mMainFragment.getView().requestFocus();
}
+
if (mCanShowHeaders) {
showHeaders(mShowingHeaders);
}
+
if (isEntranceTransitionEnabled()) {
setEntranceTransitionStartState();
}
}
+ private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+ if (expand) {
+ callback.run();
+ return;
+ }
+ // Run a "pre" layout when we go non-expand, in order to get the initial
+ // positions of added rows.
+ new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
+ }
+
+ private void setMainFragmentAlignment() {
+ int alignOffset = mContainerListAlignTop;
+ if (mMainFragmentScaleEnabled
+ && mMainFragmentAdapter.isScalingEnabled()
+ && mShowingHeaders) {
+ alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+ }
+ mMainFragmentAdapter.setAlignment(alignOffset);
+ }
+
/**
* Enables/disables headers transition on back key support. This is enabled by
* default. The BrowseFragment will add a back stack entry when headers are
@@ -903,39 +1634,88 @@
@Override
protected void onEntranceTransitionPrepare() {
mHeadersFragment.onTransitionPrepare();
- mRowsFragment.onTransitionPrepare();
+ // setEntranceTransitionStartState() might be called when mMainFragment is null,
+ // make sure it is called.
+ mMainFragmentAdapter.setEntranceTransitionState(false);
+ mMainFragmentAdapter.onTransitionPrepare();
}
@Override
protected void onEntranceTransitionStart() {
mHeadersFragment.onTransitionStart();
- mRowsFragment.onTransitionStart();
+ mMainFragmentAdapter.onTransitionStart();
}
@Override
protected void onEntranceTransitionEnd() {
- mRowsFragment.onTransitionEnd();
- mHeadersFragment.onTransitionEnd();
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.onTransitionEnd();
+ }
+
+ if (mHeadersFragment != null) {
+ mHeadersFragment.onTransitionEnd();
+ }
}
void setSearchOrbViewOnScreen(boolean onScreen) {
- View searchOrbView = getTitleView().getSearchAffordanceView();
- MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
- lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
- searchOrbView.setLayoutParams(lp);
+ View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
+ if (searchOrbView != null) {
+ MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ searchOrbView.setLayoutParams(lp);
+ }
}
void setEntranceTransitionStartState() {
setHeadersOnScreen(false);
setSearchOrbViewOnScreen(false);
- mRowsFragment.setEntranceTransitionState(false);
+ mMainFragmentAdapter.setEntranceTransitionState(false);
}
void setEntranceTransitionEndState() {
setHeadersOnScreen(mShowingHeaders);
setSearchOrbViewOnScreen(true);
- mRowsFragment.setEntranceTransitionState(true);
+ mMainFragmentAdapter.setEntranceTransitionState(true);
}
-}
+ private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+ private final View mView;
+ private final Runnable mCallback;
+ private int mState;
+ private MainFragmentAdapter mainFragmentAdapter;
+
+ final static int STATE_INIT = 0;
+ final static int STATE_FIRST_DRAW = 1;
+ final static int STATE_SECOND_DRAW = 2;
+
+ ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+ mView = view;
+ mCallback = callback;
+ mainFragmentAdapter = adapter;
+ }
+
+ void execute() {
+ mView.getViewTreeObserver().addOnPreDrawListener(this);
+ mainFragmentAdapter.setExpand(false);
+ mState = STATE_INIT;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ if (getView() == null || getActivity() == null) {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ if (mState == STATE_INIT) {
+ mainFragmentAdapter.setExpand(true);
+ mState = STATE_FIRST_DRAW;
+ } else if (mState == STATE_FIRST_DRAW) {
+ mCallback.run();
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mState = STATE_SECOND_DRAW;
+ }
+ return false;
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
index fa7f61e..bfea088 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -15,44 +15,46 @@
*/
package android.support.v17.leanback.app;
-import android.support.annotation.ColorInt;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.transition.LeanbackTransitionHelper;
-import android.support.v17.leanback.transition.TransitionHelper;
-import android.support.v17.leanback.transition.TransitionListener;
-import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.ItemBridgeAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.Presenter;
-import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.TitleView;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Row;
-import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.SearchOrbView;
-import android.support.v4.view.ViewCompat;
-import android.util.Log;
-import android.support.v4.app.FragmentActivity;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentManager.BackStackEntry;
-import android.content.Context;
+import android.support.v4.app.FragmentTransaction;
import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.ColorInt;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.BrowseFrameLayout;
+import android.support.v17.leanback.widget.InvisibleRowPresenter;
+import android.support.v17.leanback.widget.ListRow;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.OnItemViewClickedListener;
+import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.PageRow;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.Row;
+import android.support.v17.leanback.widget.RowHeaderPresenter;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.ScaleFrameLayout;
+import android.support.v17.leanback.widget.TitleViewAdapter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
-import android.view.View.OnClickListener;
import android.view.ViewGroup;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewTreeObserver;
-import android.graphics.Color;
-import android.graphics.Rect;
-import android.graphics.drawable.Drawable;
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
+import java.util.HashMap;
+import java.util.Map;
/**
* A fragment for creating Leanback browse screens. It is composed of a
@@ -82,7 +84,8 @@
static final String HEADER_STACK_INDEX = "headerStackIndex";
// BUNDLE attribute for saving header show/hide status when backstack is not used:
static final String HEADER_SHOW = "headerShow";
-
+ private static final String IS_PAGE_ROW = "isPageRow";
+ private static final String CURRENT_SELECTED_POSITION = "currentSelectedPosition";
final class BackStackListener implements FragmentManager.OnBackStackChangedListener {
int mLastEntryCount;
@@ -127,6 +130,13 @@
} else if (count < mLastEntryCount) {
// if popped "headers" backstack, initiate the show header transition if needed
if (mIndexOfHeadersBackStack >= count) {
+ if (!isHeadersDataReady()) {
+ // if main fragment was restored first before BrowseSupportFragment's adapater gets
+ // restored: dont start header transition, but add the entry back.
+ getFragmentManager().beginTransaction()
+ .addToBackStack(mWithHeadersBackStackName).commit();
+ return;
+ }
mIndexOfHeadersBackStack = -1;
if (!mShowingHeaders) {
startHeadersTransitionInternal(true);
@@ -200,6 +210,390 @@
}
}
+ /**
+ * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
+ * fragments can interact with {@link BrowseSupportFragment} using this interface.
+ */
+ public interface FragmentHost {
+ /**
+ * Fragments are required to invoke this callback once their view is created
+ * inside {@link Fragment#onViewCreated} method. {@link BrowseSupportFragment} starts the entrance
+ * animation only after receiving this callback. Failure to invoke this method
+ * will lead to fragment not showing up.
+ *
+ * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+ */
+ void notifyViewCreated(MainFragmentAdapter fragmentAdapter);
+
+ /**
+ * Fragments mapped to {@link PageRow} are required to invoke this callback once their data
+ * is created for transition, the entrance animation only after receiving this callback.
+ * Failure to invoke this method will lead to fragment not showing up.
+ *
+ * @param fragmentAdapter {@link MainFragmentAdapter} used by the current fragment.
+ */
+ void notifyDataReady(MainFragmentAdapter fragmentAdapter);
+
+ /**
+ * Show or hide title view in {@link BrowseSupportFragment} for fragments mapped to
+ * {@link PageRow}. Otherwise the request is ignored, in that case BrowseSupportFragment is fully
+ * in control of showing/hiding title view.
+ * <p>
+ * When HeadersSupportFragment is visible, BrowseSupportFragment will hide search affordance view if
+ * there are other focusable rows above currently focused row.
+ *
+ * @param show Boolean indicating whether or not to show the title view.
+ */
+ void showTitleView(boolean show);
+ }
+
+ /**
+ * Default implementation of {@link FragmentHost} that is used only by
+ * {@link BrowseSupportFragment}.
+ */
+ private final class FragmentHostImpl implements FragmentHost {
+ boolean mShowTitleView = true;
+ boolean mDataReady = false;
+
+ @Override
+ public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+ performPendingStates();
+ }
+
+ @Override
+ public void notifyDataReady(MainFragmentAdapter fragmentAdapter) {
+ mDataReady = true;
+
+ // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
+ // ignore the request.
+ if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+ return;
+ }
+
+ // We only honor showTitle request for PageRows.
+ if (!mIsPageRow) {
+ return;
+ }
+
+ performPendingStates();
+ }
+
+ @Override
+ public void showTitleView(boolean show) {
+ mShowTitleView = show;
+
+ // If fragment host is not the currently active fragment (in BrowseSupportFragment), then
+ // ignore the request.
+ if (mMainFragmentAdapter == null || mMainFragmentAdapter.getFragmentHost() != this) {
+ return;
+ }
+
+ // We only honor showTitle request for PageRows.
+ if (!mIsPageRow) {
+ return;
+ }
+
+ updateTitleViewVisibility();
+ }
+ }
+
+ /**
+ * Interface that defines the interaction between {@link BrowseSupportFragment} and it's main
+ * content fragment. The key method is {@link MainFragmentAdapter#getFragment()},
+ * it will be used to get the fragment to be shown in the content section. Clients can
+ * provide any implementation of fragment and customize it's interaction with
+ * {@link BrowseSupportFragment} by overriding the necessary methods.
+ *
+ * <p>
+ * Clients are expected to provide
+ * an instance of {@link MainFragmentAdapterRegistry} which will be responsible for providing
+ * implementations of {@link MainFragmentAdapter} for given content types. Currently
+ * we support different types of content - {@link ListRow}, {@link PageRow} or any subtype
+ * of {@link Row}. We provide an out of the box adapter implementation for any rows other than
+ * {@link PageRow} - {@link android.support.v17.leanback.app.RowsSupportFragment.MainFragmentAdapter}.
+ *
+ * <p>
+ * {@link PageRow} is intended to give full flexibility to developers in terms of Fragment
+ * design. Users will have to provide an implementation of {@link MainFragmentAdapter}
+ * and provide that through {@link MainFragmentAdapterRegistry}.
+ * {@link MainFragmentAdapter} implementation can supply any fragment and override
+ * just those interactions that makes sense.
+ */
+ public static class MainFragmentAdapter<T extends Fragment> {
+ private boolean mScalingEnabled;
+ private final T mFragment;
+ private FragmentHostImpl mFragmentHost;
+
+ public MainFragmentAdapter(T fragment) {
+ this.mFragment = fragment;
+ }
+
+ public final T getFragment() {
+ return mFragment;
+ }
+
+ /**
+ * Returns whether its scrolling.
+ */
+ public boolean isScrolling() {
+ return false;
+ }
+
+ /**
+ * Set the visibility of titles/hovercard of browse rows.
+ */
+ public void setExpand(boolean expand) {
+ }
+
+ /**
+ * For rows that willing to participate entrance transition, this function
+ * hide views if afterTransition is true, show views if afterTransition is false.
+ */
+ public void setEntranceTransitionState(boolean state) {
+ }
+
+ /**
+ * Sets the window alignment and also the pivots for scale operation.
+ */
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ }
+
+ /**
+ * Callback indicating transition prepare start.
+ */
+ public boolean onTransitionPrepare() {
+ return false;
+ }
+
+ /**
+ * Callback indicating transition start.
+ */
+ public void onTransitionStart() {
+ }
+
+ /**
+ * Callback indicating transition end.
+ */
+ public void onTransitionEnd() {
+ }
+
+ /**
+ * Returns whether row scaling is enabled.
+ */
+ public boolean isScalingEnabled() {
+ return mScalingEnabled;
+ }
+
+ /**
+ * Sets the row scaling property.
+ */
+ public void setScalingEnabled(boolean scalingEnabled) {
+ this.mScalingEnabled = scalingEnabled;
+ }
+
+ /**
+ * Returns the current host interface so that main fragment can interact with
+ * {@link BrowseSupportFragment}.
+ */
+ public final FragmentHost getFragmentHost() {
+ return mFragmentHost;
+ }
+
+ void setFragmentHost(FragmentHostImpl fragmentHost) {
+ this.mFragmentHost = fragmentHost;
+ }
+ }
+
+ /**
+ * Interface to be implemented by all fragments for providing an instance of
+ * {@link MainFragmentAdapter}. Both {@link RowsSupportFragment} and custom fragment provided
+ * against {@link PageRow} will need to implement this interface.
+ */
+ public interface MainFragmentAdapterProvider {
+ /**
+ * Returns an instance of {@link MainFragmentAdapter} that {@link BrowseSupportFragment}
+ * would use to communicate with the target fragment.
+ */
+ MainFragmentAdapter getMainFragmentAdapter();
+ }
+
+ /**
+ * Interface to be implemented by {@link RowsSupportFragment} and it's subclasses for providing
+ * an instance of {@link MainFragmentRowsAdapter}.
+ */
+ public interface MainFragmentRowsAdapterProvider {
+ /**
+ * Returns an instance of {@link MainFragmentRowsAdapter} that {@link BrowseSupportFragment}
+ * would use to communicate with the target fragment.
+ */
+ MainFragmentRowsAdapter getMainFragmentRowsAdapter();
+ }
+
+ /**
+ * This is used to pass information to {@link RowsSupportFragment} or its subclasses.
+ * {@link BrowseSupportFragment} uses this interface to pass row based interaction events to
+ * the target fragment.
+ */
+ public static class MainFragmentRowsAdapter<T extends Fragment> {
+ private final T mFragment;
+
+ public MainFragmentRowsAdapter(T fragment) {
+ if (fragment == null) {
+ throw new IllegalArgumentException("Fragment can't be null");
+ }
+ this.mFragment = fragment;
+ }
+
+ public final T getFragment() {
+ return mFragment;
+ }
+ /**
+ * Set the visibility titles/hover of browse rows.
+ */
+ public void setAdapter(ObjectAdapter adapter) {
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ */
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ }
+
+ /**
+ * Sets an item selection listener.
+ */
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ }
+
+ /**
+ * Selects a Row and perform an optional task on the Row.
+ */
+ public void setSelectedPosition(int rowPosition,
+ boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ }
+
+ /**
+ * Selects a Row.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth) {
+ }
+
+ /**
+ * Returns the selected position.
+ */
+ public int getSelectedPosition() {
+ return 0;
+ }
+ }
+
+ private boolean createMainFragment(ObjectAdapter adapter, int position) {
+ Object item = null;
+ if (adapter == null || adapter.size() == 0) {
+ return false;
+ } else {
+ if (position < 0) {
+ position = 0;
+ } else if (position >= adapter.size()) {
+ throw new IllegalArgumentException(
+ String.format("Invalid position %d requested", position));
+ }
+ item = adapter.get(position);
+ }
+
+ mSelectedPosition = position;
+ boolean oldIsPageRow = mIsPageRow;
+ mIsPageRow = item instanceof PageRow;
+ boolean swap;
+
+ if (mMainFragment == null) {
+ swap = true;
+ } else {
+ if (oldIsPageRow) {
+ swap = true;
+ } else {
+ swap = mIsPageRow;
+ }
+ }
+
+ if (swap) {
+ mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
+ if (!(mMainFragment instanceof MainFragmentAdapterProvider)) {
+ throw new IllegalArgumentException(
+ "Fragment must implement MainFragmentAdapterProvider");
+ }
+
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider)mMainFragment)
+ .getMainFragmentRowsAdapter();
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ mIsPageRow = mMainFragmentRowsAdapter == null;
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ }
+
+ return swap;
+ }
+
+ /**
+ * Factory class responsible for creating fragment given the current item. {@link ListRow}
+ * should returns {@link RowsSupportFragment} or it's subclass whereas {@link PageRow}
+ * can return any fragment class.
+ */
+ public abstract static class FragmentFactory<T extends Fragment> {
+ public abstract T createFragment(Object row);
+ }
+
+ /**
+ * FragmentFactory implementation for {@link ListRow}.
+ */
+ public static class ListRowFragmentFactory extends FragmentFactory<RowsSupportFragment> {
+ @Override
+ public RowsSupportFragment createFragment(Object row) {
+ return new RowsSupportFragment();
+ }
+ }
+
+ /**
+ * Registry class maintaining the mapping of {@link Row} subclasses to {@link FragmentFactory}.
+ * BrowseRowFragment automatically registers {@link ListRowFragmentFactory} for
+ * handling {@link ListRow}. Developers can override that and also if they want to
+ * use custom fragment, they can register a custom {@link FragmentFactory}
+ * against {@link PageRow}.
+ */
+ public final static class MainFragmentAdapterRegistry {
+ private final Map<Class, FragmentFactory> mItemToFragmentFactoryMapping = new HashMap();
+ private final static FragmentFactory sDefaultFragmentFactory = new ListRowFragmentFactory();
+
+ public MainFragmentAdapterRegistry() {
+ registerFragment(ListRow.class, sDefaultFragmentFactory);
+ }
+
+ public void registerFragment(Class rowClass, FragmentFactory factory) {
+ mItemToFragmentFactoryMapping.put(rowClass, factory);
+ }
+
+ public Fragment createFragment(Object item) {
+ if (item == null) {
+ throw new IllegalArgumentException("Item can't be null");
+ }
+
+ FragmentFactory fragmentFactory = mItemToFragmentFactoryMapping.get(item.getClass());
+ if (fragmentFactory == null && !(item instanceof PageRow)) {
+ fragmentFactory = sDefaultFragmentFactory;
+ }
+
+ return fragmentFactory.createFragment(item);
+ }
+ }
+
private static final String TAG = "BrowseSupportFragment";
private static final String LB_HEADERS_BACKSTACK = "lbHeadersBackStack_";
@@ -215,26 +609,35 @@
/** The headers fragment is disabled and will never be shown. */
public static final int HEADERS_DISABLED = 3;
- private RowsSupportFragment mRowsSupportFragment;
+ private MainFragmentAdapterRegistry mMainFragmentAdapterRegistry
+ = new MainFragmentAdapterRegistry();
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private Fragment mMainFragment;
private HeadersSupportFragment mHeadersSupportFragment;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
private ObjectAdapter mAdapter;
+ private PresenterSelector mAdapterPresenter;
+ private PresenterSelector mWrappingPresenterSelector;
private int mHeadersState = HEADERS_ENABLED;
private int mBrandColor = Color.TRANSPARENT;
private boolean mBrandColorSet;
private BrowseFrameLayout mBrowseFrame;
+ private ScaleFrameLayout mScaleFrameLayout;
private boolean mHeadersBackStackEnabled = true;
private String mWithHeadersBackStackName;
private boolean mShowingHeaders = true;
private boolean mCanShowHeaders = true;
private int mContainerListMarginStart;
private int mContainerListAlignTop;
- private boolean mRowScaleEnabled = true;
+ private boolean mMainFragmentScaleEnabled = true;
private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
private OnItemViewClickedListener mOnItemViewClickedListener;
private int mSelectedPosition = -1;
+ private float mScaleFactor;
+ private boolean mIsPageRow;
private PresenterSelector mHeaderPresenterSelector;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
@@ -248,7 +651,6 @@
private BrowseTransitionListener mBrowseTransitionListener;
private static final String ARG_TITLE = BrowseSupportFragment.class.getCanonicalName() + ".title";
- private static final String ARG_BADGE_URI = BrowseSupportFragment.class.getCanonicalName() + ".badge";
private static final String ARG_HEADERS_STATE =
BrowseSupportFragment.class.getCanonicalName() + ".headersState";
@@ -298,6 +700,43 @@
}
/**
+ * Wrapping app provided PresenterSelector to support InvisibleRowPresenter for SectionRow
+ * DividerRow and PageRow.
+ */
+ private void createAndSetWrapperPresenter() {
+ final PresenterSelector adapterPresenter = mAdapter.getPresenterSelector();
+ if (adapterPresenter == null) {
+ throw new IllegalArgumentException("Adapter.getPresenterSelector() is null");
+ }
+ if (adapterPresenter == mAdapterPresenter) {
+ return;
+ }
+ mAdapterPresenter = adapterPresenter;
+
+ Presenter[] presenters = adapterPresenter.getPresenters();
+ final Presenter invisibleRowPresenter = new InvisibleRowPresenter();
+ final Presenter[] allPresenters = new Presenter[presenters.length + 1];
+ System.arraycopy(allPresenters, 0, presenters, 0, presenters.length);
+ allPresenters[allPresenters.length - 1] = invisibleRowPresenter;
+ mAdapter.setPresenterSelector(new PresenterSelector() {
+ @Override
+ public Presenter getPresenter(Object item) {
+ Row row = (Row) item;
+ if (row.isRenderedAsRowView()) {
+ return adapterPresenter.getPresenter(item);
+ } else {
+ return invisibleRowPresenter;
+ }
+ }
+
+ @Override
+ public Presenter[] getPresenters() {
+ return allPresenters;
+ }
+ });
+ }
+
+ /**
* Sets the adapter containing the rows for the fragment.
*
* <p>The items referenced by the adapter must be be derived from
@@ -309,12 +748,24 @@
*/
public void setAdapter(ObjectAdapter adapter) {
mAdapter = adapter;
- if (mRowsSupportFragment != null) {
- mRowsSupportFragment.setAdapter(adapter);
+ createAndSetWrapperPresenter();
+ if (getView() == null) {
+ return;
+ }
+ replaceMainFragment(mSelectedPosition);
+
+ if (adapter != null) {
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(adapter);
+ }
mHeadersSupportFragment.setAdapter(adapter);
}
}
+ public final MainFragmentAdapterRegistry getMainFragmentRegistry() {
+ return mMainFragmentAdapterRegistry;
+ }
+
/**
* Returns the adapter containing the rows for the fragment.
*/
@@ -337,15 +788,37 @@
}
/**
+ * Get RowsSupportFragment if it's bound to BrowseSupportFragment or null if either BrowseSupportFragment has
+ * not been created yet or a different fragment is bound to it.
+ *
+ * @return RowsSupportFragment if it's bound to BrowseSupportFragment or null otherwise.
+ */
+ public RowsSupportFragment getRowsSupportFragment() {
+ if (mMainFragment instanceof RowsSupportFragment) {
+ return (RowsSupportFragment) mMainFragment;
+ }
+
+ return null;
+ }
+
+ /**
+ * Get currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
+ * @return Currently bound HeadersSupportFragment or null if HeadersSupportFragment has not been created yet.
+ */
+ public HeadersSupportFragment getHeadersSupportFragment() {
+ return mHeadersSupportFragment;
+ }
+
+ /**
* Sets an item clicked listener on the fragment.
* OnItemViewClickedListener will override {@link View.OnClickListener} that
* item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
- * So in general, developer should choose one of the listeners but not both.
+ * So in general, developer should choose one of the listeners but not both.
*/
public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
mOnItemViewClickedListener = listener;
- if (mRowsSupportFragment != null) {
- mRowsSupportFragment.setOnItemViewClickedListener(listener);
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(listener);
}
}
@@ -401,24 +874,37 @@
}
/**
- * Enables scaling of rows when headers are present.
- * By default enabled to increase density.
+ * @deprecated use {@link BrowseSupportFragment#enableMainFragmentScaling(boolean)} instead.
*
* @param enable true to enable row scaling
*/
+ @Deprecated
public void enableRowScaling(boolean enable) {
- mRowScaleEnabled = enable;
- if (mRowsSupportFragment != null) {
- mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
- }
+ enableMainFragmentScaling(enable);
+ }
+
+ /**
+ * Enables scaling of main fragment when headers are present. For the page/row fragment,
+ * scaling is enabled only when both this method and
+ * {@link MainFragmentAdapter#isScalingEnabled()} are enabled.
+ *
+ * @param enable true to enable row scaling
+ */
+ public void enableMainFragmentScaling(boolean enable) {
+ mMainFragmentScaleEnabled = enable;
}
private void startHeadersTransitionInternal(final boolean withHeaders) {
if (getFragmentManager().isDestroyed()) {
return;
}
+ if (!isHeadersDataReady()) {
+ return;
+ }
mShowingHeaders = withHeaders;
- mRowsSupportFragment.onExpandTransitionStart(!withHeaders, new Runnable() {
+ mMainFragmentAdapter.onTransitionPrepare();
+ mMainFragmentAdapter.onTransitionStart();
+ onExpandTransitionStart(!withHeaders, new Runnable() {
@Override
public void run() {
mHeadersSupportFragment.onTransitionPrepare();
@@ -427,8 +913,8 @@
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStart(withHeaders);
}
- TransitionHelper.runTransition(withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders,
- mHeadersTransition);
+ TransitionHelper.runTransition(
+ withHeaders ? mSceneWithHeaders : mSceneWithoutHeaders, mHeadersTransition);
if (mHeadersBackStackEnabled) {
if (!withHeaders) {
getFragmentManager().beginTransaction()
@@ -446,12 +932,9 @@
});
}
- private boolean isVerticalScrolling() {
+ boolean isVerticalScrolling() {
// don't run transition
- return mHeadersSupportFragment.getVerticalGridView().getScrollState()
- != HorizontalGridView.SCROLL_STATE_IDLE
- || mRowsSupportFragment.getVerticalGridView().getScrollState()
- != HorizontalGridView.SCROLL_STATE_IDLE;
+ return mHeadersSupportFragment.isScrolling() || mMainFragmentAdapter.isScrolling();
}
@@ -472,15 +955,14 @@
if (getTitleView() != null && getTitleView().hasFocus() &&
direction == View.FOCUS_DOWN) {
return mCanShowHeaders && mShowingHeaders ?
- mHeadersSupportFragment.getVerticalGridView() :
- mRowsSupportFragment.getVerticalGridView();
+ mHeadersSupportFragment.getVerticalGridView() : mMainFragment.getView();
}
boolean isRtl = ViewCompat.getLayoutDirection(focused) == View.LAYOUT_DIRECTION_RTL;
int towardStart = isRtl ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
int towardEnd = isRtl ? View.FOCUS_LEFT : View.FOCUS_RIGHT;
if (mCanShowHeaders && direction == towardStart) {
- if (isVerticalScrolling() || mShowingHeaders) {
+ if (isVerticalScrolling() || mShowingHeaders || !isHeadersDataReady()) {
return focused;
}
return mHeadersSupportFragment.getVerticalGridView();
@@ -488,13 +970,20 @@
if (isVerticalScrolling()) {
return focused;
}
- return mRowsSupportFragment.getVerticalGridView();
+ return mMainFragment.getView();
+ } else if (direction == View.FOCUS_DOWN && mShowingHeaders) {
+ // disable focus_down moving into PageFragment.
+ return focused;
} else {
return null;
}
}
};
+ private final boolean isHeadersDataReady() {
+ return mAdapter != null && mAdapter.size() != 0;
+ }
+
private final BrowseFrameLayout.OnChildFocusListener mOnChildFocusListener =
new BrowseFrameLayout.OnChildFocusListener() {
@@ -510,8 +999,8 @@
return true;
}
}
- if (mRowsSupportFragment != null && mRowsSupportFragment.getView() != null &&
- mRowsSupportFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
+ if (mMainFragment != null && mMainFragment.getView() != null &&
+ mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) {
return true;
}
if (getTitleView() != null &&
@@ -519,7 +1008,7 @@
return true;
}
return false;
- };
+ }
@Override
public void onRequestChildFocus(View child, View focused) {
@@ -539,6 +1028,9 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ outState.putInt(CURRENT_SELECTED_POSITION, mSelectedPosition);
+ outState.putBoolean(IS_PAGE_ROW, mIsPageRow);
+
if (mBackStackChangedListener != null) {
mBackStackChangedListener.save(outState);
} else {
@@ -572,6 +1064,17 @@
}
}
}
+
+ mScaleFactor = getResources().getFraction(R.fraction.lb_browse_rows_scale, 1, 1);
+ }
+
+ @Override
+ public void onDestroyView() {
+ mMainFragmentRowsAdapter = null;
+ mMainFragmentAdapter = null;
+ mMainFragment = null;
+ mHeadersSupportFragment = null;
+ super.onDestroyView();
}
@Override
@@ -585,41 +1088,77 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- if (getChildFragmentManager().findFragmentById(R.id.browse_container_dock) == null) {
- mRowsSupportFragment = new RowsSupportFragment();
+
+ if (getChildFragmentManager().findFragmentById(R.id.scale_frame) == null) {
mHeadersSupportFragment = new HeadersSupportFragment();
- getChildFragmentManager().beginTransaction()
- .replace(R.id.browse_headers_dock, mHeadersSupportFragment)
- .replace(R.id.browse_container_dock, mRowsSupportFragment).commit();
+
+ createMainFragment(mAdapter, mSelectedPosition);
+ FragmentTransaction ft = getChildFragmentManager().beginTransaction()
+ .replace(R.id.browse_headers_dock, mHeadersSupportFragment);
+
+ if (mMainFragment != null) {
+ ft.replace(R.id.scale_frame, mMainFragment);
+ } else {
+ // Empty adapter used to guard against lazy adapter loading. When this
+ // fragment is instantiated, mAdapter might not have the data or might not
+ // have been set. In either of those cases mFragmentAdapter will be null.
+ // This way we can maintain the invariant that mMainFragmentAdapter is never
+ // null and it avoids doing null checks all over the code.
+ mMainFragmentAdapter = new MainFragmentAdapter(null);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+ }
+
+ ft.commit();
} else {
mHeadersSupportFragment = (HeadersSupportFragment) getChildFragmentManager()
.findFragmentById(R.id.browse_headers_dock);
- mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager()
- .findFragmentById(R.id.browse_container_dock);
+ mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
+ mMainFragmentAdapter = ((MainFragmentAdapterProvider)mMainFragment)
+ .getMainFragmentAdapter();
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
+
+ mIsPageRow = savedInstanceState != null ?
+ savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
+
+ mSelectedPosition = savedInstanceState != null ?
+ savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0;
+
+ if (!mIsPageRow) {
+ if (mMainFragment instanceof MainFragmentRowsAdapterProvider) {
+ mMainFragmentRowsAdapter = ((MainFragmentRowsAdapterProvider) mMainFragment)
+ .getMainFragmentRowsAdapter();
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
+ } else {
+ mMainFragmentRowsAdapter = null;
+ }
}
mHeadersSupportFragment.setHeadersGone(!mCanShowHeaders);
-
- mRowsSupportFragment.setAdapter(mAdapter);
if (mHeaderPresenterSelector != null) {
mHeadersSupportFragment.setPresenterSelector(mHeaderPresenterSelector);
}
mHeadersSupportFragment.setAdapter(mAdapter);
-
- mRowsSupportFragment.enableRowScaling(mRowScaleEnabled);
- mRowsSupportFragment.setOnItemViewSelectedListener(mRowViewSelectedListener);
mHeadersSupportFragment.setOnHeaderViewSelectedListener(mHeaderViewSelectedListener);
mHeadersSupportFragment.setOnHeaderClickedListener(mHeaderClickedListener);
- mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
View root = inflater.inflate(R.layout.lb_browse_fragment, container, false);
- setTitleView((TitleView) root.findViewById(R.id.browse_title_group));
+ getProgressBarManager().setRootView((ViewGroup)root);
mBrowseFrame = (BrowseFrameLayout) root.findViewById(R.id.browse_frame);
mBrowseFrame.setOnChildFocusListener(mOnChildFocusListener);
mBrowseFrame.setOnFocusSearchListener(mOnFocusSearchListener);
+ installTitleView(inflater, mBrowseFrame, savedInstanceState);
+
+ mScaleFrameLayout = (ScaleFrameLayout) root.findViewById(R.id.scale_frame);
+ mScaleFrameLayout.setPivotX(0);
+ mScaleFrameLayout.setPivotY(mContainerListAlignTop);
+
+ setupMainFragment();
+
if (mBrandColorSet) {
mHeadersSupportFragment.setBackgroundColor(mBrandColor);
}
@@ -642,9 +1181,30 @@
setEntranceTransitionEndState();
}
});
+
return root;
}
+ private void setupMainFragment() {
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setAdapter(mAdapter);
+ mMainFragmentRowsAdapter.setOnItemViewSelectedListener(
+ new MainFragmentItemViewSelectedListener(mMainFragmentRowsAdapter));
+ mMainFragmentRowsAdapter.setOnItemViewClickedListener(mOnItemViewClickedListener);
+ }
+ }
+
+ @Override
+ boolean isReadyForPrepareEntranceTransition() {
+ return mMainFragment != null && mMainFragment.getView() != null;
+ }
+
+ @Override
+ boolean isReadyForStartEntranceTransition() {
+ return mMainFragment != null && mMainFragment.getView() != null
+ && (!mIsPageRow || mMainFragmentAdapter.mFragmentHost.mDataReady);
+ }
+
private void createHeadersTransition() {
mHeadersTransition = TransitionHelper.loadTransition(getActivity(),
mShowingHeaders ?
@@ -657,19 +1217,28 @@
@Override
public void onTransitionEnd(Object transition) {
mHeadersTransition = null;
- mRowsSupportFragment.onTransitionEnd();
- mHeadersSupportFragment.onTransitionEnd();
- if (mShowingHeaders) {
- VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
- if (headerGridView != null && !headerGridView.hasFocus()) {
- headerGridView.requestFocus();
- }
- } else {
- VerticalGridView rowsGridView = mRowsSupportFragment.getVerticalGridView();
- if (rowsGridView != null && !rowsGridView.hasFocus()) {
- rowsGridView.requestFocus();
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.onTransitionEnd();
+ if (!mShowingHeaders && mMainFragment != null) {
+ View mainFragmentView = mMainFragment.getView();
+ if (mainFragmentView != null && !mainFragmentView.hasFocus()) {
+ mainFragmentView.requestFocus();
+ }
}
}
+ if (mHeadersSupportFragment != null) {
+ mHeadersSupportFragment.onTransitionEnd();
+ if (mShowingHeaders) {
+ VerticalGridView headerGridView = mHeadersSupportFragment.getVerticalGridView();
+ if (headerGridView != null && !headerGridView.hasFocus()) {
+ headerGridView.requestFocus();
+ }
+ }
+ }
+
+ // Animate TitleView once header animation is complete.
+ updateTitleViewVisibility();
+
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
}
@@ -677,6 +1246,68 @@
});
}
+ void updateTitleViewVisibility() {
+ if (!mShowingHeaders) {
+ boolean showTitleView;
+ if (mIsPageRow && mMainFragmentAdapter != null) {
+ // page fragment case:
+ showTitleView = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+ } else {
+ // regular row view case:
+ showTitleView = isFirstRowWithContent(mSelectedPosition);
+ }
+ if (showTitleView) {
+ showTitle(TitleViewAdapter.FULL_VIEW_VISIBLE);
+ } else {
+ showTitle(false);
+ }
+ } else {
+ // when HeaderFragment is showing, showBranding and showSearch are slightly different
+ boolean showBranding;
+ boolean showSearch;
+ if (mIsPageRow && mMainFragmentAdapter != null) {
+ showBranding = mMainFragmentAdapter.mFragmentHost.mShowTitleView;
+ } else {
+ showBranding = isFirstRowWithContent(mSelectedPosition);
+ }
+ showSearch = isFirstRowWithContentOrPageRow(mSelectedPosition);
+ int flags = 0;
+ if (showBranding) flags |= TitleViewAdapter.BRANDING_VIEW_VISIBLE;
+ if (showSearch) flags |= TitleViewAdapter.SEARCH_VIEW_VISIBLE;
+ if (flags != 0) {
+ showTitle(flags);
+ } else {
+ showTitle(false);
+ }
+ }
+ }
+
+ boolean isFirstRowWithContentOrPageRow(int rowPosition) {
+ if (mAdapter == null || mAdapter.size() == 0) {
+ return true;
+ }
+ for (int i = 0; i < mAdapter.size(); i++) {
+ final Row row = (Row) mAdapter.get(i);
+ if (row.isRenderedAsRowView() || row instanceof PageRow) {
+ return rowPosition == i;
+ }
+ }
+ return true;
+ }
+
+ boolean isFirstRowWithContent(int rowPosition) {
+ if (mAdapter == null || mAdapter.size() == 0) {
+ return true;
+ }
+ for (int i = 0; i < mAdapter.size(); i++) {
+ final Row row = (Row) mAdapter.get(i);
+ if (row.isRenderedAsRowView()) {
+ return rowPosition == i;
+ }
+ }
+ return true;
+ }
+
/**
* Sets the {@link PresenterSelector} used to render the row headers.
*
@@ -690,15 +1321,6 @@
}
}
- private void setRowsAlignedLeft(boolean alignLeft) {
- MarginLayoutParams lp;
- View containerList;
- containerList = mRowsSupportFragment.getView();
- lp = (MarginLayoutParams) containerList.getLayoutParams();
- lp.setMarginStart(alignLeft ? 0 : mContainerListMarginStart);
- containerList.setLayoutParams(lp);
- }
-
private void setHeadersOnScreen(boolean onScreen) {
MarginLayoutParams lp;
View containerList;
@@ -712,27 +1334,46 @@
if (DEBUG) Log.v(TAG, "showHeaders " + show);
mHeadersSupportFragment.setHeadersEnabled(show);
setHeadersOnScreen(show);
- setRowsAlignedLeft(!show);
- mRowsSupportFragment.setExpand(!show);
+ expandMainFragment(!show);
+ }
+
+ private void expandMainFragment(boolean expand) {
+ MarginLayoutParams params = (MarginLayoutParams) mScaleFrameLayout.getLayoutParams();
+ params.setMarginStart(!expand ? mContainerListMarginStart : 0);
+ mScaleFrameLayout.setLayoutParams(params);
+ mMainFragmentAdapter.setExpand(expand);
+
+ setMainFragmentAlignment();
+ final float scaleFactor = !expand
+ && mMainFragmentScaleEnabled
+ && mMainFragmentAdapter.isScalingEnabled() ? mScaleFactor : 1;
+ mScaleFrameLayout.setLayoutScaleY(scaleFactor);
+ mScaleFrameLayout.setChildScale(scaleFactor);
}
private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
new HeadersSupportFragment.OnHeaderClickedListener() {
@Override
- public void onHeaderClicked() {
+ public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
return;
}
startHeadersTransitionInternal(false);
- mRowsSupportFragment.getVerticalGridView().requestFocus();
+ mMainFragment.getView().requestFocus();
}
};
- private OnItemViewSelectedListener mRowViewSelectedListener = new OnItemViewSelectedListener() {
+ class MainFragmentItemViewSelectedListener implements OnItemViewSelectedListener {
+ MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+ public MainFragmentItemViewSelectedListener(MainFragmentRowsAdapter fragmentRowsAdapter) {
+ mMainFragmentRowsAdapter = fragmentRowsAdapter;
+ }
+
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
- int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
+ int position = mMainFragmentRowsAdapter.getSelectedPosition();
if (DEBUG) Log.v(TAG, "row selected position " + position);
onRowSelected(position);
if (mExternalOnItemViewSelectedListener != null) {
@@ -746,7 +1387,7 @@
new HeadersSupportFragment.OnHeaderViewSelectedListener() {
@Override
public void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
- int position = mHeadersSupportFragment.getVerticalGridView().getSelectedPosition();
+ int position = mHeadersSupportFragment.getSelectedPosition();
if (DEBUG) Log.v(TAG, "header selected position " + position);
onRowSelected(position);
}
@@ -756,21 +1397,60 @@
if (position != mSelectedPosition) {
mSetSelectionRunnable.post(
position, SetSelectionRunnable.TYPE_INTERNAL_SYNC, true);
-
- if (getAdapter() == null || getAdapter().size() == 0 || position == 0) {
- showTitle(true);
- } else {
- showTitle(false);
- }
}
}
private void setSelection(int position, boolean smooth) {
- if (position != NO_POSITION) {
- mRowsSupportFragment.setSelectedPosition(position, smooth);
- mHeadersSupportFragment.setSelectedPosition(position, smooth);
+ if (position == NO_POSITION) {
+ return;
+ }
+
+ mHeadersSupportFragment.setSelectedPosition(position, smooth);
+ replaceMainFragment(position);
+
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setSelectedPosition(position, smooth);
}
mSelectedPosition = position;
+
+ updateTitleViewVisibility();
+ }
+
+ private void replaceMainFragment(int position) {
+ if (createMainFragment(mAdapter, position)) {
+ swapToMainFragment();
+ expandMainFragment(!(mCanShowHeaders && mShowingHeaders));
+ setupMainFragment();
+ performPendingStates();
+ }
+ }
+
+ private void swapToMainFragment() {
+ final VerticalGridView gridView = mHeadersSupportFragment.getVerticalGridView();
+ if (isShowingHeaders() && gridView != null
+ && gridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ // if user is scrolling HeadersSupportFragment, swap to empty fragment and wait scrolling
+ // finishes.
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.scale_frame, new Fragment()).commit();
+ gridView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == RecyclerView.SCROLL_STATE_IDLE) {
+ gridView.removeOnScrollListener(this);
+ FragmentManager fm = getChildFragmentManager();
+ Fragment currentFragment = fm.findFragmentById(R.id.scale_frame);
+ if (currentFragment != mMainFragment) {
+ fm.beginTransaction().replace(R.id.scale_frame, mMainFragment).commit();
+ }
+ }
+ }
+ });
+ } else {
+ // Otherwise swap immediately
+ getChildFragmentManager().beginTransaction()
+ .replace(R.id.scale_frame, mMainFragment).commit();
+ }
}
/**
@@ -781,6 +1461,14 @@
}
/**
+ * Gets position of currently selected row.
+ * @return Position of currently selected row.
+ */
+ public int getSelectedPosition() {
+ return mSelectedPosition;
+ }
+
+ /**
* Sets the selected row position.
*/
public void setSelectedPosition(int position, boolean smooth) {
@@ -788,30 +1476,73 @@
position, SetSelectionRunnable.TYPE_USER_REQUEST, smooth);
}
+ /**
+ * Selects a Row and perform an optional task on the Row. For example
+ * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+ * scrolls to 11th row and selects 6th item on that row. The method will be ignored if
+ * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+ * ViewGroup, Bundle)}).
+ *
+ * @param rowPosition Which row to select.
+ * @param smooth True to scroll to the row, false for no animation.
+ * @param rowHolderTask Optional task to perform on the Row. When the task is not null, headers
+ * fragment will be collapsed.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ if (mMainFragmentAdapterRegistry == null) {
+ return;
+ }
+ if (rowHolderTask != null) {
+ startHeadersTransition(false);
+ }
+ if (mMainFragmentRowsAdapter != null) {
+ mMainFragmentRowsAdapter.setSelectedPosition(rowPosition, smooth, rowHolderTask);
+ }
+ }
+
@Override
public void onStart() {
super.onStart();
- mHeadersSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
- mHeadersSupportFragment.setItemAlignment();
- mRowsSupportFragment.setWindowAlignmentFromTop(mContainerListAlignTop);
- mRowsSupportFragment.setItemAlignment();
-
- mRowsSupportFragment.setScalePivots(0, mContainerListAlignTop);
+ mHeadersSupportFragment.setAlignment(mContainerListAlignTop);
+ setMainFragmentAlignment();
if (mCanShowHeaders && mShowingHeaders && mHeadersSupportFragment.getView() != null) {
mHeadersSupportFragment.getView().requestFocus();
} else if ((!mCanShowHeaders || !mShowingHeaders)
- && mRowsSupportFragment.getView() != null) {
- mRowsSupportFragment.getView().requestFocus();
+ && mMainFragment.getView() != null) {
+ mMainFragment.getView().requestFocus();
}
+
if (mCanShowHeaders) {
showHeaders(mShowingHeaders);
}
+
if (isEntranceTransitionEnabled()) {
setEntranceTransitionStartState();
}
}
+ private void onExpandTransitionStart(boolean expand, final Runnable callback) {
+ if (expand) {
+ callback.run();
+ return;
+ }
+ // Run a "pre" layout when we go non-expand, in order to get the initial
+ // positions of added rows.
+ new ExpandPreLayout(callback, mMainFragmentAdapter, getView()).execute();
+ }
+
+ private void setMainFragmentAlignment() {
+ int alignOffset = mContainerListAlignTop;
+ if (mMainFragmentScaleEnabled
+ && mMainFragmentAdapter.isScalingEnabled()
+ && mShowingHeaders) {
+ alignOffset = (int) (alignOffset / mScaleFactor + 0.5f);
+ }
+ mMainFragmentAdapter.setAlignment(alignOffset);
+ }
+
/**
* Enables/disables headers transition on back key support. This is enabled by
* default. The BrowseSupportFragment will add a back stack entry when headers are
@@ -905,39 +1636,88 @@
@Override
protected void onEntranceTransitionPrepare() {
mHeadersSupportFragment.onTransitionPrepare();
- mRowsSupportFragment.onTransitionPrepare();
+ // setEntranceTransitionStartState() might be called when mMainFragment is null,
+ // make sure it is called.
+ mMainFragmentAdapter.setEntranceTransitionState(false);
+ mMainFragmentAdapter.onTransitionPrepare();
}
@Override
protected void onEntranceTransitionStart() {
mHeadersSupportFragment.onTransitionStart();
- mRowsSupportFragment.onTransitionStart();
+ mMainFragmentAdapter.onTransitionStart();
}
@Override
protected void onEntranceTransitionEnd() {
- mRowsSupportFragment.onTransitionEnd();
- mHeadersSupportFragment.onTransitionEnd();
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.onTransitionEnd();
+ }
+
+ if (mHeadersSupportFragment != null) {
+ mHeadersSupportFragment.onTransitionEnd();
+ }
}
void setSearchOrbViewOnScreen(boolean onScreen) {
- View searchOrbView = getTitleView().getSearchAffordanceView();
- MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
- lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
- searchOrbView.setLayoutParams(lp);
+ View searchOrbView = getTitleViewAdapter().getSearchAffordanceView();
+ if (searchOrbView != null) {
+ MarginLayoutParams lp = (MarginLayoutParams) searchOrbView.getLayoutParams();
+ lp.setMarginStart(onScreen ? 0 : -mContainerListMarginStart);
+ searchOrbView.setLayoutParams(lp);
+ }
}
void setEntranceTransitionStartState() {
setHeadersOnScreen(false);
setSearchOrbViewOnScreen(false);
- mRowsSupportFragment.setEntranceTransitionState(false);
+ mMainFragmentAdapter.setEntranceTransitionState(false);
}
void setEntranceTransitionEndState() {
setHeadersOnScreen(mShowingHeaders);
setSearchOrbViewOnScreen(true);
- mRowsSupportFragment.setEntranceTransitionState(true);
+ mMainFragmentAdapter.setEntranceTransitionState(true);
}
-}
+ private class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
+ private final View mView;
+ private final Runnable mCallback;
+ private int mState;
+ private MainFragmentAdapter mainFragmentAdapter;
+
+ final static int STATE_INIT = 0;
+ final static int STATE_FIRST_DRAW = 1;
+ final static int STATE_SECOND_DRAW = 2;
+
+ ExpandPreLayout(Runnable callback, MainFragmentAdapter adapter, View view) {
+ mView = view;
+ mCallback = callback;
+ mainFragmentAdapter = adapter;
+ }
+
+ void execute() {
+ mView.getViewTreeObserver().addOnPreDrawListener(this);
+ mainFragmentAdapter.setExpand(false);
+ mState = STATE_INIT;
+ }
+
+ @Override
+ public boolean onPreDraw() {
+ if (getView() == null || getActivity() == null) {
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ if (mState == STATE_INIT) {
+ mainFragmentAdapter.setExpand(true);
+ mState = STATE_FIRST_DRAW;
+ } else if (mState == STATE_FIRST_DRAW) {
+ mCallback.run();
+ mView.getViewTreeObserver().removeOnPreDrawListener(this);
+ mState = STATE_SECOND_DRAW;
+ }
+ return false;
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
index ddafd5f..a19eccc 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsFragment.java
@@ -16,22 +16,20 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.ItemAlignmentFacet;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
import android.support.v17.leanback.widget.VerticalGridView;
import android.os.Bundle;
import android.util.Log;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -41,8 +39,7 @@
*
* <p>
* A DetailsFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}, the Adapter's {@link PresenterSelector} must maintains subclasses
+ * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
*
@@ -81,6 +78,9 @@
@Override
public void run() {
+ if (mRowsFragment == null) {
+ return;
+ }
mRowsFragment.setSelectedPosition(mPosition, mSmooth);
}
}
@@ -89,18 +89,18 @@
private ObjectAdapter mAdapter;
private int mContainerListAlignTop;
- private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
- private OnItemViewClickedListener mOnItemViewClickedListener;
+ private BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+ private BaseOnItemViewClickedListener mOnItemViewClickedListener;
private Object mSceneAfterEntranceTransition;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
- private final OnItemViewSelectedListener mOnItemViewSelectedListener =
- new OnItemViewSelectedListener() {
+ private final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
+ new BaseOnItemViewSelectedListener<Object>() {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
+ RowPresenter.ViewHolder rowViewHolder, Object row) {
int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
int subposition = mRowsFragment.getVerticalGridView().getSelectedSubPosition();
if (DEBUG) Log.v(TAG, "row selected position " + position
@@ -141,14 +141,14 @@
/**
* Sets an item selection listener.
*/
- public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mExternalOnItemViewSelectedListener = listener;
}
/**
* Sets an item clicked listener.
*/
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
if (mOnItemViewClickedListener != listener) {
mOnItemViewClickedListener = listener;
if (mRowsFragment != null) {
@@ -160,7 +160,7 @@
/**
* Returns the item clicked listener.
*/
- public OnItemViewClickedListener getOnItemViewClickedListener() {
+ public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
@@ -177,10 +177,7 @@
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
ViewGroup fragment_root = (ViewGroup) view.findViewById(R.id.details_fragment_root);
- View titleView = inflateTitle(inflater, fragment_root, savedInstanceState);
- if (titleView != null) {
- fragment_root.addView(titleView);
- }
+ installTitleView(inflater, fragment_root, savedInstanceState);
mRowsFragment = (RowsFragment) getChildFragmentManager().findFragmentById(
R.id.details_rows_dock);
if (mRowsFragment == null) {
@@ -192,15 +189,6 @@
mRowsFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
- if (titleView != null) {
- View titleGroup = titleView.findViewById(R.id.browse_title_group);
- if (titleGroup instanceof TitleView) {
- setTitleView((TitleView) titleGroup);
- } else {
- setTitleView(null);
- }
- }
-
mSceneAfterEntranceTransition = TransitionHelper.createScene(
(ViewGroup) view, new Runnable() {
@Override
@@ -212,13 +200,18 @@
}
/**
- * Called by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to inflate
- * TitleView. Default implementation uses layout file lb_browse_title.
- * Subclass may override and use its own layout or return null if no title is needed.
+ * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
*/
+ @Deprecated
protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.lb_browse_title, parent, false);
+ return super.onInflateTitleView(inflater, parent, savedInstanceState);
+ }
+
+ @Override
+ public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ return inflateTitle(inflater, parent, savedInstanceState);
}
void setVerticalGridViewLayout(VerticalGridView listview) {
@@ -273,7 +266,12 @@
return mRowsFragment == null ? null : mRowsFragment.getVerticalGridView();
}
- RowsFragment getRowsFragment() {
+ /**
+ * Gets embedded RowsFragment showing multiple rows for DetailsFragment. If view of
+ * DetailsFragment is not created, the method returns null.
+ * @return Embedded RowsFragment showing multiple rows for DetailsFragment.
+ */
+ public RowsFragment getRowsFragment() {
return mRowsFragment;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
index 7532d79..c4f9bc3 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/DetailsSupportFragment.java
@@ -18,22 +18,20 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BrowseFrameLayout;
-import android.support.v17.leanback.widget.DetailsOverviewRow;
import android.support.v17.leanback.widget.FullWidthDetailsOverviewRowPresenter;
import android.support.v17.leanback.widget.ItemAlignmentFacet;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
-import android.support.v17.leanback.widget.OnItemViewClickedListener;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
import android.support.v17.leanback.widget.VerticalGridView;
import android.os.Bundle;
import android.util.Log;
+import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -43,8 +41,7 @@
*
* <p>
* A DetailsSupportFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}, the Adapter's {@link PresenterSelector} must maintains subclasses
+ * of rows in a vertical list.The Adapter's {@link PresenterSelector} must maintain subclasses
* of {@link RowPresenter}.
* </p>
*
@@ -83,6 +80,9 @@
@Override
public void run() {
+ if (mRowsSupportFragment == null) {
+ return;
+ }
mRowsSupportFragment.setSelectedPosition(mPosition, mSmooth);
}
}
@@ -91,18 +91,18 @@
private ObjectAdapter mAdapter;
private int mContainerListAlignTop;
- private OnItemViewSelectedListener mExternalOnItemViewSelectedListener;
- private OnItemViewClickedListener mOnItemViewClickedListener;
+ private BaseOnItemViewSelectedListener mExternalOnItemViewSelectedListener;
+ private BaseOnItemViewClickedListener mOnItemViewClickedListener;
private Object mSceneAfterEntranceTransition;
private final SetSelectionRunnable mSetSelectionRunnable = new SetSelectionRunnable();
- private final OnItemViewSelectedListener mOnItemViewSelectedListener =
- new OnItemViewSelectedListener() {
+ private final BaseOnItemViewSelectedListener<Object> mOnItemViewSelectedListener =
+ new BaseOnItemViewSelectedListener<Object>() {
@Override
public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row) {
+ RowPresenter.ViewHolder rowViewHolder, Object row) {
int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
int subposition = mRowsSupportFragment.getVerticalGridView().getSelectedSubPosition();
if (DEBUG) Log.v(TAG, "row selected position " + position
@@ -143,14 +143,14 @@
/**
* Sets an item selection listener.
*/
- public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mExternalOnItemViewSelectedListener = listener;
}
/**
* Sets an item clicked listener.
*/
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
if (mOnItemViewClickedListener != listener) {
mOnItemViewClickedListener = listener;
if (mRowsSupportFragment != null) {
@@ -162,7 +162,7 @@
/**
* Returns the item clicked listener.
*/
- public OnItemViewClickedListener getOnItemViewClickedListener() {
+ public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
@@ -179,10 +179,7 @@
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.lb_details_fragment, container, false);
ViewGroup fragment_root = (ViewGroup) view.findViewById(R.id.details_fragment_root);
- View titleView = inflateTitle(inflater, fragment_root, savedInstanceState);
- if (titleView != null) {
- fragment_root.addView(titleView);
- }
+ installTitleView(inflater, fragment_root, savedInstanceState);
mRowsSupportFragment = (RowsSupportFragment) getChildFragmentManager().findFragmentById(
R.id.details_rows_dock);
if (mRowsSupportFragment == null) {
@@ -194,15 +191,6 @@
mRowsSupportFragment.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
mRowsSupportFragment.setOnItemViewClickedListener(mOnItemViewClickedListener);
- if (titleView != null) {
- View titleGroup = titleView.findViewById(R.id.browse_title_group);
- if (titleGroup instanceof TitleView) {
- setTitleView((TitleView) titleGroup);
- } else {
- setTitleView(null);
- }
- }
-
mSceneAfterEntranceTransition = TransitionHelper.createScene(
(ViewGroup) view, new Runnable() {
@Override
@@ -214,13 +202,18 @@
}
/**
- * Called by {@link #onCreateView(LayoutInflater, ViewGroup, Bundle)} to inflate
- * TitleView. Default implementation uses layout file lb_browse_title.
- * Subclass may override and use its own layout or return null if no title is needed.
+ * @deprecated override {@link #onInflateTitleView(LayoutInflater,ViewGroup,Bundle)} instead.
*/
+ @Deprecated
protected View inflateTitle(LayoutInflater inflater, ViewGroup parent,
Bundle savedInstanceState) {
- return inflater.inflate(R.layout.lb_browse_title, parent, false);
+ return super.onInflateTitleView(inflater, parent, savedInstanceState);
+ }
+
+ @Override
+ public View onInflateTitleView(LayoutInflater inflater, ViewGroup parent,
+ Bundle savedInstanceState) {
+ return inflateTitle(inflater, parent, savedInstanceState);
}
void setVerticalGridViewLayout(VerticalGridView listview) {
@@ -275,7 +268,12 @@
return mRowsSupportFragment == null ? null : mRowsSupportFragment.getVerticalGridView();
}
- RowsSupportFragment getRowsSupportFragment() {
+ /**
+ * Gets embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment. If view of
+ * DetailsSupportFragment is not created, the method returns null.
+ * @return Embedded RowsSupportFragment showing multiple rows for DetailsSupportFragment.
+ */
+ public RowsSupportFragment getRowsSupportFragment() {
return mRowsSupportFragment;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
index a9f2a3e..ac7b933 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorFragment.java
@@ -34,12 +34,9 @@
/**
* A fragment for displaying an error indication.
*/
-public class ErrorFragment extends Fragment {
+public class ErrorFragment extends BrandedFragment {
- private View mErrorFrame;
- private String mTitle;
- private Drawable mBadgeDrawable;
- private TitleView mTitleView;
+ private ViewGroup mErrorFrame;
private ImageView mImageView;
private TextView mTextView;
private Button mButton;
@@ -51,40 +48,6 @@
private boolean mIsBackgroundTranslucent = true;
/**
- * Sets the drawable displayed in the browse fragment title.
- *
- * @param drawable The drawable to display in the browse fragment title.
- */
- public void setBadgeDrawable(Drawable drawable) {
- mBadgeDrawable = drawable;
- updateTitle();
- }
-
- /**
- * Returns the badge drawable used in the fragment title.
- */
- public Drawable getBadgeDrawable() {
- return mBadgeDrawable;
- }
-
- /**
- * Sets a title for the browse fragment.
- *
- * @param title The title of the browse fragment.
- */
- public void setTitle(String title) {
- mTitle = title;
- updateTitle();
- }
-
- /**
- * Returns the title for the browse fragment.
- */
- public String getTitle() {
- return mTitle;
- }
-
- /**
* Sets the default background.
*
* @param translucent True to set a translucent background.
@@ -199,9 +162,11 @@
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
- mErrorFrame = root.findViewById(R.id.error_frame);
+ mErrorFrame = (ViewGroup) root.findViewById(R.id.error_frame);
updateBackground();
+ installTitleView(inflater, mErrorFrame, savedInstanceState);
+
mImageView = (ImageView) root.findViewById(R.id.image);
updateImageDrawable();
@@ -211,9 +176,6 @@
mButton = (Button) root.findViewById(R.id.button);
updateButton();
- mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
- updateTitle();
-
FontMetricsInt metrics = getFontMetricsInt(mTextView);
int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
R.dimen.lb_error_under_image_baseline_margin);
@@ -239,13 +201,6 @@
}
}
- private void updateTitle() {
- if (mTitleView != null) {
- mTitleView.setTitle(mTitle);
- mTitleView.setBadgeDrawable(mBadgeDrawable);
- }
- }
-
private void updateMessage() {
if (mTextView != null) {
mTextView.setText(mMessage);
@@ -287,4 +242,5 @@
lp.topMargin = topMargin;
textView.setLayoutParams(lp);
}
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
index 2881921..1cc5e21 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ErrorSupportFragment.java
@@ -36,12 +36,9 @@
/**
* A fragment for displaying an error indication.
*/
-public class ErrorSupportFragment extends Fragment {
+public class ErrorSupportFragment extends BrandedSupportFragment {
- private View mErrorFrame;
- private String mTitle;
- private Drawable mBadgeDrawable;
- private TitleView mTitleView;
+ private ViewGroup mErrorFrame;
private ImageView mImageView;
private TextView mTextView;
private Button mButton;
@@ -53,40 +50,6 @@
private boolean mIsBackgroundTranslucent = true;
/**
- * Sets the drawable displayed in the browse fragment title.
- *
- * @param drawable The drawable to display in the browse fragment title.
- */
- public void setBadgeDrawable(Drawable drawable) {
- mBadgeDrawable = drawable;
- updateTitle();
- }
-
- /**
- * Returns the badge drawable used in the fragment title.
- */
- public Drawable getBadgeDrawable() {
- return mBadgeDrawable;
- }
-
- /**
- * Sets a title for the browse fragment.
- *
- * @param title The title of the browse fragment.
- */
- public void setTitle(String title) {
- mTitle = title;
- updateTitle();
- }
-
- /**
- * Returns the title for the browse fragment.
- */
- public String getTitle() {
- return mTitle;
- }
-
- /**
* Sets the default background.
*
* @param translucent True to set a translucent background.
@@ -201,9 +164,11 @@
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.lb_error_fragment, container, false);
- mErrorFrame = root.findViewById(R.id.error_frame);
+ mErrorFrame = (ViewGroup) root.findViewById(R.id.error_frame);
updateBackground();
+ installTitleView(inflater, mErrorFrame, savedInstanceState);
+
mImageView = (ImageView) root.findViewById(R.id.image);
updateImageDrawable();
@@ -213,9 +178,6 @@
mButton = (Button) root.findViewById(R.id.button);
updateButton();
- mTitleView = (TitleView) root.findViewById(R.id.browse_title_group);
- updateTitle();
-
FontMetricsInt metrics = getFontMetricsInt(mTextView);
int underImageBaselineMargin = container.getResources().getDimensionPixelSize(
R.dimen.lb_error_under_image_baseline_margin);
@@ -241,13 +203,6 @@
}
}
- private void updateTitle() {
- if (mTitleView != null) {
- mTitleView.setTitle(mTitle);
- mTitleView.setBadgeDrawable(mBadgeDrawable);
- }
- }
-
private void updateMessage() {
if (mTextView != null) {
mTextView.setText(mMessage);
@@ -289,4 +244,5 @@
lp.topMargin = topMargin;
textView.setLayoutParams(lp);
}
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
deleted file mode 100644
index 142e971..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapter.java
+++ /dev/null
@@ -1,520 +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.v17.leanback.app;
-
-import android.content.Context;
-import android.database.DataSetObserver;
-import android.media.AudioManager;
-import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.ImeKeyMonitor;
-import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewParent;
-import android.view.inputmethod.EditorInfo;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.AdapterView.OnItemSelectedListener;
-import android.widget.EditText;
-import android.widget.ImageView;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
- * Presentation (view creation and state animation) is delegated to a {@link
- * GuidedActionsStylist}, while clients are notified of interactions via
- * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
- */
-class GuidedActionAdapter extends RecyclerView.Adapter {
- private static final String TAG = "GuidedActionAdapter";
- private static final boolean DEBUG = false;
-
- private static final String TAG_EDIT = "EditableAction";
- private static final boolean DEBUG_EDIT = false;
-
- /**
- * Object listening for click events within a {@link GuidedActionAdapter}.
- */
- public interface ClickListener {
-
- /**
- * Called when the user clicks on an action.
- */
- public void onGuidedActionClicked(GuidedAction action);
- }
-
- /**
- * Object listening for focus events within a {@link GuidedActionAdapter}.
- */
- public interface FocusListener {
-
- /**
- * Called when the user focuses on an action.
- */
- public void onGuidedActionFocused(GuidedAction action);
- }
-
- /**
- * Object listening for edit events within a {@link GuidedActionAdapter}.
- */
- public interface EditListener {
-
- /**
- * Called when the user exits edit mode on an action.
- */
- public long onGuidedActionEdited(GuidedAction action);
-
- /**
- * Called when Ime Open
- */
- public void onImeOpen();
-
- /**
- * Called when Ime Close
- */
- public void onImeClose();
- }
-
- /**
- * View holder containing a {@link GuidedAction}.
- */
- static class ActionViewHolder extends ViewHolder {
-
- final GuidedActionsStylist.ViewHolder mStylistViewHolder;
- private GuidedAction mAction;
-
- /**
- * Constructs a view holder that can be associated with a GuidedAction.
- */
- public ActionViewHolder(View v, GuidedActionsStylist.ViewHolder subViewHolder) {
- super(v);
- mStylistViewHolder = subViewHolder;
- }
-
- /**
- * Retrieves the action associated with this view holder.
- * @return The GuidedAction associated with this view holder.
- */
- public GuidedAction getAction() {
- return mAction;
- }
-
- /**
- * Sets the action associated with this view holder.
- * @param action The GuidedAction associated with this view holder.
- */
- public void setAction(GuidedAction action) {
- mAction = action;
- }
- }
-
- private final ActionOnKeyListener mActionOnKeyListener;
- private final ActionOnFocusListener mActionOnFocusListener;
- private final ActionEditListener mActionEditListener;
- private final List<GuidedAction> mActions;
- private ClickListener mClickListener;
- private final GuidedActionsStylist mStylist;
- private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- if (v != null && v.getWindowToken() != null && getRecyclerView() != null) {
- ActionViewHolder avh = (ActionViewHolder)getRecyclerView().getChildViewHolder(v);
- GuidedAction action = avh.getAction();
- if (action.isEditable() || action.isDescriptionEditable()) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
- mGroup.openIme(GuidedActionAdapter.this, avh);
- } else {
- handleCheckedActions(avh);
- if (action.isEnabled() && !action.infoOnly()) {
- performOnActionClick(avh);
- }
- }
- }
- }
- };
- GuidedActionAdapterGroup mGroup;
-
- /**
- * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
- * focus listeners, and the given presenter.
- * @param actions The list of guided actions this adapter will manage.
- * @param focusListener The focus listener for items in this adapter.
- * @param presenter The presenter that will manage the display of items in this adapter.
- */
- public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
- FocusListener focusListener, GuidedActionsStylist presenter) {
- super();
- mActions = new ArrayList<GuidedAction>(actions);
- mClickListener = clickListener;
- mStylist = presenter;
- mActionOnKeyListener = new ActionOnKeyListener();
- mActionOnFocusListener = new ActionOnFocusListener(focusListener);
- mActionEditListener = new ActionEditListener();
- }
-
- /**
- * Sets the list of actions managed by this adapter.
- * @param actions The list of actions to be managed.
- */
- public void setActions(List<GuidedAction> actions) {
- mActionOnFocusListener.unFocus();
- mActions.clear();
- mActions.addAll(actions);
- notifyDataSetChanged();
- }
-
- /**
- * Returns the count of actions managed by this adapter.
- * @return The count of actions managed by this adapter.
- */
- public int getCount() {
- return mActions.size();
- }
-
- /**
- * Returns the GuidedAction at the given position in the managed list.
- * @param position The position of the desired GuidedAction.
- * @return The GuidedAction at the given position.
- */
- public GuidedAction getItem(int position) {
- return mActions.get(position);
- }
-
- /**
- * Return index of action in array
- * @param action Action to search index.
- * @return Index of Action in array.
- */
- public int indexOf(GuidedAction action) {
- return mActions.indexOf(action);
- }
-
- /**
- * @return GuidedActionsStylist used to build the actions list UI.
- */
- public GuidedActionsStylist getGuidedActionsStylist() {
- return mStylist;
- }
-
- /**
- * Sets the click listener for items managed by this adapter.
- * @param clickListener The click listener for this adapter.
- */
- public void setClickListener(ClickListener clickListener) {
- mClickListener = clickListener;
- }
-
- /**
- * Sets the focus listener for items managed by this adapter.
- * @param focusListener The focus listener for this adapter.
- */
- public void setFocusListener(FocusListener focusListener) {
- mActionOnFocusListener.setFocusListener(focusListener);
- }
-
- /**
- * Used for serialization only.
- * @hide
- */
- public List<GuidedAction> getActions() {
- return new ArrayList<GuidedAction>(mActions);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getItemViewType(int position) {
- return mStylist.getItemViewType(mActions.get(position));
- }
-
- private RecyclerView getRecyclerView() {
- return mStylist.getActionsGridView();
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
- GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent, viewType);
- View v = vh.view;
- v.setOnKeyListener(mActionOnKeyListener);
- v.setOnClickListener(mOnClickListener);
- v.setOnFocusChangeListener(mActionOnFocusListener);
-
- setupListeners(vh.getEditableTitleView());
- setupListeners(vh.getEditableDescriptionView());
-
- return new ActionViewHolder(v, vh);
- }
-
- private void setupListeners(EditText edit) {
- if (edit != null) {
- edit.setPrivateImeOptions("EscapeNorth=1;");
- edit.setOnEditorActionListener(mActionEditListener);
- if (edit instanceof ImeKeyMonitor) {
- ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
- monitor.setImeKeyListener(mActionEditListener);
- }
- }
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public void onBindViewHolder(ViewHolder holder, int position) {
- if (position >= mActions.size()) {
- return;
- }
- final ActionViewHolder avh = (ActionViewHolder)holder;
- GuidedAction action = mActions.get(position);
- avh.setAction(action);
- mStylist.onBindViewHolder(avh.mStylistViewHolder, action);
- }
-
- /**
- * {@inheritDoc}
- */
- @Override
- public int getItemCount() {
- return mActions.size();
- }
-
- private class ActionOnFocusListener implements View.OnFocusChangeListener {
-
- private FocusListener mFocusListener;
- private View mSelectedView;
-
- ActionOnFocusListener(FocusListener focusListener) {
- mFocusListener = focusListener;
- }
-
- public void setFocusListener(FocusListener focusListener) {
- mFocusListener = focusListener;
- }
-
- public void unFocus() {
- if (mSelectedView != null && getRecyclerView() != null) {
- ViewHolder vh = getRecyclerView().getChildViewHolder(mSelectedView);
- if (vh != null) {
- ActionViewHolder avh = (ActionViewHolder)vh;
- mStylist.onAnimateItemFocused(avh.mStylistViewHolder, false);
- } else {
- Log.w(TAG, "RecyclerView returned null view holder",
- new Throwable());
- }
- }
- }
-
- @Override
- public void onFocusChange(View v, boolean hasFocus) {
- if (getRecyclerView() == null) {
- return;
- }
- ActionViewHolder avh = (ActionViewHolder) getRecyclerView().getChildViewHolder(v);
- if (hasFocus) {
- mSelectedView = v;
- if (mFocusListener != null) {
- // We still call onGuidedActionFocused so that listeners can clear
- // state if they want.
- mFocusListener.onGuidedActionFocused(avh.getAction());
- }
- } else {
- if (mSelectedView == v) {
- mStylist.onAnimateItemPressedCancelled(avh.mStylistViewHolder);
- mSelectedView = null;
- }
- }
- mStylist.onAnimateItemFocused(avh.mStylistViewHolder, hasFocus);
- }
- }
-
- public ActionViewHolder findSubChildViewHolder(View v) {
- // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
- if (getRecyclerView() == null) {
- return null;
- }
- ActionViewHolder result = null;
- ViewParent parent = v.getParent();
- while (parent != getRecyclerView() && parent != null && v != null) {
- v = (View)parent;
- parent = parent.getParent();
- }
- if (parent != null && v != null) {
- result = (ActionViewHolder)getRecyclerView().getChildViewHolder(v);
- }
- return result;
- }
-
- public void handleCheckedActions(ActionViewHolder avh) {
- GuidedAction action = avh.getAction();
- int actionCheckSetId = action.getCheckSetId();
- if (getRecyclerView() != null && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
- // Find any actions that are checked and are in the same group
- // as the selected action. Fade their checkmarks out.
- if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
- for (int i = 0, size = mActions.size(); i < size; i++) {
- GuidedAction a = mActions.get(i);
- if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
- a.setChecked(false);
- ViewHolder vh = getRecyclerView().findViewHolderForPosition(i);
- if (vh != null) {
- GuidedActionsStylist.ViewHolder subViewHolder =
- ((ActionViewHolder)vh).mStylistViewHolder;
- mStylist.onAnimateItemChecked(subViewHolder, false);
- }
- }
- }
- }
-
- // If we we'ren't already checked, fade our checkmark in.
- if (!action.isChecked()) {
- action.setChecked(true);
- mStylist.onAnimateItemChecked(avh.mStylistViewHolder, true);
- } else {
- if (actionCheckSetId == GuidedAction.CHECKBOX_CHECK_SET_ID) {
- action.setChecked(false);
- mStylist.onAnimateItemChecked(avh.mStylistViewHolder, false);
- }
- }
- }
- }
-
- public void performOnActionClick(ActionViewHolder avh) {
- if (mClickListener != null) {
- mClickListener.onGuidedActionClicked(avh.getAction());
- }
- }
-
- private class ActionOnKeyListener implements View.OnKeyListener {
-
- 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.
- */
- @Override
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (v == null || event == null || getRecyclerView() == null) {
- return false;
- }
- boolean handled = false;
- switch (keyCode) {
- case KeyEvent.KEYCODE_DPAD_CENTER:
- case KeyEvent.KEYCODE_NUMPAD_ENTER:
- case KeyEvent.KEYCODE_BUTTON_X:
- case KeyEvent.KEYCODE_BUTTON_Y:
- case KeyEvent.KEYCODE_ENTER:
-
- ActionViewHolder avh = (ActionViewHolder) getRecyclerView()
- .getChildViewHolder(v);
- GuidedAction action = avh.getAction();
-
- if (!action.isEnabled() || action.infoOnly()) {
- if (event.getAction() == KeyEvent.ACTION_DOWN) {
- // TODO: requires API 19
- //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
- }
- return true;
- }
-
- switch (event.getAction()) {
- case KeyEvent.ACTION_DOWN:
- if (DEBUG) {
- Log.d(TAG, "Enter Key down");
- }
- if (!mKeyPressed) {
- mKeyPressed = true;
- playSound(v, AudioManager.FX_KEY_CLICK);
- mStylist.onAnimateItemPressed(avh.mStylistViewHolder,
- mKeyPressed);
- }
- break;
- case KeyEvent.ACTION_UP:
- if (DEBUG) {
- Log.d(TAG, "Enter Key up");
- }
- // Sometimes we are losing ACTION_DOWN for the first ENTER after pressed
- // Escape in IME.
- if (mKeyPressed) {
- mKeyPressed = false;
- mStylist.onAnimateItemPressed(avh.mStylistViewHolder, mKeyPressed);
- }
- break;
- default:
- break;
- }
- break;
- default:
- break;
- }
- return handled;
- }
-
- }
-
- private class ActionEditListener implements OnEditorActionListener,
- ImeKeyMonitor.ImeKeyListener {
-
- @Override
- public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
- boolean handled = false;
- if (actionId == EditorInfo.IME_ACTION_NEXT ||
- actionId == EditorInfo.IME_ACTION_DONE) {
- mGroup.fillAndGoNext(GuidedActionAdapter.this, v);
- handled = true;
- } else if (actionId == EditorInfo.IME_ACTION_NONE) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
- // Escape north handling: stay on current item, but close editor
- handled = true;
- mGroup.fillAndStay(GuidedActionAdapter.this, v);
- }
- return handled;
- }
-
- @Override
- public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
- if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
- mGroup.fillAndStay(GuidedActionAdapter.this, editText);
- } else if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() ==
- KeyEvent.ACTION_UP) {
- mGroup.fillAndGoNext(GuidedActionAdapter.this, editText);
- }
- return false;
- }
-
- }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapterGroup.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapterGroup.java
deleted file mode 100644
index 8f8951c..0000000
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedActionAdapterGroup.java
+++ /dev/null
@@ -1,188 +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.v17.leanback.app;
-
-import android.content.Context;
-import android.support.v17.leanback.app.GuidedActionAdapter.ActionViewHolder;
-import android.support.v17.leanback.app.GuidedActionAdapter.ClickListener;
-import android.support.v17.leanback.app.GuidedActionAdapter.EditListener;
-import android.support.v17.leanback.widget.GuidedAction;
-import android.support.v17.leanback.widget.ImeKeyMonitor;
-import android.util.Log;
-import android.view.KeyEvent;
-import android.view.View;
-import android.view.ViewParent;
-import android.view.inputmethod.InputMethodManager;
-import android.widget.EditText;
-import android.widget.TextView;
-import android.widget.TextView.OnEditorActionListener;
-
-import java.util.ArrayList;
-
-/**
- * Internal implementation manages a group of GuidedActionAdapters, control the next action after
- * editing finished, maintain the Ime open/close status.
- */
-class GuidedActionAdapterGroup {
-
- private static final String TAG_EDIT = "EditableAction";
- private static final boolean DEBUG_EDIT = false;
-
- ArrayList<GuidedActionAdapter> mAdapters = new ArrayList<GuidedActionAdapter>();
- private boolean mImeOpened;
- private EditListener mEditListener;
-
- GuidedActionAdapterGroup() {
- }
-
- public void addAdpter(GuidedActionAdapter adapter) {
- mAdapters.add(adapter);
- adapter.mGroup = this;
- }
-
- public void setEditListener(EditListener listener) {
- mEditListener = listener;
- }
-
- boolean focusToNextAction(GuidedActionAdapter adapter, GuidedAction action, long nextActionId) {
- // for ACTION_ID_NEXT, we first find out the matching index in Actions list.
- int index = 0;
- if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
- index = adapter.indexOf(action);
- if (index < 0) {
- return false;
- }
- // start from next, if reach end, will go next Adapter below
- index++;
- }
-
- int adapterIndex = mAdapters.indexOf(adapter);
- do {
- int size = adapter.getCount();
- if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
- while (index < size && !adapter.getItem(index).isFocusable()) {
- index++;
- }
- } else {
- while (index < size && adapter.getItem(index).getId() != nextActionId) {
- index++;
- }
- }
- if (index < size) {
- ActionViewHolder vh = (ActionViewHolder) adapter.getGuidedActionsStylist()
- .getActionsGridView().findViewHolderForPosition(index);
- if (vh != null) {
- if (vh.getAction().isEditable() || vh.getAction().isDescriptionEditable()) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme of next Action");
- // open Ime on next action.
- openIme(adapter, vh);
- } else {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme and focus to next Action");
- // close IME and focus to next (not editable) action
- closeIme(vh.mStylistViewHolder.view);
- vh.mStylistViewHolder.view.requestFocus();
- }
- return true;
- }
- return false;
- }
- // search from index 0 of next Adapter
- adapterIndex++;
- if (adapterIndex >= mAdapters.size()) {
- break;
- }
- adapter = mAdapters.get(adapterIndex);
- index = 0;
- } while (true);
- return false;
- }
-
- public void openIme(GuidedActionAdapter adapter, ActionViewHolder avh) {
- adapter.getGuidedActionsStylist().setEditingMode(avh.mStylistViewHolder, avh.getAction(),
- true);
- View v = avh.mStylistViewHolder.getEditingView();
- if (v == null) {
- return;
- }
- InputMethodManager mgr = (InputMethodManager)
- v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- v.requestFocus();
- mgr.showSoftInput(v, 0);
- if (!mImeOpened) {
- mImeOpened = true;
- mEditListener.onImeOpen();
- }
- }
-
- public void closeIme(View v) {
- if (mImeOpened) {
- mImeOpened = false;
- InputMethodManager mgr = (InputMethodManager)
- v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
- mgr.hideSoftInputFromWindow(v.getWindowToken(), 0);
- mEditListener.onImeClose();
- }
- }
-
- private long finishEditing(GuidedActionAdapter adapter, ActionViewHolder avh) {
- long nextActionId = mEditListener.onGuidedActionEdited(avh.getAction());
- adapter.getGuidedActionsStylist().setEditingMode(avh.mStylistViewHolder, avh.getAction(),
- false);
- return nextActionId;
- }
-
- public void fillAndStay(GuidedActionAdapter adapter, TextView v) {
- ActionViewHolder avh = adapter.findSubChildViewHolder(v);
- updateTextIntoAction(avh, v);
- finishEditing(adapter, avh);
- closeIme(v);
- avh.mStylistViewHolder.view.requestFocus();
- }
-
- public void fillAndGoNext(GuidedActionAdapter adapter, TextView v) {
- boolean handled = false;
- ActionViewHolder avh = adapter.findSubChildViewHolder(v);
- updateTextIntoAction(avh, v);
- adapter.performOnActionClick(avh);
- long nextActionId = finishEditing(adapter, avh);
- if (nextActionId != GuidedAction.ACTION_ID_CURRENT
- && nextActionId != avh.getAction().getId()) {
- handled = focusToNextAction(adapter, avh.getAction(), nextActionId);
- }
- if (!handled) {
- if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next action");
- handled = true;
- closeIme(v);
- avh.mStylistViewHolder.view.requestFocus();
- }
- }
-
- private void updateTextIntoAction(ActionViewHolder avh, TextView v) {
- GuidedAction action = avh.getAction();
- if (v == avh.mStylistViewHolder.getDescriptionView()) {
- if (action.getEditDescription() != null) {
- action.setEditDescription(v.getText());
- } else {
- action.setDescription(v.getText());
- }
- } else if (v == avh.mStylistViewHolder.getTitleView()) {
- if (action.getEditTitle() != null) {
- action.setEditTitle(v.getText());
- } else {
- action.setTitle(v.getText());
- }
- }
- }
-
-}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
index 8be8f24..0706be5 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -21,20 +21,20 @@
import android.app.FragmentManager.BackStackEntry;
import android.app.FragmentTransaction;
import android.content.Context;
-import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionAdapter;
+import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.ViewHolderTask;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -42,11 +42,8 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
@@ -138,24 +135,33 @@
* @see GuidedAction
* @see GuidedActionsStylist
*/
-public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.ClickListener,
- GuidedActionAdapter.FocusListener {
+public class GuidedStepFragment extends Fragment implements GuidedActionAdapter.FocusListener {
private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepFragment";
private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
+ private static final String EXTRA_ACTION_PREFIX = "action_";
+ private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+ private static final boolean IS_FRAMEWORK_FRAGMENT = true;
+
/**
- * Fragment argument name for UI style. The argument value is persisted in fragment state.
- * The value is initially {@link #UI_STYLE_ENTRANCE} and might be changed in one of the three
- * helper functions:
+ * Fragment argument name for UI style. The argument value is persisted in fragment state and
+ * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
+ * might be changed in one of the three helper functions:
* <ul>
- * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)}</li>
+ * <li>{@link #addAsRoot(Activity, GuidedStepFragment, int)} sets to
+ * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
* <li>{@link #add(FragmentManager, GuidedStepFragment)} or {@link #add(FragmentManager,
- * GuidedStepFragment, int)}</li>
+ * GuidedStepFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
+ * GuidedStepFragment on stack.</li>
+ * <li>{@link #finishGuidedStepFragments()} changes current GuidedStepFragment to
+ * {@link #UI_STYLE_ENTRANCE} for the non activity case. This is a special case that changes
+ * the transition settings after fragment has been created, in order to force current
+ * GuidedStepFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
* </ul>
* <p>
* Argument value can be either:
@@ -179,6 +185,12 @@
public static final int UI_STYLE_REPLACE = 0;
/**
+ * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
+ */
+ @Deprecated
+ public static final int UI_STYLE_DEFAULT = 0;
+
+ /**
* Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
* GuidedStepFragment constructor. This is the case that we show GuidedStepFragment on top of
* other content. The default behavior of this style:
@@ -188,6 +200,9 @@
* because fragment transition asks for exit transition before UI style is restored in Fragment
* .onCreate().</li>
* </ul>
+ * When popping multiple GuidedStepFragment, {@link #finishGuidedStepFragments()} also changes
+ * the top GuidedStepFragment to UI_STYLE_ENTRANCE in order to run the return transition
+ * (reverse of enter transition) of UI_STYLE_ENTRANCE.
*/
public static final int UI_STYLE_ENTRANCE = 1;
@@ -203,21 +218,48 @@
*/
public static final int UI_STYLE_ACTIVITY_ROOT = 2;
+ /**
+ * Animation to slide the contents from the side (left/right).
+ * @hide
+ */
+ public static final int SLIDE_FROM_SIDE = 0;
+
+ /**
+ * Animation to slide the contents from the bottom.
+ * @hide
+ */
+ public static final int SLIDE_FROM_BOTTOM = 1;
+
private static final String TAG = "GuidedStepFragment";
private static final boolean DEBUG = false;
+ /**
+ * @hide
+ */
+ public static class DummyFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View v = new View(inflater.getContext());
+ v.setVisibility(View.GONE);
+ return v;
+ }
+ }
+
private int mTheme;
private ContextThemeWrapper mThemeWrapper;
private GuidanceStylist mGuidanceStylist;
private GuidedActionsStylist mActionsStylist;
private GuidedActionsStylist mButtonActionsStylist;
private GuidedActionAdapter mAdapter;
+ private GuidedActionAdapter mSubAdapter;
private GuidedActionAdapter mButtonAdapter;
private GuidedActionAdapterGroup mAdapterGroup;
private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
private int mSelectedIndex = -1;
private int mButtonSelectedIndex = -1;
+ private int entranceTransitionType = SLIDE_FROM_SIDE;
public GuidedStepFragment() {
// We need to supply the theme before any potential call to onInflate in order
@@ -253,7 +295,9 @@
* @return The GuidedActionsStylist used in this fragment.
*/
public GuidedActionsStylist onCreateButtonActionsStylist() {
- return new GuidedActionsStylist();
+ GuidedActionsStylist stylist = new GuidedActionsStylist();
+ stylist.setAsButtonActions();
+ return stylist;
}
/**
@@ -302,11 +346,56 @@
* order to act on the user's decisions.
* @param action The chosen action.
*/
- @Override
public void onGuidedActionClicked(GuidedAction action) {
}
/**
+ * Callback invoked when an action in sub actions is taken by the user. Subclasses should
+ * override in order to act on the user's decisions. Default return value is true to close
+ * the sub actions list.
+ * @param action The chosen action.
+ * @return true to collapse the sub actions list, false to keep it expanded.
+ */
+ public boolean onSubGuidedActionClicked(GuidedAction action) {
+ return true;
+ }
+
+ /**
+ * @return True if the sub actions list is expanded, false otherwise.
+ */
+ public boolean isSubActionsExpanded() {
+ return mActionsStylist.isSubActionsExpanded();
+ }
+
+ /**
+ * Expand a given action's sub actions list.
+ * @param action GuidedAction to expand.
+ * @see GuidedAction#getSubActions()
+ */
+ public void expandSubActions(GuidedAction action) {
+ final int actionPosition = mActions.indexOf(action);
+ if (actionPosition < 0) {
+ return;
+ }
+ mActionsStylist.getActionsGridView().setSelectedPositionSmooth(actionPosition,
+ new ViewHolderTask() {
+ @Override
+ public void run(RecyclerView.ViewHolder vh) {
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder) vh;
+ mActionsStylist.setExpandedViewHolder(avh);
+ }
+ });
+ }
+
+ /**
+ * Collapse sub actions list.
+ * @see GuidedAction#getSubActions()
+ */
+ public void collapseSubActions() {
+ mActionsStylist.setExpandedViewHolder(null);
+ }
+
+ /**
* Callback invoked when an action is focused (made to be the current selection) by the user.
*/
@Override
@@ -314,17 +403,29 @@
}
/**
- * Callback invoked when an action's title or description has been edited.
- * Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} instead of app wants to
- * control the next action to focus on.
+ * Callback invoked when an action's title or description has been edited, this happens either
+ * when user clicks confirm button in IME or user closes IME window by BACK key.
+ * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
+ * {@link #onGuidedActionEditCanceled(GuidedAction)}.
*/
+ @Deprecated
public void onGuidedActionEdited(GuidedAction action) {
}
/**
- * Callback invoked when an action's title or description has been edited. Default
- * implementation calls {@link #onGuidedActionEdited(GuidedAction)} and returns
- * {@link GuidedAction#ACTION_ID_NEXT}.
+ * Callback invoked when an action has been canceled editing, for example when user closes
+ * IME window by BACK key. Default implementation calls deprecated method
+ * {@link #onGuidedActionEdited(GuidedAction)}.
+ * @param action The action which has been canceled editing.
+ */
+ public void onGuidedActionEditCanceled(GuidedAction action) {
+ onGuidedActionEdited(action);
+ }
+
+ /**
+ * Callback invoked when an action has been edited, for example when user clicks confirm button
+ * in IME window. Default implementation calls deprecated method
+ * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
*
* @param action The action that has been edited.
* @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
@@ -347,7 +448,7 @@
* than via XML.
* @param fragmentManager The FragmentManager to be used in the transaction.
* @param fragment The GuidedStepFragment to be inserted into the fragment stack.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit.
*/
public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment) {
return add(fragmentManager, fragment, android.R.id.content);
@@ -368,11 +469,18 @@
* @param fragmentManager The FragmentManager to be used in the transaction.
* @param fragment The GuidedStepFragment to be inserted into the fragment stack.
* @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit.
*/
public static int add(FragmentManager fragmentManager, GuidedStepFragment fragment, int id) {
GuidedStepFragment current = getCurrentGuidedStepFragment(fragmentManager);
boolean inGuidedStep = current != null;
+ if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
+ && !inGuidedStep) {
+ // workaround b/22631964 for framework fragment
+ fragmentManager.beginTransaction()
+ .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
+ .commit();
+ }
FragmentTransaction ft = fragmentManager.beginTransaction();
fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
@@ -398,30 +506,34 @@
*/
protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepFragment
disappearing) {
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ View fragmentView = disappearing.getView();
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment_root), "action_fragment_root");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment_background), "action_fragment_background");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment), "action_fragment");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_root), "guidedactions_root");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
- R.id.guidedactions_selector), "guidedactions_selector");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_content), "guidedactions_content");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_list_background), "guidedactions_list_background");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_root2), "guidedactions_root2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
- R.id.guidedactions_selector2), "guidedactions_selector2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_content2), "guidedactions_content2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_list_background2), "guidedactions_list_background2");
}
+ private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
+ String transitionName)
+ {
+ if (subView != null)
+ TransitionHelper.addSharedElement(ft, subView, transitionName);
+ }
+
/**
* Returns BackStackEntry name for the GuidedStepFragment or empty String if no entry is
* associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method
@@ -429,7 +541,7 @@
* @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
* associated.
*/
- public String generateStackEntryName() {
+ String generateStackEntryName() {
return generateStackEntryName(getUiStyle(), getClass());
}
@@ -440,7 +552,7 @@
* @return BackStackEntry name for the GuidedStepFragment or empty String if no entry is
* associated.
*/
- public static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+ static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
if (!GuidedStepFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
return "";
}
@@ -456,33 +568,24 @@
}
/**
- * Returns true if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
- * false otherwise.
+ * Returns true if the backstack entry represents GuidedStepFragment with
+ * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepFragment pushed to stack; false
+ * otherwise.
+ * @see #generateStackEntryName(int, Class)
* @param backStackEntryName Name of BackStackEntry.
* @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_ENTRANCE};
* false otherwise.
*/
- public static boolean isUiStyleEntrance(String backStackEntryName) {
+ static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
}
/**
- * Returns true if the backstack represents GuidedStepFragment with {@link #UI_STYLE_REPLACE};
- * false otherwise.
- * @param backStackEntryName Name of BackStackEntry.
- * @return True if the backstack represents GuidedStepFragment with {@link #UI_STYLE_REPLACE};
- * false otherwise.
- */
- public static boolean isUiStyleDefault(String backStackEntryName) {
- return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_REPLACE);
- }
-
- /**
* Extract Class name from BackStackEntry name.
* @param backStackEntryName Name of BackStackEntry.
* @return Class name of GuidedStepFragment.
*/
- public static String getGuidedStepFragmentClassName(String backStackEntryName) {
+ static String getGuidedStepFragmentClassName(String backStackEntryName) {
if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
} else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
@@ -494,7 +597,10 @@
/**
* Adds the specified GuidedStepFragment as content of Activity; no backstack entry is added so
- * the activity will be dismissed when BACK key is pressed.
+ * the activity will be dismissed when BACK key is pressed. The method is typically called in
+ * Activity.onCreate() when savedInstanceState is null. When savedInstanceState is not null,
+ * the Activity is being restored, do not call addAsRoot() to duplicate the Fragment restored
+ * by FragmentManager.
* {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
*
* Note: currently fragments added using this method must be created programmatically rather
@@ -502,13 +608,18 @@
* @param activity The Activity to be used to insert GuidedstepFragment.
* @param fragment The GuidedStepFragment to be inserted into the fragment stack.
* @param id The id of container to add GuidedStepFragment, can be android.R.id.content.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+ * GuidedStepFragment.
*/
public static int addAsRoot(Activity activity, GuidedStepFragment fragment, int id) {
// Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
activity.getWindow().getDecorView();
-
FragmentManager fragmentManager = activity.getFragmentManager();
+ if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+ Log.w(TAG, "Fragment is already exists, likely calling " +
+ "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+ return -1;
+ }
FragmentTransaction ft = fragmentManager.beginTransaction();
fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
@@ -753,18 +864,24 @@
Object changeBounds = TransitionHelper.createChangeBounds(false);
TransitionHelper.setSharedElementEnterTransition(this, changeBounds);
} else if (uiStyle == UI_STYLE_ENTRANCE) {
- Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
- TransitionHelper.FADE_OUT);
- TransitionHelper.include(fade, R.id.guidedstep_background);
- Object slide = TransitionHelper.createFadeAndShortSlide(Gravity.END |
- Gravity.START);
- TransitionHelper.include(slide, R.id.content_fragment);
- TransitionHelper.include(slide, R.id.action_fragment_root);
- Object enterTransition = TransitionHelper.createTransitionSet(false);
- TransitionHelper.addTransition(enterTransition, fade);
- TransitionHelper.addTransition(enterTransition, slide);
- TransitionHelper.setEnterTransition(this, enterTransition);
-
+ if (entranceTransitionType == SLIDE_FROM_SIDE) {
+ Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
+ TransitionHelper.FADE_OUT);
+ TransitionHelper.include(fade, R.id.guidedstep_background);
+ Object slideFromSide = TransitionHelper.createFadeAndShortSlide(Gravity.END | Gravity.START);
+ TransitionHelper.include(slideFromSide, R.id.content_fragment);
+ TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
+ Object enterTransition = TransitionHelper.createTransitionSet(false);
+ TransitionHelper.addTransition(enterTransition, fade);
+ TransitionHelper.addTransition(enterTransition, slideFromSide);
+ TransitionHelper.setEnterTransition(this, enterTransition);
+ } else {
+ Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(Gravity.BOTTOM);
+ TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
+ Object enterTransition = TransitionHelper.createTransitionSet(false);
+ TransitionHelper.addTransition(enterTransition, slideFromBottom);
+ TransitionHelper.setEnterTransition(this, enterTransition);
+ }
// No shared element transition
TransitionHelper.setSharedElementEnterTransition(this, null);
} else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
@@ -856,9 +973,15 @@
}
ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
onCreateActions(actions, savedInstanceState);
+ if (savedInstanceState != null) {
+ onRestoreActions(actions, savedInstanceState);
+ }
setActions(actions);
ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
onCreateButtonActions(buttonActions, savedInstanceState);
+ if (savedInstanceState != null) {
+ onRestoreButtonActions(buttonActions, savedInstanceState);
+ }
setButtonActions(buttonActions);
}
@@ -871,6 +994,7 @@
mActionsStylist.onDestroyView();
mButtonActionsStylist.onDestroyView();
mAdapter = null;
+ mSubAdapter = null;
mButtonAdapter = null;
mAdapterGroup = null;
super.onDestroyView();
@@ -887,8 +1011,12 @@
resolveTheme();
inflater = getThemeInflater(inflater);
- ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_guidedstep_fragment,
- container, false);
+ GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
+ R.layout.lb_guidedstep_fragment, container, false);
+
+ root.setFocusOutStart(isFocusOutStartAllowed());
+ root.setFocusOutEnd(isFocusOutEndAllowed());
+
ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
@@ -900,7 +1028,6 @@
actionContainer.addView(actionsView);
View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
- mButtonActionsStylist.setAsButtonActions();
actionContainer.addView(buttonActionsView);
GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
@@ -916,19 +1043,55 @@
}
@Override
- public long onGuidedActionEdited(GuidedAction action) {
+ public long onGuidedActionEditedAndProceed(GuidedAction action) {
return GuidedStepFragment.this.onGuidedActionEditedAndProceed(action);
}
+
+ @Override
+ public void onGuidedActionEditCanceled(GuidedAction action) {
+ GuidedStepFragment.this.onGuidedActionEditCanceled(action);
+ }
};
- mAdapter = new GuidedActionAdapter(mActions, this, this, mActionsStylist);
- mButtonAdapter = new GuidedActionAdapter(mButtonActions, this, this, mButtonActionsStylist);
+ mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ GuidedStepFragment.this.onGuidedActionClicked(action);
+ if (isSubActionsExpanded()) {
+ collapseSubActions();
+ } else if (action.hasSubActions()) {
+ expandSubActions(action);
+ }
+ }
+ }, this, mActionsStylist, false);
+ mButtonAdapter =
+ new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ GuidedStepFragment.this.onGuidedActionClicked(action);
+ }
+ }, this, mButtonActionsStylist, false);
+ mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ if (mActionsStylist.isInExpandTransition()) {
+ return;
+ }
+ if (GuidedStepFragment.this.onSubGuidedActionClicked(action)) {
+ collapseSubActions();
+ }
+ }
+ }, this, mActionsStylist, true);
mAdapterGroup = new GuidedActionAdapterGroup();
- mAdapterGroup.addAdpter(mAdapter);
- mAdapterGroup.addAdpter(mButtonAdapter);
+ mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
+ mAdapterGroup.addAdpter(mSubAdapter, null);
mAdapterGroup.setEditListener(editListener);
+ mActionsStylist.setEditListener(editListener);
mActionsStylist.getActionsGridView().setAdapter(mAdapter);
+ if (mActionsStylist.getSubActionsGridView() != null) {
+ mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
+ }
mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
if (mButtonActions.size() == 0) {
// when there is no button actions, we dont need show the second panel, but keep
@@ -959,9 +1122,12 @@
setSelectedButtonActionPosition(0);
+ // Add the background view.
View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
if (backgroundView != null) {
- root.addView(backgroundView, 0);
+ FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+ R.id.guidedstep_background_view_root);
+ backgroundViewRoot.addView(backgroundView, 0);
}
return root;
}
@@ -969,7 +1135,65 @@
@Override
public void onResume() {
super.onResume();
- mActionsStylist.getActionsGridView().requestFocus();
+ getView().findViewById(R.id.action_fragment).requestFocus();
+ }
+
+ /**
+ * Get the key will be used to save GuidedAction with Fragment.
+ * @param action GuidedAction to get key.
+ * @return Key to save the GuidedAction.
+ */
+ final String getAutoRestoreKey(GuidedAction action) {
+ return EXTRA_ACTION_PREFIX + action.getId();
+ }
+
+ /**
+ * Get the key will be used to save GuidedAction with Fragment.
+ * @param action GuidedAction to get key.
+ * @return Key to save the GuidedAction.
+ */
+ final String getButtonAutoRestoreKey(GuidedAction action) {
+ return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+ }
+
+ final static boolean isSaveEnabled(GuidedAction action) {
+ return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+ }
+
+ final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+ }
+ }
}
/**
@@ -978,6 +1202,8 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ onSaveActions(mActions, outState);
+ onSaveButtonActions(mButtonActions, outState);
outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
(mActionsStylist.getActionsGridView() != null) ?
getSelectedActionPosition() : mSelectedIndex);
@@ -1004,7 +1230,7 @@
if (entryCount > 0) {
for (int i = entryCount - 1; i >= 0; i--) {
BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
- if (isUiStyleEntrance(entry.getName())) {
+ if (isStackEntryUiStyleEntrance(entry.getName())) {
GuidedStepFragment top = getCurrentGuidedStepFragment(fragmentManager);
if (top != null) {
top.setUiStyle(UI_STYLE_ENTRANCE);
@@ -1042,6 +1268,43 @@
}
}
+ /**
+ * Returns true if allows focus out of start edge of GuidedStepFragment, false otherwise.
+ * Default value is false, the reason is to disable FocusFinder to find focusable views
+ * beneath content of GuidedStepFragment. Subclass may override.
+ * @return True if allows focus out of start edge of GuidedStepFragment.
+ */
+ public boolean isFocusOutStartAllowed() {
+ return false;
+ }
+
+ /**
+ * Returns true if allows focus out of end edge of GuidedStepFragment, false otherwise.
+ * Default value is false, the reason is to disable FocusFinder to find focusable views
+ * beneath content of GuidedStepFragment. Subclass may override.
+ * @return True if allows focus out of end edge of GuidedStepFragment.
+ */
+ public boolean isFocusOutEndAllowed() {
+ return false;
+ }
+
+ /**
+ * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
+ * Currently we provide 2 different variations for animation - slide in from
+ * side (default) or bottom.
+ *
+ * Ideally we can retireve the screen mode settings from the theme attribute
+ * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
+ * determine the transition. But the fragment context to retrieve the theme
+ * isn't available on platform v23 or earlier.
+ *
+ * For now clients(subclasses) can call this method inside the contructor.
+ * @hide
+ */
+ public void setEntranceTransitionType(int transitionType) {
+ this.entranceTransitionType = transitionType;
+ }
+
private void resolveTheme() {
// Look up the guidedStepTheme in the currently specified theme. If it exists,
// replace the theme with its value.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
new file mode 100644
index 0000000..deb8160
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepRootLayout.java
@@ -0,0 +1,69 @@
+/*
+ * 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.v17.leanback.app;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.Util;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+/**
+ * Utility class used by GuidedStepFragment to disable focus out left/right.
+ * @hide
+ */
+class GuidedStepRootLayout extends LinearLayout {
+
+ private boolean mFocusOutStart = false;
+ private boolean mFocusOutEnd = false;
+
+ public GuidedStepRootLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public GuidedStepRootLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public void setFocusOutStart(boolean focusOutStart) {
+ mFocusOutStart = focusOutStart;
+ }
+
+ public void setFocusOutEnd(boolean focusOutEnd) {
+ mFocusOutEnd = focusOutEnd;
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ View newFocus = super.focusSearch(focused, direction);
+ if (direction == FOCUS_LEFT || direction == FOCUS_RIGHT) {
+ if (Util.isDescendant(this, newFocus)) {
+ return newFocus;
+ }
+ if (getLayoutDirection() == ViewGroup.LAYOUT_DIRECTION_LTR ?
+ direction == FOCUS_LEFT : direction == FOCUS_RIGHT) {
+ if (!mFocusOutStart) {
+ return focused;
+ }
+ } else {
+ if (!mFocusOutEnd) {
+ return focused;
+ }
+ }
+ }
+ return newFocus;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
index ad186ff..b20dfbb 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -23,20 +23,20 @@
import android.support.v4.app.FragmentManager.BackStackEntry;
import android.support.v4.app.FragmentTransaction;
import android.content.Context;
-import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.NonNull;
-import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.GuidanceStylist;
import android.support.v17.leanback.widget.GuidanceStylist.Guidance;
import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionAdapter;
+import android.support.v17.leanback.widget.GuidedActionAdapterGroup;
import android.support.v17.leanback.widget.GuidedActionsStylist;
-import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.ViewHolderTask;
import android.support.v4.app.ActivityCompat;
import android.support.v7.widget.RecyclerView;
-import android.util.AttributeSet;
import android.util.Log;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -44,11 +44,8 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.ImageView;
-import android.widget.RelativeLayout;
+import android.widget.FrameLayout;
import android.widget.LinearLayout;
-import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
@@ -140,24 +137,33 @@
* @see GuidedAction
* @see GuidedActionsStylist
*/
-public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.ClickListener,
- GuidedActionAdapter.FocusListener {
+public class GuidedStepSupportFragment extends Fragment implements GuidedActionAdapter.FocusListener {
private static final String TAG_LEAN_BACK_ACTIONS_FRAGMENT = "leanBackGuidedStepSupportFragment";
private static final String EXTRA_ACTION_SELECTED_INDEX = "selectedIndex";
+ private static final String EXTRA_ACTION_PREFIX = "action_";
+ private static final String EXTRA_BUTTON_ACTION_PREFIX = "buttonaction_";
private static final String ENTRY_NAME_REPLACE = "GuidedStepDefault";
private static final String ENTRY_NAME_ENTRANCE = "GuidedStepEntrance";
+ private static final boolean IS_FRAMEWORK_FRAGMENT = false;
+
/**
- * Fragment argument name for UI style. The argument value is persisted in fragment state.
- * The value is initially {@link #UI_STYLE_ENTRANCE} and might be changed in one of the three
- * helper functions:
+ * Fragment argument name for UI style. The argument value is persisted in fragment state and
+ * used to select fragment transition. The value is initially {@link #UI_STYLE_ENTRANCE} and
+ * might be changed in one of the three helper functions:
* <ul>
- * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)}</li>
+ * <li>{@link #addAsRoot(FragmentActivity, GuidedStepSupportFragment, int)} sets to
+ * {@link #UI_STYLE_ACTIVITY_ROOT}</li>
* <li>{@link #add(FragmentManager, GuidedStepSupportFragment)} or {@link #add(FragmentManager,
- * GuidedStepSupportFragment, int)}</li>
+ * GuidedStepSupportFragment, int)} sets it to {@link #UI_STYLE_REPLACE} if there is already a
+ * GuidedStepSupportFragment on stack.</li>
+ * <li>{@link #finishGuidedStepSupportFragments()} changes current GuidedStepSupportFragment to
+ * {@link #UI_STYLE_ENTRANCE} for the non activity case. This is a special case that changes
+ * the transition settings after fragment has been created, in order to force current
+ * GuidedStepSupportFragment run a return transition of {@link #UI_STYLE_ENTRANCE}</li>
* </ul>
* <p>
* Argument value can be either:
@@ -181,6 +187,12 @@
public static final int UI_STYLE_REPLACE = 0;
/**
+ * @deprecated Same value as {@link #UI_STYLE_REPLACE}.
+ */
+ @Deprecated
+ public static final int UI_STYLE_DEFAULT = 0;
+
+ /**
* Default value for argument {@link #EXTRA_UI_STYLE}. The default value is assigned in
* GuidedStepSupportFragment constructor. This is the case that we show GuidedStepSupportFragment on top of
* other content. The default behavior of this style:
@@ -190,6 +202,9 @@
* because fragment transition asks for exit transition before UI style is restored in Fragment
* .onCreate().</li>
* </ul>
+ * When popping multiple GuidedStepSupportFragment, {@link #finishGuidedStepSupportFragments()} also changes
+ * the top GuidedStepSupportFragment to UI_STYLE_ENTRANCE in order to run the return transition
+ * (reverse of enter transition) of UI_STYLE_ENTRANCE.
*/
public static final int UI_STYLE_ENTRANCE = 1;
@@ -205,21 +220,48 @@
*/
public static final int UI_STYLE_ACTIVITY_ROOT = 2;
+ /**
+ * Animation to slide the contents from the side (left/right).
+ * @hide
+ */
+ public static final int SLIDE_FROM_SIDE = 0;
+
+ /**
+ * Animation to slide the contents from the bottom.
+ * @hide
+ */
+ public static final int SLIDE_FROM_BOTTOM = 1;
+
private static final String TAG = "GuidedStepSupportFragment";
private static final boolean DEBUG = false;
+ /**
+ * @hide
+ */
+ public static class DummyFragment extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ final View v = new View(inflater.getContext());
+ v.setVisibility(View.GONE);
+ return v;
+ }
+ }
+
private int mTheme;
private ContextThemeWrapper mThemeWrapper;
private GuidanceStylist mGuidanceStylist;
private GuidedActionsStylist mActionsStylist;
private GuidedActionsStylist mButtonActionsStylist;
private GuidedActionAdapter mAdapter;
+ private GuidedActionAdapter mSubAdapter;
private GuidedActionAdapter mButtonAdapter;
private GuidedActionAdapterGroup mAdapterGroup;
private List<GuidedAction> mActions = new ArrayList<GuidedAction>();
private List<GuidedAction> mButtonActions = new ArrayList<GuidedAction>();
private int mSelectedIndex = -1;
private int mButtonSelectedIndex = -1;
+ private int entranceTransitionType = SLIDE_FROM_SIDE;
public GuidedStepSupportFragment() {
// We need to supply the theme before any potential call to onInflate in order
@@ -255,7 +297,9 @@
* @return The GuidedActionsStylist used in this fragment.
*/
public GuidedActionsStylist onCreateButtonActionsStylist() {
- return new GuidedActionsStylist();
+ GuidedActionsStylist stylist = new GuidedActionsStylist();
+ stylist.setAsButtonActions();
+ return stylist;
}
/**
@@ -304,11 +348,56 @@
* order to act on the user's decisions.
* @param action The chosen action.
*/
- @Override
public void onGuidedActionClicked(GuidedAction action) {
}
/**
+ * Callback invoked when an action in sub actions is taken by the user. Subclasses should
+ * override in order to act on the user's decisions. Default return value is true to close
+ * the sub actions list.
+ * @param action The chosen action.
+ * @return true to collapse the sub actions list, false to keep it expanded.
+ */
+ public boolean onSubGuidedActionClicked(GuidedAction action) {
+ return true;
+ }
+
+ /**
+ * @return True if the sub actions list is expanded, false otherwise.
+ */
+ public boolean isSubActionsExpanded() {
+ return mActionsStylist.isSubActionsExpanded();
+ }
+
+ /**
+ * Expand a given action's sub actions list.
+ * @param action GuidedAction to expand.
+ * @see GuidedAction#getSubActions()
+ */
+ public void expandSubActions(GuidedAction action) {
+ final int actionPosition = mActions.indexOf(action);
+ if (actionPosition < 0) {
+ return;
+ }
+ mActionsStylist.getActionsGridView().setSelectedPositionSmooth(actionPosition,
+ new ViewHolderTask() {
+ @Override
+ public void run(RecyclerView.ViewHolder vh) {
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder) vh;
+ mActionsStylist.setExpandedViewHolder(avh);
+ }
+ });
+ }
+
+ /**
+ * Collapse sub actions list.
+ * @see GuidedAction#getSubActions()
+ */
+ public void collapseSubActions() {
+ mActionsStylist.setExpandedViewHolder(null);
+ }
+
+ /**
* Callback invoked when an action is focused (made to be the current selection) by the user.
*/
@Override
@@ -316,17 +405,29 @@
}
/**
- * Callback invoked when an action's title or description has been edited.
- * Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} instead of app wants to
- * control the next action to focus on.
+ * Callback invoked when an action's title or description has been edited, this happens either
+ * when user clicks confirm button in IME or user closes IME window by BACK key.
+ * @deprecated Override {@link #onGuidedActionEditedAndProceed(GuidedAction)} and/or
+ * {@link #onGuidedActionEditCanceled(GuidedAction)}.
*/
+ @Deprecated
public void onGuidedActionEdited(GuidedAction action) {
}
/**
- * Callback invoked when an action's title or description has been edited. Default
- * implementation calls {@link #onGuidedActionEdited(GuidedAction)} and returns
- * {@link GuidedAction#ACTION_ID_NEXT}.
+ * Callback invoked when an action has been canceled editing, for example when user closes
+ * IME window by BACK key. Default implementation calls deprecated method
+ * {@link #onGuidedActionEdited(GuidedAction)}.
+ * @param action The action which has been canceled editing.
+ */
+ public void onGuidedActionEditCanceled(GuidedAction action) {
+ onGuidedActionEdited(action);
+ }
+
+ /**
+ * Callback invoked when an action has been edited, for example when user clicks confirm button
+ * in IME window. Default implementation calls deprecated method
+ * {@link #onGuidedActionEdited(GuidedAction)} and returns {@link GuidedAction#ACTION_ID_NEXT}.
*
* @param action The action that has been edited.
* @return ID of the action will be focused or {@link GuidedAction#ACTION_ID_NEXT},
@@ -349,7 +450,7 @@
* than via XML.
* @param fragmentManager The FragmentManager to be used in the transaction.
* @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit.
*/
public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment) {
return add(fragmentManager, fragment, android.R.id.content);
@@ -370,11 +471,18 @@
* @param fragmentManager The FragmentManager to be used in the transaction.
* @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
* @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit.
*/
public static int add(FragmentManager fragmentManager, GuidedStepSupportFragment fragment, int id) {
GuidedStepSupportFragment current = getCurrentGuidedStepSupportFragment(fragmentManager);
boolean inGuidedStep = current != null;
+ if (IS_FRAMEWORK_FRAGMENT && Build.VERSION.SDK_INT >= 21 && Build.VERSION.SDK_INT < 23
+ && !inGuidedStep) {
+ // workaround b/22631964 for framework fragment
+ fragmentManager.beginTransaction()
+ .replace(id, new DummyFragment(), TAG_LEAN_BACK_ACTIONS_FRAGMENT)
+ .commit();
+ }
FragmentTransaction ft = fragmentManager.beginTransaction();
fragment.setUiStyle(inGuidedStep ? UI_STYLE_REPLACE : UI_STYLE_ENTRANCE);
@@ -400,30 +508,34 @@
*/
protected void onAddSharedElementTransition(FragmentTransaction ft, GuidedStepSupportFragment
disappearing) {
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ View fragmentView = disappearing.getView();
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment_root), "action_fragment_root");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment_background), "action_fragment_background");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.action_fragment), "action_fragment");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_root), "guidedactions_root");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
- R.id.guidedactions_selector), "guidedactions_selector");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_content), "guidedactions_content");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_list_background), "guidedactions_list_background");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_root2), "guidedactions_root2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
- R.id.guidedactions_selector2), "guidedactions_selector2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_content2), "guidedactions_content2");
- TransitionHelper.addSharedElement(ft, disappearing.getView().findViewById(
+ addNonNullSharedElementTransition(ft, fragmentView.findViewById(
R.id.guidedactions_list_background2), "guidedactions_list_background2");
}
+ private static void addNonNullSharedElementTransition (FragmentTransaction ft, View subView,
+ String transitionName)
+ {
+ if (subView != null)
+ TransitionHelper.addSharedElement(ft, subView, transitionName);
+ }
+
/**
* Returns BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
* associated. Note {@link #UI_STYLE_ACTIVITY_ROOT} will return empty String. The method
@@ -431,7 +543,7 @@
* @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
* associated.
*/
- public String generateStackEntryName() {
+ String generateStackEntryName() {
return generateStackEntryName(getUiStyle(), getClass());
}
@@ -442,7 +554,7 @@
* @return BackStackEntry name for the GuidedStepSupportFragment or empty String if no entry is
* associated.
*/
- public static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
+ static String generateStackEntryName(int uiStyle, Class guidedStepFragmentClass) {
if (!GuidedStepSupportFragment.class.isAssignableFrom(guidedStepFragmentClass)) {
return "";
}
@@ -458,33 +570,24 @@
}
/**
- * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
- * false otherwise.
+ * Returns true if the backstack entry represents GuidedStepSupportFragment with
+ * {@link #UI_STYLE_ENTRANCE}, i.e. this is the first GuidedStepSupportFragment pushed to stack; false
+ * otherwise.
+ * @see #generateStackEntryName(int, Class)
* @param backStackEntryName Name of BackStackEntry.
* @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_ENTRANCE};
* false otherwise.
*/
- public static boolean isUiStyleEntrance(String backStackEntryName) {
+ static boolean isStackEntryUiStyleEntrance(String backStackEntryName) {
return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE);
}
/**
- * Returns true if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_REPLACE};
- * false otherwise.
- * @param backStackEntryName Name of BackStackEntry.
- * @return True if the backstack represents GuidedStepSupportFragment with {@link #UI_STYLE_REPLACE};
- * false otherwise.
- */
- public static boolean isUiStyleDefault(String backStackEntryName) {
- return backStackEntryName != null && backStackEntryName.startsWith(ENTRY_NAME_REPLACE);
- }
-
- /**
* Extract Class name from BackStackEntry name.
* @param backStackEntryName Name of BackStackEntry.
* @return Class name of GuidedStepSupportFragment.
*/
- public static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
+ static String getGuidedStepSupportFragmentClassName(String backStackEntryName) {
if (backStackEntryName.startsWith(ENTRY_NAME_REPLACE)) {
return backStackEntryName.substring(ENTRY_NAME_REPLACE.length());
} else if (backStackEntryName.startsWith(ENTRY_NAME_ENTRANCE)) {
@@ -496,7 +599,10 @@
/**
* Adds the specified GuidedStepSupportFragment as content of Activity; no backstack entry is added so
- * the activity will be dismissed when BACK key is pressed.
+ * the activity will be dismissed when BACK key is pressed. The method is typically called in
+ * Activity.onCreate() when savedInstanceState is null. When savedInstanceState is not null,
+ * the Activity is being restored, do not call addAsRoot() to duplicate the Fragment restored
+ * by FragmentManager.
* {@link #UI_STYLE_ACTIVITY_ROOT} is assigned.
*
* Note: currently fragments added using this method must be created programmatically rather
@@ -504,13 +610,18 @@
* @param activity The Activity to be used to insert GuidedstepFragment.
* @param fragment The GuidedStepSupportFragment to be inserted into the fragment stack.
* @param id The id of container to add GuidedStepSupportFragment, can be android.R.id.content.
- * @return The ID returned by the call FragmentTransaction.replace.
+ * @return The ID returned by the call FragmentTransaction.commit, or -1 there is already
+ * GuidedStepSupportFragment.
*/
public static int addAsRoot(FragmentActivity activity, GuidedStepSupportFragment fragment, int id) {
// Workaround b/23764120: call getDecorView() to force requestFeature of ActivityTransition.
activity.getWindow().getDecorView();
-
FragmentManager fragmentManager = activity.getSupportFragmentManager();
+ if (fragmentManager.findFragmentByTag(TAG_LEAN_BACK_ACTIONS_FRAGMENT) != null) {
+ Log.w(TAG, "Fragment is already exists, likely calling " +
+ "addAsRoot() when savedInstanceState is not null in Activity.onCreate().");
+ return -1;
+ }
FragmentTransaction ft = fragmentManager.beginTransaction();
fragment.setUiStyle(UI_STYLE_ACTIVITY_ROOT);
return ft.replace(id, fragment, TAG_LEAN_BACK_ACTIONS_FRAGMENT).commit();
@@ -755,18 +866,24 @@
Object changeBounds = TransitionHelper.createChangeBounds(false);
TransitionHelper.setSharedElementEnterTransition(this, changeBounds);
} else if (uiStyle == UI_STYLE_ENTRANCE) {
- Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
- TransitionHelper.FADE_OUT);
- TransitionHelper.include(fade, R.id.guidedstep_background);
- Object slide = TransitionHelper.createFadeAndShortSlide(Gravity.END |
- Gravity.START);
- TransitionHelper.include(slide, R.id.content_fragment);
- TransitionHelper.include(slide, R.id.action_fragment_root);
- Object enterTransition = TransitionHelper.createTransitionSet(false);
- TransitionHelper.addTransition(enterTransition, fade);
- TransitionHelper.addTransition(enterTransition, slide);
- TransitionHelper.setEnterTransition(this, enterTransition);
-
+ if (entranceTransitionType == SLIDE_FROM_SIDE) {
+ Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
+ TransitionHelper.FADE_OUT);
+ TransitionHelper.include(fade, R.id.guidedstep_background);
+ Object slideFromSide = TransitionHelper.createFadeAndShortSlide(Gravity.END | Gravity.START);
+ TransitionHelper.include(slideFromSide, R.id.content_fragment);
+ TransitionHelper.include(slideFromSide, R.id.action_fragment_root);
+ Object enterTransition = TransitionHelper.createTransitionSet(false);
+ TransitionHelper.addTransition(enterTransition, fade);
+ TransitionHelper.addTransition(enterTransition, slideFromSide);
+ TransitionHelper.setEnterTransition(this, enterTransition);
+ } else {
+ Object slideFromBottom = TransitionHelper.createFadeAndShortSlide(Gravity.BOTTOM);
+ TransitionHelper.include(slideFromBottom, R.id.guidedstep_background_view_root);
+ Object enterTransition = TransitionHelper.createTransitionSet(false);
+ TransitionHelper.addTransition(enterTransition, slideFromBottom);
+ TransitionHelper.setEnterTransition(this, enterTransition);
+ }
// No shared element transition
TransitionHelper.setSharedElementEnterTransition(this, null);
} else if (uiStyle == UI_STYLE_ACTIVITY_ROOT) {
@@ -858,9 +975,15 @@
}
ArrayList<GuidedAction> actions = new ArrayList<GuidedAction>();
onCreateActions(actions, savedInstanceState);
+ if (savedInstanceState != null) {
+ onRestoreActions(actions, savedInstanceState);
+ }
setActions(actions);
ArrayList<GuidedAction> buttonActions = new ArrayList<GuidedAction>();
onCreateButtonActions(buttonActions, savedInstanceState);
+ if (savedInstanceState != null) {
+ onRestoreButtonActions(buttonActions, savedInstanceState);
+ }
setButtonActions(buttonActions);
}
@@ -873,6 +996,7 @@
mActionsStylist.onDestroyView();
mButtonActionsStylist.onDestroyView();
mAdapter = null;
+ mSubAdapter = null;
mButtonAdapter = null;
mAdapterGroup = null;
super.onDestroyView();
@@ -889,8 +1013,12 @@
resolveTheme();
inflater = getThemeInflater(inflater);
- ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_guidedstep_fragment,
- container, false);
+ GuidedStepRootLayout root = (GuidedStepRootLayout) inflater.inflate(
+ R.layout.lb_guidedstep_fragment, container, false);
+
+ root.setFocusOutStart(isFocusOutStartAllowed());
+ root.setFocusOutEnd(isFocusOutEndAllowed());
+
ViewGroup guidanceContainer = (ViewGroup) root.findViewById(R.id.content_fragment);
ViewGroup actionContainer = (ViewGroup) root.findViewById(R.id.action_fragment);
@@ -902,7 +1030,6 @@
actionContainer.addView(actionsView);
View buttonActionsView = mButtonActionsStylist.onCreateView(inflater, actionContainer);
- mButtonActionsStylist.setAsButtonActions();
actionContainer.addView(buttonActionsView);
GuidedActionAdapter.EditListener editListener = new GuidedActionAdapter.EditListener() {
@@ -918,19 +1045,55 @@
}
@Override
- public long onGuidedActionEdited(GuidedAction action) {
+ public long onGuidedActionEditedAndProceed(GuidedAction action) {
return GuidedStepSupportFragment.this.onGuidedActionEditedAndProceed(action);
}
+
+ @Override
+ public void onGuidedActionEditCanceled(GuidedAction action) {
+ GuidedStepSupportFragment.this.onGuidedActionEditCanceled(action);
+ }
};
- mAdapter = new GuidedActionAdapter(mActions, this, this, mActionsStylist);
- mButtonAdapter = new GuidedActionAdapter(mButtonActions, this, this, mButtonActionsStylist);
+ mAdapter = new GuidedActionAdapter(mActions, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ GuidedStepSupportFragment.this.onGuidedActionClicked(action);
+ if (isSubActionsExpanded()) {
+ collapseSubActions();
+ } else if (action.hasSubActions()) {
+ expandSubActions(action);
+ }
+ }
+ }, this, mActionsStylist, false);
+ mButtonAdapter =
+ new GuidedActionAdapter(mButtonActions, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ GuidedStepSupportFragment.this.onGuidedActionClicked(action);
+ }
+ }, this, mButtonActionsStylist, false);
+ mSubAdapter = new GuidedActionAdapter(null, new GuidedActionAdapter.ClickListener() {
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ if (mActionsStylist.isInExpandTransition()) {
+ return;
+ }
+ if (GuidedStepSupportFragment.this.onSubGuidedActionClicked(action)) {
+ collapseSubActions();
+ }
+ }
+ }, this, mActionsStylist, true);
mAdapterGroup = new GuidedActionAdapterGroup();
- mAdapterGroup.addAdpter(mAdapter);
- mAdapterGroup.addAdpter(mButtonAdapter);
+ mAdapterGroup.addAdpter(mAdapter, mButtonAdapter);
+ mAdapterGroup.addAdpter(mSubAdapter, null);
mAdapterGroup.setEditListener(editListener);
+ mActionsStylist.setEditListener(editListener);
mActionsStylist.getActionsGridView().setAdapter(mAdapter);
+ if (mActionsStylist.getSubActionsGridView() != null) {
+ mActionsStylist.getSubActionsGridView().setAdapter(mSubAdapter);
+ }
mButtonActionsStylist.getActionsGridView().setAdapter(mButtonAdapter);
if (mButtonActions.size() == 0) {
// when there is no button actions, we dont need show the second panel, but keep
@@ -961,9 +1124,12 @@
setSelectedButtonActionPosition(0);
+ // Add the background view.
View backgroundView = onCreateBackgroundView(inflater, root, savedInstanceState);
if (backgroundView != null) {
- root.addView(backgroundView, 0);
+ FrameLayout backgroundViewRoot = (FrameLayout)root.findViewById(
+ R.id.guidedstep_background_view_root);
+ backgroundViewRoot.addView(backgroundView, 0);
}
return root;
}
@@ -971,7 +1137,65 @@
@Override
public void onResume() {
super.onResume();
- mActionsStylist.getActionsGridView().requestFocus();
+ getView().findViewById(R.id.action_fragment).requestFocus();
+ }
+
+ /**
+ * Get the key will be used to save GuidedAction with Fragment.
+ * @param action GuidedAction to get key.
+ * @return Key to save the GuidedAction.
+ */
+ final String getAutoRestoreKey(GuidedAction action) {
+ return EXTRA_ACTION_PREFIX + action.getId();
+ }
+
+ /**
+ * Get the key will be used to save GuidedAction with Fragment.
+ * @param action GuidedAction to get key.
+ * @return Key to save the GuidedAction.
+ */
+ final String getButtonAutoRestoreKey(GuidedAction action) {
+ return EXTRA_BUTTON_ACTION_PREFIX + action.getId();
+ }
+
+ final static boolean isSaveEnabled(GuidedAction action) {
+ return action.isAutoSaveRestoreEnabled() && action.getId() != GuidedAction.NO_ID;
+ }
+
+ final void onRestoreActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onRestoreInstanceState(savedInstanceState, getAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onRestoreButtonActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onRestoreInstanceState(savedInstanceState, getButtonAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onSaveActions(List<GuidedAction> actions, Bundle outState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onSaveInstanceState(outState, getAutoRestoreKey(action));
+ }
+ }
+ }
+
+ final void onSaveButtonActions(List<GuidedAction> actions, Bundle outState) {
+ for (int i = 0, size = actions.size(); i < size; i++) {
+ GuidedAction action = actions.get(i);
+ if (isSaveEnabled(action)) {
+ action.onSaveInstanceState(outState, getButtonAutoRestoreKey(action));
+ }
+ }
}
/**
@@ -980,6 +1204,8 @@
@Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
+ onSaveActions(mActions, outState);
+ onSaveButtonActions(mButtonActions, outState);
outState.putInt(EXTRA_ACTION_SELECTED_INDEX,
(mActionsStylist.getActionsGridView() != null) ?
getSelectedActionPosition() : mSelectedIndex);
@@ -1006,7 +1232,7 @@
if (entryCount > 0) {
for (int i = entryCount - 1; i >= 0; i--) {
BackStackEntry entry = fragmentManager.getBackStackEntryAt(i);
- if (isUiStyleEntrance(entry.getName())) {
+ if (isStackEntryUiStyleEntrance(entry.getName())) {
GuidedStepSupportFragment top = getCurrentGuidedStepSupportFragment(fragmentManager);
if (top != null) {
top.setUiStyle(UI_STYLE_ENTRANCE);
@@ -1044,6 +1270,43 @@
}
}
+ /**
+ * Returns true if allows focus out of start edge of GuidedStepSupportFragment, false otherwise.
+ * Default value is false, the reason is to disable FocusFinder to find focusable views
+ * beneath content of GuidedStepSupportFragment. Subclass may override.
+ * @return True if allows focus out of start edge of GuidedStepSupportFragment.
+ */
+ public boolean isFocusOutStartAllowed() {
+ return false;
+ }
+
+ /**
+ * Returns true if allows focus out of end edge of GuidedStepSupportFragment, false otherwise.
+ * Default value is false, the reason is to disable FocusFinder to find focusable views
+ * beneath content of GuidedStepSupportFragment. Subclass may override.
+ * @return True if allows focus out of end edge of GuidedStepSupportFragment.
+ */
+ public boolean isFocusOutEndAllowed() {
+ return false;
+ }
+
+ /**
+ * Sets the transition type to be used for {@link #UI_STYLE_ENTRANCE} animation.
+ * Currently we provide 2 different variations for animation - slide in from
+ * side (default) or bottom.
+ *
+ * Ideally we can retireve the screen mode settings from the theme attribute
+ * {@code Theme.Leanback.GuidedStep#guidedStepHeightWeight} and use that to
+ * determine the transition. But the fragment context to retrieve the theme
+ * isn't available on platform v23 or earlier.
+ *
+ * For now clients(subclasses) can call this method inside the contructor.
+ * @hide
+ */
+ public void setEntranceTransitionType(int transitionType) {
+ this.entranceTransitionType = transitionType;
+ }
+
private void resolveTheme() {
// Look up the guidedStepTheme in the currently specified theme. If it exists,
// replace the theme with its value.
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
index 219bb98..d09fc01 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -16,20 +16,23 @@
import android.content.Context;
import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DividerPresenter;
+import android.support.v17.leanback.widget.DividerRow;
import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.SectionRow;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnLayoutChangeListener;
@@ -40,11 +43,29 @@
*/
public class HeadersFragment extends BaseRowFragment {
- interface OnHeaderClickedListener {
- void onHeaderClicked();
+ /**
+ * Interface definition for a callback to be invoked when a header item is clicked.
+ */
+ public interface OnHeaderClickedListener {
+ /**
+ * Called when a header item has been clicked.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
+ void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
- interface OnHeaderViewSelectedListener {
+ /**
+ * Interface definition for a callback to be invoked when a header item is selected.
+ */
+ public interface OnHeaderViewSelectedListener {
+ /**
+ * Called when a header item has been selected.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
@@ -55,8 +76,11 @@
private int mBackgroundColor;
private boolean mBackgroundColorSet;
- private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
- new RowHeaderPresenter(R.layout.lb_header));
+ private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector()
+ .addClassPresenter(DividerRow.class, new DividerPresenter())
+ .addClassPresenter(SectionRow.class,
+ new RowHeaderPresenter(R.layout.lb_section_header, false))
+ .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header));
public HeadersFragment() {
setPresenterSelector(sHeaderPresenter);
@@ -80,10 +104,9 @@
int position, int subposition) {
if (mOnHeaderViewSelectedListener != null) {
if (viewHolder != null && position >= 0) {
- Row row = (Row) getAdapter().get(position);
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
mOnHeaderViewSelectedListener.onHeaderSelected(
- (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), row);
+ (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
} else {
mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
}
@@ -93,18 +116,18 @@
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
- public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
View headerView = viewHolder.getViewHolder().view;
headerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnHeaderClickedListener != null) {
- mOnHeaderClickedListener.onHeaderClicked();
+ mOnHeaderClickedListener.onHeaderClicked(
+ (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
+ (Row) viewHolder.getItem());
}
}
});
- headerView.setFocusable(true);
- headerView.setFocusableInTouchMode(true);
if (mWrapper != null) {
viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
} else {
@@ -139,8 +162,13 @@
FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
}
if (mBackgroundColorSet) {
- view.setBackgroundColor(mBackgroundColor);
+ listView.setBackgroundColor(mBackgroundColor);
updateFadingEdgeToBrandColor(mBackgroundColor);
+ } else {
+ Drawable d = listView.getBackground();
+ if (d instanceof ColorDrawable) {
+ updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor());
+ }
}
updateListViewVisibility();
}
@@ -214,8 +242,8 @@
mBackgroundColor = color;
mBackgroundColorSet = true;
- if (getView() != null) {
- getView().setBackgroundColor(mBackgroundColor);
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().setBackgroundColor(mBackgroundColor);
updateFadingEdgeToBrandColor(mBackgroundColor);
}
}
@@ -231,7 +259,7 @@
}
@Override
- void onTransitionStart() {
+ public void onTransitionStart() {
super.onTransitionStart();
if (!mHeadersEnabled) {
// When enabling headers fragment, the RowHeaderView gets a focus but
@@ -250,7 +278,7 @@
}
@Override
- void onTransitionEnd() {
+ public void onTransitionEnd() {
if (mHeadersEnabled) {
final VerticalGridView listView = getVerticalGridView();
if (listView != null) {
@@ -262,4 +290,9 @@
}
super.onTransitionEnd();
}
+
+ public boolean isScrolling() {
+ return getVerticalGridView().getScrollState()
+ != HorizontalGridView.SCROLL_STATE_IDLE;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
index ecf04d8..a6d598b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -18,20 +18,23 @@
import android.content.Context;
import android.graphics.Color;
+import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.ClassPresenterSelector;
+import android.support.v17.leanback.widget.DividerPresenter;
+import android.support.v17.leanback.widget.DividerRow;
import android.support.v17.leanback.widget.FocusHighlightHelper;
+import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.PresenterSelector;
-import android.support.v17.leanback.widget.OnItemViewSelectedListener;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowHeaderPresenter;
-import android.support.v17.leanback.widget.SinglePresenterSelector;
+import android.support.v17.leanback.widget.SectionRow;
import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v7.widget.RecyclerView;
-import android.util.TypedValue;
import android.view.View;
import android.view.ViewGroup;
import android.view.View.OnLayoutChangeListener;
@@ -42,11 +45,29 @@
*/
public class HeadersSupportFragment extends BaseRowSupportFragment {
- interface OnHeaderClickedListener {
- void onHeaderClicked();
+ /**
+ * Interface definition for a callback to be invoked when a header item is clicked.
+ */
+ public interface OnHeaderClickedListener {
+ /**
+ * Called when a header item has been clicked.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
+ void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
- interface OnHeaderViewSelectedListener {
+ /**
+ * Interface definition for a callback to be invoked when a header item is selected.
+ */
+ public interface OnHeaderViewSelectedListener {
+ /**
+ * Called when a header item has been selected.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
@@ -57,8 +78,11 @@
private int mBackgroundColor;
private boolean mBackgroundColorSet;
- private static final PresenterSelector sHeaderPresenter = new SinglePresenterSelector(
- new RowHeaderPresenter(R.layout.lb_header));
+ private static final PresenterSelector sHeaderPresenter = new ClassPresenterSelector()
+ .addClassPresenter(DividerRow.class, new DividerPresenter())
+ .addClassPresenter(SectionRow.class,
+ new RowHeaderPresenter(R.layout.lb_section_header, false))
+ .addClassPresenter(Row.class, new RowHeaderPresenter(R.layout.lb_header));
public HeadersSupportFragment() {
setPresenterSelector(sHeaderPresenter);
@@ -82,10 +106,9 @@
int position, int subposition) {
if (mOnHeaderViewSelectedListener != null) {
if (viewHolder != null && position >= 0) {
- Row row = (Row) getAdapter().get(position);
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
mOnHeaderViewSelectedListener.onHeaderSelected(
- (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), row);
+ (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
} else {
mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
}
@@ -95,18 +118,18 @@
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
- public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
View headerView = viewHolder.getViewHolder().view;
headerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnHeaderClickedListener != null) {
- mOnHeaderClickedListener.onHeaderClicked();
+ mOnHeaderClickedListener.onHeaderClicked(
+ (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
+ (Row) viewHolder.getItem());
}
}
});
- headerView.setFocusable(true);
- headerView.setFocusableInTouchMode(true);
if (mWrapper != null) {
viewHolder.itemView.addOnLayoutChangeListener(sLayoutChangeListener);
} else {
@@ -141,8 +164,13 @@
FocusHighlightHelper.setupHeaderItemFocusHighlight(listView);
}
if (mBackgroundColorSet) {
- view.setBackgroundColor(mBackgroundColor);
+ listView.setBackgroundColor(mBackgroundColor);
updateFadingEdgeToBrandColor(mBackgroundColor);
+ } else {
+ Drawable d = listView.getBackground();
+ if (d instanceof ColorDrawable) {
+ updateFadingEdgeToBrandColor(((ColorDrawable) d).getColor());
+ }
}
updateListViewVisibility();
}
@@ -216,8 +244,8 @@
mBackgroundColor = color;
mBackgroundColorSet = true;
- if (getView() != null) {
- getView().setBackgroundColor(mBackgroundColor);
+ if (getVerticalGridView() != null) {
+ getVerticalGridView().setBackgroundColor(mBackgroundColor);
updateFadingEdgeToBrandColor(mBackgroundColor);
}
}
@@ -233,7 +261,7 @@
}
@Override
- void onTransitionStart() {
+ public void onTransitionStart() {
super.onTransitionStart();
if (!mHeadersEnabled) {
// When enabling headers fragment, the RowHeaderView gets a focus but
@@ -252,7 +280,7 @@
}
@Override
- void onTransitionEnd() {
+ public void onTransitionEnd() {
if (mHeadersEnabled) {
final VerticalGridView listView = getVerticalGridView();
if (listView != null) {
@@ -264,4 +292,9 @@
}
super.onTransitionEnd();
}
+
+ public boolean isScrolling() {
+ return getVerticalGridView().getScrollState()
+ != HorizontalGridView.SCROLL_STATE_IDLE;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
new file mode 100644
index 0000000..bfd0f14
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingFragment.java
@@ -0,0 +1,693 @@
+/*
+ * 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.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.app.Activity;
+import android.app.Fragment;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.PagingIndicator;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An OnboardingFragment provides a common and simple way to build onboarding screen for
+ * applications.
+ * <p>
+ * <h3>Building the screen</h3>
+ * The view structure of onboarding screen is composed of the common parts and custom parts. The
+ * common parts are composed of title, description and page navigator and the custom parts are
+ * composed of background, contents and foreground.
+ * <p>
+ * To build the screen views, the inherited class should override:
+ * <ul>
+ * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
+ * size as the screen and the lowest z-order.</li>
+ * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
+ * the content area at the center of the screen.</li>
+ * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
+ * size as the screen and the highest z-order</li>
+ * </ul>
+ * <p>
+ * Each of these methods can return {@code null} if the application doesn't want to provide it.
+ * <p>
+ * <h3>Page information</h3>
+ * The onboarding screen may have several pages which explain the functionality of the application.
+ * The inherited class should provide the page information by overriding the methods:
+ * <p>
+ * <ul>
+ * <li>{@link #getPageCount} to provide the number of pages.</li>
+ * <li>{@link #getPageTitle} to provide the title of the page.</li>
+ * <li>{@link #getPageDescription} to provide the description of the page.</li>
+ * </ul>
+ * <p>
+ * Note that the information is used in {@link #onCreateView}, so should be initialized before
+ * calling {@code super.onCreateView}.
+ * <p>
+ * <h3>Animation</h3>
+ * Onboarding screen has three kinds of animations:
+ * <p>
+ * <h4>Logo Splash Animation</a></h4>
+ * When onboarding screen appears, the logo splash animation is played by default. The animation
+ * fades in the logo image, pauses in a few seconds and fades it out.
+ * <p>
+ * In most cases, the logo animation needs to be customized because the logo images of applications
+ * are different from each other, or some applications may want to show their own animations.
+ * <p>
+ * The logo animation can be customized in two ways:
+ * <ul>
+ * <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
+ * the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
+ * <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
+ * {@link Animator} object to run.</li>
+ * </ul>
+ * <p>
+ * If the inherited class provides neither the logo image nor the animation, the logo animation will
+ * be omitted.
+ * <h4>Page enter animation</h4>
+ * After logo animation finishes, page enter animation starts. The application can provide the
+ * animations of custom views by overriding {@link #onCreateEnterAnimation}.
+ * <h4>Page change animation</h4>
+ * When the page changes, the default animations of the title and description are played. The
+ * inherited class can override {@link #onPageChanged} to start the custom animations.
+ * <p>
+ * <h3>Finishing the screen</h3>
+ * <p>
+ * If the user finishes the onboarding screen after navigating all the pages,
+ * {@link #onFinishFragment} is called. The inherited class can override this method to show another
+ * fragment or activity, or just remove this fragment.
+ * <p>
+ * <h3>Theming</h3>
+ * <p>
+ * OnboardingFragment must have access to an appropriate theme. Specifically, the fragment must
+ * receive {@link R.style#Theme_Leanback_Onboarding}, or a theme whose parent is set to that theme.
+ * Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
+ * that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute
+ * {@link R.styleable#LeanbackOnboardingTheme_onboardingTheme}. If present, this theme will be used
+ * by OnboardingFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of OnboardingFragment may provide a theme through the
+ * {@link #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
+ * need to set the onboardingTheme attribute; if set, it will be ignored.)
+ *
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ */
+abstract public class OnboardingFragment extends Fragment {
+ private static final String TAG = "OnboardingFragment";
+ private static final boolean DEBUG = false;
+
+ private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
+ private static final long START_DELAY_TITLE_MS = 33;
+ private static final long START_DELAY_DESCRIPTION_MS = 33;
+
+ private static final long HEADER_ANIMATION_DURATION_MS = 417;
+ private static final long DESCRIPTION_START_DELAY_MS = 33;
+ private static final long HEADER_APPEAR_DELAY_MS = 500;
+ private static final int SLIDE_DISTANCE = 60;
+
+ private static int sSlideDistance;
+
+ private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
+ private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR
+ = new AccelerateInterpolator();
+
+ // Keys used to save and restore the states.
+ private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
+
+ private ContextThemeWrapper mThemeWrapper;
+
+ private PagingIndicator mPageIndicator;
+ private View mStartButton;
+ private ImageView mLogoView;
+ private TextView mTitleView;
+ private TextView mDescriptionView;
+
+ private boolean mIsLtr;
+
+ // No need to save/restore the logo resource ID, because the logo animation will not appear when
+ // the fragment is restored.
+ private int mLogoResourceId;
+ private boolean mEnterTransitionFinished;
+ private int mCurrentPageIndex;
+
+ private AnimatorSet mAnimator;
+
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!mEnterTransitionFinished) {
+ // Do not change page until the enter transition finishes.
+ return;
+ }
+ if (mCurrentPageIndex == getPageCount() - 1) {
+ onFinishFragment();
+ } else {
+ moveToNextPage();
+ }
+ }
+ };
+
+ private final OnKeyListener mOnKeyListener = new OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (!mEnterTransitionFinished) {
+ // Ignore key event until the enter transition finishes.
+ return keyCode != KeyEvent.KEYCODE_BACK;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (mCurrentPageIndex == 0) {
+ return false;
+ }
+ moveToPreviousPage();
+ return true;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (mIsLtr) {
+ moveToPreviousPage();
+ } else {
+ moveToNextPage();
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (mIsLtr) {
+ moveToNextPage();
+ } else {
+ moveToPreviousPage();
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private void moveToPreviousPage() {
+ if (mCurrentPageIndex > 0) {
+ --mCurrentPageIndex;
+ onPageChangedInternal(mCurrentPageIndex + 1);
+ }
+ }
+ private void moveToNextPage() {
+ if (mCurrentPageIndex < getPageCount() - 1) {
+ ++mCurrentPageIndex;
+ onPageChangedInternal(mCurrentPageIndex - 1);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+ Bundle savedInstanceState) {
+ resolveTheme();
+ LayoutInflater localInflater = getThemeInflater(inflater);
+ final ViewGroup view = (ViewGroup) localInflater.inflate(R.layout.lb_onboarding_fragment,
+ container, false);
+ mIsLtr = getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
+ mPageIndicator.setOnClickListener(mOnClickListener);
+ mPageIndicator.setOnKeyListener(mOnKeyListener);
+ mStartButton = view.findViewById(R.id.button_start);
+ mStartButton.setOnClickListener(mOnClickListener);
+ mStartButton.setOnKeyListener(mOnKeyListener);
+ mLogoView = (ImageView) view.findViewById(R.id.logo);
+ mTitleView = (TextView) view.findViewById(R.id.title);
+ mDescriptionView = (TextView) view.findViewById(R.id.description);
+ if (sSlideDistance == 0) {
+ sSlideDistance = (int) (SLIDE_DISTANCE * getActivity().getResources()
+ .getDisplayMetrics().scaledDensity);
+ }
+ if (savedInstanceState == null) {
+ mCurrentPageIndex = 0;
+ mEnterTransitionFinished = false;
+ mPageIndicator.onPageSelected(0, false);
+ view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ view.getViewTreeObserver().removeOnPreDrawListener(this);
+ if (!startLogoAnimation()) {
+ startEnterAnimation();
+ }
+ return true;
+ }
+ });
+ } else {
+ mEnterTransitionFinished = true;
+ mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
+ initializeViews(view);
+ }
+ view.requestFocus();
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
+ }
+
+ /**
+ * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+ * host Activity's theme should be used.
+ *
+ * @return The theme resource ID of the theme to use in this fragment, or -1 to use the host
+ * Activity's theme.
+ */
+ public int onProvideTheme() {
+ return -1;
+ }
+
+ private void resolveTheme() {
+ Activity activity = getActivity();
+ int theme = onProvideTheme();
+ if (theme == -1) {
+ // Look up the onboardingTheme in the activity's currently specified theme. If it
+ // exists, wrap the theme with its value.
+ int resId = R.attr.onboardingTheme;
+ TypedValue typedValue = new TypedValue();
+ boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true);
+ if (DEBUG) Log.v(TAG, "Found onboarding theme reference? " + found);
+ if (found) {
+ mThemeWrapper = new ContextThemeWrapper(activity, typedValue.resourceId);
+ }
+ } else {
+ mThemeWrapper = new ContextThemeWrapper(activity, theme);
+ }
+ }
+
+ private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+ return mThemeWrapper == null ? inflater : inflater.cloneInContext(mThemeWrapper);
+ }
+
+ /**
+ * Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
+ * splash animation will be played.
+ *
+ * @param id The resource ID of the logo image.
+ */
+ public final void setLogoResourceId(int id) {
+ mLogoResourceId = id;
+ }
+
+ /**
+ * Returns the resource ID of the splash logo image.
+ *
+ * @return The resource ID of the splash logo image.
+ */
+ public final int getLogoResourceId() {
+ return mLogoResourceId;
+ }
+
+ /**
+ * Called to have the inherited class create its own logo animation.
+ * <p>
+ * This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
+ * If this returns {@code null}, the logo animation is skipped.
+ *
+ * @return The {@link Animator} object which runs the logo animation.
+ */
+ @Nullable
+ protected Animator onCreateLogoAnimation() {
+ return null;
+ }
+
+ private boolean startLogoAnimation() {
+ Animator animator = null;
+ if (mLogoResourceId != 0) {
+ mLogoView.setVisibility(View.VISIBLE);
+ mLogoView.setImageResource(mLogoResourceId);
+ Animator inAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_logo_enter);
+ Animator outAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_logo_exit);
+ outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
+ AnimatorSet logoAnimator = new AnimatorSet();
+ logoAnimator.playSequentially(inAnimator, outAnimator);
+ logoAnimator.setTarget(mLogoView);
+ animator = logoAnimator;
+ } else {
+ animator = onCreateLogoAnimation();
+ }
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (getActivity() != null) {
+ startEnterAnimation();
+ }
+ }
+ });
+ animator.start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Called to have the inherited class create its enter animation. The start animation runs after
+ * logo animation ends.
+ *
+ * @return The {@link Animator} object which runs the page enter animation.
+ */
+ @Nullable
+ protected Animator onCreateEnterAnimation() {
+ return null;
+ }
+
+ private void initializeViews(View container) {
+ mLogoView.setVisibility(View.GONE);
+ // Create custom views.
+ LayoutInflater inflater = getThemeInflater(LayoutInflater.from(getActivity()));
+ ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
+ R.id.background_container);
+ View background = onCreateBackgroundView(inflater, backgroundContainer);
+ if (background != null) {
+ backgroundContainer.setVisibility(View.VISIBLE);
+ backgroundContainer.addView(background);
+ }
+ ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
+ View content = onCreateContentView(inflater, contentContainer);
+ if (content != null) {
+ contentContainer.setVisibility(View.VISIBLE);
+ contentContainer.addView(content);
+ }
+ ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
+ R.id.foreground_container);
+ View foreground = onCreateForegroundView(inflater, foregroundContainer);
+ if (foreground != null) {
+ foregroundContainer.setVisibility(View.VISIBLE);
+ foregroundContainer.addView(foreground);
+ }
+ // Make views visible which were invisible while logo animation is running.
+ container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
+ container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
+ if (getPageCount() > 1) {
+ mPageIndicator.setPageCount(getPageCount());
+ mPageIndicator.onPageSelected(mCurrentPageIndex, false);
+ }
+ if (mCurrentPageIndex == getPageCount() - 1) {
+ mStartButton.setVisibility(View.VISIBLE);
+ } else {
+ mPageIndicator.setVisibility(View.VISIBLE);
+ }
+ // Header views.
+ mTitleView.setText(getPageTitle(mCurrentPageIndex));
+ mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
+ }
+
+ private void startEnterAnimation() {
+ mEnterTransitionFinished = true;
+ initializeViews(getView());
+ List<Animator> animators = new ArrayList<>();
+ Animator animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_enter);
+ animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
+ animators.add(animator);
+ // Header title
+ View view = getActivity().findViewById(R.id.title);
+ view.setAlpha(0);
+ animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_title_enter);
+ animator.setStartDelay(START_DELAY_TITLE_MS);
+ animator.setTarget(view);
+ animators.add(animator);
+ // Header description
+ view = getActivity().findViewById(R.id.description);
+ view.setAlpha(0);
+ animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_description_enter);
+ animator.setStartDelay(START_DELAY_DESCRIPTION_MS);
+ animator.setTarget(view);
+ animators.add(animator);
+ // Customized animation by the inherited class.
+ Animator customAnimator = onCreateEnterAnimation();
+ if (customAnimator != null) {
+ animators.add(customAnimator);
+ }
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(animators);
+ mAnimator.start();
+ // Search focus and give the focus to the appropriate child which has become visible.
+ getView().requestFocus();
+ }
+
+ /**
+ * Returns the page count.
+ *
+ * @return The page count.
+ */
+ abstract protected int getPageCount();
+
+ /**
+ * Returns the title of the given page.
+ *
+ * @param pageIndex The page index.
+ *
+ * @return The title of the page.
+ */
+ abstract protected CharSequence getPageTitle(int pageIndex);
+
+ /**
+ * Returns the description of the given page.
+ *
+ * @param pageIndex The page index.
+ *
+ * @return The description of the page.
+ */
+ abstract protected CharSequence getPageDescription(int pageIndex);
+
+ /**
+ * Returns the index of the current page.
+ *
+ * @return The index of the current page.
+ */
+ protected final int getCurrentPageIndex() {
+ return mCurrentPageIndex;
+ }
+
+ /**
+ * Called to have the inherited class create background view. This is optional and the fragment
+ * which doesn't have the background view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The background view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called to have the inherited class create content view. This is optional and the fragment
+ * which doesn't have the content view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * <p>The content view would be located at the center of the screen.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The content view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called to have the inherited class create foreground view. This is optional and the fragment
+ * which doesn't need the foreground view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * <p>This foreground view would have the highest z-order.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The foreground view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called when the onboarding flow finishes.
+ */
+ protected void onFinishFragment() { }
+
+ /**
+ * Called when the page changes.
+ */
+ private void onPageChangedInternal(int previousPage) {
+ if (mAnimator != null) {
+ mAnimator.end();
+ }
+ mPageIndicator.onPageSelected(mCurrentPageIndex, true);
+
+ List<Animator> animators = new ArrayList<>();
+ // Header animation
+ Animator fadeAnimator = null;
+ if (previousPage < getCurrentPageIndex()) {
+ // sliding to left
+ animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
+ animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
+ DESCRIPTION_START_DELAY_MS));
+ animators.add(createAnimator(mTitleView, true, Gravity.END,
+ HEADER_APPEAR_DELAY_MS));
+ animators.add(createAnimator(mDescriptionView, true, Gravity.END,
+ HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+ } else {
+ // sliding to right
+ animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
+ animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
+ DESCRIPTION_START_DELAY_MS));
+ animators.add(createAnimator(mTitleView, true, Gravity.START,
+ HEADER_APPEAR_DELAY_MS));
+ animators.add(createAnimator(mDescriptionView, true, Gravity.START,
+ HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+ }
+ final int currentPageIndex = getCurrentPageIndex();
+ fadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTitleView.setText(getPageTitle(currentPageIndex));
+ mDescriptionView.setText(getPageDescription(currentPageIndex));
+ }
+ });
+
+ // Animator for switching between page indicator and button.
+ if (getCurrentPageIndex() == getPageCount() - 1) {
+ mStartButton.setVisibility(View.VISIBLE);
+ Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_fade_out);
+ navigatorFadeOutAnimator.setTarget(mPageIndicator);
+ navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPageIndicator.setVisibility(View.GONE);
+ }
+ });
+ animators.add(navigatorFadeOutAnimator);
+ Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_start_button_fade_in);
+ buttonFadeInAnimator.setTarget(mStartButton);
+ animators.add(buttonFadeInAnimator);
+ } else if (previousPage == getPageCount() - 1) {
+ mPageIndicator.setVisibility(View.VISIBLE);
+ Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_fade_in);
+ navigatorFadeInAnimator.setTarget(mPageIndicator);
+ animators.add(navigatorFadeInAnimator);
+ Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_start_button_fade_out);
+ buttonFadeOutAnimator.setTarget(mStartButton);
+ buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartButton.setVisibility(View.GONE);
+ }
+ });
+ animators.add(buttonFadeOutAnimator);
+ }
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(animators);
+ mAnimator.start();
+ onPageChanged(mCurrentPageIndex, previousPage);
+ }
+
+ /**
+ * Called when the page has been changed.
+ *
+ * @param newPage The new page.
+ * @param previousPage The previous page.
+ */
+ protected void onPageChanged(int newPage, int previousPage) { }
+
+ private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
+ long startDelay) {
+ boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ boolean slideRight = (isLtr && slideDirection == Gravity.END)
+ || (!isLtr && slideDirection == Gravity.START)
+ || slideDirection == Gravity.RIGHT;
+ Animator fadeAnimator;
+ Animator slideAnimator;
+ if (fadeIn) {
+ fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
+ slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ slideRight ? sSlideDistance : -sSlideDistance, 0);
+ fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+ slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+ } else {
+ fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
+ slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
+ slideRight ? sSlideDistance : -sSlideDistance);
+ fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+ slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+ }
+ fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+ fadeAnimator.setTarget(view);
+ slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+ slideAnimator.setTarget(view);
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(fadeAnimator, slideAnimator);
+ if (startDelay > 0) {
+ animator.setStartDelay(startDelay);
+ }
+ return animator;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
new file mode 100644
index 0000000..d873f61
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/OnboardingSupportFragment.java
@@ -0,0 +1,695 @@
+/* This file is auto-generated from OnboardingFragment.java. DO NOT MODIFY. */
+
+/*
+ * 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.v17.leanback.app;
+
+import android.animation.Animator;
+import android.animation.AnimatorInflater;
+import android.animation.AnimatorListenerAdapter;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.app.Fragment;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.PagingIndicator;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewGroup;
+import android.view.ViewTreeObserver.OnPreDrawListener;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.ImageView;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * An OnboardingSupportFragment provides a common and simple way to build onboarding screen for
+ * applications.
+ * <p>
+ * <h3>Building the screen</h3>
+ * The view structure of onboarding screen is composed of the common parts and custom parts. The
+ * common parts are composed of title, description and page navigator and the custom parts are
+ * composed of background, contents and foreground.
+ * <p>
+ * To build the screen views, the inherited class should override:
+ * <ul>
+ * <li>{@link #onCreateBackgroundView} to provide the background view. Background view has the same
+ * size as the screen and the lowest z-order.</li>
+ * <li>{@link #onCreateContentView} to provide the contents view. The content view is located in
+ * the content area at the center of the screen.</li>
+ * <li>{@link #onCreateForegroundView} to provide the foreground view. Foreground view has the same
+ * size as the screen and the highest z-order</li>
+ * </ul>
+ * <p>
+ * Each of these methods can return {@code null} if the application doesn't want to provide it.
+ * <p>
+ * <h3>Page information</h3>
+ * The onboarding screen may have several pages which explain the functionality of the application.
+ * The inherited class should provide the page information by overriding the methods:
+ * <p>
+ * <ul>
+ * <li>{@link #getPageCount} to provide the number of pages.</li>
+ * <li>{@link #getPageTitle} to provide the title of the page.</li>
+ * <li>{@link #getPageDescription} to provide the description of the page.</li>
+ * </ul>
+ * <p>
+ * Note that the information is used in {@link #onCreateView}, so should be initialized before
+ * calling {@code super.onCreateView}.
+ * <p>
+ * <h3>Animation</h3>
+ * Onboarding screen has three kinds of animations:
+ * <p>
+ * <h4>Logo Splash Animation</a></h4>
+ * When onboarding screen appears, the logo splash animation is played by default. The animation
+ * fades in the logo image, pauses in a few seconds and fades it out.
+ * <p>
+ * In most cases, the logo animation needs to be customized because the logo images of applications
+ * are different from each other, or some applications may want to show their own animations.
+ * <p>
+ * The logo animation can be customized in two ways:
+ * <ul>
+ * <li>The simplest way is to provide the logo image by calling {@link #setLogoResourceId} to show
+ * the default logo animation. This method should be called in {@link Fragment#onCreateView}.</li>
+ * <li>If the logo animation is complex, then override {@link #onCreateLogoAnimation} and return the
+ * {@link Animator} object to run.</li>
+ * </ul>
+ * <p>
+ * If the inherited class provides neither the logo image nor the animation, the logo animation will
+ * be omitted.
+ * <h4>Page enter animation</h4>
+ * After logo animation finishes, page enter animation starts. The application can provide the
+ * animations of custom views by overriding {@link #onCreateEnterAnimation}.
+ * <h4>Page change animation</h4>
+ * When the page changes, the default animations of the title and description are played. The
+ * inherited class can override {@link #onPageChanged} to start the custom animations.
+ * <p>
+ * <h3>Finishing the screen</h3>
+ * <p>
+ * If the user finishes the onboarding screen after navigating all the pages,
+ * {@link #onFinishFragment} is called. The inherited class can override this method to show another
+ * fragment or activity, or just remove this fragment.
+ * <p>
+ * <h3>Theming</h3>
+ * <p>
+ * OnboardingSupportFragment must have access to an appropriate theme. Specifically, the fragment must
+ * receive {@link R.style#Theme_Leanback_Onboarding}, or a theme whose parent is set to that theme.
+ * Themes can be provided in one of three ways:
+ * <ul>
+ * <li>The simplest way is to set the theme for the host Activity to the Onboarding theme or a theme
+ * that derives from it.</li>
+ * <li>If the Activity already has a theme and setting its parent theme is inconvenient, the
+ * existing Activity theme can have an entry added for the attribute
+ * {@link R.styleable#LeanbackOnboardingTheme_onboardingTheme}. If present, this theme will be used
+ * by OnboardingSupportFragment as an overlay to the Activity's theme.</li>
+ * <li>Finally, custom subclasses of OnboardingSupportFragment may provide a theme through the
+ * {@link #onProvideTheme} method. This can be useful if a subclass is used across multiple
+ * Activities.</li>
+ * </ul>
+ * <p>
+ * If the theme is provided in multiple ways, the onProvideTheme override has priority, followed by
+ * the Activity's theme. (Themes whose parent theme is already set to the onboarding theme do not
+ * need to set the onboardingTheme attribute; if set, it will be ignored.)
+ *
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTheme
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingHeaderStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingTitleStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingDescriptionStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingNavigatorContainerStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingPageIndicatorStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingStartButtonStyle
+ * @attr ref R.styleable#LeanbackOnboardingTheme_onboardingLogoStyle
+ */
+abstract public class OnboardingSupportFragment extends Fragment {
+ private static final String TAG = "OnboardingSupportFragment";
+ private static final boolean DEBUG = false;
+
+ private static final long LOGO_SPLASH_PAUSE_DURATION_MS = 1333;
+ private static final long START_DELAY_TITLE_MS = 33;
+ private static final long START_DELAY_DESCRIPTION_MS = 33;
+
+ private static final long HEADER_ANIMATION_DURATION_MS = 417;
+ private static final long DESCRIPTION_START_DELAY_MS = 33;
+ private static final long HEADER_APPEAR_DELAY_MS = 500;
+ private static final int SLIDE_DISTANCE = 60;
+
+ private static int sSlideDistance;
+
+ private static final TimeInterpolator HEADER_APPEAR_INTERPOLATOR = new DecelerateInterpolator();
+ private static final TimeInterpolator HEADER_DISAPPEAR_INTERPOLATOR
+ = new AccelerateInterpolator();
+
+ // Keys used to save and restore the states.
+ private static final String KEY_CURRENT_PAGE_INDEX = "leanback.onboarding.current_page_index";
+
+ private ContextThemeWrapper mThemeWrapper;
+
+ private PagingIndicator mPageIndicator;
+ private View mStartButton;
+ private ImageView mLogoView;
+ private TextView mTitleView;
+ private TextView mDescriptionView;
+
+ private boolean mIsLtr;
+
+ // No need to save/restore the logo resource ID, because the logo animation will not appear when
+ // the fragment is restored.
+ private int mLogoResourceId;
+ private boolean mEnterTransitionFinished;
+ private int mCurrentPageIndex;
+
+ private AnimatorSet mAnimator;
+
+ private final OnClickListener mOnClickListener = new OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (!mEnterTransitionFinished) {
+ // Do not change page until the enter transition finishes.
+ return;
+ }
+ if (mCurrentPageIndex == getPageCount() - 1) {
+ onFinishFragment();
+ } else {
+ moveToNextPage();
+ }
+ }
+ };
+
+ private final OnKeyListener mOnKeyListener = new OnKeyListener() {
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (!mEnterTransitionFinished) {
+ // Ignore key event until the enter transition finishes.
+ return keyCode != KeyEvent.KEYCODE_BACK;
+ }
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ return false;
+ }
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_BACK:
+ if (mCurrentPageIndex == 0) {
+ return false;
+ }
+ moveToPreviousPage();
+ return true;
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ if (mIsLtr) {
+ moveToPreviousPage();
+ } else {
+ moveToNextPage();
+ }
+ return true;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ if (mIsLtr) {
+ moveToNextPage();
+ } else {
+ moveToPreviousPage();
+ }
+ return true;
+ }
+ return false;
+ }
+ };
+
+ private void moveToPreviousPage() {
+ if (mCurrentPageIndex > 0) {
+ --mCurrentPageIndex;
+ onPageChangedInternal(mCurrentPageIndex + 1);
+ }
+ }
+ private void moveToNextPage() {
+ if (mCurrentPageIndex < getPageCount() - 1) {
+ ++mCurrentPageIndex;
+ onPageChangedInternal(mCurrentPageIndex - 1);
+ }
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, final ViewGroup container,
+ Bundle savedInstanceState) {
+ resolveTheme();
+ LayoutInflater localInflater = getThemeInflater(inflater);
+ final ViewGroup view = (ViewGroup) localInflater.inflate(R.layout.lb_onboarding_fragment,
+ container, false);
+ mIsLtr = getResources().getConfiguration().getLayoutDirection()
+ == View.LAYOUT_DIRECTION_LTR;
+ mPageIndicator = (PagingIndicator) view.findViewById(R.id.page_indicator);
+ mPageIndicator.setOnClickListener(mOnClickListener);
+ mPageIndicator.setOnKeyListener(mOnKeyListener);
+ mStartButton = view.findViewById(R.id.button_start);
+ mStartButton.setOnClickListener(mOnClickListener);
+ mStartButton.setOnKeyListener(mOnKeyListener);
+ mLogoView = (ImageView) view.findViewById(R.id.logo);
+ mTitleView = (TextView) view.findViewById(R.id.title);
+ mDescriptionView = (TextView) view.findViewById(R.id.description);
+ if (sSlideDistance == 0) {
+ sSlideDistance = (int) (SLIDE_DISTANCE * getActivity().getResources()
+ .getDisplayMetrics().scaledDensity);
+ }
+ if (savedInstanceState == null) {
+ mCurrentPageIndex = 0;
+ mEnterTransitionFinished = false;
+ mPageIndicator.onPageSelected(0, false);
+ view.getViewTreeObserver().addOnPreDrawListener(new OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ view.getViewTreeObserver().removeOnPreDrawListener(this);
+ if (!startLogoAnimation()) {
+ startEnterAnimation();
+ }
+ return true;
+ }
+ });
+ } else {
+ mEnterTransitionFinished = true;
+ mCurrentPageIndex = savedInstanceState.getInt(KEY_CURRENT_PAGE_INDEX);
+ initializeViews(view);
+ }
+ view.requestFocus();
+ return view;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(KEY_CURRENT_PAGE_INDEX, mCurrentPageIndex);
+ }
+
+ /**
+ * Returns the theme used for styling the fragment. The default returns -1, indicating that the
+ * host Activity's theme should be used.
+ *
+ * @return The theme resource ID of the theme to use in this fragment, or -1 to use the host
+ * Activity's theme.
+ */
+ public int onProvideTheme() {
+ return -1;
+ }
+
+ private void resolveTheme() {
+ FragmentActivity activity = getActivity();
+ int theme = onProvideTheme();
+ if (theme == -1) {
+ // Look up the onboardingTheme in the activity's currently specified theme. If it
+ // exists, wrap the theme with its value.
+ int resId = R.attr.onboardingTheme;
+ TypedValue typedValue = new TypedValue();
+ boolean found = activity.getTheme().resolveAttribute(resId, typedValue, true);
+ if (DEBUG) Log.v(TAG, "Found onboarding theme reference? " + found);
+ if (found) {
+ mThemeWrapper = new ContextThemeWrapper(activity, typedValue.resourceId);
+ }
+ } else {
+ mThemeWrapper = new ContextThemeWrapper(activity, theme);
+ }
+ }
+
+ private LayoutInflater getThemeInflater(LayoutInflater inflater) {
+ return mThemeWrapper == null ? inflater : inflater.cloneInContext(mThemeWrapper);
+ }
+
+ /**
+ * Sets the resource ID of the splash logo image. If the logo resource id set, the default logo
+ * splash animation will be played.
+ *
+ * @param id The resource ID of the logo image.
+ */
+ public final void setLogoResourceId(int id) {
+ mLogoResourceId = id;
+ }
+
+ /**
+ * Returns the resource ID of the splash logo image.
+ *
+ * @return The resource ID of the splash logo image.
+ */
+ public final int getLogoResourceId() {
+ return mLogoResourceId;
+ }
+
+ /**
+ * Called to have the inherited class create its own logo animation.
+ * <p>
+ * This is called only if the logo image resource ID is not set by {@link #setLogoResourceId}.
+ * If this returns {@code null}, the logo animation is skipped.
+ *
+ * @return The {@link Animator} object which runs the logo animation.
+ */
+ @Nullable
+ protected Animator onCreateLogoAnimation() {
+ return null;
+ }
+
+ private boolean startLogoAnimation() {
+ Animator animator = null;
+ if (mLogoResourceId != 0) {
+ mLogoView.setVisibility(View.VISIBLE);
+ mLogoView.setImageResource(mLogoResourceId);
+ Animator inAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_logo_enter);
+ Animator outAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_logo_exit);
+ outAnimator.setStartDelay(LOGO_SPLASH_PAUSE_DURATION_MS);
+ AnimatorSet logoAnimator = new AnimatorSet();
+ logoAnimator.playSequentially(inAnimator, outAnimator);
+ logoAnimator.setTarget(mLogoView);
+ animator = logoAnimator;
+ } else {
+ animator = onCreateLogoAnimation();
+ }
+ if (animator != null) {
+ animator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ if (getActivity() != null) {
+ startEnterAnimation();
+ }
+ }
+ });
+ animator.start();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Called to have the inherited class create its enter animation. The start animation runs after
+ * logo animation ends.
+ *
+ * @return The {@link Animator} object which runs the page enter animation.
+ */
+ @Nullable
+ protected Animator onCreateEnterAnimation() {
+ return null;
+ }
+
+ private void initializeViews(View container) {
+ mLogoView.setVisibility(View.GONE);
+ // Create custom views.
+ LayoutInflater inflater = getThemeInflater(LayoutInflater.from(getActivity()));
+ ViewGroup backgroundContainer = (ViewGroup) container.findViewById(
+ R.id.background_container);
+ View background = onCreateBackgroundView(inflater, backgroundContainer);
+ if (background != null) {
+ backgroundContainer.setVisibility(View.VISIBLE);
+ backgroundContainer.addView(background);
+ }
+ ViewGroup contentContainer = (ViewGroup) container.findViewById(R.id.content_container);
+ View content = onCreateContentView(inflater, contentContainer);
+ if (content != null) {
+ contentContainer.setVisibility(View.VISIBLE);
+ contentContainer.addView(content);
+ }
+ ViewGroup foregroundContainer = (ViewGroup) container.findViewById(
+ R.id.foreground_container);
+ View foreground = onCreateForegroundView(inflater, foregroundContainer);
+ if (foreground != null) {
+ foregroundContainer.setVisibility(View.VISIBLE);
+ foregroundContainer.addView(foreground);
+ }
+ // Make views visible which were invisible while logo animation is running.
+ container.findViewById(R.id.page_container).setVisibility(View.VISIBLE);
+ container.findViewById(R.id.content_container).setVisibility(View.VISIBLE);
+ if (getPageCount() > 1) {
+ mPageIndicator.setPageCount(getPageCount());
+ mPageIndicator.onPageSelected(mCurrentPageIndex, false);
+ }
+ if (mCurrentPageIndex == getPageCount() - 1) {
+ mStartButton.setVisibility(View.VISIBLE);
+ } else {
+ mPageIndicator.setVisibility(View.VISIBLE);
+ }
+ // Header views.
+ mTitleView.setText(getPageTitle(mCurrentPageIndex));
+ mDescriptionView.setText(getPageDescription(mCurrentPageIndex));
+ }
+
+ private void startEnterAnimation() {
+ mEnterTransitionFinished = true;
+ initializeViews(getView());
+ List<Animator> animators = new ArrayList<>();
+ Animator animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_enter);
+ animator.setTarget(getPageCount() <= 1 ? mStartButton : mPageIndicator);
+ animators.add(animator);
+ // Header title
+ View view = getActivity().findViewById(R.id.title);
+ view.setAlpha(0);
+ animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_title_enter);
+ animator.setStartDelay(START_DELAY_TITLE_MS);
+ animator.setTarget(view);
+ animators.add(animator);
+ // Header description
+ view = getActivity().findViewById(R.id.description);
+ view.setAlpha(0);
+ animator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_description_enter);
+ animator.setStartDelay(START_DELAY_DESCRIPTION_MS);
+ animator.setTarget(view);
+ animators.add(animator);
+ // Customized animation by the inherited class.
+ Animator customAnimator = onCreateEnterAnimation();
+ if (customAnimator != null) {
+ animators.add(customAnimator);
+ }
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(animators);
+ mAnimator.start();
+ // Search focus and give the focus to the appropriate child which has become visible.
+ getView().requestFocus();
+ }
+
+ /**
+ * Returns the page count.
+ *
+ * @return The page count.
+ */
+ abstract protected int getPageCount();
+
+ /**
+ * Returns the title of the given page.
+ *
+ * @param pageIndex The page index.
+ *
+ * @return The title of the page.
+ */
+ abstract protected CharSequence getPageTitle(int pageIndex);
+
+ /**
+ * Returns the description of the given page.
+ *
+ * @param pageIndex The page index.
+ *
+ * @return The description of the page.
+ */
+ abstract protected CharSequence getPageDescription(int pageIndex);
+
+ /**
+ * Returns the index of the current page.
+ *
+ * @return The index of the current page.
+ */
+ protected final int getCurrentPageIndex() {
+ return mCurrentPageIndex;
+ }
+
+ /**
+ * Called to have the inherited class create background view. This is optional and the fragment
+ * which doesn't have the background view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The background view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateBackgroundView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called to have the inherited class create content view. This is optional and the fragment
+ * which doesn't have the content view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * <p>The content view would be located at the center of the screen.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The content view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateContentView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called to have the inherited class create foreground view. This is optional and the fragment
+ * which doesn't need the foreground view can return {@code null}. This is called inside
+ * {@link #onCreateView}.
+ *
+ * <p>This foreground view would have the highest z-order.
+ *
+ * @param inflater The LayoutInflater object that can be used to inflate the views,
+ * @param container The parent view that the additional views are attached to.The fragment
+ * should not add the view by itself.
+ *
+ * @return The foreground view for the onboarding screen, or {@code null}.
+ */
+ @Nullable
+ abstract protected View onCreateForegroundView(LayoutInflater inflater, ViewGroup container);
+
+ /**
+ * Called when the onboarding flow finishes.
+ */
+ protected void onFinishFragment() { }
+
+ /**
+ * Called when the page changes.
+ */
+ private void onPageChangedInternal(int previousPage) {
+ if (mAnimator != null) {
+ mAnimator.end();
+ }
+ mPageIndicator.onPageSelected(mCurrentPageIndex, true);
+
+ List<Animator> animators = new ArrayList<>();
+ // Header animation
+ Animator fadeAnimator = null;
+ if (previousPage < getCurrentPageIndex()) {
+ // sliding to left
+ animators.add(createAnimator(mTitleView, false, Gravity.START, 0));
+ animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.START,
+ DESCRIPTION_START_DELAY_MS));
+ animators.add(createAnimator(mTitleView, true, Gravity.END,
+ HEADER_APPEAR_DELAY_MS));
+ animators.add(createAnimator(mDescriptionView, true, Gravity.END,
+ HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+ } else {
+ // sliding to right
+ animators.add(createAnimator(mTitleView, false, Gravity.END, 0));
+ animators.add(fadeAnimator = createAnimator(mDescriptionView, false, Gravity.END,
+ DESCRIPTION_START_DELAY_MS));
+ animators.add(createAnimator(mTitleView, true, Gravity.START,
+ HEADER_APPEAR_DELAY_MS));
+ animators.add(createAnimator(mDescriptionView, true, Gravity.START,
+ HEADER_APPEAR_DELAY_MS + DESCRIPTION_START_DELAY_MS));
+ }
+ final int currentPageIndex = getCurrentPageIndex();
+ fadeAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mTitleView.setText(getPageTitle(currentPageIndex));
+ mDescriptionView.setText(getPageDescription(currentPageIndex));
+ }
+ });
+
+ // Animator for switching between page indicator and button.
+ if (getCurrentPageIndex() == getPageCount() - 1) {
+ mStartButton.setVisibility(View.VISIBLE);
+ Animator navigatorFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_fade_out);
+ navigatorFadeOutAnimator.setTarget(mPageIndicator);
+ navigatorFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mPageIndicator.setVisibility(View.GONE);
+ }
+ });
+ animators.add(navigatorFadeOutAnimator);
+ Animator buttonFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_start_button_fade_in);
+ buttonFadeInAnimator.setTarget(mStartButton);
+ animators.add(buttonFadeInAnimator);
+ } else if (previousPage == getPageCount() - 1) {
+ mPageIndicator.setVisibility(View.VISIBLE);
+ Animator navigatorFadeInAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_page_indicator_fade_in);
+ navigatorFadeInAnimator.setTarget(mPageIndicator);
+ animators.add(navigatorFadeInAnimator);
+ Animator buttonFadeOutAnimator = AnimatorInflater.loadAnimator(getActivity(),
+ R.animator.lb_onboarding_start_button_fade_out);
+ buttonFadeOutAnimator.setTarget(mStartButton);
+ buttonFadeOutAnimator.addListener(new AnimatorListenerAdapter() {
+ @Override
+ public void onAnimationEnd(Animator animation) {
+ mStartButton.setVisibility(View.GONE);
+ }
+ });
+ animators.add(buttonFadeOutAnimator);
+ }
+ mAnimator = new AnimatorSet();
+ mAnimator.playTogether(animators);
+ mAnimator.start();
+ onPageChanged(mCurrentPageIndex, previousPage);
+ }
+
+ /**
+ * Called when the page has been changed.
+ *
+ * @param newPage The new page.
+ * @param previousPage The previous page.
+ */
+ protected void onPageChanged(int newPage, int previousPage) { }
+
+ private Animator createAnimator(View view, boolean fadeIn, int slideDirection,
+ long startDelay) {
+ boolean isLtr = getView().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ boolean slideRight = (isLtr && slideDirection == Gravity.END)
+ || (!isLtr && slideDirection == Gravity.START)
+ || slideDirection == Gravity.RIGHT;
+ Animator fadeAnimator;
+ Animator slideAnimator;
+ if (fadeIn) {
+ fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 0.0f, 1.0f);
+ slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X,
+ slideRight ? sSlideDistance : -sSlideDistance, 0);
+ fadeAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+ slideAnimator.setInterpolator(HEADER_APPEAR_INTERPOLATOR);
+ } else {
+ fadeAnimator = ObjectAnimator.ofFloat(view, View.ALPHA, 1.0f, 0.0f);
+ slideAnimator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0,
+ slideRight ? sSlideDistance : -sSlideDistance);
+ fadeAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+ slideAnimator.setInterpolator(HEADER_DISAPPEAR_INTERPOLATOR);
+ }
+ fadeAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+ fadeAnimator.setTarget(view);
+ slideAnimator.setDuration(HEADER_ANIMATION_DURATION_MS);
+ slideAnimator.setTarget(view);
+ AnimatorSet animator = new AnimatorSet();
+ animator.playTogether(fadeAnimator, slideAnimator);
+ if (startDelay > 0) {
+ animator.setStartDelay(startDelay);
+ }
+ return animator;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java b/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java
new file mode 100644
index 0000000..ee9f866
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/PermissionHelper.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.app;
+
+import android.os.Build;
+
+/**
+ * @hide
+ */
+public class PermissionHelper {
+
+ public static void requestPermissions(android.app.Fragment fragment, String[] permissions,
+ int requestCode) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ PermissionHelper23.requestPermissions(fragment, permissions, requestCode);
+ }
+ }
+
+ public static void requestPermissions(android.support.v4.app.Fragment fragment,
+ String[] permissions, int requestCode) {
+ fragment.requestPermissions(permissions, requestCode);
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
index e3af403..120ffa7 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java
@@ -373,6 +373,7 @@
* @deprecated Don't call this. Instead set the listener on the fragment yourself,
* and call {@link #onActionClicked} to handle clicks.
*/
+ @Deprecated
public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
mExternalOnItemViewClickedListener = listener;
if (mFragment != null) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
index 5857e65..f0deb2c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java
@@ -375,6 +375,7 @@
* @deprecated Don't call this. Instead set the listener on the fragment yourself,
* and call {@link #onActionClicked} to handle clicks.
*/
+ @Deprecated
public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
mExternalOnItemViewClickedListener = listener;
if (mFragment != null) {
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
index 46e91c8..64029cd 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java
@@ -21,7 +21,6 @@
import android.animation.ValueAnimator;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Row;
import android.view.InputEvent;
import android.view.animation.AccelerateInterpolator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -33,12 +32,14 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -52,8 +53,8 @@
* A fragment for displaying playback controls and related content.
* <p>
* A PlaybackOverlayFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
* </p>
* <p>
* An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
@@ -112,7 +113,7 @@
private static final int IN = 1;
private static final int OUT = 2;
- private int mAlignPosition;
+ private int mPaddingTop;
private int mPaddingBottom;
private View mRootView;
private int mBackgroundType = BG_DARK;
@@ -156,9 +157,9 @@
mFadeCompleteListener.onFadeInComplete();
}
} else {
- if (getVerticalGridView() != null) {
- // Reset focus to the controls row
- getVerticalGridView().setSelectedPosition(0);
+ VerticalGridView verticalView = getVerticalGridView();
+ // reset focus to the primary actions only if the selected row was the controls row
+ if (verticalView != null && verticalView.getSelectedPosition() == 0) {
resetControlsToPrimaryActions(null);
}
if (mFadeCompleteListener != null) {
@@ -295,6 +296,14 @@
}
}
+ /**
+ * Fades out the playback overlay immediately.
+ */
+ public void fadeOut() {
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
@@ -587,29 +596,29 @@
}
// Padding affects alignment when last row is focused
// (last is first when there's only one row).
- setBottomPadding(listview, mPaddingBottom);
+ setPadding(listview, mPaddingTop, mPaddingBottom);
// Item alignment affects focused row that isn't the last.
- listview.setItemAlignmentOffset(mAlignPosition);
- listview.setItemAlignmentOffsetPercent(100);
+ listview.setItemAlignmentOffset(0);
+ listview.setItemAlignmentOffsetPercent(50);
// Push rows to the bottom.
listview.setWindowAlignmentOffset(0);
- listview.setWindowAlignmentOffsetPercent(100);
- listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+ listview.setWindowAlignmentOffsetPercent(50);
+ listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
}
- private static void setBottomPadding(View view, int padding) {
- view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
- view.getPaddingRight(), padding);
+ private static void setPadding(View view, int paddingTop, int paddingBottom) {
+ view.setPadding(view.getPaddingLeft(), paddingTop,
+ view.getPaddingRight(), paddingBottom);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mAlignPosition =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
+ mPaddingTop =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_top);
mPaddingBottom =
getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
mBgDarkColor =
@@ -762,5 +771,6 @@
mViews.clear();
}
abstract void getViews(ArrayList<View> views);
+
};
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
index 58433ef..a4e3f24 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java
@@ -23,7 +23,6 @@
import android.animation.ValueAnimator;
import android.support.v17.leanback.widget.Action;
import android.support.v17.leanback.widget.PlaybackControlsRow;
-import android.support.v17.leanback.widget.Row;
import android.view.InputEvent;
import android.view.animation.AccelerateInterpolator;
import android.animation.ValueAnimator.AnimatorUpdateListener;
@@ -35,12 +34,14 @@
import android.support.v17.leanback.R;
import android.support.v17.leanback.animation.LogAccelerateInterpolator;
import android.support.v17.leanback.animation.LogDecelerateInterpolator;
-import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
-import android.support.v17.leanback.widget.VerticalGridView;
import android.support.v17.leanback.widget.PlaybackControlsRowPresenter;
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.LayoutInflater;
@@ -54,8 +55,8 @@
* A fragment for displaying playback controls and related content.
* <p>
* A PlaybackOverlaySupportFragment renders the elements of its {@link ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
* </p>
* <p>
* An instance of {@link android.support.v17.leanback.widget.PlaybackControlsRow} is expected to be
@@ -114,7 +115,7 @@
private static final int IN = 1;
private static final int OUT = 2;
- private int mAlignPosition;
+ private int mPaddingTop;
private int mPaddingBottom;
private View mRootView;
private int mBackgroundType = BG_DARK;
@@ -158,9 +159,9 @@
mFadeCompleteListener.onFadeInComplete();
}
} else {
- if (getVerticalGridView() != null) {
- // Reset focus to the controls row
- getVerticalGridView().setSelectedPosition(0);
+ VerticalGridView verticalView = getVerticalGridView();
+ // reset focus to the primary actions only if the selected row was the controls row
+ if (verticalView != null && verticalView.getSelectedPosition() == 0) {
resetControlsToPrimaryActions(null);
}
if (mFadeCompleteListener != null) {
@@ -297,6 +298,14 @@
}
}
+ /**
+ * Fades out the playback overlay immediately.
+ */
+ public void fadeOut() {
+ mHandler.removeMessages(START_FADE_OUT);
+ fade(false);
+ }
+
private boolean areControlsHidden() {
return mFadingStatus == IDLE && mBgAlpha == 0;
}
@@ -589,29 +598,29 @@
}
// Padding affects alignment when last row is focused
// (last is first when there's only one row).
- setBottomPadding(listview, mPaddingBottom);
+ setPadding(listview, mPaddingTop, mPaddingBottom);
// Item alignment affects focused row that isn't the last.
- listview.setItemAlignmentOffset(mAlignPosition);
- listview.setItemAlignmentOffsetPercent(100);
+ listview.setItemAlignmentOffset(0);
+ listview.setItemAlignmentOffsetPercent(50);
// Push rows to the bottom.
listview.setWindowAlignmentOffset(0);
- listview.setWindowAlignmentOffsetPercent(100);
- listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_HIGH_EDGE);
+ listview.setWindowAlignmentOffsetPercent(50);
+ listview.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
}
- private static void setBottomPadding(View view, int padding) {
- view.setPadding(view.getPaddingLeft(), view.getPaddingTop(),
- view.getPaddingRight(), padding);
+ private static void setPadding(View view, int paddingTop, int paddingBottom) {
+ view.setPadding(view.getPaddingLeft(), paddingTop,
+ view.getPaddingRight(), paddingBottom);
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- mAlignPosition =
- getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_align_bottom);
+ mPaddingTop =
+ getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_top);
mPaddingBottom =
getResources().getDimensionPixelSize(R.dimen.lb_playback_controls_padding_bottom);
mBgDarkColor =
@@ -764,5 +773,6 @@
mViews.clear();
}
abstract void getViews(ArrayList<View> views);
+
};
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
new file mode 100644
index 0000000..e05ca0c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
@@ -0,0 +1,131 @@
+package android.support.v17.leanback.app;
+
+import android.os.Handler;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.ProgressBar;
+
+/**
+ * Manager for showing/hiding progress bar widget. This class lets user specify an initial
+ * delay after which the progress bar will be shown. This is currently being used in
+ * {@link BrowseFragment} & {@link VerticalGridFragment} to show {@link ProgressBar}
+ * while the data is being loaded.
+ */
+public final class ProgressBarManager {
+ // Default delay for progress bar widget.
+ private static final long DEFAULT_PROGRESS_BAR_DELAY = 1000;
+
+ private long mInitialDelay = DEFAULT_PROGRESS_BAR_DELAY;
+ private ViewGroup rootView;
+ private View mProgressBarView;
+ private Handler mHandler = new Handler();
+ private boolean mEnableProgressBar = true;
+ private boolean mUserProvidedProgressBar;
+ private boolean mIsShowing;
+
+ private Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ if (!mEnableProgressBar || (!mUserProvidedProgressBar && rootView == null)) {
+ return;
+ }
+
+ if (mIsShowing) {
+ if (mProgressBarView == null) {
+ mProgressBarView = new ProgressBar(
+ rootView.getContext(), null, android.R.attr.progressBarStyleLarge);
+ FrameLayout.LayoutParams progressBarParams = new FrameLayout.LayoutParams(
+ FrameLayout.LayoutParams.WRAP_CONTENT,
+ FrameLayout.LayoutParams.WRAP_CONTENT);
+ progressBarParams.gravity = Gravity.CENTER;
+ rootView.addView(mProgressBarView, progressBarParams);
+ } else if (mUserProvidedProgressBar) {
+ mProgressBarView.setVisibility(View.VISIBLE);
+ }
+ }
+ }
+ };
+
+ /**
+ * Sets the root view on which the progress bar will be attached. This class assumes the
+ * root view to be {@link FrameLayout} in order to position the progress bar widget
+ * in the center of the screen.
+ *
+ * @param rootView view that will contain the progress bar.
+ */
+ public void setRootView(ViewGroup rootView) {
+ this.rootView = rootView;
+ }
+
+ /**
+ * Displays the progress bar.
+ */
+ public void show() {
+ if (mEnableProgressBar) {
+ mIsShowing = true;
+ mHandler.postDelayed(runnable, mInitialDelay);
+ }
+ }
+
+ /**
+ * Hides the progress bar.
+ */
+ public void hide() {
+ mIsShowing = false;
+ if (mUserProvidedProgressBar) {
+ mProgressBarView.setVisibility(View.INVISIBLE);
+ } else if (mProgressBarView != null) {
+ rootView.removeView(mProgressBarView);
+ }
+
+ mHandler.removeCallbacks(runnable);
+ }
+
+ /**
+ * Sets a custom view to be shown in place of the default {@link ProgressBar}. This
+ * view must have a parent. Once set, we maintain the visibility property of this view.
+ *
+ * @param progressBarView custom view that will be shown to indicate progress.
+ */
+ public void setProgressBarView(View progressBarView) {
+ if (progressBarView.getParent() == null) {
+ throw new IllegalArgumentException("Must have a parent");
+ }
+
+ this.mProgressBarView = progressBarView;
+ this.mProgressBarView.setVisibility(View.INVISIBLE);
+ mUserProvidedProgressBar = true;
+ }
+
+ /**
+ * Returns the initial delay.
+ */
+ public long getInitialDelay() {
+ return mInitialDelay;
+ }
+
+ /**
+ * Sets the initial delay. Progress bar will be shown after this delay has elapsed.
+ *
+ * @param initialDelay millisecond representing the initial delay.
+ */
+ public void setInitialDelay(long initialDelay) {
+ this.mInitialDelay = initialDelay;
+ }
+
+ /**
+ * Disables progress bar.
+ */
+ public void disableProgressBar() {
+ mEnableProgressBar = false;
+ }
+
+ /**
+ * Enables progress bar.
+ */
+ public void enableProgressBar() {
+ mEnableProgressBar = true;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
index a5a7ccd..89859f2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -13,41 +13,64 @@
*/
package android.support.v17.leanback.app;
-import java.util.ArrayList;
-
import android.animation.TimeAnimator;
import android.animation.TimeAnimator.TimeListener;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
-import android.support.v17.leanback.widget.ScaleFrameLayout;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.ViewHolderTask;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import java.util.ArrayList;
+
/**
* An ordered set of rows of leanback widgets.
* <p>
* A RowsFragment renders the elements of its
* {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link android.support.v17.leanback.widget.Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
* </p>
*/
-public class RowsFragment extends BaseRowFragment {
+public class RowsFragment extends BaseRowFragment implements
+ BrowseFragment.MainFragmentRowsAdapterProvider,
+ BrowseFragment.MainFragmentAdapterProvider {
+
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+ @Override
+ public BrowseFragment.MainFragmentAdapter getMainFragmentAdapter() {
+ if (mMainFragmentAdapter == null) {
+ mMainFragmentAdapter = new MainFragmentAdapter(this);
+ }
+ return mMainFragmentAdapter;
+ }
+
+ @Override
+ public BrowseFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter() {
+ if (mMainFragmentRowsAdapter == null) {
+ mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ }
+ return mMainFragmentRowsAdapter;
+ }
/**
* Internal helper class that manages row select animation and apply a default
@@ -88,7 +111,7 @@
if (mSelectAnimatorInterpolatorInUse != null) {
fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
}
- float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+ float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
mRowPresenter.setSelectLevel(mRowViewHolder, level);
}
@@ -115,14 +138,11 @@
private int mSubPosition;
private boolean mExpand = true;
private boolean mViewsCreated;
- private float mRowScaleFactor;
private int mAlignedTop;
- private boolean mRowScaleEnabled;
- private ScaleFrameLayout mScaleFrameLayout;
private boolean mAfterEntranceTransition = true;
- private OnItemViewSelectedListener mOnItemViewSelectedListener;
- private OnItemViewClickedListener mOnItemViewClickedListener;
+ private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+ private BaseOnItemViewClickedListener mOnItemViewClickedListener;
// Select animation and interpolator are not intended to be
// exposed at this moment. They might be synced with vertical scroll
@@ -144,9 +164,9 @@
* Sets an item clicked listener on the fragment.
* OnItemViewClickedListener will override {@link View.OnClickListener} that
* item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
- * So in general, developer should choose one of the listeners but not both.
+ * So in general, developer should choose one of the listeners but not both.
*/
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
mOnItemViewClickedListener = listener;
if (mViewsCreated) {
throw new IllegalStateException(
@@ -157,23 +177,32 @@
/**
* Returns the item clicked listener.
*/
- public OnItemViewClickedListener getOnItemViewClickedListener() {
+ public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
/**
+ * @deprecated use {@link BrowseFragment#enableRowScaling(boolean)} instead.
+ *
+ * @param enable true to enable row scaling
+ */
+ @Deprecated
+ public void enableRowScaling(boolean enable) {
+ }
+
+ /**
* Set the visibility of titles/hovercard of browse rows.
*/
public void setExpand(boolean expand) {
mExpand = expand;
VerticalGridView listView = getVerticalGridView();
if (listView != null) {
- updateRowScaling();
final int count = listView.getChildCount();
if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
for (int i = 0; i < count; i++) {
View view = listView.getChildAt(i);
- ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+ ItemBridgeAdapter.ViewHolder vh
+ = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
setRowViewExpanded(vh, mExpand);
}
}
@@ -182,7 +211,7 @@
/**
* Sets an item selection listener.
*/
- public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mOnItemViewSelectedListener = listener;
VerticalGridView listView = getVerticalGridView();
if (listView != null) {
@@ -191,9 +220,7 @@
View view = listView.getChildAt(i);
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
listView.getChildViewHolder(view);
- RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
- RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
- vh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+ getRowViewHolder(ibvh).setOnItemViewSelectedListener(mOnItemViewSelectedListener);
}
}
}
@@ -201,19 +228,10 @@
/**
* Returns an item selection listener.
*/
- public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+ public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
return mOnItemViewSelectedListener;
}
- /**
- * Enables scaling of rows.
- *
- * @param enable true to enable row scaling
- */
- public void enableRowScaling(boolean enable) {
- mRowScaleEnabled = enable;
- }
-
@Override
void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
int position, int subposition) {
@@ -229,6 +247,27 @@
setRowViewSelected(mSelectedViewHolder, true, false);
}
}
+ // When RowsFragment is embedded inside a page fragment, we want to show
+ // the title view only when we're on the first row or there is no data.
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().showTitleView(position <= 0);
+ }
+ }
+
+ /**
+ * Get row ViewHolder at adapter position. Returns null if the row object is not in adapter or
+ * the row object has not been bound to a row view.
+ *
+ * @param position Position of row in adapter.
+ * @return Row ViewHolder at a given adapter position.
+ */
+ public RowPresenter.ViewHolder getRowViewHolder(int position) {
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView == null) {
+ return null;
+ }
+ return getRowViewHolder((ItemBridgeAdapter.ViewHolder)
+ verticalView.findViewHolderForAdapterPosition(position));
}
@Override
@@ -241,16 +280,6 @@
super.onCreate(savedInstanceState);
mSelectAnimatorDuration = getResources().getInteger(
R.integer.lb_browse_rows_anim_duration);
- mRowScaleFactor = getResources().getFraction(
- R.fraction.lb_browse_rows_scale, 1, 1);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
- return view;
}
@Override
@@ -262,8 +291,14 @@
getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
+ setAlignment(mAlignedTop);
+
mRecycledViewPool = null;
mPresenterMapper = null;
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+ }
+
}
@Override
@@ -272,35 +307,10 @@
super.onDestroyView();
}
- @Override
- void setItemAlignment() {
- super.setItemAlignment();
- if (getVerticalGridView() != null) {
- getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
- }
- }
-
void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
mExternalAdapterListener = listener;
}
- /**
- * Returns the view that will change scale.
- */
- View getScaleView() {
- return getVerticalGridView();
- }
-
- /**
- * Sets the pivots to scale rows fragment.
- */
- void setScalePivots(float pivotX, float pivotY) {
- // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
- // where we actually change scale.
- mScaleFrameLayout.setPivotX(pivotX);
- mScaleFrameLayout.setPivotY(pivotY);
- }
-
private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
}
@@ -320,6 +330,7 @@
mExternalAdapterListener.onAddPresenter(presenter, type);
}
}
+
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
VerticalGridView listView = getVerticalGridView();
@@ -338,6 +349,7 @@
mExternalAdapterListener.onCreate(vh);
}
}
+
@Override
public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
if (DEBUG) Log.v(TAG, "onAttachToWindow");
@@ -356,6 +368,7 @@
mExternalAdapterListener.onAttachedToWindow(vh);
}
}
+
@Override
public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
if (mSelectedViewHolder == vh) {
@@ -366,12 +379,14 @@
mExternalAdapterListener.onDetachedFromWindow(vh);
}
}
+
@Override
public void onBind(ItemBridgeAdapter.ViewHolder vh) {
if (mExternalAdapterListener != null) {
mExternalAdapterListener.onBind(vh);
}
}
+
@Override
public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
setRowViewSelected(vh, false, true);
@@ -417,7 +432,7 @@
}
@Override
- boolean onTransitionPrepare() {
+ public boolean onTransitionPrepare() {
boolean prepared = super.onTransitionPrepare();
if (prepared) {
freezeRows(true);
@@ -425,92 +440,8 @@
return prepared;
}
- class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
-
- final View mVerticalView;
- final Runnable mCallback;
- int mState;
-
- final static int STATE_INIT = 0;
- final static int STATE_FIRST_DRAW = 1;
- final static int STATE_SECOND_DRAW = 2;
-
- ExpandPreLayout(Runnable callback) {
- mVerticalView = getVerticalGridView();
- mCallback = callback;
- }
-
- void execute() {
- mVerticalView.getViewTreeObserver().addOnPreDrawListener(this);
- setExpand(false);
- mState = STATE_INIT;
- }
-
- @Override
- public boolean onPreDraw() {
- if (getView() == null || getActivity() == null) {
- mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- if (mState == STATE_INIT) {
- setExpand(true);
- mState = STATE_FIRST_DRAW;
- } else if (mState == STATE_FIRST_DRAW) {
- mCallback.run();
- mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
- mState = STATE_SECOND_DRAW;
- }
- return false;
- }
- }
-
- void onExpandTransitionStart(boolean expand, final Runnable callback) {
- onTransitionPrepare();
- onTransitionStart();
- if (expand) {
- callback.run();
- return;
- }
- // Run a "pre" layout when we go non-expand, in order to get the initial
- // positions of added rows.
- new ExpandPreLayout(callback).execute();
- }
-
- private boolean needsScale() {
- return mRowScaleEnabled && !mExpand;
- }
-
- private void updateRowScaling() {
- final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
- mScaleFrameLayout.setLayoutScaleY(scaleFactor);
- getScaleView().setScaleY(scaleFactor);
- getScaleView().setScaleX(scaleFactor);
- updateWindowAlignOffset();
- }
-
- private void updateWindowAlignOffset() {
- int alignOffset = mAlignedTop;
- if (needsScale()) {
- alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
- }
- getVerticalGridView().setWindowAlignmentOffset(alignOffset);
- }
-
@Override
- void setWindowAlignmentFromTop(int alignedTop) {
- mAlignedTop = alignedTop;
- final VerticalGridView gridView = getVerticalGridView();
- if (gridView != null) {
- updateWindowAlignOffset();
- // align to a fixed position from top
- gridView.setWindowAlignmentOffsetPercent(
- VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
- gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- }
- }
-
- @Override
- void onTransitionEnd() {
+ public void onTransitionEnd() {
super.onTransitionEnd();
freezeRows(false);
}
@@ -521,7 +452,7 @@
final int count = verticalView.getChildCount();
for (int i = 0; i < count; i++) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
- verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
rowPresenter.freeze(vh, freeze);
@@ -533,18 +464,171 @@
* For rows that willing to participate entrance transition, this function
* hide views if afterTransition is true, show views if afterTransition is false.
*/
- void setEntranceTransitionState(boolean afterTransition) {
+ public void setEntranceTransitionState(boolean afterTransition) {
mAfterEntranceTransition = afterTransition;
VerticalGridView verticalView = getVerticalGridView();
if (verticalView != null) {
final int count = verticalView.getChildCount();
for (int i = 0; i < count; i++) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
- verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
}
}
}
+
+ /**
+ * Selects a Row and perform an optional task on the Row. For example
+ * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+ * Scroll to 11th row and selects 6th item on that row. The method will be ignored if
+ * RowsFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+ * ViewGroup, Bundle)}).
+ *
+ * @param rowPosition Which row to select.
+ * @param smooth True to scroll to the row, false for no animation.
+ * @param rowHolderTask Task to perform on the Row.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView == null) {
+ return;
+ }
+ ViewHolderTask task = null;
+ if (rowHolderTask != null) {
+ task = new ViewHolderTask() {
+ @Override
+ public void run(RecyclerView.ViewHolder rvh) {
+ rowHolderTask.run(getRowViewHolder((ItemBridgeAdapter.ViewHolder) rvh));
+ }
+ };
+ }
+ if (smooth) {
+ verticalView.setSelectedPositionSmooth(rowPosition, task);
+ } else {
+ verticalView.setSelectedPosition(rowPosition, task);
+ }
+ }
+
+ static RowPresenter.ViewHolder getRowViewHolder(ItemBridgeAdapter.ViewHolder ibvh) {
+ if (ibvh == null) {
+ return null;
+ }
+ RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+ return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+ }
+
+ public boolean isScrolling() {
+ if (getVerticalGridView() == null) {
+ return false;
+ }
+ return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
+ }
+
+ @Override
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ mAlignedTop = windowAlignOffsetFromTop;
+ final VerticalGridView gridView = getVerticalGridView();
+
+ if (gridView != null) {
+ gridView.setItemAlignmentOffset(0);
+ gridView.setItemAlignmentOffsetPercent(
+ VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setItemAlignmentOffsetWithPadding(true);
+ gridView.setWindowAlignmentOffset(mAlignedTop);
+ // align to a fixed position from top
+ gridView.setWindowAlignmentOffsetPercent(
+ VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+ }
+
+ public static class MainFragmentAdapter extends BrowseFragment.MainFragmentAdapter<RowsFragment> {
+
+ public MainFragmentAdapter(RowsFragment fragment) {
+ super(fragment);
+ setScalingEnabled(true);
+ }
+
+ @Override
+ public boolean isScrolling() {
+ return getFragment().isScrolling();
+ }
+
+ @Override
+ public void setExpand(boolean expand) {
+ getFragment().setExpand(expand);
+ }
+
+ @Override
+ public void setEntranceTransitionState(boolean state) {
+ getFragment().setEntranceTransitionState(state);
+ }
+
+ @Override
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ getFragment().setAlignment(windowAlignOffsetFromTop);
+ }
+
+ @Override
+ public boolean onTransitionPrepare() {
+ return getFragment().onTransitionPrepare();
+ }
+
+ @Override
+ public void onTransitionStart() {
+ getFragment().onTransitionStart();
+ }
+
+ @Override
+ public void onTransitionEnd() {
+ getFragment().onTransitionEnd();
+ }
+
+ }
+
+ public static class MainFragmentRowsAdapter
+ extends BrowseFragment.MainFragmentRowsAdapter<RowsFragment> {
+
+ public MainFragmentRowsAdapter(RowsFragment fragment) {
+ super(fragment);
+ }
+
+ @Override
+ public void setAdapter(ObjectAdapter adapter) {
+ getFragment().setAdapter(adapter);
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ */
+ @Override
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ getFragment().setOnItemViewClickedListener(listener);
+ }
+
+ @Override
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ getFragment().setOnItemViewSelectedListener(listener);
+ }
+
+ @Override
+ public void setSelectedPosition(int rowPosition,
+ boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
+ }
+
+ @Override
+ public void setSelectedPosition(int rowPosition, boolean smooth) {
+ getFragment().setSelectedPosition(rowPosition, smooth);
+ }
+
+ @Override
+ public int getSelectedPosition() {
+ return getFragment().getSelectedPosition();
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
index 7390acf..9a16353 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -15,41 +15,64 @@
*/
package android.support.v17.leanback.app;
-import java.util.ArrayList;
-
import android.animation.TimeAnimator;
import android.animation.TimeAnimator.TimeListener;
import android.os.Bundle;
import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.HorizontalGridView;
import android.support.v17.leanback.widget.ItemBridgeAdapter;
+import android.support.v17.leanback.widget.ListRowPresenter;
+import android.support.v17.leanback.widget.ObjectAdapter;
+import android.support.v17.leanback.widget.BaseOnItemViewClickedListener;
+import android.support.v17.leanback.widget.BaseOnItemViewSelectedListener;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
-import android.support.v17.leanback.widget.RowPresenter.ViewHolder;
-import android.support.v17.leanback.widget.ScaleFrameLayout;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.HorizontalGridView;
-import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.PresenterSelector;
+import android.support.v17.leanback.widget.RowPresenter;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.ViewHolderTask;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
+import java.util.ArrayList;
+
/**
* An ordered set of rows of leanback widgets.
* <p>
* A RowsSupportFragment renders the elements of its
* {@link android.support.v17.leanback.widget.ObjectAdapter} as a set
- * of rows in a vertical list. The elements in this adapter must be subclasses
- * of {@link android.support.v17.leanback.widget.Row}.
+ * of rows in a vertical list. The Adapter's {@link PresenterSelector} must maintain subclasses
+ * of {@link RowPresenter}.
* </p>
*/
-public class RowsSupportFragment extends BaseRowSupportFragment {
+public class RowsSupportFragment extends BaseRowSupportFragment implements
+ BrowseSupportFragment.MainFragmentRowsAdapterProvider,
+ BrowseSupportFragment.MainFragmentAdapterProvider {
+
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
+
+ @Override
+ public BrowseSupportFragment.MainFragmentAdapter getMainFragmentAdapter() {
+ if (mMainFragmentAdapter == null) {
+ mMainFragmentAdapter = new MainFragmentAdapter(this);
+ }
+ return mMainFragmentAdapter;
+ }
+
+ @Override
+ public BrowseSupportFragment.MainFragmentRowsAdapter getMainFragmentRowsAdapter() {
+ if (mMainFragmentRowsAdapter == null) {
+ mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ }
+ return mMainFragmentRowsAdapter;
+ }
/**
* Internal helper class that manages row select animation and apply a default
@@ -90,7 +113,7 @@
if (mSelectAnimatorInterpolatorInUse != null) {
fraction = mSelectAnimatorInterpolatorInUse.getInterpolation(fraction);
}
- float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
+ float level = mSelectLevelAnimStart + fraction * mSelectLevelAnimDelta;
mRowPresenter.setSelectLevel(mRowViewHolder, level);
}
@@ -117,14 +140,11 @@
private int mSubPosition;
private boolean mExpand = true;
private boolean mViewsCreated;
- private float mRowScaleFactor;
private int mAlignedTop;
- private boolean mRowScaleEnabled;
- private ScaleFrameLayout mScaleFrameLayout;
private boolean mAfterEntranceTransition = true;
- private OnItemViewSelectedListener mOnItemViewSelectedListener;
- private OnItemViewClickedListener mOnItemViewClickedListener;
+ private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+ private BaseOnItemViewClickedListener mOnItemViewClickedListener;
// Select animation and interpolator are not intended to be
// exposed at this moment. They might be synced with vertical scroll
@@ -146,9 +166,9 @@
* Sets an item clicked listener on the fragment.
* OnItemViewClickedListener will override {@link View.OnClickListener} that
* item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
- * So in general, developer should choose one of the listeners but not both.
+ * So in general, developer should choose one of the listeners but not both.
*/
- public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ public void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
mOnItemViewClickedListener = listener;
if (mViewsCreated) {
throw new IllegalStateException(
@@ -159,23 +179,32 @@
/**
* Returns the item clicked listener.
*/
- public OnItemViewClickedListener getOnItemViewClickedListener() {
+ public BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
/**
+ * @deprecated use {@link BrowseSupportFragment#enableRowScaling(boolean)} instead.
+ *
+ * @param enable true to enable row scaling
+ */
+ @Deprecated
+ public void enableRowScaling(boolean enable) {
+ }
+
+ /**
* Set the visibility of titles/hovercard of browse rows.
*/
public void setExpand(boolean expand) {
mExpand = expand;
VerticalGridView listView = getVerticalGridView();
if (listView != null) {
- updateRowScaling();
final int count = listView.getChildCount();
if (DEBUG) Log.v(TAG, "setExpand " + expand + " count " + count);
for (int i = 0; i < count; i++) {
View view = listView.getChildAt(i);
- ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
+ ItemBridgeAdapter.ViewHolder vh
+ = (ItemBridgeAdapter.ViewHolder) listView.getChildViewHolder(view);
setRowViewExpanded(vh, mExpand);
}
}
@@ -184,7 +213,7 @@
/**
* Sets an item selection listener.
*/
- public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ public void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mOnItemViewSelectedListener = listener;
VerticalGridView listView = getVerticalGridView();
if (listView != null) {
@@ -193,9 +222,7 @@
View view = listView.getChildAt(i);
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
listView.getChildViewHolder(view);
- RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
- RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
- vh.setOnItemViewSelectedListener(mOnItemViewSelectedListener);
+ getRowViewHolder(ibvh).setOnItemViewSelectedListener(mOnItemViewSelectedListener);
}
}
}
@@ -203,19 +230,10 @@
/**
* Returns an item selection listener.
*/
- public OnItemViewSelectedListener getOnItemViewSelectedListener() {
+ public BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
return mOnItemViewSelectedListener;
}
- /**
- * Enables scaling of rows.
- *
- * @param enable true to enable row scaling
- */
- public void enableRowScaling(boolean enable) {
- mRowScaleEnabled = enable;
- }
-
@Override
void onRowSelected(RecyclerView parent, RecyclerView.ViewHolder viewHolder,
int position, int subposition) {
@@ -231,6 +249,27 @@
setRowViewSelected(mSelectedViewHolder, true, false);
}
}
+ // When RowsSupportFragment is embedded inside a page fragment, we want to show
+ // the title view only when we're on the first row or there is no data.
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().showTitleView(position <= 0);
+ }
+ }
+
+ /**
+ * Get row ViewHolder at adapter position. Returns null if the row object is not in adapter or
+ * the row object has not been bound to a row view.
+ *
+ * @param position Position of row in adapter.
+ * @return Row ViewHolder at a given adapter position.
+ */
+ public RowPresenter.ViewHolder getRowViewHolder(int position) {
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView == null) {
+ return null;
+ }
+ return getRowViewHolder((ItemBridgeAdapter.ViewHolder)
+ verticalView.findViewHolderForAdapterPosition(position));
}
@Override
@@ -243,16 +282,6 @@
super.onCreate(savedInstanceState);
mSelectAnimatorDuration = getResources().getInteger(
R.integer.lb_browse_rows_anim_duration);
- mRowScaleFactor = getResources().getFraction(
- R.fraction.lb_browse_rows_scale, 1, 1);
- }
-
- @Override
- public View onCreateView(LayoutInflater inflater, ViewGroup container,
- Bundle savedInstanceState) {
- View view = super.onCreateView(inflater, container, savedInstanceState);
- mScaleFrameLayout = (ScaleFrameLayout) view.findViewById(R.id.scale_frame);
- return view;
}
@Override
@@ -264,8 +293,14 @@
getVerticalGridView().setItemAlignmentViewId(R.id.row_content);
getVerticalGridView().setSaveChildrenPolicy(VerticalGridView.SAVE_LIMITED_CHILD);
+ setAlignment(mAlignedTop);
+
mRecycledViewPool = null;
mPresenterMapper = null;
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+ }
+
}
@Override
@@ -274,35 +309,10 @@
super.onDestroyView();
}
- @Override
- void setItemAlignment() {
- super.setItemAlignment();
- if (getVerticalGridView() != null) {
- getVerticalGridView().setItemAlignmentOffsetWithPadding(true);
- }
- }
-
void setExternalAdapterListener(ItemBridgeAdapter.AdapterListener listener) {
mExternalAdapterListener = listener;
}
- /**
- * Returns the view that will change scale.
- */
- View getScaleView() {
- return getVerticalGridView();
- }
-
- /**
- * Sets the pivots to scale rows fragment.
- */
- void setScalePivots(float pivotX, float pivotY) {
- // set pivot on ScaleFrameLayout, it will be propagated to its child VerticalGridView
- // where we actually change scale.
- mScaleFrameLayout.setPivotX(pivotX);
- mScaleFrameLayout.setPivotY(pivotY);
- }
-
private static void setRowViewExpanded(ItemBridgeAdapter.ViewHolder vh, boolean expanded) {
((RowPresenter) vh.getPresenter()).setRowViewExpanded(vh.getViewHolder(), expanded);
}
@@ -322,6 +332,7 @@
mExternalAdapterListener.onAddPresenter(presenter, type);
}
}
+
@Override
public void onCreate(ItemBridgeAdapter.ViewHolder vh) {
VerticalGridView listView = getVerticalGridView();
@@ -340,6 +351,7 @@
mExternalAdapterListener.onCreate(vh);
}
}
+
@Override
public void onAttachedToWindow(ItemBridgeAdapter.ViewHolder vh) {
if (DEBUG) Log.v(TAG, "onAttachToWindow");
@@ -358,6 +370,7 @@
mExternalAdapterListener.onAttachedToWindow(vh);
}
}
+
@Override
public void onDetachedFromWindow(ItemBridgeAdapter.ViewHolder vh) {
if (mSelectedViewHolder == vh) {
@@ -368,12 +381,14 @@
mExternalAdapterListener.onDetachedFromWindow(vh);
}
}
+
@Override
public void onBind(ItemBridgeAdapter.ViewHolder vh) {
if (mExternalAdapterListener != null) {
mExternalAdapterListener.onBind(vh);
}
}
+
@Override
public void onUnbind(ItemBridgeAdapter.ViewHolder vh) {
setRowViewSelected(vh, false, true);
@@ -419,7 +434,7 @@
}
@Override
- boolean onTransitionPrepare() {
+ public boolean onTransitionPrepare() {
boolean prepared = super.onTransitionPrepare();
if (prepared) {
freezeRows(true);
@@ -427,92 +442,8 @@
return prepared;
}
- class ExpandPreLayout implements ViewTreeObserver.OnPreDrawListener {
-
- final View mVerticalView;
- final Runnable mCallback;
- int mState;
-
- final static int STATE_INIT = 0;
- final static int STATE_FIRST_DRAW = 1;
- final static int STATE_SECOND_DRAW = 2;
-
- ExpandPreLayout(Runnable callback) {
- mVerticalView = getVerticalGridView();
- mCallback = callback;
- }
-
- void execute() {
- mVerticalView.getViewTreeObserver().addOnPreDrawListener(this);
- setExpand(false);
- mState = STATE_INIT;
- }
-
- @Override
- public boolean onPreDraw() {
- if (getView() == null || getActivity() == null) {
- mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
- return true;
- }
- if (mState == STATE_INIT) {
- setExpand(true);
- mState = STATE_FIRST_DRAW;
- } else if (mState == STATE_FIRST_DRAW) {
- mCallback.run();
- mVerticalView.getViewTreeObserver().removeOnPreDrawListener(this);
- mState = STATE_SECOND_DRAW;
- }
- return false;
- }
- }
-
- void onExpandTransitionStart(boolean expand, final Runnable callback) {
- onTransitionPrepare();
- onTransitionStart();
- if (expand) {
- callback.run();
- return;
- }
- // Run a "pre" layout when we go non-expand, in order to get the initial
- // positions of added rows.
- new ExpandPreLayout(callback).execute();
- }
-
- private boolean needsScale() {
- return mRowScaleEnabled && !mExpand;
- }
-
- private void updateRowScaling() {
- final float scaleFactor = needsScale() ? mRowScaleFactor : 1f;
- mScaleFrameLayout.setLayoutScaleY(scaleFactor);
- getScaleView().setScaleY(scaleFactor);
- getScaleView().setScaleX(scaleFactor);
- updateWindowAlignOffset();
- }
-
- private void updateWindowAlignOffset() {
- int alignOffset = mAlignedTop;
- if (needsScale()) {
- alignOffset = (int) (alignOffset / mRowScaleFactor + 0.5f);
- }
- getVerticalGridView().setWindowAlignmentOffset(alignOffset);
- }
-
@Override
- void setWindowAlignmentFromTop(int alignedTop) {
- mAlignedTop = alignedTop;
- final VerticalGridView gridView = getVerticalGridView();
- if (gridView != null) {
- updateWindowAlignOffset();
- // align to a fixed position from top
- gridView.setWindowAlignmentOffsetPercent(
- VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
- gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- }
- }
-
- @Override
- void onTransitionEnd() {
+ public void onTransitionEnd() {
super.onTransitionEnd();
freezeRows(false);
}
@@ -523,7 +454,7 @@
final int count = verticalView.getChildCount();
for (int i = 0; i < count; i++) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
- verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
rowPresenter.freeze(vh, freeze);
@@ -535,18 +466,171 @@
* For rows that willing to participate entrance transition, this function
* hide views if afterTransition is true, show views if afterTransition is false.
*/
- void setEntranceTransitionState(boolean afterTransition) {
+ public void setEntranceTransitionState(boolean afterTransition) {
mAfterEntranceTransition = afterTransition;
VerticalGridView verticalView = getVerticalGridView();
if (verticalView != null) {
final int count = verticalView.getChildCount();
for (int i = 0; i < count; i++) {
ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder)
- verticalView.getChildViewHolder(verticalView.getChildAt(i));
+ verticalView.getChildViewHolder(verticalView.getChildAt(i));
RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
RowPresenter.ViewHolder vh = rowPresenter.getRowViewHolder(ibvh.getViewHolder());
rowPresenter.setEntranceTransitionState(vh, mAfterEntranceTransition);
}
}
}
+
+ /**
+ * Selects a Row and perform an optional task on the Row. For example
+ * <code>setSelectedPosition(10, true, new ListRowPresenterSelectItemViewHolderTask(5))</code>
+ * Scroll to 11th row and selects 6th item on that row. The method will be ignored if
+ * RowsSupportFragment has not been created (i.e. before {@link #onCreateView(LayoutInflater,
+ * ViewGroup, Bundle)}).
+ *
+ * @param rowPosition Which row to select.
+ * @param smooth True to scroll to the row, false for no animation.
+ * @param rowHolderTask Task to perform on the Row.
+ */
+ public void setSelectedPosition(int rowPosition, boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ VerticalGridView verticalView = getVerticalGridView();
+ if (verticalView == null) {
+ return;
+ }
+ ViewHolderTask task = null;
+ if (rowHolderTask != null) {
+ task = new ViewHolderTask() {
+ @Override
+ public void run(RecyclerView.ViewHolder rvh) {
+ rowHolderTask.run(getRowViewHolder((ItemBridgeAdapter.ViewHolder) rvh));
+ }
+ };
+ }
+ if (smooth) {
+ verticalView.setSelectedPositionSmooth(rowPosition, task);
+ } else {
+ verticalView.setSelectedPosition(rowPosition, task);
+ }
+ }
+
+ static RowPresenter.ViewHolder getRowViewHolder(ItemBridgeAdapter.ViewHolder ibvh) {
+ if (ibvh == null) {
+ return null;
+ }
+ RowPresenter rowPresenter = (RowPresenter) ibvh.getPresenter();
+ return rowPresenter.getRowViewHolder(ibvh.getViewHolder());
+ }
+
+ public boolean isScrolling() {
+ if (getVerticalGridView() == null) {
+ return false;
+ }
+ return getVerticalGridView().getScrollState() != HorizontalGridView.SCROLL_STATE_IDLE;
+ }
+
+ @Override
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ mAlignedTop = windowAlignOffsetFromTop;
+ final VerticalGridView gridView = getVerticalGridView();
+
+ if (gridView != null) {
+ gridView.setItemAlignmentOffset(0);
+ gridView.setItemAlignmentOffsetPercent(
+ VerticalGridView.ITEM_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setItemAlignmentOffsetWithPadding(true);
+ gridView.setWindowAlignmentOffset(mAlignedTop);
+ // align to a fixed position from top
+ gridView.setWindowAlignmentOffsetPercent(
+ VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
+ gridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ }
+ }
+
+ public static class MainFragmentAdapter extends BrowseSupportFragment.MainFragmentAdapter<RowsSupportFragment> {
+
+ public MainFragmentAdapter(RowsSupportFragment fragment) {
+ super(fragment);
+ setScalingEnabled(true);
+ }
+
+ @Override
+ public boolean isScrolling() {
+ return getFragment().isScrolling();
+ }
+
+ @Override
+ public void setExpand(boolean expand) {
+ getFragment().setExpand(expand);
+ }
+
+ @Override
+ public void setEntranceTransitionState(boolean state) {
+ getFragment().setEntranceTransitionState(state);
+ }
+
+ @Override
+ public void setAlignment(int windowAlignOffsetFromTop) {
+ getFragment().setAlignment(windowAlignOffsetFromTop);
+ }
+
+ @Override
+ public boolean onTransitionPrepare() {
+ return getFragment().onTransitionPrepare();
+ }
+
+ @Override
+ public void onTransitionStart() {
+ getFragment().onTransitionStart();
+ }
+
+ @Override
+ public void onTransitionEnd() {
+ getFragment().onTransitionEnd();
+ }
+
+ }
+
+ public static class MainFragmentRowsAdapter
+ extends BrowseSupportFragment.MainFragmentRowsAdapter<RowsSupportFragment> {
+
+ public MainFragmentRowsAdapter(RowsSupportFragment fragment) {
+ super(fragment);
+ }
+
+ @Override
+ public void setAdapter(ObjectAdapter adapter) {
+ getFragment().setAdapter(adapter);
+ }
+
+ /**
+ * Sets an item clicked listener on the fragment.
+ */
+ @Override
+ public void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ getFragment().setOnItemViewClickedListener(listener);
+ }
+
+ @Override
+ public void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ getFragment().setOnItemViewSelectedListener(listener);
+ }
+
+ @Override
+ public void setSelectedPosition(int rowPosition,
+ boolean smooth,
+ final Presenter.ViewHolderTask rowHolderTask) {
+ getFragment().setSelectedPosition(rowPosition, smooth, rowHolderTask);
+ }
+
+ @Override
+ public void setSelectedPosition(int rowPosition, boolean smooth) {
+ getFragment().setSelectedPosition(rowPosition, smooth);
+ }
+
+ @Override
+ public int getSelectedPosition() {
+ return getFragment().getSelectedPosition();
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
index d828963..ea9e138 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchFragment.java
@@ -13,34 +13,37 @@
*/
package android.support.v17.leanback.app;
+import android.Manifest;
import android.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
-import android.speech.SpeechRecognizer;
import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
+import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SearchBar;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Presenter.ViewHolder;
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.CompletionInfo;
import android.widget.FrameLayout;
-import android.support.v17.leanback.R;
import java.util.ArrayList;
import java.util.List;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
/**
* A fragment to handle searches. An application will supply an implementation
* of the {@link SearchResultProvider} interface to handle the search and return
@@ -50,8 +53,10 @@
*
* <p>If you do not supply a callback via
* {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to request
- * android.permission.RECORD_AUDIO.
+ * recognizer will be used for which your application will need to declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
+ * the device version is >= 23, a permission dialog will show first time using speech recognition.
+ * 0 will be used as requestCode in requestPermissions() call.
* </p>
* <p>
* Speech recognition is automatically started when fragment is created, but
@@ -73,6 +78,8 @@
private static final int RESULTS_CHANGED = 0x1;
private static final int QUERY_COMPLETE = 0x2;
+ private static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
+
/**
* Search API to be provided by the application.
*/
@@ -135,6 +142,7 @@
mRowsFragment.setSelectedPosition(0);
}
}
+ updateSearchBarVisiblity();
mStatus |= RESULTS_CHANGED;
if ((mStatus & QUERY_COMPLETE) != 0) {
updateFocus();
@@ -213,6 +221,26 @@
private int mStatus;
private boolean mAutoStartRecognition = true;
+ private boolean mIsPaused;
+ private boolean mPendingStartRecognitionWhenPaused;
+ private SearchBar.SearchBarPermissionListener mPermissionListener
+ = new SearchBar.SearchBarPermissionListener() {
+ public void requestAudioPermission() {
+ PermissionHelper.requestPermissions(SearchFragment.this,
+ new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
+ }
+ };
+
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
+ if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
+ && grantResults[0] == PERMISSION_GRANTED) {
+ startRecognition();
+ }
+ }
+ }
+
/**
* @param args Bundle to use for the arguments, if null a new Bundle will be created.
*/
@@ -285,6 +313,7 @@
}
});
mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+ mSearchBar.setPermissionListener(mPermissionListener);
applyExternalQuery();
readArguments(getArguments());
@@ -308,9 +337,11 @@
@Override
public void onItemSelected(ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
- int position = mRowsFragment.getVerticalGridView().getSelectedPosition();
- if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
- mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+ if (DEBUG) {
+ int position = mRowsFragment.getSelectedPosition();
+ Log.v(TAG, String.format("onItemSelected %d", position));
+ }
+ updateSearchBarVisiblity();
if (null != mOnItemViewSelectedListener) {
mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
rowViewHolder, row);
@@ -344,22 +375,32 @@
list.setWindowAlignmentOffset(mContainerListAlignTop);
list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ // VerticalGridView should not be focusable (see b/26894680 for details).
+ list.setFocusable(false);
+ list.setFocusableInTouchMode(false);
}
@Override
public void onResume() {
super.onResume();
+ mIsPaused = false;
if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
}
- // Ensure search bar state consistency when using external recognizer
- mSearchBar.stopRecognition();
+ if (mPendingStartRecognitionWhenPaused) {
+ mPendingStartRecognitionWhenPaused = false;
+ mSearchBar.startRecognition();
+ } else {
+ // Ensure search bar state consistency when using external recognizer
+ mSearchBar.stopRecognition();
+ }
}
@Override
public void onPause() {
releaseRecognizer();
+ mIsPaused = true;
super.onPause();
}
@@ -385,7 +426,11 @@
* when fragment is created.
*/
public void startRecognition() {
- mSearchBar.startRecognition();
+ if (mIsPaused) {
+ mPendingStartRecognitionWhenPaused = true;
+ } else {
+ mSearchBar.startRecognition();
+ }
}
/**
@@ -587,6 +632,12 @@
focusOnResults();
}
+ private void updateSearchBarVisiblity() {
+ int position = mRowsFragment != null ? mRowsFragment.getSelectedPosition() : -1;
+ mSearchBar.setVisibility(position <=0 || mResultAdapter == null
+ || mResultAdapter.size() == 0 ? View.VISIBLE : View.GONE);
+ }
+
private void updateSearchBarNextFocusId() {
if (mSearchBar == null || mResultAdapter == null) {
return;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
index 7ff364e..0368339 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/SearchSupportFragment.java
@@ -15,34 +15,37 @@
*/
package android.support.v17.leanback.app;
+import android.Manifest;
import android.support.v4.app.Fragment;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
-import android.speech.SpeechRecognizer;
import android.speech.RecognizerIntent;
+import android.speech.SpeechRecognizer;
+import android.support.v17.leanback.R;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.support.v17.leanback.widget.ObjectAdapter.DataObserver;
import android.support.v17.leanback.widget.OnItemViewClickedListener;
import android.support.v17.leanback.widget.OnItemViewSelectedListener;
+import android.support.v17.leanback.widget.Presenter.ViewHolder;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
import android.support.v17.leanback.widget.SearchBar;
-import android.support.v17.leanback.widget.VerticalGridView;
-import android.support.v17.leanback.widget.Presenter.ViewHolder;
import android.support.v17.leanback.widget.SpeechRecognitionCallback;
+import android.support.v17.leanback.widget.VerticalGridView;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.CompletionInfo;
import android.widget.FrameLayout;
-import android.support.v17.leanback.R;
import java.util.ArrayList;
import java.util.List;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+
/**
* A fragment to handle searches. An application will supply an implementation
* of the {@link SearchResultProvider} interface to handle the search and return
@@ -52,8 +55,10 @@
*
* <p>If you do not supply a callback via
* {@link #setSpeechRecognitionCallback(SpeechRecognitionCallback)}, an internal speech
- * recognizer will be used for which your application will need to request
- * android.permission.RECORD_AUDIO.
+ * recognizer will be used for which your application will need to declare
+ * android.permission.RECORD_AUDIO in AndroidManifest file. If app's target version is >= 23 and
+ * the device version is >= 23, a permission dialog will show first time using speech recognition.
+ * 0 will be used as requestCode in requestPermissions() call.
* </p>
* <p>
* Speech recognition is automatically started when fragment is created, but
@@ -75,6 +80,8 @@
private static final int RESULTS_CHANGED = 0x1;
private static final int QUERY_COMPLETE = 0x2;
+ private static final int AUDIO_PERMISSION_REQUEST_CODE = 0;
+
/**
* Search API to be provided by the application.
*/
@@ -137,6 +144,7 @@
mRowsSupportFragment.setSelectedPosition(0);
}
}
+ updateSearchBarVisiblity();
mStatus |= RESULTS_CHANGED;
if ((mStatus & QUERY_COMPLETE) != 0) {
updateFocus();
@@ -215,6 +223,26 @@
private int mStatus;
private boolean mAutoStartRecognition = true;
+ private boolean mIsPaused;
+ private boolean mPendingStartRecognitionWhenPaused;
+ private SearchBar.SearchBarPermissionListener mPermissionListener
+ = new SearchBar.SearchBarPermissionListener() {
+ public void requestAudioPermission() {
+ PermissionHelper.requestPermissions(SearchSupportFragment.this,
+ new String[]{Manifest.permission.RECORD_AUDIO}, AUDIO_PERMISSION_REQUEST_CODE);
+ }
+ };
+
+ public void onRequestPermissionsResult(int requestCode, String[] permissions,
+ int[] grantResults) {
+ if (requestCode == AUDIO_PERMISSION_REQUEST_CODE && permissions.length > 0) {
+ if (permissions[0].equals(Manifest.permission.RECORD_AUDIO)
+ && grantResults[0] == PERMISSION_GRANTED) {
+ startRecognition();
+ }
+ }
+ }
+
/**
* @param args Bundle to use for the arguments, if null a new Bundle will be created.
*/
@@ -287,6 +315,7 @@
}
});
mSearchBar.setSpeechRecognitionCallback(mSpeechRecognitionCallback);
+ mSearchBar.setPermissionListener(mPermissionListener);
applyExternalQuery();
readArguments(getArguments());
@@ -310,9 +339,11 @@
@Override
public void onItemSelected(ViewHolder itemViewHolder, Object item,
RowPresenter.ViewHolder rowViewHolder, Row row) {
- int position = mRowsSupportFragment.getVerticalGridView().getSelectedPosition();
- if (DEBUG) Log.v(TAG, String.format("onItemSelected %d", position));
- mSearchBar.setVisibility(0 >= position ? View.VISIBLE : View.GONE);
+ if (DEBUG) {
+ int position = mRowsSupportFragment.getSelectedPosition();
+ Log.v(TAG, String.format("onItemSelected %d", position));
+ }
+ updateSearchBarVisiblity();
if (null != mOnItemViewSelectedListener) {
mOnItemViewSelectedListener.onItemSelected(itemViewHolder, item,
rowViewHolder, row);
@@ -346,22 +377,32 @@
list.setWindowAlignmentOffset(mContainerListAlignTop);
list.setWindowAlignmentOffsetPercent(VerticalGridView.WINDOW_ALIGN_OFFSET_PERCENT_DISABLED);
list.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ // VerticalGridView should not be focusable (see b/26894680 for details).
+ list.setFocusable(false);
+ list.setFocusableInTouchMode(false);
}
@Override
public void onResume() {
super.onResume();
+ mIsPaused = false;
if (mSpeechRecognitionCallback == null && null == mSpeechRecognizer) {
mSpeechRecognizer = SpeechRecognizer.createSpeechRecognizer(getActivity());
mSearchBar.setSpeechRecognizer(mSpeechRecognizer);
}
- // Ensure search bar state consistency when using external recognizer
- mSearchBar.stopRecognition();
+ if (mPendingStartRecognitionWhenPaused) {
+ mPendingStartRecognitionWhenPaused = false;
+ mSearchBar.startRecognition();
+ } else {
+ // Ensure search bar state consistency when using external recognizer
+ mSearchBar.stopRecognition();
+ }
}
@Override
public void onPause() {
releaseRecognizer();
+ mIsPaused = true;
super.onPause();
}
@@ -387,7 +428,11 @@
* when fragment is created.
*/
public void startRecognition() {
- mSearchBar.startRecognition();
+ if (mIsPaused) {
+ mPendingStartRecognitionWhenPaused = true;
+ } else {
+ mSearchBar.startRecognition();
+ }
}
/**
@@ -589,6 +634,12 @@
focusOnResults();
}
+ private void updateSearchBarVisiblity() {
+ int position = mRowsSupportFragment != null ? mRowsSupportFragment.getSelectedPosition() : -1;
+ mSearchBar.setVisibility(position <=0 || mResultAdapter == null
+ || mResultAdapter.size() == 0 ? View.VISIBLE : View.GONE);
+ }
+
private void updateSearchBarNextFocusId() {
if (mSearchBar == null || mResultAdapter == null) {
return;
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
index 3e51989..c02fd69 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridFragment.java
@@ -13,7 +13,6 @@
*/
package android.support.v17.leanback.app;
-import android.support.annotation.ColorInt;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BrowseFrameLayout;
@@ -23,8 +22,6 @@
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
import android.support.v17.leanback.widget.VerticalGridPresenter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.os.Bundle;
@@ -160,7 +157,9 @@
Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
container, false);
- setTitleView((TitleView) root.findViewById(R.id.browse_title_group));
+ ViewGroup gridFrame = (ViewGroup) root.findViewById(R.id.grid_frame);
+ installTitleView(inflater, gridFrame, savedInstanceState);
+ getProgressBarManager().setRootView(root);
return root;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
index eb0b337..3bf5e12 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/VerticalGridSupportFragment.java
@@ -15,7 +15,6 @@
*/
package android.support.v17.leanback.app;
-import android.support.annotation.ColorInt;
import android.support.v17.leanback.R;
import android.support.v17.leanback.transition.TransitionHelper;
import android.support.v17.leanback.widget.BrowseFrameLayout;
@@ -25,8 +24,6 @@
import android.support.v17.leanback.widget.Presenter;
import android.support.v17.leanback.widget.Row;
import android.support.v17.leanback.widget.RowPresenter;
-import android.support.v17.leanback.widget.TitleHelper;
-import android.support.v17.leanback.widget.TitleView;
import android.support.v17.leanback.widget.VerticalGridPresenter;
import android.support.v17.leanback.widget.ObjectAdapter;
import android.os.Bundle;
@@ -162,7 +159,9 @@
Bundle savedInstanceState) {
ViewGroup root = (ViewGroup) inflater.inflate(R.layout.lb_vertical_grid_fragment,
container, false);
- setTitleView((TitleView) root.findViewById(R.id.browse_title_group));
+ ViewGroup gridFrame = (ViewGroup) root.findViewById(R.id.grid_frame);
+ installTitleView(inflater, gridFrame, savedInstanceState);
+ getProgressBarManager().setRootView(root);
return root;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
index 9420154..198c914 100644
--- a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java
@@ -99,10 +99,14 @@
public Object createFadeTransition(int fadingMode);
+ public Object createChangeTransform();
+
public Object createChangeBounds(boolean reparent);
public Object createFadeAndShortSlide(int edge);
+ public Object createFadeAndShortSlide(int edge, float distance);
+
public void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay);
public void setChangeBoundsStartDelay(Object changeBounds, int viewId, int startDelay);
@@ -146,6 +150,8 @@
public Object loadTransition(Context context, int resId);
+ public void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject);
+
public void setTransitionGroup(ViewGroup viewGroup, boolean transitionGroup);
}
@@ -233,11 +239,21 @@
}
@Override
+ public Object createChangeTransform() {
+ return new TransitionStub();
+ }
+
+ @Override
public Object createFadeAndShortSlide(int edge) {
return new TransitionStub();
}
@Override
+ public Object createFadeAndShortSlide(int edge, float distance) {
+ return new TransitionStub();
+ }
+
+ @Override
public Object createSlide(int slideEdge) {
return new TransitionStub();
}
@@ -360,6 +376,10 @@
}
@Override
+ public void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) {
+ }
+
+ @Override
public void setTransitionGroup(ViewGroup viewGroup, boolean transitionGroup) {
}
}
@@ -552,6 +572,16 @@
}
@Override
+ public Object createFadeAndShortSlide(int edge, float distance) {
+ return TransitionHelperApi21.createFadeAndShortSlide(edge, distance);
+ }
+
+ @Override
+ public void beginDelayedTransition(ViewGroup sceneRoot, Object transition) {
+ TransitionHelperApi21.beginDelayedTransition(sceneRoot, transition);
+ }
+
+ @Override
public Object getEnterTransition(Window window) {
return TransitionHelperApi21.getEnterTransition(window);
}
@@ -585,6 +615,12 @@
public void setTransitionGroup(ViewGroup viewGroup, boolean transitionGroup) {
TransitionHelperApi21.setTransitionGroup(viewGroup, transitionGroup);
}
+
+ @Override
+ public Object createChangeTransform() {
+ return TransitionHelperApi21.createChangeTransform();
+ }
+
}
static {
@@ -637,6 +673,10 @@
return sImpl.createChangeBounds(reparent);
}
+ public static Object createChangeTransform() {
+ return sImpl.createChangeTransform();
+ }
+
public static void setChangeBoundsStartDelay(Object changeBounds, View view, int startDelay) {
sImpl.setChangeBoundsStartDelay(changeBounds, view, startDelay);
}
@@ -780,7 +820,31 @@
return sImpl.createFadeAndShortSlide(edge);
}
+ public static Object createFadeAndShortSlide(int edge, float distance) {
+ return sImpl.createFadeAndShortSlide(edge, distance);
+ }
+
+ public static void beginDelayedTransition(ViewGroup sceneRoot, Object transitionObject) {
+ sImpl.beginDelayedTransition(sceneRoot, transitionObject);
+ }
+
public static void setTransitionGroup(ViewGroup viewGroup, boolean transitionGroup) {
sImpl.setTransitionGroup(viewGroup, transitionGroup);
}
+
+ /**
+ * @deprecated Use static calls.
+ */
+ @Deprecated
+ public static TransitionHelper getInstance() {
+ return new TransitionHelper();
+ }
+
+ /**
+ * @deprecated Use {@link #addTransitionListener(Object, TransitionListener)}
+ */
+ @Deprecated
+ public static void setTransitionListener(Object transition, TransitionListener listener) {
+ sImpl.addTransitionListener(transition, listener);
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
new file mode 100644
index 0000000..4155af6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/util/StateMachine.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+/**
+ * Linear or DAG of {@link State}s. StateMachine is by default a linear model, until
+ * {@link #addState(State, State)} is called. Each State has three status:
+ * STATUS_ZERO, STATUS_INVOKED, STATUS_EXECUTED. We allow client to run a State, which will
+ * put State in STATUS_INVOKED. A State will be executed when prior States are executed and
+ * Precondition for this State is true, then the State will be marked as STATUS_EXECUTED.
+ *
+ * @hide
+ */
+public final class StateMachine {
+
+ /**
+ * No request on the State
+ */
+ public static final int STATUS_ZERO = 0;
+ /**
+ * Somebody wants to run the state but not yet executed because either the condition is
+ * false or lower States are not executed.
+ */
+ public static final int STATUS_INVOKED = 1;
+ /**
+ * Somebody wants to run the State and the State was executed.
+ */
+ public static final int STATUS_EXECUTED = 2;
+
+ public static class State {
+
+ private int mStatus;
+ private ArrayList<State> mPriorStates;
+
+ /**
+ * Run State, Subclass may override.
+ */
+ public void run() {
+ }
+
+ /**
+ * Returns true if State can run, false otherwise. Subclass may override.
+ * @return True if State can run, false otherwise. Subclass may override.
+ */
+ public boolean canRun() {
+ return true;
+ }
+
+ /**
+ * @return True if the State has been executed.
+ */
+ final boolean runIfNeeded() {
+ if (mStatus!= STATUS_EXECUTED) {
+ if (mStatus == STATUS_INVOKED && canRun()) {
+ run();
+ mStatus = STATUS_EXECUTED;
+ } else {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ void addPriorState(State state) {
+ if (mPriorStates == null) {
+ mPriorStates = new ArrayList<State>();
+ }
+ if (!mPriorStates.contains(state)) {
+ mPriorStates.add(state);
+ }
+ }
+
+ final void markInvoked() {
+ if (mStatus == STATUS_ZERO) {
+ mStatus = STATUS_INVOKED;
+ }
+ }
+
+ final void updateStatus(int status) {
+ mStatus = status;
+ }
+
+ /**
+ * Get status, return one of {@link #STATUS_ZERO}, {@link #STATUS_INVOKED},
+ * {@link #STATUS_EXECUTED}.
+ * @return Status of the State.
+ */
+ public final int getStatus() {
+ return mStatus;
+ }
+
+ @Override
+ public final boolean equals(Object other) {
+ return this == other;
+ }
+ }
+
+ private boolean mSorted = true;
+ private final ArrayList<State> mSortedList = new ArrayList<State>();
+
+ /**
+ * Add a State to StateMachine, ignore if it is already added.
+ * @param state The state to add.
+ */
+ public void addState(State state) {
+ if (!mSortedList.contains(state)) {
+ state.updateStatus(STATUS_ZERO);
+ mSortedList.add(state);
+ }
+ }
+
+ /**
+ * Add two States to StateMachine and create an edge between this two.
+ * StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
+ * sort() is required to sort the Direct acyclic graph.
+ * @param fromState The from state to add.
+ * @param toState The to state to add.
+ */
+ public void addState(State fromState, State toState) {
+ addState(fromState);
+ addState(toState);
+ toState.addPriorState(fromState);
+ mSorted = false;
+ }
+
+ void verifySorted() {
+ if (!mSorted) {
+ throw new RuntimeException("Graph not sorted");
+ }
+ }
+
+ public void runState(State state) {
+ verifySorted();
+ state.markInvoked();
+ runPendingStates();
+ }
+
+ public void runPendingStates() {
+ verifySorted();
+ for (int i = 0, size = mSortedList.size(); i < size; i++) {
+ if (!mSortedList.get(i).runIfNeeded()) {
+ break;
+ }
+ }
+ }
+
+ public void resetStatus() {
+ for (int i = 0, size = mSortedList.size(); i < size; i++) {
+ mSortedList.get(i).updateStatus(STATUS_ZERO);
+ }
+ }
+
+ /**
+ * StateMachine is by default a linear model, until {@link #addState(State, State)} is called.
+ * sort() is required to sort the Direct acyclic graph.
+ */
+ public void sort() {
+ if (mSorted) {
+ return;
+ }
+ // L: Empty list that will contain the sorted States
+ ArrayList<State> L = new ArrayList<State>();
+ // S: Set of all nodes with no incoming edges
+ ArrayList<State> S = new ArrayList<State>();
+ HashMap<State, ArrayList<State>> edges = new HashMap<State, ArrayList<State>>();
+ for (int i = mSortedList.size() - 1; i >= 0 ; i--) {
+ State state = mSortedList.get(i);
+ if (state.mPriorStates != null && state.mPriorStates.size() > 0) {
+ edges.put(state, new ArrayList<State>(state.mPriorStates));
+ } else {
+ S.add(state);
+ }
+ }
+
+ while (!S.isEmpty()) {
+ // remove a State without incoming Node from S, add to L
+ State state = S.remove(S.size() - 1);
+ L.add(state);
+ // for each toState that having an incoming edge from "state":
+ for (Iterator<Map.Entry<State, ArrayList<State>>> iterator =
+ edges.entrySet().iterator(); iterator.hasNext();) {
+ Map.Entry<State, ArrayList<State>> entry = iterator.next();
+ ArrayList<State> fromStates = entry.getValue();
+ // remove edge from graph
+ if (fromStates.remove(state)) {
+ if (fromStates.size() == 0) {
+ State toState = entry.getKey();
+ // insert the toState to S if it has no more incoming edges
+ S.add(toState);
+ iterator.remove();
+ }
+ }
+ }
+ }
+ if (edges.size() > 0) {
+ throw new RuntimeException("Cycle in Graph");
+ }
+
+ mSortedList.clear();
+ mSortedList.addAll(L);
+ mSorted = true;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
new file mode 100644
index 0000000..db88ba9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.java
@@ -0,0 +1,511 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+
+import android.animation.ValueAnimator;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.support.v17.leanback.R;
+import android.support.v4.view.ViewCompat;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.DecelerateInterpolator;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Abstract {@link Presenter} class for rendering media items in a playlist format.
+ * Media item data provided for this presenter can implement the interface
+ * {@link MultiActionsProvider}, if the media rows wish to contain custom actions.
+ * Media items in the playlist are arranged as a vertical list with each row holding each media
+ * item's details provided by the user of this class and a set of optional custom actions.
+ * Each media item's details and actions are separately focusable.
+ * The appearance of each one of the media row components can be controlled through setting
+ * theme's attributes.
+ * The presenter can optionally provide line separators between media rows by setting
+ * {@link #setHasMediaRowSeparator(boolean)} to true.
+ * <p>
+ * Subclasses must override {@link #onBindMediaDetails} to implement their media item model
+ * data binding to each row view.
+ * </p>
+ * <p>
+ * The {@link OnItemViewClickedListener} and {@link OnItemViewSelectedListener}
+ * can be used in the same fashion to handle selection or click events on either of
+ * media details or each individual action views.
+ * </p>
+ * <p>
+ * {@link AbstractMediaListHeaderPresenter} can be used in conjunction with this presenter in
+ * order to display a playlist with a header view.
+ * </p>
+ */
+public abstract class AbstractMediaItemPresenter extends RowPresenter {
+
+ final static Rect sTempRect = new Rect();
+ private int mBackgroundColor = Color.TRANSPARENT;
+ private boolean mBackgroundColorSet;
+ private boolean mMediaRowSeparator;
+ private int mThemeId;
+
+ private Presenter mMediaItemActionPresenter = new MediaItemActionPresenter();
+
+ /**
+ * Constructor used for creating an abstract media item presenter.
+ */
+ public AbstractMediaItemPresenter() {
+ this(0);
+ }
+
+ /**
+ * Constructor used for creating an abstract media item presenter.
+ * @param themeId The resource id of the theme that defines attributes controlling the
+ * appearance of different widgets in a media item row.
+ */
+ public AbstractMediaItemPresenter(int themeId) {
+ mThemeId = themeId;
+ setHeaderPresenter(null);
+ }
+
+ /**
+ * Sets the theme used to style a media item row components.
+ * @param themeId The resource id of the theme that defines attributes controlling the
+ * appearance of different widgets in a media item row.
+ */
+ public void setThemeId(int themeId) {
+ mThemeId = themeId;
+ }
+
+ /**
+ * Return The resource id of the theme that defines attributes controlling the appearance of
+ * different widgets in a media item row.
+ *
+ * @return The resource id of the theme that defines attributes controlling the appearance of
+ * different widgets in a media item row.
+ */
+ public int getThemeId() {
+ return mThemeId;
+ }
+
+ /**
+ * Sets the action presenter rendering each optional custom action within each media item row.
+ * @param actionPresenter the presenter to be used for rendering a media item row actions.
+ */
+ public void setActionPresenter(Presenter actionPresenter) {
+ mMediaItemActionPresenter = actionPresenter;
+ }
+
+ /**
+ * Return the presenter used to render a media item row actions.
+ *
+ * @return the presenter used to render a media item row actions.
+ */
+ public Presenter getActionPresenter() {
+ return mMediaItemActionPresenter;
+ }
+
+ /**
+ * The ViewHolder for the {@link AbstractMediaItemPresenter}. It references different views
+ * that place different meta-data corresponding to a media item details, actions, selector,
+ * listeners, and presenters,
+ */
+ public static class ViewHolder extends RowPresenter.ViewHolder {
+
+ private final View mMediaRowView;
+ private final View mSelectorView;
+ private final View mMediaItemDetailsView;
+ private final TextView mMediaItemNumberView;
+ private final TextView mMediaItemNameView;
+ private final TextView mMediaItemDurationView;
+ private final View mMediaItemRowSeparator;
+ private final ViewGroup mMediaItemActionsContainer;
+ private final List<Presenter.ViewHolder> mActionViewHolders;
+ private MultiActionsProvider.MultiAction[] mMediaItemRowActions;
+ AbstractMediaItemPresenter mRowPresenter;
+ private ValueAnimator mFocusViewAnimator;
+
+ public ViewHolder(View view) {
+ super(view);
+ mSelectorView = view.findViewById(R.id.mediaRowSelector);
+ mMediaRowView = view.findViewById(R.id.mediaItemRow);
+ mMediaItemDetailsView = view.findViewById(R.id.mediaItemDetails);
+ mMediaItemNumberView = (TextView) view.findViewById(R.id.mediaItemNumber);
+ mMediaItemNameView = (TextView) view.findViewById(R.id.mediaItemName);
+ mMediaItemDurationView = (TextView) view.findViewById(R.id.mediaItemDuration);
+ mMediaItemRowSeparator = view.findViewById(R.id.mediaRowSeparator);
+ mMediaItemActionsContainer = (ViewGroup) view.findViewById(
+ R.id.mediaItemActionsContainer);
+ mActionViewHolders = new ArrayList<Presenter.ViewHolder>();
+ getMediaItemDetailsView().setOnClickListener(new View.OnClickListener(){
+ @Override
+ public void onClick(View view) {
+ if (getOnItemViewClickedListener() != null) {
+ getOnItemViewClickedListener().onItemClicked(null, null,
+ ViewHolder.this, getRowObject());
+ }
+ }
+ });
+ getMediaItemDetailsView().setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ mFocusViewAnimator = updateSelector(mSelectorView, view, mFocusViewAnimator,
+ true);
+ }
+ });
+
+ }
+
+ /**
+ * Binds the actions in a media item row object to their views. This consists of creating
+ * (or reusing the existing) action view holders, and populating them with the actions'
+ * icons.
+ */
+ public void onBindRowActions() {
+ for (int i = getMediaItemActionsContainer().getChildCount() - 1;
+ i >= mActionViewHolders.size(); i--) {
+ getMediaItemActionsContainer().removeViewAt(i);
+ mActionViewHolders.remove(i);
+ }
+ mMediaItemRowActions = null;
+
+ Object rowObject = getRowObject();
+ final MultiActionsProvider.MultiAction[] actionList;
+ if (rowObject instanceof MultiActionsProvider) {
+ actionList = ((MultiActionsProvider) rowObject).getActions();
+ } else {
+ return;
+ }
+ Presenter actionPresenter = mRowPresenter.getActionPresenter();
+ if (actionPresenter == null) {
+ return;
+ }
+
+ mMediaItemRowActions = actionList;
+ for (int i = mActionViewHolders.size(); i < actionList.length; i++) {
+ final int actionIndex = i;
+ final Presenter.ViewHolder actionViewHolder = actionPresenter.
+ onCreateViewHolder(getMediaItemActionsContainer());
+ getMediaItemActionsContainer().addView(actionViewHolder.view);
+ mActionViewHolders.add(actionViewHolder);
+ actionViewHolder.view.setOnFocusChangeListener(new View.OnFocusChangeListener() {
+ @Override
+ public void onFocusChange(View view, boolean hasFocus) {
+ mFocusViewAnimator = updateSelector(mSelectorView, view,
+ mFocusViewAnimator, false);
+ }
+ });
+ actionViewHolder.view.setOnClickListener(
+ new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ if (getOnItemViewClickedListener() != null) {
+ getOnItemViewClickedListener().onItemClicked(
+ actionViewHolder, mMediaItemRowActions[actionIndex],
+ ViewHolder.this, getRowObject());
+ }
+ }
+ });
+ }
+
+ if (mMediaItemActionsContainer != null) {
+ for (int i = 0; i < actionList.length; i++) {
+ Presenter.ViewHolder avh = mActionViewHolders.get(i);
+ actionPresenter.onUnbindViewHolder(avh);
+ actionPresenter.onBindViewHolder(avh, mMediaItemRowActions[i]);
+ }
+ }
+
+ }
+
+ int findActionIndex(MultiActionsProvider.MultiAction action) {
+ if (mMediaItemRowActions != null) {
+ for (int i = 0; i < mMediaItemRowActions.length; i++) {
+ if (mMediaItemRowActions[i] == action) {
+ return i;
+ }
+ }
+ }
+ return -1;
+ }
+
+ /**
+ * Notifies an action has changed in this media row and the UI needs to be updated
+ * @param action The action whose state has changed
+ */
+ public void notifyActionChanged(MultiActionsProvider.MultiAction action) {
+ Presenter actionPresenter = mRowPresenter.getActionPresenter();
+ if (actionPresenter == null) {
+ return;
+ }
+ int actionIndex = findActionIndex(action);
+ if (actionIndex >= 0) {
+ Presenter.ViewHolder actionViewHolder = mActionViewHolders.get(actionIndex);
+ actionPresenter.onUnbindViewHolder(actionViewHolder);
+ actionPresenter.onBindViewHolder(actionViewHolder, action);
+ }
+ }
+
+ /**
+ * Notifies the content of the media item details in a row has changed and triggers updating
+ * the UI. This causes {@link #onBindMediaDetails(ViewHolder, Object)}
+ * on the user's provided presenter to be called back, allowing them to update UI
+ * accordingly.
+ */
+ public void notifyDetailsChanged() {
+ mRowPresenter.onUnbindMediaDetails(this);
+ mRowPresenter.onBindMediaDetails(this, getRowObject());
+ }
+
+
+ /**
+ * @return The SelectorView responsible for highlighting the in-focus view within each
+ * media item row
+ */
+ public View getSelectorView() {
+ return mSelectorView;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track number
+ */
+ public TextView getMediaItemNumberView() {
+ return mMediaItemNumberView;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track name
+ */
+ public TextView getMediaItemNameView() {
+ return mMediaItemNameView;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track duration
+ */
+ public TextView getMediaItemDurationView() {
+ return mMediaItemDurationView;
+ }
+
+ /**
+ * @return The view container of track details
+ */
+ public View getMediaItemDetailsView() {
+ return mMediaItemDetailsView;
+ }
+
+ /**
+ * @return The view responsible for rendering the separator line between media rows
+ */
+ public View getMediaItemRowSeparator() {
+ return mMediaItemRowSeparator;
+ }
+
+ /**
+ * @return The view containing the set of custom actions
+ */
+ public ViewGroup getMediaItemActionsContainer() {
+ return mMediaItemActionsContainer;
+ }
+
+ public MultiActionsProvider.MultiAction[] getMediaItemRowActions() {
+ return mMediaItemRowActions;
+ }
+ }
+
+ @Override
+ protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+ Context context = parent.getContext();
+ if (mThemeId != 0) {
+ context = new ContextThemeWrapper(context, mThemeId);
+ }
+ View view = LayoutInflater.from(context).
+ inflate(R.layout.lb_row_media_item, parent, false);
+ final ViewHolder vh = new ViewHolder(view);
+ vh.mRowPresenter = this;
+ if (mBackgroundColorSet) {
+ vh.mMediaRowView.setBackgroundColor(mBackgroundColor);
+ }
+ return vh;
+ }
+
+ @Override
+ public boolean isUsingDefaultSelectEffect() {
+ return false;
+ }
+
+ @Override
+ protected boolean isClippingChildren() {
+ return true;
+ }
+
+ @Override
+ protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+ super.onBindRowViewHolder(vh, item);
+
+ final ViewHolder mvh = (ViewHolder) vh;
+
+ onBindRowActions(mvh);
+
+ mvh.getMediaItemRowSeparator().setVisibility(hasMediaRowSeparator() ? View.VISIBLE :
+ View.GONE);
+
+ onBindMediaDetails((ViewHolder) vh, item);
+ }
+
+ /**
+ * Binds the given media item object action to the given ViewHolder's action views.
+ * @param vh ViewHolder for the media item.
+ */
+ protected void onBindRowActions(ViewHolder vh) {
+ vh.onBindRowActions();
+ }
+
+ /**
+ * Sets the background color for the row views within the playlist.
+ * If this is not set, a default color, defaultBrandColor, from theme is used.
+ * This defaultBrandColor defaults to android:attr/colorPrimary on v21, if it's specified.
+ * @param color The ARGB color used to set as the media list background color.
+ */
+ public void setBackgroundColor(int color) {
+ mBackgroundColorSet = true;
+ mBackgroundColor = color;
+ }
+
+ /**
+ * Specifies whether a line separator should be used between media item rows.
+ * @param hasSeparator true if a separator should be displayed, false otherwise.
+ */
+ public void setHasMediaRowSeparator(boolean hasSeparator) {
+ mMediaRowSeparator = hasSeparator;
+ }
+
+ public boolean hasMediaRowSeparator() {
+ return mMediaRowSeparator;
+ }
+ /**
+ * Binds the media item details to their views provided by the
+ * {@link AbstractMediaItemPresenter}.
+ * This method is to be overridden by the users of this presenter.
+ * The subclasses of this presenter can access and bind individual views for either of the
+ * media item number, name, or duration (depending on whichever views are visible according to
+ * the providing theme attributes), by calling {@link ViewHolder#getMediaItemNumberView()},
+ * {@link ViewHolder#getMediaItemNameView()}, and {@link ViewHolder#getMediaItemDurationView()},
+ * on the {@link ViewHolder} provided as the argument {@code vh} of this presenter.
+ *
+ * @param vh The ViewHolder for this {@link AbstractMediaItemPresenter}.
+ * @param item The media item row object being presented.
+ */
+ protected abstract void onBindMediaDetails(ViewHolder vh, Object item);
+
+ /**
+ * Unbinds the media item details from their views provided by the
+ * {@link AbstractMediaItemPresenter}.
+ * This method can be overridden by the subclasses of this presenter if required.
+ * @param vh ViewHolder to unbind from.
+ */
+ protected void onUnbindMediaDetails(ViewHolder vh) {
+ }
+
+ /**
+ * Each media item row can have multiple focusable elements; the details on the left and a set
+ * of optional custom actions on the right.
+ * The selector is a highlight that moves to highlight to cover whichever views is in focus.
+ *
+ * @param selectorView the selector view used to highlight an individual element within a row.
+ * @param focusChangedView The component within the media row whose focus got changed.
+ * @param layoutAnimator the ValueAnimator producing animation frames for the selector's width
+ * and x-translation, generated by this method and stored for the each
+ * {@link ViewHolder}.
+ * @param isDetails Whether the changed-focused view is for a media item details (true) or
+ * an action (false).
+ */
+ private static ValueAnimator updateSelector(final View selectorView,
+ View focusChangedView, ValueAnimator layoutAnimator, boolean isDetails) {
+ int animationDuration = focusChangedView.getContext().getResources()
+ .getInteger(android.R.integer.config_shortAnimTime);
+ DecelerateInterpolator interpolator = new DecelerateInterpolator();
+
+ int layoutDirection = ViewCompat.getLayoutDirection(selectorView);
+ if (!focusChangedView.hasFocus()) {
+ // if neither of the details or action views are in focus (ie. another row is in focus),
+ // animate the selector out.
+ selectorView.animate().cancel();
+ selectorView.animate().alpha(0f).setDuration(animationDuration)
+ .setInterpolator(interpolator).start();
+ // keep existing layout animator
+ return layoutAnimator;
+ } else {
+ // cancel existing layout animator
+ if (layoutAnimator != null) {
+ layoutAnimator.cancel();
+ layoutAnimator = null;
+ }
+ float currentAlpha = selectorView.getAlpha();
+ selectorView.animate().alpha(1f).setDuration(animationDuration)
+ .setInterpolator(interpolator).start();
+
+ final ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
+ selectorView.getLayoutParams();
+ ViewGroup rootView = (ViewGroup) selectorView.getParent();
+ sTempRect.set(0, 0, focusChangedView.getWidth(), focusChangedView.getHeight());
+ rootView.offsetDescendantRectToMyCoords(focusChangedView, sTempRect);
+ if (isDetails) {
+ if (layoutDirection == View.LAYOUT_DIRECTION_RTL ) {
+ sTempRect.right += rootView.getHeight();
+ sTempRect.left -= rootView.getHeight() / 2;
+ } else {
+ sTempRect.left -= rootView.getHeight();
+ sTempRect.right += rootView.getHeight() / 2;
+ }
+ }
+ final int targetLeft = sTempRect.left;
+ final int targetWidth = sTempRect.width();
+ final float deltaWidth = lp.width - targetWidth;
+ final float deltaLeft = lp.leftMargin - targetLeft;
+
+ if (deltaLeft == 0f && deltaWidth == 0f)
+ {
+ // no change needed
+ } else if (currentAlpha == 0f) {
+ // change selector to the proper width and marginLeft without animation.
+ lp.width = targetWidth;
+ lp.leftMargin = targetLeft;
+ selectorView.requestLayout();
+ } else {
+ // animate the selector to the proper width and marginLeft.
+ layoutAnimator = ValueAnimator.ofFloat(0f, 1f);
+ layoutAnimator.setDuration(animationDuration);
+ layoutAnimator.setInterpolator(interpolator);
+
+ layoutAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
+ @Override
+ public void onAnimationUpdate(ValueAnimator valueAnimator) {
+ // Set width to the proper width for this animation step.
+ float fractionToEnd = 1f - valueAnimator.getAnimatedFraction();
+ lp.leftMargin = Math.round(targetLeft + deltaLeft * fractionToEnd);
+ lp.width = Math.round(targetWidth + deltaWidth * fractionToEnd);
+ selectorView.requestLayout();
+ }
+ });
+ layoutAnimator.start();
+ }
+ return layoutAnimator;
+
+ }
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
new file mode 100644
index 0000000..ddcf3c6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaListHeaderPresenter.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.util.TypedValue;
+import android.view.ContextThemeWrapper;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.support.v17.leanback.R;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Abstract presenter class for rendering the header for a list of media items in a playlist.
+ * The presenter creates a {@link ViewHolder} for the TextView holding the header text.
+ * <p>
+ * Subclasses of this class must override {@link
+ * #onBindMediaListHeaderViewHolder(ViewHolder, Object)} in order to bind their header text to
+ * the media list header view.
+ * </p>
+ * <p>
+ * {@link AbstractMediaItemPresenter} can be used in conjunction with this presenter in order to
+ * display a playlist with a header view.
+ * </p>
+ */
+public abstract class AbstractMediaListHeaderPresenter extends RowPresenter{
+
+ private final Context mContext;
+ private int mBackgroundColor = Color.TRANSPARENT;
+ private boolean mBackgroundColorSet;
+
+ /**
+ * The ViewHolder for the {@link AbstractMediaListHeaderPresenter}. It references the TextView
+ * that places the header text provided by the data binder.
+ */
+ public static class ViewHolder extends RowPresenter.ViewHolder {
+
+ private final TextView mHeaderView;
+
+ public ViewHolder(View view) {
+ super(view);
+ mHeaderView = (TextView) view.findViewById(R.id.mediaListHeader);
+ }
+
+ /**
+ *
+ * @return the header {@link TextView} responsible for rendering the playlist header text.
+ */
+ public TextView getHeaderView() {
+ return mHeaderView;
+ }
+ }
+
+ /**
+ * Constructor used for creating an abstract media-list header presenter of a given theme.
+ * @param context The context the user of this presenter is running in.
+ * @param mThemeResId The resource id of the desired theme used for styling of this presenter.
+ */
+ public AbstractMediaListHeaderPresenter(Context context, int mThemeResId) {
+ mContext = new ContextThemeWrapper(context.getApplicationContext(), mThemeResId);
+ setHeaderPresenter(null);
+ }
+
+ /**
+ * Constructor used for creating an abstract media-list header presenter.
+ * The styling for this presenter is extracted from Context of parent in
+ * {@link #createRowViewHolder(ViewGroup)}.
+ */
+ public AbstractMediaListHeaderPresenter() {
+ mContext = null;
+ setHeaderPresenter(null);
+ }
+
+ @Override
+ public boolean isUsingDefaultSelectEffect() {
+ return false;
+ }
+
+ @Override
+ protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+ Context context = (mContext != null) ? mContext : parent.getContext();
+ View view = LayoutInflater.from(context).inflate(R.layout.lb_media_list_header,
+ parent, false);
+ view.setFocusable(false);
+ view.setFocusableInTouchMode(false);
+ ViewHolder vh = new ViewHolder(view);
+ if (mBackgroundColorSet) {
+ vh.view.setBackgroundColor(mBackgroundColor);
+ }
+ return vh;
+ }
+
+ @Override
+ protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+ super.onBindRowViewHolder(vh, item);
+ onBindMediaListHeaderViewHolder((ViewHolder) vh, item);
+ }
+
+ /**
+ * Sets the background color for the row views within the playlist.
+ * If this is not set, a default color, defaultBrandColor, from theme is used.
+ * This defaultBrandColor defaults to android:attr/colorPrimary on v21, if it's specified.
+ * @param color The ARGB color used to set as the header text background color.
+ */
+ public void setBackgroundColor(int color) {
+ mBackgroundColorSet = true;
+ mBackgroundColor = color;
+ }
+
+ /**
+ * Binds the playlist header data model provided by the user to the {@link ViewHolder}
+ * provided by the {@link AbstractMediaListHeaderPresenter}.
+ * The subclasses of this presenter can access and bind the text view corresponding to the
+ * header by calling {@link ViewHolder#getHeaderView()}, on the
+ * {@link ViewHolder} provided as the argument {@code vh} by this presenter.
+ *
+ * @param vh The ViewHolder for this {@link AbstractMediaListHeaderPresenter}.
+ * @param item The header data object being presented.
+ */
+ protected abstract void onBindMediaListHeaderViewHolder(ViewHolder vh, Object item);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Action.java b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
index 7bb696a..5e6e313 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Action.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Action.java
@@ -19,14 +19,15 @@
import java.util.ArrayList;
-import static android.support.v17.leanback.widget.ObjectAdapter.NO_ID;
-
/**
* An action contains one or two lines of text, an optional image and an optional id. It may also
* be invoked by one or more keycodes.
*/
public class Action {
+ /** Indicates that an id has not been set. */
+ public static final long NO_ID = -1;
+
private long mId = NO_ID;
private Drawable mIcon;
private CharSequence mLabel1;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
index 085aac3..40127dc 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseCardView.java
@@ -148,10 +148,11 @@
this(context, attrs, R.attr.baseCardViewStyle);
}
- public BaseCardView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
+ public BaseCardView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
- TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView, defStyle, 0);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbBaseCardView,
+ defStyleAttr, 0);
try {
mCardType = a.getInteger(R.styleable.lbBaseCardView_cardType, CARD_TYPE_MAIN_ONLY);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
index 73e5b40..e8b55dc 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java
@@ -219,6 +219,9 @@
boolean throughFront = a.getBoolean(R.styleable.lbBaseGridView_focusOutFront, false);
boolean throughEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutEnd, false);
mLayoutManager.setFocusOutAllowed(throughFront, throughEnd);
+ boolean throughSideStart = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideStart, true);
+ boolean throughSideEnd = a.getBoolean(R.styleable.lbBaseGridView_focusOutSideEnd, true);
+ mLayoutManager.setFocusOutSideAllowed(throughSideStart, throughSideEnd);
mLayoutManager.setVerticalMargin(
a.getDimensionPixelSize(R.styleable.lbBaseGridView_verticalMargin, 0));
mLayoutManager.setHorizontalMargin(
@@ -487,6 +490,8 @@
* been selected. Note that the listener may be invoked when there is a
* layout pending on the view, affording the listener an opportunity to
* adjust the upcoming layout based on the selection state.
+ * This method will clear all existing listeners added by
+ * {@link #addOnChildViewHolderSelectedListener}.
*
* @param listener The listener to be invoked.
*/
@@ -495,17 +500,39 @@
}
/**
+ * Registers a callback to be invoked when an item in BaseGridView has
+ * been selected. Note that the listener may be invoked when there is a
+ * layout pending on the view, affording the listener an opportunity to
+ * adjust the upcoming layout based on the selection state.
+ *
+ * @param listener The listener to be invoked.
+ */
+ public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+ mLayoutManager.addOnChildViewHolderSelectedListener(listener);
+ }
+
+ /**
+ * Remove the callback invoked when an item in BaseGridView has been selected.
+ *
+ * @param listener The listener to be removed.
+ */
+ public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener)
+ {
+ mLayoutManager.removeOnChildViewHolderSelectedListener(listener);
+ }
+
+ /**
* Changes the selected item immediately without animation.
*/
public void setSelectedPosition(int position) {
- mLayoutManager.setSelection(this, position, 0);
+ mLayoutManager.setSelection(position, 0);
}
/**
* Changes the selected item and/or subposition immediately without animation.
*/
public void setSelectedPositionWithSub(int position, int subposition) {
- mLayoutManager.setSelectionWithSub(this, position, subposition, 0);
+ mLayoutManager.setSelectionWithSub(position, subposition, 0);
}
/**
@@ -514,7 +541,7 @@
* another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
*/
public void setSelectedPosition(int position, int scrollExtra) {
- mLayoutManager.setSelection(this, position, scrollExtra);
+ mLayoutManager.setSelection(position, scrollExtra);
}
/**
@@ -523,7 +550,7 @@
* another {@link #setSelectedPosition} or {@link #setSelectedPositionSmooth} call.
*/
public void setSelectedPositionWithSub(int position, int subposition, int scrollExtra) {
- mLayoutManager.setSelectionWithSub(this, position, subposition, scrollExtra);
+ mLayoutManager.setSelectionWithSub(position, subposition, scrollExtra);
}
/**
@@ -531,7 +558,7 @@
* position.
*/
public void setSelectedPositionSmooth(int position) {
- mLayoutManager.setSelectionSmooth(this, position);
+ mLayoutManager.setSelectionSmooth(position);
}
/**
@@ -539,7 +566,57 @@
* position.
*/
public void setSelectedPositionSmoothWithSub(int position, int subposition) {
- mLayoutManager.setSelectionSmoothWithSub(this, position, subposition);
+ mLayoutManager.setSelectionSmoothWithSub(position, subposition);
+ }
+
+ /**
+ * Perform a task on ViewHolder at given position after smooth scrolling to it.
+ * @param position Position of item in adapter.
+ * @param task Task to executed on the ViewHolder at a given position.
+ */
+ public void setSelectedPositionSmooth(final int position, final ViewHolderTask task) {
+ if (task != null) {
+ RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
+ if (vh == null) {
+ addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
+ public void onChildViewHolderSelected(RecyclerView parent,
+ RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
+ if (selectedPosition == position) {
+ removeOnChildViewHolderSelectedListener(this);
+ task.run(child);
+ }
+ }
+ });
+ } else {
+ task.run(vh);
+ }
+ }
+ setSelectedPositionSmooth(position);
+ }
+
+ /**
+ * Perform a task on ViewHolder at given position after scroll to it.
+ * @param position Position of item in adapter.
+ * @param task Task to executed on the ViewHolder at a given position.
+ */
+ public void setSelectedPosition(final int position, final ViewHolderTask task) {
+ if (task != null) {
+ RecyclerView.ViewHolder vh = findViewHolderForPosition(position);
+ if (vh == null) {
+ addOnChildViewHolderSelectedListener(new OnChildViewHolderSelectedListener() {
+ public void onChildViewHolderSelected(RecyclerView parent,
+ RecyclerView.ViewHolder child, int selectedPosition, int subposition) {
+ if (selectedPosition == position) {
+ removeOnChildViewHolderSelectedListener(this);
+ task.run(child);
+ }
+ }
+ });
+ } else {
+ task.run(vh);
+ }
+ }
+ setSelectedPosition(position);
}
/**
@@ -865,4 +942,5 @@
public int getExtraLayoutSpace() {
return mLayoutManager.getExtraLayoutSpace();
}
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
new file mode 100644
index 0000000..0613667
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewClickedListener.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when an item view holder is clicked.
+ */
+public interface BaseOnItemViewClickedListener<T> {
+
+ /**
+ * Called when an item inside a row gets clicked.
+ * @param itemViewHolder The view holder of the item that is clicked.
+ * @param item The item that is currently selected.
+ * @param rowViewHolder The view holder of the row which the clicked item belongs to.
+ * @param row The row which the clicked item belongs to.
+ */
+ public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, T row);
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
new file mode 100644
index 0000000..b43e146
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseOnItemViewSelectedListener.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Interface for receiving notification when a row or item becomes selected. The concept of
+ * current selection is different than focus. A row or item can be selected without having focus;
+ * for example, when a row header view gains focus then the corresponding row view becomes selected.
+ */
+public interface BaseOnItemViewSelectedListener<T> {
+
+ /**
+ * Called when a row or a new item becomes selected.
+ * <p>
+ * For a non {@link ListRow} case, parameter item may be null. Event is fired when
+ * selection changes between rows, regardless if row view has focus or not.
+ * <p>
+ * For a {@link ListRow} case, parameter item is null if the list row is empty.
+ * </p>
+ * <p>
+ * In the case of a grid, the row parameter is always null.
+ * </p>
+ * <li>
+ * Row has focus: event is fired when focus changes between children of the row.
+ * </li>
+ * <li>
+ * No row has focus: the event is fired with the currently selected row and last
+ * focused item in the row.
+ * </li>
+ *
+ * @param itemViewHolder The view holder of the item that is currently selected.
+ * @param item The item that is currently selected.
+ * @param rowViewHolder The view holder of the row that is currently selected.
+ * @param row The row that is currently selected.
+ */
+ public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
+ RowPresenter.ViewHolder rowViewHolder, T row);
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
index 7512dcd..c19c390 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/BrowseFrameLayout.java
@@ -33,7 +33,7 @@
* Returns the view where focus should be requested given the current focused view and
* the direction of focus search.
*/
- public View onFocusSearch(View focused, int direction);
+ View onFocusSearch(View focused, int direction);
}
/**
@@ -44,13 +44,13 @@
* See {@link android.view.ViewGroup#onRequestFocusInDescendants(
* int, android.graphics.Rect)}.
*/
- public boolean onRequestFocusInDescendants(int direction,
+ boolean onRequestFocusInDescendants(int direction,
Rect previouslyFocusedRect);
/**
* See {@link android.view.ViewGroup#requestChildFocus(
* android.view.View, android.view.View)}.
*/
- public void onRequestChildFocus(View child, View focused);
+ void onRequestChildFocus(View child, View focused);
}
public BrowseFrameLayout(Context context) {
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
index 7c0c8e1..7a0ac20 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ClassPresenterSelector.java
@@ -24,29 +24,57 @@
private final ArrayList<Presenter> mPresenters = new ArrayList<Presenter>();
- private final HashMap<Class<?>, Presenter> mClassMap = new HashMap<Class<?>, Presenter>();
+ private final HashMap<Class<?>, Object> mClassMap = new HashMap<Class<?>, Object>();
/**
- * Adds a presenter to be used for the given class.
+ * Sets a presenter to be used for the given class.
+ * @param cls The data model class to be rendered.
+ * @param presenter The presenter that renders the objects of the given class.
+ * @return This ClassPresenterSelector object.
*/
- public void addClassPresenter(Class<?> cls, Presenter presenter) {
+ public ClassPresenterSelector addClassPresenter(Class<?> cls, Presenter presenter) {
mClassMap.put(cls, presenter);
if (!mPresenters.contains(presenter)) {
mPresenters.add(presenter);
}
+ return this;
+ }
+
+ /**
+ * Sets a presenter selector to be used for the given class.
+ * @param cls The data model class to be rendered.
+ * @param presenterSelector The presenter selector that finds the right presenter for a given
+ * class.
+ * @return This ClassPresenterSelector object.
+ */
+ public ClassPresenterSelector addClassPresenterSelector(Class<?> cls,
+ PresenterSelector presenterSelector) {
+ mClassMap.put(cls, presenterSelector);
+ Presenter[] innerPresenters = presenterSelector.getPresenters();
+ for (int i = 0; i < innerPresenters.length; i++)
+ if (!mPresenters.contains(innerPresenters[i])) {
+ mPresenters.add(innerPresenters[i]);
+ }
+ return this;
}
@Override
public Presenter getPresenter(Object item) {
Class<?> cls = item.getClass();
- Presenter presenter = null;
+ Object presenter = null;
do {
presenter = mClassMap.get(cls);
+ if (presenter instanceof PresenterSelector) {
+ Presenter innerPresenter = ((PresenterSelector) presenter).getPresenter(item);
+ if (innerPresenter != null) {
+ return innerPresenter;
+ }
+ }
cls = cls.getSuperclass();
} while (presenter == null && cls != null);
- return presenter;
+ return (Presenter) presenter;
}
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
index bbb21fd..a9e0d7a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewLogoPresenter.java
@@ -13,6 +13,11 @@
* Presenter that responsible to create a ImageView and bind to DetailsOverviewRow. The default
* implementation uses {@link DetailsOverviewRow#getImageDrawable()} and binds to {@link ImageView}.
* <p>
+ * Default implementation assumes no scaleType on ImageView and uses intrinsic width and height of
+ * {@link DetailsOverviewRow#getImageDrawable()} to initialize ImageView's layout params. To
+ * specify a fixed size and/or specify a scapeType, subclass should change ImageView's layout params
+ * and scaleType in {@link #onCreateView(ViewGroup)}.
+ * <p>
* Subclass may override and has its own image view. Subclass may also download image from URL
* instead of using {@link DetailsOverviewRow#getImageDrawable()}. It's subclass's responsibility to
* call {@link FullWidthDetailsOverviewRowPresenter#notifyOnBindLogo(FullWidthDetailsOverviewRowPresenter.ViewHolder)}
@@ -21,22 +26,79 @@
*/
public class DetailsOverviewLogoPresenter extends Presenter {
+ /**
+ * ViewHolder for Logo view of DetailsOverviewRow.
+ */
public static class ViewHolder extends Presenter.ViewHolder {
protected FullWidthDetailsOverviewRowPresenter mParentPresenter;
protected FullWidthDetailsOverviewRowPresenter.ViewHolder mParentViewHolder;
+ private boolean mSizeFromDrawableIntrinsic;
public ViewHolder(View view) {
super(view);
}
+
+ public FullWidthDetailsOverviewRowPresenter getParentPresenter() {
+ return mParentPresenter;
+ }
+
+ public FullWidthDetailsOverviewRowPresenter.ViewHolder getParentViewHolder() {
+ return mParentViewHolder;
+ }
+
+ /**
+ * @return True if layout size of ImageView should be changed to intrinsic size of Drawable,
+ * false otherwise. Used by
+ * {@link DetailsOverviewLogoPresenter#onBindViewHolder(Presenter.ViewHolder, Object)}
+ * .
+ *
+ * @see DetailsOverviewLogoPresenter#onCreateView(ViewGroup)
+ * @see DetailsOverviewLogoPresenter#onBindViewHolder(Presenter.ViewHolder, Object)
+ */
+ public boolean isSizeFromDrawableIntrinsic() {
+ return mSizeFromDrawableIntrinsic;
+ }
+
+ /**
+ * Change if the ImageView layout size should be synchronized to Drawable intrinsic size.
+ * Used by
+ * {@link DetailsOverviewLogoPresenter#onBindViewHolder(Presenter.ViewHolder, Object)}.
+ *
+ * @param sizeFromDrawableIntrinsic True if layout size of ImageView should be changed to
+ * intrinsic size of Drawable, false otherwise.
+ *
+ * @see DetailsOverviewLogoPresenter#onCreateView(ViewGroup)
+ * @see DetailsOverviewLogoPresenter#onBindViewHolder(Presenter.ViewHolder, Object)
+ */
+ public void setSizeFromDrawableIntrinsic(boolean sizeFromDrawableIntrinsic) {
+ mSizeFromDrawableIntrinsic = sizeFromDrawableIntrinsic;
+ }
+ }
+
+ /**
+ * Create a View for the Logo, default implementation loads from
+ * {@link R.layout#lb_fullwidth_details_overview_logo}. Subclass may override this method to use
+ * a fixed layout size and change ImageView scaleType. If the layout params is WRAP_CONTENT for
+ * both width and size, the ViewHolder would be using intrinsic size of Drawable in
+ * {@link #onBindViewHolder(Presenter.ViewHolder, Object)}.
+ *
+ * @param parent Parent view.
+ * @return View created for the logo.
+ */
+ public View onCreateView(ViewGroup parent) {
+ return LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.lb_fullwidth_details_overview_logo, parent, false);
}
@Override
public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
- View view = LayoutInflater.from(parent.getContext())
- .inflate(R.layout.lb_fullwidth_details_overview_logo, parent, false);
- view.setLayoutParams(new ViewGroup.MarginLayoutParams(0, 0));
- return new ViewHolder(view);
+ View view = onCreateView(parent);
+ ViewHolder vh = new ViewHolder(view);
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ vh.setSizeFromDrawableIntrinsic(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
+ lp.width == ViewGroup.LayoutParams.WRAP_CONTENT);
+ return vh;
}
/**
@@ -65,17 +127,43 @@
return row != null && row.getImageDrawable() != null;
}
+ /**
+ * Bind logo View to drawble of DetailsOverviewRow and call notifyOnBindLogo(). The
+ * default implementation assumes the Logo View is an ImageView and change layout size to
+ * intrinsic size of ImageDrawable if {@link ViewHolder#isSizeFromDrawableIntrinsic()} is true.
+ * @param viewHolder ViewHolder to bind.
+ * @param item DetailsOverviewRow object to bind.
+ */
@Override
public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
DetailsOverviewRow row = (DetailsOverviewRow) item;
ImageView imageView = ((ImageView) viewHolder.view);
imageView.setImageDrawable(row.getImageDrawable());
if (isBoundToImage((ViewHolder) viewHolder, row)) {
- ViewGroup.LayoutParams lp = imageView.getLayoutParams();
- lp.width = row.getImageDrawable().getIntrinsicWidth();
- lp.height = row.getImageDrawable().getIntrinsicHeight();
- imageView.setLayoutParams(lp);
ViewHolder vh = (ViewHolder) viewHolder;
+ if (vh.isSizeFromDrawableIntrinsic()) {
+ ViewGroup.LayoutParams lp = imageView.getLayoutParams();
+ lp.width = row.getImageDrawable().getIntrinsicWidth();
+ lp.height = row.getImageDrawable().getIntrinsicHeight();
+ if (imageView.getMaxWidth() > 0 || imageView.getMaxHeight() > 0) {
+ float maxScaleWidth = 1f;
+ if (imageView.getMaxWidth() > 0) {
+ if (lp.width > imageView.getMaxWidth()) {
+ maxScaleWidth = imageView.getMaxWidth() / (float) lp.width;
+ }
+ }
+ float maxScaleHeight = 1f;
+ if (imageView.getMaxHeight() > 0) {
+ if (lp.height > imageView.getMaxHeight()) {
+ maxScaleHeight = imageView.getMaxHeight() / (float) lp.height;
+ }
+ }
+ float scale = Math.min(maxScaleWidth, maxScaleHeight);
+ lp.width = (int) (lp.width * scale);
+ lp.height = (int) (lp.height * scale);
+ }
+ imageView.setLayoutParams(lp);
+ }
vh.mParentPresenter.notifyOnBindLogo(vh.mParentViewHolder);
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
index 3ce0afb..c7aaf6c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewRow.java
@@ -267,6 +267,7 @@
* @param action The Action to add.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
+ @Deprecated
public final void addAction(Action action) {
getArrayObjectAdapter().add(action);
}
@@ -280,6 +281,7 @@
* @param action The Action to add.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
+ @Deprecated
public final void addAction(int pos, Action action) {
getArrayObjectAdapter().add(pos, action);
}
@@ -292,6 +294,7 @@
* @return true if the overview contained the specified Action.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
+ @Deprecated
public final boolean removeAction(Action action) {
return getArrayObjectAdapter().remove(action);
}
@@ -304,6 +307,7 @@
* @return An unmodifiable view of the list of Actions.
* @deprecated Use {@link #setActionsAdapter(ObjectAdapter)} and {@link #getActionsAdapter()}
*/
+ @Deprecated
public final List<Action> getActions() {
return getArrayObjectAdapter().unmodifiableList();
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java
new file mode 100644
index 0000000..9ddd64c
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DividerPresenter.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.Paint;
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * DividerPresenter provides a default presentation for {@link DividerRow} in HeadersFragment.
+ */
+public class DividerPresenter extends Presenter {
+
+ private final int mLayoutResourceId;
+
+ public DividerPresenter() {
+ this(R.layout.lb_divider);
+ }
+
+ /**
+ * @hide
+ */
+ public DividerPresenter(int layoutResourceId) {
+ mLayoutResourceId = layoutResourceId;
+ }
+
+ @Override
+ public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+ View headerView = LayoutInflater.from(parent.getContext())
+ .inflate(mLayoutResourceId, parent, false);
+
+ return new ViewHolder(headerView);
+ }
+
+ @Override
+ public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ }
+
+ @Override
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DividerRow.java b/v17/leanback/src/android/support/v17/leanback/widget/DividerRow.java
new file mode 100644
index 0000000..1b3a016
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/DividerRow.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Used to represent divider in HeadersFragment.
+ */
+public class DividerRow extends Row {
+
+ public DividerRow() {
+ }
+
+ @Override
+ final public boolean isRenderedAsRowView() {
+ return false;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 20d54e2..7de13f2 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -21,10 +21,9 @@
import android.os.Parcelable;
import android.support.v4.util.CircularIntArray;
import android.support.v4.view.ViewCompat;
-import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v4.view.accessibility.AccessibilityRecordCompat;
import android.support.v7.widget.LinearSmoothScroller;
+import android.support.v7.widget.OrientationHelper;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Recycler;
import android.support.v7.widget.RecyclerView.State;
@@ -40,7 +39,6 @@
import android.view.FocusFinder;
import android.view.Gravity;
import android.view.View;
-import android.view.ViewParent;
import android.view.View.MeasureSpec;
import android.view.ViewGroup.MarginLayoutParams;
import android.view.ViewGroup;
@@ -48,7 +46,6 @@
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
-import java.util.List;
final class GridLayoutManager extends RecyclerView.LayoutManager {
@@ -128,32 +125,6 @@
return view.getHeight() - mTopInset - mBottomInset;
}
- int getDecoratedOpticalLeftWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedLeft(view) + mLeftInset - leftMargin;
- }
-
- int getDecoratedOpticalTopWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedTop(view) + mTopInset - topMargin;
- }
-
- int getDecoratedOpticalRightWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedRight(view) - mRightInset + rightMargin;
- }
-
- int getDecoratedOpticalBottomWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedBottom(view) - mBottomInset + bottomMargin;
- }
-
- int getDecoratedOpticalWidthWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedRight(view) - lm.getDecoratedLeft(view)
- - mLeftInset - mRightInset + leftMargin + rightMargin;
- }
-
- int getDecoratedOpticalHeightWithMargin(RecyclerView.LayoutManager lm, View view) {
- return lm.getDecoratedBottom(view) - lm.getDecoratedTop(view)
- - mTopInset - mBottomInset + topMargin + bottomMargin;
- }
-
int getOpticalLeftInset() {
return mLeftInset;
}
@@ -231,7 +202,7 @@
if (getTargetPosition() >= 0) {
// if smooth scroller is stopped without target, immediately jumps
// to the target position.
- scrollToSelection(mBaseGridView, getTargetPosition(), 0, false, 0);
+ scrollToSelection(getTargetPosition(), 0, false, 0);
}
super.onStop();
return;
@@ -246,6 +217,19 @@
}
@Override
+ protected int calculateTimeForScrolling(int dx) {
+ int ms = super.calculateTimeForScrolling(dx);
+ if (mWindowAlignment.mainAxis().getSize() > 0) {
+ float minMs = (float) MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN /
+ mWindowAlignment.mainAxis().getSize() * dx;
+ if (ms < minMs) {
+ ms = (int) minMs;
+ }
+ }
+ return ms;
+ }
+
+ @Override
protected void onTargetFound(View targetView,
RecyclerView.State state, Action action) {
if (getScrollPosition(targetView, null, sTwoInts)) {
@@ -387,6 +371,9 @@
// maximum pending movement in one direction.
private final static int MAX_PENDING_MOVES = 10;
+ // minimal milliseconds to scroll window size in major direction, we put a cap to prevent the
+ // effect smooth scrolling too over to bind an item view then drag the item view back.
+ private final static int MIN_MS_SMOOTH_SCROLL_MAIN_SCREEN = 30;
private String getTag() {
return TAG + ":" + mBaseGridView.getId();
@@ -422,6 +409,7 @@
* The orientation of a "row".
*/
private int mOrientation = HORIZONTAL;
+ private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
private RecyclerView.State mState;
private RecyclerView.Recycler mRecycler;
@@ -440,7 +428,7 @@
private OnChildSelectedListener mChildSelectedListener = null;
- private OnChildViewHolderSelectedListener mChildViewHolderSelectedListener = null;
+ private ArrayList<OnChildViewHolderSelectedListener> mChildViewHolderSelectedListeners = null;
private OnChildLaidOutListener mChildLaidOutListener = null;
@@ -596,6 +584,17 @@
private boolean mFocusOutEnd;
/**
+ * Allow DPAD key to navigate out of second axis.
+ * default is true.
+ */
+ private boolean mFocusOutSideStart = true;
+
+ /**
+ * Allow DPAD key to navigate out of second axis.
+ */
+ private boolean mFocusOutSideEnd = true;
+
+ /**
* True if focus search is disabled.
*/
private boolean mFocusSearchDisabled;
@@ -648,6 +647,7 @@
}
mOrientation = orientation;
+ mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
mWindowAlignment.setOrientation(orientation);
mItemAlignment.setOrientation(orientation);
mForceFullLayout = true;
@@ -737,6 +737,11 @@
mFocusOutEnd = throughEnd;
}
+ public void setFocusOutSideAllowed(boolean throughStart, boolean throughEnd) {
+ mFocusOutSideStart = throughStart;
+ mFocusOutSideEnd = throughEnd;
+ }
+
public void setNumRows(int numRows) {
if (numRows < 0) throw new IllegalArgumentException();
mNumRowsRequested = numRows;
@@ -795,7 +800,46 @@
}
public void setOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
- mChildViewHolderSelectedListener = listener;
+ if (listener == null) {
+ mChildViewHolderSelectedListeners = null;
+ return;
+ }
+ if (mChildViewHolderSelectedListeners == null) {
+ mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
+ } else {
+ mChildViewHolderSelectedListeners.clear();
+ }
+ mChildViewHolderSelectedListeners.add(listener);
+ }
+
+ public void addOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener listener) {
+ if (mChildViewHolderSelectedListeners == null) {
+ mChildViewHolderSelectedListeners = new ArrayList<OnChildViewHolderSelectedListener>();
+ }
+ mChildViewHolderSelectedListeners.add(listener);
+ }
+
+ public void removeOnChildViewHolderSelectedListener(OnChildViewHolderSelectedListener
+ listener) {
+ if (mChildViewHolderSelectedListeners != null) {
+ mChildViewHolderSelectedListeners.remove(listener);
+ }
+ }
+
+ boolean hasOnChildViewHolderSelectedListener() {
+ return mChildViewHolderSelectedListeners != null &&
+ mChildViewHolderSelectedListeners.size() > 0;
+ }
+
+ void fireOnChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
+ int position, int subposition) {
+ if (mChildViewHolderSelectedListeners == null) {
+ return;
+ }
+ for (int i = mChildViewHolderSelectedListeners.size() - 1; i >= 0 ; i--) {
+ mChildViewHolderSelectedListeners.get(i).onChildViewHolderSelected(parent, child,
+ position, subposition);
+ }
}
void setOnChildLaidOutListener(OnChildLaidOutListener listener) {
@@ -844,7 +888,7 @@
}
private void dispatchChildSelected() {
- if (mChildSelectedListener == null && mChildViewHolderSelectedListener == null) {
+ if (mChildSelectedListener == null && !hasOnChildViewHolderSelectedListener()) {
return;
}
@@ -856,18 +900,12 @@
mChildSelectedListener.onChildSelected(mBaseGridView, view, mFocusPosition,
vh == null? NO_ID: vh.getItemId());
}
- if (mChildViewHolderSelectedListener != null) {
- mChildViewHolderSelectedListener.onChildViewHolderSelected(mBaseGridView, vh,
- mFocusPosition, mSubFocusPosition);
- }
+ fireOnChildViewHolderSelected(mBaseGridView, vh, mFocusPosition, mSubFocusPosition);
} else {
if (mChildSelectedListener != null) {
mChildSelectedListener.onChildSelected(mBaseGridView, null, NO_POSITION, NO_ID);
}
- if (mChildViewHolderSelectedListener != null) {
- mChildViewHolderSelectedListener.onChildViewHolderSelected(mBaseGridView, null,
- NO_POSITION, 0);
- }
+ fireOnChildViewHolderSelected(mBaseGridView, null, NO_POSITION, 0);
}
if (TRACE) TraceHelper.endSection();
@@ -949,22 +987,49 @@
return ((LayoutParams) v.getLayoutParams()).getOpticalBottom(v);
}
+ @Override
+ public int getDecoratedLeft(View child) {
+ return super.getDecoratedLeft(child) + ((LayoutParams) child.getLayoutParams()).mLeftInset;
+ }
+
+ @Override
+ public int getDecoratedTop(View child) {
+ return super.getDecoratedTop(child) + ((LayoutParams) child.getLayoutParams()).mTopInset;
+ }
+
+ @Override
+ public int getDecoratedRight(View child) {
+ return super.getDecoratedRight(child) -
+ ((LayoutParams) child.getLayoutParams()).mRightInset;
+ }
+
+ @Override
+ public int getDecoratedBottom(View child) {
+ return super.getDecoratedBottom(child) -
+ ((LayoutParams) child.getLayoutParams()).mBottomInset;
+ }
+
+ @Override
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ super.getDecoratedBoundsWithMargins(view, outBounds);
+ LayoutParams params = ((LayoutParams) view.getLayoutParams());
+ outBounds.left += params.mLeftInset;
+ outBounds.top += params.mTopInset;
+ outBounds.right -= params.mRightInset;
+ outBounds.bottom -= params.mBottomInset;
+ }
+
private int getViewMin(View v) {
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- return (mOrientation == HORIZONTAL) ? lp.getDecoratedOpticalLeftWithMargin(this, v)
- : lp.getDecoratedOpticalTopWithMargin(this, v);
+ return mOrientationHelper.getDecoratedStart(v);
}
private int getViewMax(View v) {
- LayoutParams lp = (LayoutParams) v.getLayoutParams();
- return (mOrientation == HORIZONTAL) ? lp.getDecoratedOpticalRightWithMargin(this, v)
- : lp.getDecoratedOpticalBottomWithMargin(this, v);
+ return mOrientationHelper.getDecoratedEnd(v);
}
private int getViewPrimarySize(View view) {
- LayoutParams p = (LayoutParams) view.getLayoutParams();
- return mOrientation == HORIZONTAL ? p.getDecoratedOpticalWidthWithMargin(this, view)
- : p.getDecoratedOpticalHeightWithMargin(this, view);
+ getDecoratedBoundsWithMargins(view, sTempRect);
+ return mOrientation == HORIZONTAL ? sTempRect.width() : sTempRect.height();
}
private int getViewCenter(View view) {
@@ -1153,7 +1218,7 @@
if (view == null) {
continue;
}
- if (measure && view.isLayoutRequested()) {
+ if (measure) {
measureChild(view);
}
final int secondarySize = mOrientation == HORIZONTAL ?
@@ -1166,7 +1231,7 @@
}
final int itemCount = mState.getItemCount();
- if (measure && rowSize < 0 && itemCount > 0) {
+ if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
if (scrapChildWidth < 0 && scrapChildHeight < 0) {
int position;
if (mFocusPosition == NO_POSITION) {
@@ -1234,6 +1299,27 @@
}
};
+ private final Runnable mAskFocusRunnable = new Runnable() {
+ @Override
+ public void run() {
+ if (hasFocus()) {
+ return;
+ }
+ View view = findViewByPosition(mFocusPosition);
+ if (view != null && view.hasFocusable()) {
+ mBaseGridView.focusableViewAvailable(view);
+ return;
+ }
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ view = getChildAt(i);
+ if (view != null && view.hasFocusable()) {
+ mBaseGridView.focusableViewAvailable(view);
+ break;
+ }
+ }
+ }
+ };
+
@Override
public void onMeasure(Recycler recycler, State state, int widthSpec, int heightSpec) {
saveContext(recycler, state);
@@ -1563,19 +1649,17 @@
right = startSecondary + sizeSecondary;
}
LayoutParams params = (LayoutParams) v.getLayoutParams();
- layoutDecorated(v, left + params.leftMargin, top + params.topMargin,
- right - params.rightMargin, bottom - params.bottomMargin);
- updateChildOpticalInsets(v, left, top, right, bottom);
+ layoutDecoratedWithMargins(v, left, top, right, bottom);
+ // Now super.getDecoratedBoundsWithMargins() includes the extra space for optical bounds,
+ // subtracting it from value passed in layoutDecoratedWithMargins(), we can get the optical
+ // bounds insets.
+ super.getDecoratedBoundsWithMargins(v, sTempRect);
+ params.setOpticalInsets(left - sTempRect.left, top - sTempRect.top,
+ sTempRect.right - right, sTempRect.bottom - bottom);
updateChildAlignments(v);
if (TRACE) TraceHelper.endSection();
}
- private void updateChildOpticalInsets(View v, int left, int top, int right, int bottom) {
- LayoutParams p = (LayoutParams) v.getLayoutParams();
- p.setOpticalInsets(left - v.getLeft(), top - v.getTop(),
- v.getRight() - right, v.getBottom() - bottom);
- }
-
private void updateChildAlignments(View v) {
final LayoutParams p = (LayoutParams) v.getLayoutParams();
if (p.getItemAlignmentFacet() == null) {
@@ -1677,9 +1761,7 @@
addView(view, viewIndex);
}
- if (view.isLayoutRequested()) {
- measureChild(view);
- }
+ measureChild(view);
if (mOrientation == HORIZONTAL) {
primarySize = getDecoratedMeasuredWidthWithMargin(view);
end = start + primarySize;
@@ -1862,7 +1944,7 @@
// For fastRelayout, only dispatch event when focus position changes.
if (mInFastRelayout && (mFocusPosition != savedFocusPos || mSubFocusPosition !=
- savedFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
+ savedSubFocusPos || findViewByPosition(mFocusPosition) != savedFocusView)) {
dispatchChildSelected();
} else if (!mInFastRelayout && mInLayoutSearchFocus) {
// For full layout we dispatchChildSelected() in createItem() unless searched all
@@ -1872,6 +1954,9 @@
mInLayout = false;
leaveContext();
+ if (!hadFocus && !mInFastRelayout && mBaseGridView.hasFocusable()) {
+ ViewCompat.postOnAnimation(mBaseGridView, mAskFocusRunnable);
+ }
if (DEBUG) Log.v(getTag(), "layoutChildren end");
}
@@ -2127,22 +2212,27 @@
}
}
- public void setSelection(RecyclerView parent, int position,
+ @Override
+ public void scrollToPosition(int position) {
+ setSelection(position, 0, false, 0);
+ }
+
+ public void setSelection(int position,
int primaryScrollExtra) {
- setSelection(parent, position, 0, false, primaryScrollExtra);
+ setSelection(position, 0, false, primaryScrollExtra);
}
- public void setSelectionSmooth(RecyclerView parent, int position) {
- setSelection(parent, position, 0, true, 0);
+ public void setSelectionSmooth(int position) {
+ setSelection(position, 0, true, 0);
}
- public void setSelectionWithSub(RecyclerView parent, int position, int subposition,
+ public void setSelectionWithSub(int position, int subposition,
int primaryScrollExtra) {
- setSelection(parent, position, subposition, false, primaryScrollExtra);
+ setSelection(position, subposition, false, primaryScrollExtra);
}
- public void setSelectionSmoothWithSub(RecyclerView parent, int position, int subposition) {
- setSelection(parent, position, subposition, true, 0);
+ public void setSelectionSmoothWithSub(int position, int subposition) {
+ setSelection(position, subposition, true, 0);
}
public int getSelection() {
@@ -2153,15 +2243,15 @@
return mSubFocusPosition;
}
- public void setSelection(RecyclerView parent, int position, int subposition, boolean smooth,
+ public void setSelection(int position, int subposition, boolean smooth,
int primaryScrollExtra) {
if (mFocusPosition != position && position != NO_POSITION
|| subposition != mSubFocusPosition || primaryScrollExtra != mPrimaryScrollExtra) {
- scrollToSelection(parent, position, subposition, smooth, primaryScrollExtra);
+ scrollToSelection(position, subposition, smooth, primaryScrollExtra);
}
}
- private void scrollToSelection(RecyclerView parent, int position, int subposition,
+ private void scrollToSelection(int position, int subposition,
boolean smooth, int primaryScrollExtra) {
if (TRACE) TraceHelper.beginSection("scrollToSelection");
mPrimaryScrollExtra = primaryScrollExtra;
@@ -2186,7 +2276,7 @@
startPositionSmoothScroller(position);
} else {
mForceFullLayout = true;
- parent.requestLayout();
+ requestLayout();
}
}
if (TRACE) TraceHelper.endSection();
@@ -2244,7 +2334,8 @@
public void onItemsAdded(RecyclerView recyclerView, int positionStart, int itemCount) {
if (DEBUG) Log.v(getTag(), "onItemsAdded positionStart "
+ positionStart + " itemCount " + itemCount);
- if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
+ if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
+ && mFocusPositionOffset != Integer.MIN_VALUE) {
int pos = mFocusPosition + mFocusPositionOffset;
if (positionStart <= pos) {
mFocusPositionOffset += itemCount;
@@ -2264,7 +2355,8 @@
public void onItemsRemoved(RecyclerView recyclerView, int positionStart, int itemCount) {
if (DEBUG) Log.v(getTag(), "onItemsRemoved positionStart "
+ positionStart + " itemCount " + itemCount);
- if (mFocusPosition != NO_POSITION && mFocusPositionOffset != Integer.MIN_VALUE) {
+ if (mFocusPosition != NO_POSITION && mGrid != null && mGrid.getFirstVisibleIndex() >= 0
+ && mFocusPositionOffset != Integer.MIN_VALUE) {
int pos = mFocusPosition + mFocusPositionOffset;
if (positionStart <= pos) {
if (positionStart + itemCount > pos) {
@@ -2607,7 +2699,7 @@
mScrollEnabled = scrollEnabled;
if (mScrollEnabled && mFocusScrollStrategy == BaseGridView.FOCUS_SCROLL_ALIGNED
&& mFocusPosition != NO_POSITION) {
- scrollToSelection(mBaseGridView, mFocusPosition, mSubFocusPosition,
+ scrollToSelection(mFocusPosition, mSubFocusPosition,
true, mPrimaryScrollExtra);
}
}
@@ -2618,12 +2710,15 @@
}
private int findImmediateChildIndex(View view) {
- while (view != null && view != mBaseGridView) {
- int index = mBaseGridView.indexOfChild(view);
- if (index >= 0) {
- return index;
+ if (mBaseGridView != null && view != mBaseGridView) {
+ view = findContainingItemView(view);
+ if (view != null) {
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ if (getChildAt(i) == view) {
+ return i;
+ }
+ }
}
- view = (View) view.getParent();
}
return NO_POSITION;
}
@@ -2657,7 +2752,68 @@
if (mFocusSearchDisabled) {
return focused;
}
- return null;
+
+ final FocusFinder ff = FocusFinder.getInstance();
+ View result = null;
+ if (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+ // convert direction to absolute direction and see if we have a view there and if not
+ // tell LayoutManager to add if it can.
+ if (canScrollVertically()) {
+ final int absDir =
+ direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
+ result = ff.findNextFocus(mBaseGridView, focused, absDir);
+ }
+ if (canScrollHorizontally()) {
+ boolean rtl = getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+ final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ result = ff.findNextFocus(mBaseGridView, focused, absDir);
+ }
+ } else {
+ result = ff.findNextFocus(mBaseGridView, focused, direction);
+ }
+ if (result != null) {
+ return result;
+ }
+
+ if (DEBUG) Log.v(getTag(), "regular focusSearch failed direction " + direction);
+ int movement = getMovement(direction);
+ final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
+ if (movement == NEXT_ITEM) {
+ if (isScroll || !mFocusOutEnd) {
+ result = focused;
+ }
+ if (mScrollEnabled && !hasCreatedLastItem()) {
+ processPendingMovement(true);
+ result = focused;
+ }
+ } else if (movement == PREV_ITEM) {
+ if (isScroll || !mFocusOutFront) {
+ result = focused;
+ }
+ if (mScrollEnabled && !hasCreatedFirstItem()) {
+ processPendingMovement(false);
+ result = focused;
+ }
+ } else if (movement == NEXT_ROW) {
+ if (isScroll || !mFocusOutSideEnd) {
+ result = focused;
+ }
+ } else if (movement == PREV_ROW) {
+ if (isScroll || !mFocusOutSideStart) {
+ result = focused;
+ }
+ }
+ if (result != null) {
+ return result;
+ }
+
+ if (DEBUG) Log.v(getTag(), "now focusSearch in parent");
+ result = mBaseGridView.getParent().focusSearch(focused, direction);
+ if (result != null) {
+ return result;
+ }
+ return focused != null ? focused : mBaseGridView;
}
boolean hasPreviousViewInSameRow(int pos) {
@@ -2694,52 +2850,97 @@
// If this viewgroup has no focus and not using focus align, we want to
// consider the child that does not overlap with padding area.
if (recyclerView.hasFocus()) {
- final int movement = getMovement(direction);
- if (movement != PREV_ITEM && movement != NEXT_ITEM) {
- // Move on secondary direction uses default addFocusables().
- return false;
- }
if (mPendingMoveSmoothScroller != null) {
// don't find next focusable if has pending movement.
return true;
}
+ final int movement = getMovement(direction);
final View focused = recyclerView.findFocus();
- final int focusedPos = getPositionByIndex(findImmediateChildIndex(focused));
+ final int focusedIndex = findImmediateChildIndex(focused);
+ final int focusedPos = getPositionByIndex(focusedIndex);
// Add focusables of focused item.
if (focusedPos != NO_POSITION) {
findViewByPosition(focusedPos).addFocusables(views, direction, focusableMode);
}
+ if (mGrid == null || getChildCount() == 0) {
+ // no grid information, or no child, bail out.
+ return true;
+ }
+ if ((movement == NEXT_ROW || movement == PREV_ROW) && mGrid.getNumRows() <= 1) {
+ // For single row, cannot navigate to previous/next row.
+ return true;
+ }
+ // Add focusables of neighbor depending on the focus search direction.
final int focusedRow = mGrid != null && focusedPos != NO_POSITION ?
mGrid.getLocation(focusedPos).row : NO_POSITION;
- // Add focusables of next neighbor of same row on the focus search direction.
- if (mGrid != null) {
- final int focusableCount = views.size();
- for (int i = 0, count = getChildCount(); i < count; i++) {
- int index = movement == NEXT_ITEM ? i : count - 1 - i;
- final View child = getChildAt(index);
- if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
- continue;
+ final int focusableCount = views.size();
+ int inc = movement == NEXT_ITEM || movement == NEXT_ROW ? 1 : -1;
+ int loop_end = inc > 0 ? getChildCount() - 1 : 0;
+ int loop_start;
+ if (focusedIndex == NO_POSITION) {
+ loop_start = inc > 0 ? 0 : getChildCount() - 1;
+ } else {
+ loop_start = focusedIndex + inc;
+ }
+ for (int i = loop_start; inc > 0 ? i <= loop_end : i >= loop_end; i += inc) {
+ final View child = getChildAt(i);
+ if (child.getVisibility() != View.VISIBLE || !child.hasFocusable()) {
+ continue;
+ }
+ // if there wasn't any focusing item, add the very first focusable
+ // items and stop.
+ if (focusedPos == NO_POSITION) {
+ child.addFocusables(views, direction, focusableMode);
+ if (views.size() > focusableCount) {
+ break;
}
- int position = getPositionByIndex(index);
- Grid.Location loc = mGrid.getLocation(position);
- if (focusedRow == NO_POSITION || (loc != null && loc.row == focusedRow)) {
- if (focusedPos == NO_POSITION ||
- (movement == NEXT_ITEM && position > focusedPos)
- || (movement == PREV_ITEM && position < focusedPos)) {
- child.addFocusables(views, direction, focusableMode);
- if (views.size() > focusableCount) {
- break;
- }
+ continue;
+ }
+ int position = getPositionByIndex(i);
+ Grid.Location loc = mGrid.getLocation(position);
+ if (loc == null) {
+ continue;
+ }
+ if (movement == NEXT_ITEM) {
+ // Add first focusable item on the same row
+ if (loc.row == focusedRow && position > focusedPos) {
+ child.addFocusables(views, direction, focusableMode);
+ if (views.size() > focusableCount) {
+ break;
}
}
+ } else if (movement == PREV_ITEM) {
+ // Add first focusable item on the same row
+ if (loc.row == focusedRow && position < focusedPos) {
+ child.addFocusables(views, direction, focusableMode);
+ if (views.size() > focusableCount) {
+ break;
+ }
+ }
+ } else if (movement == NEXT_ROW) {
+ // Add all focusable items after this item whose row index is bigger
+ if (loc.row == focusedRow) {
+ continue;
+ } else if (loc.row < focusedRow) {
+ break;
+ }
+ child.addFocusables(views, direction, focusableMode);
+ } else if (movement == PREV_ROW) {
+ // Add all focusable items before this item whose row index is smaller
+ if (loc.row == focusedRow) {
+ continue;
+ } else if (loc.row > focusedRow) {
+ break;
+ }
+ child.addFocusables(views, direction, focusableMode);
}
}
} else {
+ int focusableCount = views.size();
if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
// adding views not overlapping padding area to avoid scrolling in gaining focus
int left = mWindowAlignment.mainAxis().getPaddingLow();
int right = mWindowAlignment.mainAxis().getClientSize() + left;
- int focusableCount = views.size();
for (int i = 0, count = getChildCount(); i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
@@ -2756,13 +2957,16 @@
child.addFocusables(views, direction, focusableMode);
}
}
- if (views.size() != focusableCount) {
- return true;
- }
- } else {
- return true;
}
- // if still cannot find any, fall through and add itself
+ } else {
+ View view = findViewByPosition(mFocusPosition);
+ if (view != null) {
+ view.addFocusables(views, direction, focusableMode);
+ }
+ }
+ // if still cannot find any, fall through and add itself
+ if (views.size() != focusableCount) {
+ return true;
}
if (recyclerView.isFocusable()) {
views.add(recyclerView);
@@ -2772,50 +2976,19 @@
}
private boolean hasCreatedLastItem() {
- int count = mState.getItemCount();
- return count == 0 || findViewByPosition(count - 1) != null;
+ int count = getItemCount();
+ return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(count - 1) != null;
}
private boolean hasCreatedFirstItem() {
- int count = mState.getItemCount();
- return count == 0 || findViewByPosition(0) != null;
+ int count = getItemCount();
+ return count == 0 || mBaseGridView.findViewHolderForAdapterPosition(0) != null;
}
boolean canScrollTo(View view) {
return view.getVisibility() == View.VISIBLE && (!hasFocus() || view.hasFocusable());
}
- @Override
- public View onFocusSearchFailed(View focused, int direction, Recycler recycler,
- RecyclerView.State state) {
- if (DEBUG) Log.v(getTag(), "onFocusSearchFailed direction " + direction);
-
- View view = null;
- int movement = getMovement(direction);
- final boolean isScroll = mBaseGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE;
- saveContext(recycler, state);
- if (movement == NEXT_ITEM) {
- if (isScroll || !mFocusOutEnd) {
- view = focused;
- }
- if (mScrollEnabled && !hasCreatedLastItem()) {
- processPendingMovement(true);
- view = focused;
- }
- } else if (movement == PREV_ITEM) {
- if (isScroll || !mFocusOutFront) {
- view = focused;
- }
- if (mScrollEnabled && !hasCreatedFirstItem()) {
- processPendingMovement(false);
- view = focused;
- }
- }
- leaveContext();
- if (DEBUG) Log.v(getTag(), "onFocusSearchFailed returning view " + view);
- return view;
- }
-
boolean gridOnRequestFocusInDescendants(RecyclerView recyclerView, int direction,
Rect previouslyFocusedRect) {
switch (mFocusScrollStrategy) {
@@ -2900,10 +3073,10 @@
} else if (mOrientation == VERTICAL) {
switch(direction) {
case View.FOCUS_LEFT:
- movement = (!mReverseFlowPrimary) ? PREV_ROW : NEXT_ROW;
+ movement = (!mReverseFlowSecondary) ? PREV_ROW : NEXT_ROW;
break;
case View.FOCUS_RIGHT:
- movement = (!mReverseFlowPrimary) ? NEXT_ROW : PREV_ROW;
+ movement = (!mReverseFlowSecondary) ? NEXT_ROW : PREV_ROW;
break;
case View.FOCUS_UP:
movement = PREV_ITEM;
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java
new file mode 100644
index 0000000..31c5770
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylingRelativeLayout.java
@@ -0,0 +1,84 @@
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.widget.ImageView;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * Relative layout implementation that lays out child views based on provided keyline percent(
+ * distance of TitleView baseline from the top).
+ *
+ * Repositioning child views in PreDraw callback in {@link GuidanceStylist} was interfering with
+ * fragment transition. To avoid that, we do that in the onLayout pass.
+ *
+ * @hide
+ */
+class GuidanceStylingRelativeLayout extends RelativeLayout {
+ private float mTitleKeylinePercent;
+
+ public GuidanceStylingRelativeLayout(Context context) {
+ this(context, null);
+ }
+
+ public GuidanceStylingRelativeLayout(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GuidanceStylingRelativeLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ init();
+ }
+
+ private void init() {
+ TypedArray ta = getContext().getTheme().obtainStyledAttributes(
+ R.styleable.LeanbackGuidedStepTheme);
+ mTitleKeylinePercent = ta.getFloat(R.styleable.LeanbackGuidedStepTheme_guidedStepKeyline,
+ 40);
+ ta.recycle();
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+
+ TextView mTitleView = (TextView) getRootView().findViewById(R.id.guidance_title);
+ TextView mBreadcrumbView = (TextView) getRootView().findViewById(R.id.guidance_breadcrumb);
+ TextView mDescriptionView = (TextView) getRootView().findViewById(
+ R.id.guidance_description);
+ ImageView mIconView = (ImageView) getRootView().findViewById(R.id.guidance_icon);
+ int mTitleKeylinePixels = (int) (getMeasuredHeight() * mTitleKeylinePercent / 100);
+
+ if (mTitleView != null && mTitleView.getParent() == this) {
+ Paint textPaint = mTitleView.getPaint();
+ int titleViewTextHeight = -textPaint.getFontMetricsInt().top;
+ int mBreadcrumbViewHeight = mBreadcrumbView.getMeasuredHeight();
+ int guidanceTextContainerTop = mTitleKeylinePixels
+ - titleViewTextHeight - mBreadcrumbViewHeight - mTitleView.getPaddingTop();
+ int offset = guidanceTextContainerTop - mBreadcrumbView.getTop();
+
+ if (mBreadcrumbView != null && mBreadcrumbView.getParent() == this) {
+ mBreadcrumbView.offsetTopAndBottom(offset);
+ }
+
+ mTitleView.offsetTopAndBottom(offset);
+
+ if (mDescriptionView != null && mDescriptionView.getParent() == this) {
+ mDescriptionView.offsetTopAndBottom(offset);
+ }
+ }
+
+ if (mIconView != null && mIconView.getParent() == this) {
+ Drawable drawable = mIconView.getDrawable();
+ if (drawable != null) {
+ mIconView.offsetTopAndBottom(
+ mTitleKeylinePixels - mIconView.getMeasuredHeight() / 2);
+ }
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
index 3fcdbba..7f28705 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java
@@ -16,10 +16,10 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.NonNull;
import android.support.v17.leanback.R;
+import android.text.TextUtils;
import android.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
@@ -135,6 +135,7 @@
private TextView mDescriptionView;
private TextView mBreadcrumbView;
private ImageView mIconView;
+ private View mGuidanceContainer;
/**
* Creates an appropriately configured view for the given Guidance, using the provided
@@ -148,27 +149,49 @@
* @param guidance The guidance data for the view.
* @return The view to be added to the caller's view hierarchy.
*/
- public View onCreateView(LayoutInflater inflater, ViewGroup container, Guidance guidance) {
+ public View onCreateView(
+ final LayoutInflater inflater, ViewGroup container, Guidance guidance) {
+
View guidanceView = inflater.inflate(onProvideLayoutId(), container, false);
mTitleView = (TextView) guidanceView.findViewById(R.id.guidance_title);
mBreadcrumbView = (TextView) guidanceView.findViewById(R.id.guidance_breadcrumb);
mDescriptionView = (TextView) guidanceView.findViewById(R.id.guidance_description);
mIconView = (ImageView) guidanceView.findViewById(R.id.guidance_icon);
+ mGuidanceContainer = guidanceView.findViewById(R.id.guidance_container);
// We allow any of the cached subviews to be null, so that subclasses can choose not to
// display a particular piece of information.
if (mTitleView != null) {
mTitleView.setText(guidance.getTitle());
}
+
if (mBreadcrumbView != null) {
mBreadcrumbView.setText(guidance.getBreadcrumb());
}
+
if (mDescriptionView != null) {
mDescriptionView.setText(guidance.getDescription());
}
+
if (mIconView != null) {
- mIconView.setImageDrawable(guidance.getIconDrawable());
+ if (guidance.getIconDrawable() != null) {
+ mIconView.setImageDrawable(guidance.getIconDrawable());
+ } else {
+ mIconView.setVisibility(View.GONE);
+ }
}
+
+ if (mGuidanceContainer != null) {
+ CharSequence contentDescription = mGuidanceContainer.getContentDescription();
+ if (TextUtils.isEmpty(contentDescription)) {
+ mGuidanceContainer.setContentDescription(new StringBuilder()
+ .append(guidance.getBreadcrumb()).append('\n')
+ .append(guidance.getTitle()).append('\n')
+ .append(guidance.getDescription())
+ .toString());
+ }
+ }
+
return guidanceView;
}
@@ -231,10 +254,6 @@
*/
@Override
public void onImeAppearing(@NonNull List<Animator> animators) {
- addAnimator(animators, mTitleView, R.attr.guidedStepImeAppearingAnimation);
- addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeAppearingAnimation);
- addAnimator(animators, mDescriptionView, R.attr.guidedStepImeAppearingAnimation);
- addAnimator(animators, mIconView, R.attr.guidedStepImeAppearingAnimation);
}
/**
@@ -242,21 +261,6 @@
*/
@Override
public void onImeDisappearing(@NonNull List<Animator> animators) {
- addAnimator(animators, mTitleView, R.attr.guidedStepImeDisappearingAnimation);
- addAnimator(animators, mBreadcrumbView, R.attr.guidedStepImeDisappearingAnimation);
- addAnimator(animators, mDescriptionView, R.attr.guidedStepImeDisappearingAnimation);
- addAnimator(animators, mIconView, R.attr.guidedStepImeDisappearingAnimation);
- }
-
- private void addAnimator(List<Animator> animators, View v, int attrId) {
- if (v != null) {
- Context ctx = v.getContext();
- TypedValue typedValue = new TypedValue();
- ctx.getTheme().resolveAttribute(attrId, typedValue, true);
- Animator animator = AnimatorInflater.loadAnimator(ctx, typedValue.resourceId);
- animator.setTarget(v);
- animators.add(animator);
- }
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
index 21986d5..2a39323 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedAction.java
@@ -13,13 +13,18 @@
*/
package android.support.v17.leanback.widget;
+import android.os.Bundle;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.StringRes;
import android.support.v17.leanback.R;
+import android.support.v4.content.ContextCompat;
import android.content.Context;
import android.content.Intent;
import android.graphics.drawable.Drawable;
import android.text.InputType;
-import android.util.Log;
+
+import java.util.List;
/**
* A data class which represents an action within a {@link
@@ -32,6 +37,10 @@
* <p>
* GuidedActions may optionally be checked. They may also indicate that they will request further
* user input on selection, in which case they will be displayed with a chevron indicator.
+ * <p>
+ * GuidedAction recommends to use {@link Builder}. When application subclass GuidedAction, it
+ * can subclass {@link BuilderBase}, implement its own builder() method where it should
+ * call {@link BuilderBase#applyValues(GuidedAction)}.
*/
public class GuidedAction extends Action {
@@ -89,45 +98,64 @@
*/
public static final long ACTION_ID_NO = -9;
+ static final int EDITING_NONE = 0;
+ static final int EDITING_TITLE = 1;
+ static final int EDITING_DESCRIPTION = 2;
+ static final int EDITING_ACTIVATOR_VIEW = 3;
+
/**
- * Builds a {@link GuidedAction} object. When subclass GuidedAction, you may override this
- * Builder class and call {@link #applyValues(GuidedAction)}.
+ * Base builder class to build a {@link GuidedAction} object. When subclass GuidedAction, you
+ * can override this BuilderBase class, implements your build() method which should call
+ * {@link #applyValues(GuidedAction)}. When using GuidedAction directly, use {@link Builder}.
*/
- public static class Builder {
+ public abstract static class BuilderBase<B extends BuilderBase> {
+ private Context mContext;
private long mId;
private CharSequence mTitle;
private CharSequence mEditTitle;
private CharSequence mDescription;
private CharSequence mEditDescription;
private Drawable mIcon;
- private boolean mChecked;
- private boolean mMultilineDescription;
- private boolean mHasNext;
- private boolean mInfoOnly;
- private boolean mEditable = false;
- private boolean mDescriptionEditable = false;
+ /**
+ * The mActionFlags holds various action states such as whether title or description are
+ * editable, or the action is focusable.
+ *
+ */
+ private int mActionFlags;
+
+ private int mEditable = EDITING_NONE;
private int mInputType = InputType.TYPE_CLASS_TEXT;
private int mDescriptionInputType = InputType.TYPE_CLASS_TEXT;
private int mEditInputType = InputType.TYPE_CLASS_TEXT;
private int mDescriptionEditInputType = InputType.TYPE_CLASS_TEXT;
private int mCheckSetId = NO_CHECK_SET;
- private boolean mEnabled = true;
- private boolean mFocusable = true;
+ private List<GuidedAction> mSubActions;
private Intent mIntent;
/**
- * Builds the GuidedAction corresponding to this Builder.
- * @return the GuidedAction as configured through this Builder.
+ * Creates a BuilderBase for GuidedAction or its subclass.
+ * @param context Context object used to build the GuidedAction.
*/
- public final GuidedAction build() {
- GuidedAction action = new GuidedAction();
- applyValues(action);
- return action;
+ public BuilderBase(Context context) {
+ mContext = context;
+ mActionFlags = PF_ENABLED | PF_FOCUSABLE | PF_AUTORESTORE;
}
/**
- * Subclass Builder may call this function to apply values.
- * @param action GuidedAction to apply Builder values.
+ * Returns Context of this Builder.
+ * @return Context of this Builder.
+ */
+ public Context getContext() {
+ return mContext;
+ }
+
+ private void setFlags(int flag, int mask) {
+ mActionFlags = (mActionFlags & ~mask) | (flag & mask);
+ }
+
+ /**
+ * Subclass of BuilderBase should call this function to apply values.
+ * @param action GuidedAction to apply BuilderBase values.
*/
protected final void applyValues(GuidedAction action) {
// Base Action values
@@ -141,85 +169,44 @@
// Subclass values
action.mIntent = mIntent;
action.mEditable = mEditable;
- action.mDescriptionEditable = mDescriptionEditable;
action.mInputType = mInputType;
action.mDescriptionInputType = mDescriptionInputType;
action.mEditInputType = mEditInputType;
action.mDescriptionEditInputType = mDescriptionEditInputType;
- action.mChecked = mChecked;
+ action.mActionFlags = mActionFlags;
action.mCheckSetId = mCheckSetId;
- action.mMultilineDescription = mMultilineDescription;
- action.mHasNext = mHasNext;
- action.mInfoOnly = mInfoOnly;
- action.mEnabled = mEnabled;
- action.mFocusable = mFocusable;
+ action.mSubActions = mSubActions;
}
/**
- * Construct a standard "OK" action with {@link GuidedAction#ACTION_ID_OK}.
- * @param context Context for loading action title.
- * @return The same Builder object.
+ * Construct a clickable action with associated id and auto assign pre-defined title for the
+ * action. If the id is not supported, the method simply does nothing.
+ * @param id One of {@link GuidedAction#ACTION_ID_OK} {@link GuidedAction#ACTION_ID_CANCEL}
+ * {@link GuidedAction#ACTION_ID_FINISH} {@link GuidedAction#ACTION_ID_CONTINUE}
+ * {@link GuidedAction#ACTION_ID_YES} {@link GuidedAction#ACTION_ID_NO}.
+ * @return The same BuilderBase object.
*/
- public Builder constructOK(Context context) {
- mId = ACTION_ID_OK;
- mTitle = context.getString(android.R.string.ok);
- return this;
- }
-
- /**
- * Construct a standard "Cancel" action with {@link GuidedAction#ACTION_ID_CANCEL}.
- * @param context Context for loading action title.
- * @return The same Builder object.
- */
- public Builder constructCancel(Context context) {
- mId = ACTION_ID_CANCEL;
- mTitle = context.getString(android.R.string.cancel);
- return this;
- }
-
- /**
- * Construct a standard "Finish" action with {@link GuidedAction#ACTION_ID_FINISH}.
- * @param context Context for loading action title.
- * @return The same Builder object.
- */
- public Builder constructFinish(Context context) {
- mId = ACTION_ID_FINISH;
- mTitle = context.getString(R.string.lb_guidedaction_finish_title);
- return this;
- }
-
- /**
- * Construct a standard "Continue" action with {@link GuidedAction#ACTION_ID_CONTINUE}.
- * @param context Context for loading action title.
- * @return The same Builder object.
- */
- public Builder constructContinue(Context context) {
- mId = ACTION_ID_CONTINUE;
- mHasNext = true;
- mTitle = context.getString(R.string.lb_guidedaction_continue_title);
- return this;
- }
-
- /**
- * Construct a standard "Yes" action with {@link GuidedAction#ACTION_ID_YES}.
- * @param context Context for loading action title.
- * @return The same Builder object.
- */
- public Builder constructYes(Context context) {
- mId = ACTION_ID_YES;
- mTitle = context.getString(android.R.string.yes);
- return this;
- }
-
- /**
- * Construct a standard "No" action with {@link GuidedAction#ACTION_ID_NO}.
- * @param context Context for loading action title.
- * @return The same Builder object.
- */
- public Builder constructNo(Context context) {
- mId = ACTION_ID_NO;
- mTitle = context.getString(android.R.string.no);
- return this;
+ public B clickAction(long id) {
+ if (id == ACTION_ID_OK) {
+ mId = ACTION_ID_OK;
+ mTitle = mContext.getString(android.R.string.ok);
+ } else if (id == ACTION_ID_CANCEL) {
+ mId = ACTION_ID_CANCEL;
+ mTitle = mContext.getString(android.R.string.cancel);
+ } else if (id == ACTION_ID_FINISH) {
+ mId = ACTION_ID_FINISH;
+ mTitle = mContext.getString(R.string.lb_guidedaction_finish_title);
+ } else if (id == ACTION_ID_CONTINUE) {
+ mId = ACTION_ID_CONTINUE;
+ mTitle = mContext.getString(R.string.lb_guidedaction_continue_title);
+ } else if (id == ACTION_ID_YES) {
+ mId = ACTION_ID_YES;
+ mTitle = mContext.getString(android.R.string.yes);
+ } else if (id == ACTION_ID_NO) {
+ mId = ACTION_ID_NO;
+ mTitle = mContext.getString(android.R.string.no);
+ }
+ return (B) this;
}
/**
@@ -227,9 +214,9 @@
* it is typically used to determine what to do when an action is clicked.
* @param id The ID to associate with this action.
*/
- public Builder id(long id) {
+ public B id(long id) {
mId = id;
- return this;
+ return (B) this;
}
/**
@@ -237,18 +224,40 @@
* action to be taken on click, e.g. "Continue" or "Cancel".
* @param title The title for this action.
*/
- public Builder title(CharSequence title) {
+ public B title(CharSequence title) {
mTitle = title;
- return this;
+ return (B) this;
+ }
+
+ /**
+ * Sets the title for this action. The title is typically a short string indicating the
+ * action to be taken on click, e.g. "Continue" or "Cancel".
+ * @param titleResourceId The resource id of title for this action.
+ */
+ public B title(@StringRes int titleResourceId) {
+ mTitle = getContext().getString(titleResourceId);
+ return (B) this;
}
/**
* Sets the optional title text to edit. When TextView is activated, the edit title
* replaces the string of title.
+ * @param editTitle The optional title text to edit when TextView is activated.
*/
- public Builder editTitle(CharSequence editTitle) {
+ public B editTitle(CharSequence editTitle) {
mEditTitle = editTitle;
- return this;
+ return (B) this;
+ }
+
+ /**
+ * Sets the optional title text to edit. When TextView is activated, the edit title
+ * replaces the string of title.
+ * @param editTitleResourceId String resource id of the optional title text to edit when
+ * TextView is activated.
+ */
+ public B editTitle(@StringRes int editTitleResourceId) {
+ mEditTitle = getContext().getString(editTitleResourceId);
+ return (B) this;
}
/**
@@ -256,9 +265,19 @@
* providing extra information on what the action will do.
* @param description The description for this action.
*/
- public Builder description(CharSequence description) {
+ public B description(CharSequence description) {
mDescription = description;
- return this;
+ return (B) this;
+ }
+
+ /**
+ * Sets the description for this action. The description is typically a longer string
+ * providing extra information on what the action will do.
+ * @param descriptionResourceId String resource id of the description for this action.
+ */
+ public B description(@StringRes int descriptionResourceId) {
+ mDescription = getContext().getString(descriptionResourceId);
+ return (B) this;
}
/**
@@ -266,9 +285,20 @@
* description replaces the string of description.
* @param description The description to edit for this action.
*/
- public Builder editDescription(CharSequence description) {
+ public B editDescription(CharSequence description) {
mEditDescription = description;
- return this;
+ return (B) this;
+ }
+
+ /**
+ * Sets the optional description text to edit. When TextView is activated, the edit
+ * description replaces the string of description.
+ * @param descriptionResourceId String resource id of the description to edit for this
+ * action.
+ */
+ public B editDescription(@StringRes int descriptionResourceId) {
+ mEditDescription = getContext().getString(descriptionResourceId);
+ return (B) this;
}
/**
@@ -276,29 +306,41 @@
* directly when the action is clicked.
* @param intent The intent associated with this action.
*/
- public Builder intent(Intent intent) {
+ public B intent(Intent intent) {
mIntent = intent;
- return this;
+ return (B) this;
}
/**
* Sets the action's icon drawable.
* @param icon The drawable for the icon associated with this action.
*/
- public Builder icon(Drawable icon) {
+ public B icon(Drawable icon) {
mIcon = icon;
- return this;
+ return (B) this;
}
/**
* Sets the action's icon drawable by retrieving it by resource ID from the specified
* context. This is a convenience function that simply looks up the drawable and calls
- * {@link #icon}.
+ * {@link #icon(Drawable)}.
* @param iconResourceId The resource ID for the icon associated with this action.
* @param context The context whose resource ID should be retrieved.
+ * @deprecated Use {@link #icon(int)}.
*/
- public Builder iconResourceId(int iconResourceId, Context context) {
- return icon(context.getResources().getDrawable(iconResourceId));
+ @Deprecated
+ public B iconResourceId(@DrawableRes int iconResourceId, Context context) {
+ return icon(ContextCompat.getDrawable(context, iconResourceId));
+ }
+
+ /**
+ * Sets the action's icon drawable by retrieving it by resource ID from Builder's
+ * context. This is a convenience function that simply looks up the drawable and calls
+ * {@link #icon(Drawable)}.
+ * @param iconResourceId The resource ID for the icon associated with this action.
+ */
+ public B icon(@DrawableRes int iconResourceId) {
+ return icon(ContextCompat.getDrawable(getContext(), iconResourceId));
}
/**
@@ -306,24 +348,54 @@
* checked, or belong to a check set.
* @param editable Whether this action is editable.
*/
- public Builder editable(boolean editable) {
- mEditable = editable;
- if (mChecked || mCheckSetId != NO_CHECK_SET) {
+ public B editable(boolean editable) {
+ if (!editable) {
+ if (mEditable == EDITING_TITLE) {
+ mEditable = EDITING_NONE;
+ }
+ return (B) this;
+ }
+ mEditable = EDITING_TITLE;
+ if (isChecked() || mCheckSetId != NO_CHECK_SET) {
throw new IllegalArgumentException("Editable actions cannot also be checked");
}
- return this;
+ return (B) this;
}
/**
* Indicates whether this action's description is editable
* @param editable Whether this action description is editable.
*/
- public Builder descriptionEditable(boolean editable) {
- mDescriptionEditable = editable;
- if (mChecked || mCheckSetId != NO_CHECK_SET) {
+ public B descriptionEditable(boolean editable) {
+ if (!editable) {
+ if (mEditable == EDITING_DESCRIPTION) {
+ mEditable = EDITING_NONE;
+ }
+ return (B) this;
+ }
+ mEditable = EDITING_DESCRIPTION;
+ if (isChecked() || mCheckSetId != NO_CHECK_SET) {
throw new IllegalArgumentException("Editable actions cannot also be checked");
}
- return this;
+ return (B) this;
+ }
+
+ /**
+ * Indicates whether this action has a view can be activated to edit, e.g. a DatePicker.
+ * @param editable Whether this action has view can be activated to edit.
+ */
+ public B hasEditableActivatorView(boolean editable) {
+ if (!editable) {
+ if (mEditable == EDITING_ACTIVATOR_VIEW) {
+ mEditable = EDITING_NONE;
+ }
+ return (B) this;
+ }
+ mEditable = EDITING_ACTIVATOR_VIEW;
+ if (isChecked() || mCheckSetId != NO_CHECK_SET) {
+ throw new IllegalArgumentException("Editable actions cannot also be checked");
+ }
+ return (B) this;
}
/**
@@ -331,9 +403,9 @@
*
* @param inputType InputType for the action title not in editing.
*/
- public Builder inputType(int inputType) {
+ public B inputType(int inputType) {
mInputType = inputType;
- return this;
+ return (B) this;
}
/**
@@ -341,9 +413,9 @@
*
* @param inputType InputType for the action description not in editing.
*/
- public Builder descriptionInputType(int inputType) {
+ public B descriptionInputType(int inputType) {
mDescriptionInputType = inputType;
- return this;
+ return (B) this;
}
@@ -352,9 +424,9 @@
*
* @param inputType InputType for the action title in editing.
*/
- public Builder editInputType(int inputType) {
+ public B editInputType(int inputType) {
mEditInputType = inputType;
- return this;
+ return (B) this;
}
/**
@@ -362,37 +434,40 @@
*
* @param inputType InputType for the action description in editing.
*/
- public Builder descriptionEditInputType(int inputType) {
+ public B descriptionEditInputType(int inputType) {
mDescriptionEditInputType = inputType;
- return this;
+ return (B) this;
}
+ private boolean isChecked() {
+ return (mActionFlags & PF_CHECKED) == PF_CHECKED;
+ }
/**
* Indicates whether this action is initially checked.
* @param checked Whether this action is checked.
*/
- public Builder checked(boolean checked) {
- mChecked = checked;
- if (mEditable || mDescriptionEditable) {
+ public B checked(boolean checked) {
+ setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
+ if (mEditable != EDITING_NONE) {
throw new IllegalArgumentException("Editable actions cannot also be checked");
}
- return this;
+ return (B) this;
}
/**
* Indicates whether this action is part of a single-select group similar to radio buttons
* or this action is a checkbox. When one item in a check set is checked, all others with
- * the same check set ID will be nchecked automatically.
+ * the same check set ID will be checked automatically.
* @param checkSetId The check set ID, or {@link GuidedAction#NO_CHECK_SET} to indicate not
* radio or checkbox, or {@link GuidedAction#CHECKBOX_CHECK_SET_ID} to indicate a checkbox.
*/
- public Builder checkSetId(int checkSetId) {
+ public B checkSetId(int checkSetId) {
mCheckSetId = checkSetId;
- if (mEditable || mDescriptionEditable) {
+ if (mEditable != EDITING_NONE) {
throw new IllegalArgumentException("Editable actions cannot also be in check sets");
}
- return this;
+ return (B) this;
}
/**
@@ -400,64 +475,126 @@
* appropriately.
* @param multilineDescription Whether this action has a multiline description.
*/
- public Builder multilineDescription(boolean multilineDescription) {
- mMultilineDescription = multilineDescription;
- return this;
+ public B multilineDescription(boolean multilineDescription) {
+ setFlags(multilineDescription ? PF_MULTI_lINE_DESCRIPTION : 0,
+ PF_MULTI_lINE_DESCRIPTION);
+ return (B) this;
}
/**
* Indicates whether this action has a next state and should display a chevron.
* @param hasNext Whether this action has a next state.
*/
- public Builder hasNext(boolean hasNext) {
- mHasNext = hasNext;
- return this;
+ public B hasNext(boolean hasNext) {
+ setFlags(hasNext ? PF_HAS_NEXT : 0, PF_HAS_NEXT);
+ return (B) this;
}
/**
* Indicates whether this action is for information purposes only and cannot be clicked.
* @param infoOnly Whether this action has a next state.
*/
- public Builder infoOnly(boolean infoOnly) {
- mInfoOnly = infoOnly;
- return this;
+ public B infoOnly(boolean infoOnly) {
+ setFlags(infoOnly ? PF_INFO_ONLY : 0, PF_INFO_ONLY);
+ return (B) this;
}
/**
* Indicates whether this action is enabled. If not enabled, an action cannot be clicked.
* @param enabled Whether the action is enabled.
*/
- public Builder enabled(boolean enabled) {
- mEnabled = enabled;
- return this;
+ public B enabled(boolean enabled) {
+ setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
+ return (B) this;
}
/**
* Indicates whether this action can take focus.
* @param focusable
- * @return The same Builder object.
+ * @return The same BuilderBase object.
*/
- public Builder focusable(boolean focusable) {
- mFocusable = focusable;
- return this;
+ public B focusable(boolean focusable) {
+ setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
+ return (B) this;
}
+
+ /**
+ * Sets sub actions list.
+ * @param subActions
+ * @return The same BuilderBase object.
+ */
+ public B subActions(List<GuidedAction> subActions) {
+ mSubActions = subActions;
+ return (B) this;
+ }
+
+ /**
+ * Explicitly sets auto restore feature on the GuidedAction. It's by default true.
+ * @param autoSaveRestoreEnanbled True if turn on auto save/restore of GuidedAction content,
+ * false otherwise.
+ * @return The same BuilderBase object.
+ * @see GuidedAction#isAutoSaveRestoreEnabled()
+ */
+ public B autoSaveRestoreEnabled(boolean autoSaveRestoreEnanbled) {
+ setFlags(autoSaveRestoreEnanbled ? PF_AUTORESTORE : 0, PF_AUTORESTORE);
+ return (B) this;
+ }
+
}
+ /**
+ * Builds a {@link GuidedAction} object.
+ */
+ public static class Builder extends BuilderBase<Builder> {
+
+ /**
+ * @deprecated Use {@link GuidedAction.Builder#GuidedAction.Builder(Context)}.
+ */
+ @Deprecated
+ public Builder() {
+ super(null);
+ }
+
+ /**
+ * Creates a Builder for GuidedAction.
+ * @param context Context to build GuidedAction.
+ */
+ public Builder(Context context) {
+ super(context);
+ }
+
+ /**
+ * Builds the GuidedAction corresponding to this Builder.
+ * @return The GuidedAction as configured through this Builder.
+ */
+ public GuidedAction build() {
+ GuidedAction action = new GuidedAction();
+ applyValues(action);
+ return action;
+ }
+
+ }
+
+ private static final int PF_CHECKED = 0x00000001;
+ private static final int PF_MULTI_lINE_DESCRIPTION = 0x00000002;
+ private static final int PF_HAS_NEXT = 0x00000004;
+ private static final int PF_INFO_ONLY = 0x00000008;
+ private static final int PF_ENABLED = 0x00000010;
+ private static final int PF_FOCUSABLE = 0x00000020;
+ private static final int PF_AUTORESTORE = 0x00000040;
+ private int mActionFlags;
+
private CharSequence mEditTitle;
private CharSequence mEditDescription;
- private boolean mEditable;
- private boolean mDescriptionEditable;
+ private int mEditable;
private int mInputType;
private int mDescriptionInputType;
private int mEditInputType;
private int mDescriptionEditInputType;
- private boolean mMultilineDescription;
- private boolean mHasNext;
- private boolean mChecked;
- private boolean mInfoOnly;
+
private int mCheckSetId;
- private boolean mEnabled;
- private boolean mFocusable;
+
+ private List<GuidedAction> mSubActions;
private Intent mIntent;
@@ -465,6 +602,10 @@
super(0);
}
+ private void setFlags(int flag, int mask) {
+ mActionFlags = (mActionFlags & ~mask) | (flag & mask);
+ }
+
/**
* Returns the title of this action.
* @return The title set when this action was built.
@@ -550,11 +691,19 @@
}
/**
+ * Sets the intent of this action.
+ * @param intent New intent to set on this action.
+ */
+ public void setIntent(Intent intent) {
+ mIntent = intent;
+ }
+
+ /**
* Returns whether this action title is editable.
* @return true if the action title is editable, false otherwise.
*/
public boolean isEditable() {
- return mEditable;
+ return mEditable == EDITING_TITLE;
}
/**
@@ -562,7 +711,23 @@
* @return true if the action description is editable, false otherwise.
*/
public boolean isDescriptionEditable() {
- return mDescriptionEditable;
+ return mEditable == EDITING_DESCRIPTION;
+ }
+
+ /**
+ * Returns if this action has editable title or editable description.
+ * @return True if this action has editable title or editable description, false otherwise.
+ */
+ public boolean hasTextEditable() {
+ return mEditable == EDITING_TITLE || mEditable == EDITING_DESCRIPTION;
+ }
+
+ /**
+ * Returns whether this action can be activated to edit, e.g. a DatePicker.
+ * @return true if the action can be activated to edit.
+ */
+ public boolean hasEditableActivatorView() {
+ return mEditable == EDITING_ACTIVATOR_VIEW;
}
/**
@@ -603,7 +768,7 @@
* @return true if the action is currently checked, false otherwise.
*/
public boolean isChecked() {
- return mChecked;
+ return (mActionFlags & PF_CHECKED) == PF_CHECKED;
}
/**
@@ -611,7 +776,7 @@
* @param checked Whether this action should be checked.
*/
public void setChecked(boolean checked) {
- mChecked = checked;
+ setFlags(checked ? PF_CHECKED : 0, PF_CHECKED);
}
/**
@@ -633,7 +798,7 @@
* otherwise.
*/
public boolean hasMultilineDescription() {
- return mMultilineDescription;
+ return (mActionFlags & PF_MULTI_lINE_DESCRIPTION) == PF_MULTI_lINE_DESCRIPTION;
}
/**
@@ -641,7 +806,7 @@
* @return true if the action is currently enabled, false otherwise.
*/
public boolean isEnabled() {
- return mEnabled;
+ return (mActionFlags & PF_ENABLED) == PF_ENABLED;
}
/**
@@ -649,7 +814,7 @@
* @param enabled Whether this action should be enabled.
*/
public void setEnabled(boolean enabled) {
- mEnabled = enabled;
+ setFlags(enabled ? PF_ENABLED : 0, PF_ENABLED);
}
/**
@@ -657,7 +822,7 @@
* @return true if the action is currently focusable, false otherwise.
*/
public boolean isFocusable() {
- return mFocusable;
+ return (mActionFlags & PF_FOCUSABLE) == PF_FOCUSABLE;
}
/**
@@ -665,7 +830,7 @@
* @param focusable Whether this action should be focusable.
*/
public void setFocusable(boolean focusable) {
- mFocusable = focusable;
+ setFlags(focusable ? PF_FOCUSABLE : 0, PF_FOCUSABLE);
}
/**
@@ -674,7 +839,7 @@
* @return true if the action will request further user input when selected, false otherwise.
*/
public boolean hasNext() {
- return mHasNext;
+ return (mActionFlags & PF_HAS_NEXT) == PF_HAS_NEXT;
}
/**
@@ -685,7 +850,106 @@
* @return true if will only display information, false otherwise.
*/
public boolean infoOnly() {
- return mInfoOnly;
+ return (mActionFlags & PF_INFO_ONLY) == PF_INFO_ONLY;
+ }
+
+ /**
+ * Change sub actions list.
+ * @param actions Sub actions list to set on this action. Sets null to disable sub actions.
+ */
+ public void setSubActions(List<GuidedAction> actions) {
+ mSubActions = actions;
+ }
+
+ /**
+ * @return List of sub actions or null if sub actions list is not enabled.
+ */
+ public List<GuidedAction> getSubActions() {
+ return mSubActions;
+ }
+
+ /**
+ * @return True if has sub actions list, even it's currently empty.
+ */
+ public boolean hasSubActions() {
+ return mSubActions != null;
+ }
+
+ /**
+ * Returns true if Action will be saved to instanceState and restored later, false otherwise.
+ * The default value is true. When isAutoSaveRestoreEnabled() is true and {@link #getId()} is
+ * not {@link #NO_ID}:
+ * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+ * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+ * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+ * <li>{@link GuidedDatePickerAction} will be saved</li>
+ * App may explicitly disable auto restore and handle by itself. App should override Fragment
+ * onSaveInstanceState() and onCreateActions()
+ * @return True if Action will be saved to instanceState and restored later, false otherwise.
+ */
+ public final boolean isAutoSaveRestoreEnabled() {
+ return (mActionFlags & PF_AUTORESTORE) == PF_AUTORESTORE;
+ }
+
+ /**
+ * Save action into a bundle using a given key. When isAutoRestoreEna() is true:
+ * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+ * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+ * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+ * <li>{@link GuidedDatePickerAction} will be saved</li>
+ * Subclass may override this method.
+ * @param bundle Bundle to save the Action.
+ * @param key Key used to save the Action.
+ */
+ public void onSaveInstanceState(Bundle bundle, String key) {
+ if (needAutoSaveTitle() && getTitle() != null) {
+ bundle.putString(key, getTitle().toString());
+ } else if (needAutoSaveDescription() && getDescription() != null) {
+ bundle.putString(key, getDescription().toString());
+ } else if (getCheckSetId() != NO_CHECK_SET) {
+ bundle.putBoolean(key, isChecked());
+ }
+ }
+
+ /**
+ * Restore action from a bundle using a given key. When isAutoRestore() is true:
+ * <li>{@link #isEditable()} is true: save text of {@link #getTitle()}</li>
+ * <li>{@link #isDescriptionEditable()} is true: save text of {@link #getDescription()}</li>
+ * <li>{@link #getCheckSetId()} is not {@link #NO_CHECK_SET}: save {@link #isChecked()}}</li>
+ * <li>{@link GuidedDatePickerAction} will be saved</li>
+ * Subclass may override this method.
+ * @param bundle Bundle to restore the Action from.
+ * @param key Key used to restore the Action.
+ */
+ public void onRestoreInstanceState(Bundle bundle, String key) {
+ if (needAutoSaveTitle()) {
+ String title = bundle.getString(key);
+ if (title != null) {
+ setTitle(title);
+ }
+ } else if (needAutoSaveDescription()) {
+ String description = bundle.getString(key);
+ if (description != null) {
+ setDescription(description);
+ }
+ } else if (getCheckSetId() != NO_CHECK_SET) {
+ setChecked(bundle.getBoolean(key, isChecked()));
+ }
+ }
+
+ final static boolean isPasswordVariant(int inputType) {
+ final int variantion = inputType & InputType.TYPE_MASK_VARIATION;
+ return variantion == InputType.TYPE_TEXT_VARIATION_PASSWORD
+ || variantion == InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD
+ || variantion == InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD;
+ }
+
+ final boolean needAutoSaveTitle() {
+ return isEditable() && !isPasswordVariant(getEditInputType());
+ }
+
+ final boolean needAutoSaveDescription() {
+ return isDescriptionEditable() && !isPasswordVariant(getDescriptionEditInputType());
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
new file mode 100644
index 0000000..0d53693
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -0,0 +1,488 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.database.DataSetObserver;
+import android.media.AudioManager;
+import android.support.v17.leanback.R;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.ViewHolder;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.EditText;
+import android.widget.ImageView;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * GuidedActionAdapter instantiates views for guided actions, and manages their interactions.
+ * Presentation (view creation and state animation) is delegated to a {@link
+ * GuidedActionsStylist}, while clients are notified of interactions via
+ * {@link GuidedActionAdapter.ClickListener} and {@link GuidedActionAdapter.FocusListener}.
+ * @hide
+ */
+public class GuidedActionAdapter extends RecyclerView.Adapter {
+ private static final String TAG = "GuidedActionAdapter";
+ private static final boolean DEBUG = false;
+
+ private static final String TAG_EDIT = "EditableAction";
+ private static final boolean DEBUG_EDIT = false;
+
+ /**
+ * Object listening for click events within a {@link GuidedActionAdapter}.
+ */
+ public interface ClickListener {
+
+ /**
+ * Called when the user clicks on an action.
+ */
+ public void onGuidedActionClicked(GuidedAction action);
+
+ }
+
+ /**
+ * Object listening for focus events within a {@link GuidedActionAdapter}.
+ */
+ public interface FocusListener {
+
+ /**
+ * Called when the user focuses on an action.
+ */
+ public void onGuidedActionFocused(GuidedAction action);
+ }
+
+ /**
+ * Object listening for edit events within a {@link GuidedActionAdapter}.
+ */
+ public interface EditListener {
+
+ /**
+ * Called when the user exits edit mode on an action.
+ */
+ public void onGuidedActionEditCanceled(GuidedAction action);
+
+ /**
+ * Called when the user exits edit mode on an action and process confirm button in IME.
+ */
+ public long onGuidedActionEditedAndProceed(GuidedAction action);
+
+ /**
+ * Called when Ime Open
+ */
+ public void onImeOpen();
+
+ /**
+ * Called when Ime Close
+ */
+ public void onImeClose();
+ }
+
+ private final boolean mIsSubAdapter;
+ private final ActionOnKeyListener mActionOnKeyListener;
+ private final ActionOnFocusListener mActionOnFocusListener;
+ private final ActionEditListener mActionEditListener;
+ private final List<GuidedAction> mActions;
+ private ClickListener mClickListener;
+ private final GuidedActionsStylist mStylist;
+ private final View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (v != null && v.getWindowToken() != null && getRecyclerView() != null) {
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+ getRecyclerView().getChildViewHolder(v);
+ GuidedAction action = avh.getAction();
+ if (action.hasTextEditable()) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme by click");
+ mGroup.openIme(GuidedActionAdapter.this, avh);
+ } else if (action.hasEditableActivatorView()) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "toggle editing mode by click");
+ getGuidedActionsStylist().setEditingMode(avh, avh.getAction(),
+ !avh.isInEditingActivatorView());
+ } else {
+ handleCheckedActions(avh);
+ if (action.isEnabled() && !action.infoOnly()) {
+ performOnActionClick(avh);
+ }
+ }
+ }
+ }
+ };
+ GuidedActionAdapterGroup mGroup;
+
+ /**
+ * Constructs a GuidedActionAdapter with the given list of guided actions, the given click and
+ * focus listeners, and the given presenter.
+ * @param actions The list of guided actions this adapter will manage.
+ * @param focusListener The focus listener for items in this adapter.
+ * @param presenter The presenter that will manage the display of items in this adapter.
+ */
+ public GuidedActionAdapter(List<GuidedAction> actions, ClickListener clickListener,
+ FocusListener focusListener, GuidedActionsStylist presenter, boolean isSubAdapter) {
+ super();
+ mActions = actions == null ? new ArrayList<GuidedAction>() :
+ new ArrayList<GuidedAction>(actions);
+ mClickListener = clickListener;
+ mStylist = presenter;
+ mActionOnKeyListener = new ActionOnKeyListener();
+ mActionOnFocusListener = new ActionOnFocusListener(focusListener);
+ mActionEditListener = new ActionEditListener();
+ mIsSubAdapter = isSubAdapter;
+ }
+
+ /**
+ * Sets the list of actions managed by this adapter.
+ * @param actions The list of actions to be managed.
+ */
+ public void setActions(List<GuidedAction> actions) {
+ mActionOnFocusListener.unFocus();
+ mActions.clear();
+ mActions.addAll(actions);
+ notifyDataSetChanged();
+ }
+
+ /**
+ * Returns the count of actions managed by this adapter.
+ * @return The count of actions managed by this adapter.
+ */
+ public int getCount() {
+ return mActions.size();
+ }
+
+ /**
+ * Returns the GuidedAction at the given position in the managed list.
+ * @param position The position of the desired GuidedAction.
+ * @return The GuidedAction at the given position.
+ */
+ public GuidedAction getItem(int position) {
+ return mActions.get(position);
+ }
+
+ /**
+ * Return index of action in array
+ * @param action Action to search index.
+ * @return Index of Action in array.
+ */
+ public int indexOf(GuidedAction action) {
+ return mActions.indexOf(action);
+ }
+
+ /**
+ * @return GuidedActionsStylist used to build the actions list UI.
+ */
+ public GuidedActionsStylist getGuidedActionsStylist() {
+ return mStylist;
+ }
+
+ /**
+ * Sets the click listener for items managed by this adapter.
+ * @param clickListener The click listener for this adapter.
+ */
+ public void setClickListener(ClickListener clickListener) {
+ mClickListener = clickListener;
+ }
+
+ /**
+ * Sets the focus listener for items managed by this adapter.
+ * @param focusListener The focus listener for this adapter.
+ */
+ public void setFocusListener(FocusListener focusListener) {
+ mActionOnFocusListener.setFocusListener(focusListener);
+ }
+
+ /**
+ * Used for serialization only.
+ * @hide
+ */
+ public List<GuidedAction> getActions() {
+ return new ArrayList<GuidedAction>(mActions);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getItemViewType(int position) {
+ return mStylist.getItemViewType(mActions.get(position));
+ }
+
+ private RecyclerView getRecyclerView() {
+ return mIsSubAdapter ? mStylist.getSubActionsGridView() : mStylist.getActionsGridView();
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ GuidedActionsStylist.ViewHolder vh = mStylist.onCreateViewHolder(parent, viewType);
+ View v = vh.itemView;
+ v.setOnKeyListener(mActionOnKeyListener);
+ v.setOnClickListener(mOnClickListener);
+ v.setOnFocusChangeListener(mActionOnFocusListener);
+
+ setupListeners(vh.getEditableTitleView());
+ setupListeners(vh.getEditableDescriptionView());
+
+ return vh;
+ }
+
+ private void setupListeners(EditText edit) {
+ if (edit != null) {
+ edit.setPrivateImeOptions("EscapeNorth=1;");
+ edit.setOnEditorActionListener(mActionEditListener);
+ if (edit instanceof ImeKeyMonitor) {
+ ImeKeyMonitor monitor = (ImeKeyMonitor)edit;
+ monitor.setImeKeyListener(mActionEditListener);
+ }
+ }
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ if (position >= mActions.size()) {
+ return;
+ }
+ final GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)holder;
+ GuidedAction action = mActions.get(position);
+ mStylist.onBindViewHolder(avh, action);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public int getItemCount() {
+ return mActions.size();
+ }
+
+ private class ActionOnFocusListener implements View.OnFocusChangeListener {
+
+ private FocusListener mFocusListener;
+ private View mSelectedView;
+
+ ActionOnFocusListener(FocusListener focusListener) {
+ mFocusListener = focusListener;
+ }
+
+ public void setFocusListener(FocusListener focusListener) {
+ mFocusListener = focusListener;
+ }
+
+ public void unFocus() {
+ if (mSelectedView != null && getRecyclerView() != null) {
+ ViewHolder vh = getRecyclerView().getChildViewHolder(mSelectedView);
+ if (vh != null) {
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)vh;
+ mStylist.onAnimateItemFocused(avh, false);
+ } else {
+ Log.w(TAG, "RecyclerView returned null view holder",
+ new Throwable());
+ }
+ }
+ }
+
+ @Override
+ public void onFocusChange(View v, boolean hasFocus) {
+ if (getRecyclerView() == null) {
+ return;
+ }
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+ getRecyclerView().getChildViewHolder(v);
+ if (hasFocus) {
+ mSelectedView = v;
+ if (mFocusListener != null) {
+ // We still call onGuidedActionFocused so that listeners can clear
+ // state if they want.
+ mFocusListener.onGuidedActionFocused(avh.getAction());
+ }
+ } else {
+ if (mSelectedView == v) {
+ mStylist.onAnimateItemPressedCancelled(avh);
+ mSelectedView = null;
+ }
+ }
+ mStylist.onAnimateItemFocused(avh, hasFocus);
+ }
+ }
+
+ public GuidedActionsStylist.ViewHolder findSubChildViewHolder(View v) {
+ // Needed because RecyclerView.getChildViewHolder does not traverse the hierarchy
+ if (getRecyclerView() == null) {
+ return null;
+ }
+ GuidedActionsStylist.ViewHolder result = null;
+ ViewParent parent = v.getParent();
+ while (parent != getRecyclerView() && parent != null && v != null) {
+ v = (View)parent;
+ parent = parent.getParent();
+ }
+ if (parent != null && v != null) {
+ result = (GuidedActionsStylist.ViewHolder)getRecyclerView().getChildViewHolder(v);
+ }
+ return result;
+ }
+
+ public void handleCheckedActions(GuidedActionsStylist.ViewHolder avh) {
+ GuidedAction action = avh.getAction();
+ int actionCheckSetId = action.getCheckSetId();
+ if (getRecyclerView() != null && actionCheckSetId != GuidedAction.NO_CHECK_SET) {
+ // Find any actions that are checked and are in the same group
+ // as the selected action. Fade their checkmarks out.
+ if (actionCheckSetId != GuidedAction.CHECKBOX_CHECK_SET_ID) {
+ for (int i = 0, size = mActions.size(); i < size; i++) {
+ GuidedAction a = mActions.get(i);
+ if (a != action && a.getCheckSetId() == actionCheckSetId && a.isChecked()) {
+ a.setChecked(false);
+ GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder)
+ getRecyclerView().findViewHolderForPosition(i);
+ if (vh != null) {
+ mStylist.onAnimateItemChecked(vh, false);
+ }
+ }
+ }
+ }
+
+ // If we we'ren't already checked, fade our checkmark in.
+ if (!action.isChecked()) {
+ action.setChecked(true);
+ mStylist.onAnimateItemChecked(avh, true);
+ } else {
+ if (actionCheckSetId == GuidedAction.CHECKBOX_CHECK_SET_ID) {
+ action.setChecked(false);
+ mStylist.onAnimateItemChecked(avh, false);
+ }
+ }
+ }
+ }
+
+ public void performOnActionClick(GuidedActionsStylist.ViewHolder avh) {
+ if (mClickListener != null) {
+ mClickListener.onGuidedActionClicked(avh.getAction());
+ }
+ }
+
+ private class ActionOnKeyListener implements View.OnKeyListener {
+
+ private boolean mKeyPressed = false;
+
+ /**
+ * Now only handles KEYCODE_ENTER and KEYCODE_NUMPAD_ENTER key event.
+ */
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (v == null || event == null || getRecyclerView() == null) {
+ return false;
+ }
+ boolean handled = false;
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_NUMPAD_ENTER:
+ case KeyEvent.KEYCODE_BUTTON_X:
+ case KeyEvent.KEYCODE_BUTTON_Y:
+ case KeyEvent.KEYCODE_ENTER:
+
+ GuidedActionsStylist.ViewHolder avh = (GuidedActionsStylist.ViewHolder)
+ getRecyclerView().getChildViewHolder(v);
+ GuidedAction action = avh.getAction();
+
+ if (!action.isEnabled() || action.infoOnly()) {
+ if (event.getAction() == KeyEvent.ACTION_DOWN) {
+ // TODO: requires API 19
+ //playSound(v, AudioManager.FX_KEYPRESS_INVALID);
+ }
+ return true;
+ }
+
+ switch (event.getAction()) {
+ case KeyEvent.ACTION_DOWN:
+ if (DEBUG) {
+ Log.d(TAG, "Enter Key down");
+ }
+ if (!mKeyPressed) {
+ mKeyPressed = true;
+ mStylist.onAnimateItemPressed(avh, mKeyPressed);
+ }
+ break;
+ case KeyEvent.ACTION_UP:
+ if (DEBUG) {
+ Log.d(TAG, "Enter Key up");
+ }
+ // Sometimes we are losing ACTION_DOWN for the first ENTER after pressed
+ // Escape in IME.
+ if (mKeyPressed) {
+ mKeyPressed = false;
+ mStylist.onAnimateItemPressed(avh, mKeyPressed);
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ default:
+ break;
+ }
+ return handled;
+ }
+
+ }
+
+ private class ActionEditListener implements OnEditorActionListener,
+ ImeKeyMonitor.ImeKeyListener {
+
+ @Override
+ public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME action: " + actionId);
+ boolean handled = false;
+ if (actionId == EditorInfo.IME_ACTION_NEXT ||
+ actionId == EditorInfo.IME_ACTION_DONE) {
+ mGroup.fillAndGoNext(GuidedActionAdapter.this, v);
+ handled = true;
+ } else if (actionId == EditorInfo.IME_ACTION_NONE) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme escape north");
+ // Escape north handling: stay on current item, but close editor
+ handled = true;
+ mGroup.fillAndStay(GuidedActionAdapter.this, v);
+ }
+ return handled;
+ }
+
+ @Override
+ public boolean onKeyPreIme(EditText editText, int keyCode, KeyEvent event) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "IME key: " + keyCode);
+ if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_UP) {
+ mGroup.fillAndStay(GuidedActionAdapter.this, editText);
+ } else if (keyCode == KeyEvent.KEYCODE_ENTER && event.getAction() ==
+ KeyEvent.ACTION_UP) {
+ mGroup.fillAndGoNext(GuidedActionAdapter.this, editText);
+ }
+ return false;
+ }
+
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
new file mode 100644
index 0000000..7198300e
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapterGroup.java
@@ -0,0 +1,196 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.widget.GuidedActionAdapter.ClickListener;
+import android.support.v17.leanback.widget.GuidedActionAdapter.EditListener;
+import android.util.Log;
+import android.util.Pair;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.view.inputmethod.InputMethodManager;
+import android.widget.EditText;
+import android.widget.TextView;
+import android.widget.TextView.OnEditorActionListener;
+
+import java.util.ArrayList;
+
+/**
+ * Internal implementation manages a group of GuidedActionAdapters, control the next action after
+ * editing finished, maintain the Ime open/close status.
+ * @hide
+ */
+public class GuidedActionAdapterGroup {
+
+ private static final String TAG_EDIT = "EditableAction";
+ private static final boolean DEBUG_EDIT = false;
+
+ ArrayList<Pair<GuidedActionAdapter, GuidedActionAdapter>> mAdapters =
+ new ArrayList<Pair<GuidedActionAdapter, GuidedActionAdapter>>();
+ private boolean mImeOpened;
+ private EditListener mEditListener;
+
+ public void addAdpter(GuidedActionAdapter adapter1, GuidedActionAdapter adapter2) {
+ mAdapters.add(new Pair<GuidedActionAdapter, GuidedActionAdapter>(adapter1, adapter2));
+ if (adapter1 != null) {
+ adapter1.mGroup = this;
+ }
+ if (adapter2 != null) {
+ adapter2.mGroup = this;
+ }
+ }
+
+ public GuidedActionAdapter getNextAdapter(GuidedActionAdapter adapter) {
+ for (int i = 0; i < mAdapters.size(); i++) {
+ Pair<GuidedActionAdapter, GuidedActionAdapter> pair = mAdapters.get(i);
+ if (pair.first == adapter) {
+ return pair.second;
+ }
+ }
+ return null;
+ }
+
+ public void setEditListener(EditListener listener) {
+ mEditListener = listener;
+ }
+
+ boolean focusToNextAction(GuidedActionAdapter adapter, GuidedAction action, long nextActionId) {
+ // for ACTION_ID_NEXT, we first find out the matching index in Actions list.
+ int index = 0;
+ if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
+ index = adapter.indexOf(action);
+ if (index < 0) {
+ return false;
+ }
+ // start from next, if reach end, will go next Adapter below
+ index++;
+ }
+
+ do {
+ int size = adapter.getCount();
+ if (nextActionId == GuidedAction.ACTION_ID_NEXT) {
+ while (index < size && !adapter.getItem(index).isFocusable()) {
+ index++;
+ }
+ } else {
+ while (index < size && adapter.getItem(index).getId() != nextActionId) {
+ index++;
+ }
+ }
+ if (index < size) {
+ GuidedActionsStylist.ViewHolder vh =
+ (GuidedActionsStylist.ViewHolder) adapter.getGuidedActionsStylist()
+ .getActionsGridView().findViewHolderForPosition(index);
+ if (vh != null) {
+ if (vh.getAction().hasTextEditable()) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "openIme of next Action");
+ // open Ime on next action.
+ openIme(adapter, vh);
+ } else {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme and focus to next Action");
+ // close IME and focus to next (not editable) action
+ closeIme(vh.itemView);
+ vh.itemView.requestFocus();
+ }
+ return true;
+ }
+ return false;
+ }
+ // search from index 0 of next Adapter
+ adapter = getNextAdapter(adapter);
+ if (adapter == null) {
+ break;
+ }
+ index = 0;
+ } while (true);
+ return false;
+ }
+
+ public void openIme(GuidedActionAdapter adapter, GuidedActionsStylist.ViewHolder avh) {
+ adapter.getGuidedActionsStylist().setEditingMode(avh, avh.getAction(), true);
+ View v = avh.getEditingView();
+ if (v == null || !avh.isInEditingText()) {
+ return;
+ }
+ InputMethodManager mgr = (InputMethodManager)
+ v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ // Make the TextView focusable during editing, avoid the TextView gets accessibility focus
+ // before editing started. see also GuidedActionEditText where setFocusable(false).
+ v.setFocusable(true);
+ v.requestFocus();
+ mgr.showSoftInput(v, 0);
+ if (!mImeOpened) {
+ mImeOpened = true;
+ mEditListener.onImeOpen();
+ }
+ }
+
+ public void closeIme(View v) {
+ if (mImeOpened) {
+ mImeOpened = false;
+ InputMethodManager mgr = (InputMethodManager)
+ v.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
+ mgr.hideSoftInputFromWindow(v.getWindowToken(), 0);
+ mEditListener.onImeClose();
+ }
+ }
+
+ public void fillAndStay(GuidedActionAdapter adapter, TextView v) {
+ GuidedActionsStylist.ViewHolder avh = adapter.findSubChildViewHolder(v);
+ updateTextIntoAction(avh, v);
+ mEditListener.onGuidedActionEditCanceled(avh.getAction());
+ adapter.getGuidedActionsStylist().setEditingMode(avh, avh.getAction(), false);
+ closeIme(v);
+ avh.itemView.requestFocus();
+ }
+
+ public void fillAndGoNext(GuidedActionAdapter adapter, TextView v) {
+ boolean handled = false;
+ GuidedActionsStylist.ViewHolder avh = adapter.findSubChildViewHolder(v);
+ updateTextIntoAction(avh, v);
+ adapter.performOnActionClick(avh);
+ long nextActionId = mEditListener.onGuidedActionEditedAndProceed(avh.getAction());
+ adapter.getGuidedActionsStylist().setEditingMode(avh, avh.getAction(), false);
+ if (nextActionId != GuidedAction.ACTION_ID_CURRENT
+ && nextActionId != avh.getAction().getId()) {
+ handled = focusToNextAction(adapter, avh.getAction(), nextActionId);
+ }
+ if (!handled) {
+ if (DEBUG_EDIT) Log.v(TAG_EDIT, "closeIme no next action");
+ handled = true;
+ closeIme(v);
+ avh.itemView.requestFocus();
+ }
+ }
+
+ private void updateTextIntoAction(GuidedActionsStylist.ViewHolder avh, TextView v) {
+ GuidedAction action = avh.getAction();
+ if (v == avh.getDescriptionView()) {
+ if (action.getEditDescription() != null) {
+ action.setEditDescription(v.getText());
+ } else {
+ action.setDescription(v.getText());
+ }
+ } else if (v == avh.getTitleView()) {
+ if (action.getEditTitle() != null) {
+ action.setEditTitle(v.getText());
+ } else {
+ action.setTitle(v.getText());
+ }
+ }
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
index 8e052fb..f6a0eab 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionEditText.java
@@ -14,17 +14,54 @@
package android.support.v17.leanback.widget;
import android.content.Context;
-import android.support.v17.leanback.widget.ImeKeyMonitor.ImeKeyListener;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PixelFormat;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.util.AttributeSet;
-import android.widget.EditText;
import android.view.KeyEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.widget.EditText;
+import android.widget.TextView;
/**
* A custom EditText that satisfies the IME key monitoring requirements of GuidedStepFragment.
*/
public class GuidedActionEditText extends EditText implements ImeKeyMonitor {
+ /**
+ * Workaround for b/26990627 forcing recompute the padding for the View when we turn on/off
+ * the default background of EditText
+ */
+ static final class NoPaddingDrawable extends Drawable {
+ @Override
+ public boolean getPadding(Rect padding) {
+ padding.set(0, 0, 0, 0);
+ return true;
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ }
+
+ @Override
+ public void setAlpha(int alpha) {
+ }
+
+ @Override
+ public void setColorFilter(ColorFilter colorFilter) {
+ }
+
+ @Override
+ public int getOpacity() {
+ return PixelFormat.TRANSPARENT;
+ }
+ }
+
private ImeKeyListener mKeyListener;
+ private final Drawable mSavedBackground;
+ private final Drawable mNoPaddingDrawable;
public GuidedActionEditText(Context ctx) {
this(ctx, null);
@@ -36,6 +73,9 @@
public GuidedActionEditText(Context ctx, AttributeSet attrs, int defStyleAttr) {
super(ctx, attrs, defStyleAttr);
+ mSavedBackground = getBackground();
+ mNoPaddingDrawable = new NoPaddingDrawable();
+ setBackground(mNoPaddingDrawable);
}
@Override
@@ -55,4 +95,24 @@
return result;
}
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+ info.setClassName(isFocused() ? EditText.class.getName() : TextView.class.getName());
+ }
+
+ @Override
+ protected void onFocusChanged(boolean focused, int direction, Rect previouslyFocusedRect) {
+ super.onFocusChanged(focused, direction, previouslyFocusedRect);
+ if (focused) {
+ setBackground(mSavedBackground);
+ } else {
+ setBackground(mNoPaddingDrawable);
+ }
+ // Make the TextView focusable during editing, avoid the TextView gets accessibility focus
+ // before editing started. see also GuidedActionAdapterGroup where setFocusable(true).
+ if (!focused) {
+ setFocusable(false);
+ }
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java
new file mode 100644
index 0000000..47c36f9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionItemContainer.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewParent;
+
+/**
+ * Root view of GuidedAction item, it supports a foreground drawable and can disable focus out
+ * of view.
+ * @hide
+ */
+class GuidedActionItemContainer extends NonOverlappingLinearLayoutWithForeground {
+
+ private boolean mFocusOutAllowed = true;
+
+ public GuidedActionItemContainer(Context context) {
+ this(context, null);
+ }
+
+ public GuidedActionItemContainer(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public GuidedActionItemContainer(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public View focusSearch(View focused, int direction) {
+ if (mFocusOutAllowed || !Util.isDescendant(this, focused)) {
+ return super.focusSearch(focused, direction);
+ }
+ View view = super.focusSearch(focused, direction);
+ if (Util.isDescendant(this, view)) {
+ return view;
+ }
+ return null;
+ }
+
+ public void setFocusOutAllowed(boolean focusOutAllowed) {
+ mFocusOutAllowed = focusOutAllowed;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 6e5d506..74b02ab 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -15,46 +15,50 @@
import android.animation.Animator;
import android.animation.AnimatorInflater;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ObjectAnimator;
import android.content.Context;
-import android.content.pm.PackageManager;
-import android.content.res.Resources;
import android.content.res.TypedArray;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.net.Uri;
+import android.os.Build.VERSION;
import android.support.annotation.NonNull;
import android.support.v17.leanback.R;
-import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v17.leanback.transition.TransitionListener;
+import android.support.v17.leanback.widget.GuidedActionAdapter.EditListener;
+import android.support.v17.leanback.widget.picker.DatePicker;
import android.support.v4.content.ContextCompat;
-import android.support.v4.view.ViewCompat;
import android.support.v7.widget.RecyclerView;
-import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.TextUtils;
-import android.util.Log;
import android.util.TypedValue;
-import android.view.animation.DecelerateInterpolator;
-import android.view.inputmethod.EditorInfo;
+import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.View.AccessibilityDelegate;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
-import android.view.ViewPropertyAnimator;
-import android.view.ViewTreeObserver;
import android.view.WindowManager;
+import android.view.accessibility.AccessibilityEvent;
+import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.inputmethod.EditorInfo;
import android.widget.Checkable;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
+import java.util.Calendar;
+import java.util.Collections;
import java.util.List;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_ACTIVATOR_VIEW;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_DESCRIPTION;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_NONE;
+import static android.support.v17.leanback.widget.GuidedAction.EDITING_TITLE;
+
/**
* GuidedActionsStylist is used within a {@link android.support.v17.leanback.app.GuidedStepFragment}
* to supply the right-side panel where users can take actions. It consists of a container for the
* list of actions, and a stationary selector view that indicates visually the location of focus.
+ * GuidedActionsStylist has two different layouts: default is for normal actions including text,
+ * radio, checkbox, DatePicker, etc, the other when {@link #setAsButtonActions()} is called is
+ * recommended for button actions such as "yes", "no".
* <p>
* Many aspects of the base GuidedActionsStylist can be customized through theming; see the
* theme attributes below. Note that these attributes are not set on individual elements in layout
@@ -64,12 +68,19 @@
* <p>
* If these hooks are insufficient, this class may also be subclassed. Subclasses may wish to
* override the {@link #onProvideLayoutId} method to change the layout used to display the
- * list container and selector, or the {@link #onProvideItemLayoutId} method to change the layout
- * used to display each action.
+ * list container and selector; override {@link #onProvideItemLayoutId(int)} and
+ * {@link #getItemViewType(GuidedAction)} method to change the layout used to display each action.
+ * <p>
+ * To support a "click to activate" view similar to DatePicker, app needs:
+ * <li> Override {@link #onProvideItemLayoutId(int)} and {@link #getItemViewType(GuidedAction)},
+ * provides a layout id for the action.
+ * <li> The layout must include a widget with id "guidedactions_activator_item", the widget is
+ * toggled edit mode by {@link View#setActivated(boolean)}.
+ * <li> Override {@link #onBindActivatorView(ViewHolder, GuidedAction)} to populate values into View.
+ * <li> Override {@link #onUpdateActivatorView(ViewHolder, GuidedAction)} to update action.
* <p>
* Note: If an alternate list layout is provided, the following view IDs must be supplied:
* <ul>
- * <li>{@link android.support.v17.leanback.R.id#guidedactions_selector}</li>
* <li>{@link android.support.v17.leanback.R.id#guidedactions_list}</li>
* </ul><p>
* These view IDs must be present in order for the stylist to function. The list ID must correspond
@@ -94,10 +105,10 @@
*
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeAppearingAnimation
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedStepImeDisappearingAnimation
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorShowAnimation
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorHideAnimation
- * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsSelectorDrawable
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionsListStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedSubActionsListStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedButtonActionsListStyle
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemContainerStyle
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemCheckmarkStyle
* @attr ref android.support.v17.leanback.R.styleable#LeanbackGuidedStepTheme_guidedActionItemIconStyle
@@ -129,35 +140,79 @@
public static final int VIEW_TYPE_DEFAULT = 0;
/**
+ * ViewType for DatePicker.
+ */
+ public static final int VIEW_TYPE_DATE_PICKER = 1;
+
+ final static ItemAlignmentFacet sGuidedActionItemAlignFacet;
+ static {
+ sGuidedActionItemAlignFacet = new ItemAlignmentFacet();
+ ItemAlignmentFacet.ItemAlignmentDef alignedDef = new ItemAlignmentFacet.ItemAlignmentDef();
+ alignedDef.setItemAlignmentViewId(R.id.guidedactions_item_title);
+ alignedDef.setAlignedToTextViewBaseline(true);
+ alignedDef.setItemAlignmentOffset(0);
+ alignedDef.setItemAlignmentOffsetWithPadding(true);
+ alignedDef.setItemAlignmentOffsetPercent(0);
+ sGuidedActionItemAlignFacet.setAlignmentDefs(new ItemAlignmentFacet.ItemAlignmentDef[]{alignedDef});
+ }
+
+ /**
* ViewHolder caches information about the action item layouts' subviews. Subclasses of {@link
* GuidedActionsStylist} may also wish to subclass this in order to add fields.
* @see GuidedAction
*/
- public static class ViewHolder {
+ public static class ViewHolder extends RecyclerView.ViewHolder implements FacetProvider {
- public final View view;
-
+ private GuidedAction mAction;
private View mContentView;
private TextView mTitleView;
private TextView mDescriptionView;
+ private View mActivatorView;
private ImageView mIconView;
private ImageView mCheckmarkView;
private ImageView mChevronView;
- private boolean mInEditing;
- private boolean mInEditingDescription;
+ private int mEditingMode = EDITING_NONE;
+ private final boolean mIsSubAction;
+
+ final AccessibilityDelegate mDelegate = new AccessibilityDelegate() {
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+ event.setChecked(mAction != null && mAction.isChecked());
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfo info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+ info.setCheckable(
+ mAction != null && mAction.getCheckSetId() != GuidedAction.NO_CHECK_SET);
+ info.setChecked(mAction != null && mAction.isChecked());
+ }
+ };
/**
* Constructs an ViewHolder and caches the relevant subviews.
*/
public ViewHolder(View v) {
- view = v;
+ this(v, false);
+ }
+
+ /**
+ * Constructs an ViewHolder for sub action and caches the relevant subviews.
+ */
+ public ViewHolder(View v, boolean isSubAction) {
+ super(v);
mContentView = v.findViewById(R.id.guidedactions_item_content);
mTitleView = (TextView) v.findViewById(R.id.guidedactions_item_title);
+ mActivatorView = v.findViewById(R.id.guidedactions_activator_item);
mDescriptionView = (TextView) v.findViewById(R.id.guidedactions_item_description);
mIconView = (ImageView) v.findViewById(R.id.guidedactions_item_icon);
mCheckmarkView = (ImageView) v.findViewById(R.id.guidedactions_item_checkmark);
mChevronView = (ImageView) v.findViewById(R.id.guidedactions_item_chevron);
+ mIsSubAction = isSubAction;
+
+ v.setAccessibilityDelegate(mDelegate);
}
/**
@@ -220,39 +275,100 @@
}
/**
- * Returns true if the TextView is in editing title or description, false otherwise.
+ * Returns true if in editing title, description, or activator View, false otherwise.
*/
public boolean isInEditing() {
- return mInEditing;
+ return mEditingMode != EDITING_NONE;
+ }
+
+ /**
+ * Returns true if in editing title, description, so IME would be open.
+ * @return True if in editing title, description, so IME would be open, false otherwise.
+ */
+ public boolean isInEditingText() {
+ return mEditingMode == EDITING_TITLE || mEditingMode == EDITING_DESCRIPTION;
+ }
+
+ /**
+ * Returns true if the TextView is in editing title, false otherwise.
+ */
+ public boolean isInEditingTitle() {
+ return mEditingMode == EDITING_TITLE;
}
/**
* Returns true if the TextView is in editing description, false otherwise.
*/
public boolean isInEditingDescription() {
- return mInEditingDescription;
+ return mEditingMode == EDITING_DESCRIPTION;
}
+ /**
+ * Returns true if is in editing activator view with id guidedactions_activator_item, false
+ * otherwise.
+ */
+ public boolean isInEditingActivatorView() {
+ return mEditingMode == EDITING_ACTIVATOR_VIEW;
+ }
+
+ /**
+ * @return Current editing title view or description view or activator view or null if not
+ * in editing.
+ */
public View getEditingView() {
- if (mInEditing) {
- return mInEditingDescription ? mDescriptionView : mTitleView;
- } else {
+ switch(mEditingMode) {
+ case EDITING_TITLE:
+ return mTitleView;
+ case EDITING_DESCRIPTION:
+ return mDescriptionView;
+ case EDITING_ACTIVATOR_VIEW:
+ return mActivatorView;
+ case EDITING_NONE:
+ default:
return null;
}
}
+
+ /**
+ * @return True if bound action is inside {@link GuidedAction#getSubActions()}, false
+ * otherwise.
+ */
+ public boolean isSubAction() {
+ return mIsSubAction;
+ }
+
+ /**
+ * @return Currently bound action.
+ */
+ public GuidedAction getAction() {
+ return mAction;
+ }
+
+ void setActivated(boolean activated) {
+ mActivatorView.setActivated(activated);
+ if (itemView instanceof GuidedActionItemContainer) {
+ ((GuidedActionItemContainer) itemView).setFocusOutAllowed(!activated);
+ }
+ }
+
+ @Override
+ public Object getFacet(Class<?> facetClass) {
+ if (facetClass == ItemAlignmentFacet.class) {
+ return sGuidedActionItemAlignFacet;
+ }
+ return null;
+ }
}
private static String TAG = "GuidedActionsStylist";
private ViewGroup mMainView;
private VerticalGridView mActionsGridView;
+ private VerticalGridView mSubActionsGridView;
private View mBgView;
- private View mSelectorView;
private View mContentView;
private boolean mButtonActions;
- private Animator mSelectorAnimator;
-
// Cached values from resources
private float mEnabledTextAlpha;
private float mDisabledTextAlpha;
@@ -266,6 +382,11 @@
private int mVerticalPadding;
private int mDisplayHeight;
+ private EditListener mEditListener;
+
+ private GuidedAction mExpandedAction = null;
+ private Object mExpandTransition;
+
/**
* Creates a view appropriate for displaying a list of GuidedActions, using the provided
* inflater and container.
@@ -277,47 +398,33 @@
* <code>LayoutInflater.inflate</code>.
* @return The view to be added to the caller's view hierarchy.
*/
- public View onCreateView(LayoutInflater inflater, ViewGroup container) {
+ public View onCreateView(LayoutInflater inflater, final ViewGroup container) {
+ TypedArray ta = inflater.getContext().getTheme().obtainStyledAttributes(
+ R.styleable.LeanbackGuidedStepTheme);
+ float keylinePercent = ta.getFloat(R.styleable.LeanbackGuidedStepTheme_guidedStepKeyline,
+ 40);
mMainView = (ViewGroup) inflater.inflate(onProvideLayoutId(), container, false);
- mContentView = mMainView.findViewById(R.id.guidedactions_content);
- mSelectorView = mMainView.findViewById(R.id.guidedactions_selector);
- mSelectorView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom,
- int oldLeft, int oldTop, int oldRight, int oldBottom) {
- updateSelectorView(false);
- }
- });
- mBgView = mMainView.findViewById(R.id.guidedactions_list_background);
+ mContentView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_content2 :
+ R.id.guidedactions_content);
+ mBgView = mMainView.findViewById(mButtonActions ? R.id.guidedactions_list_background2 :
+ R.id.guidedactions_list_background);
if (mMainView instanceof VerticalGridView) {
mActionsGridView = (VerticalGridView) mMainView;
} else {
- mActionsGridView = (VerticalGridView) mMainView.findViewById(R.id.guidedactions_list);
+ mActionsGridView = (VerticalGridView) mMainView.findViewById(mButtonActions ?
+ R.id.guidedactions_list2 : R.id.guidedactions_list);
if (mActionsGridView == null) {
throw new IllegalStateException("No ListView exists.");
}
- mActionsGridView.setWindowAlignmentOffset(0);
- mActionsGridView.setWindowAlignmentOffsetPercent(50f);
+ mActionsGridView.setWindowAlignmentOffsetPercent(keylinePercent);
mActionsGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
- if (mSelectorView != null) {
- mActionsGridView.setOnScrollListener(new RecyclerView.OnScrollListener() {
- @Override
- public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
- if (newState == RecyclerView.SCROLL_STATE_IDLE) {
- if (mSelectorView.getAlpha() != 1f) {
- updateSelectorView(true);
- }
- }
- }
- });
+ if (!mButtonActions) {
+ mSubActionsGridView = (VerticalGridView) mMainView.findViewById(
+ R.id.guidedactions_sub_list);
}
}
-
- if (mSelectorView != null) {
- // ALlow focus to move to other views
- mActionsGridView.getViewTreeObserver().addOnGlobalFocusChangeListener(
- mGlobalFocusChangeListener);
- }
+ mActionsGridView.setFocusable(false);
+ mActionsGridView.setFocusableInTouchMode(false);
// Cache widths, chevron alpha values, max and min text lines, etc
Context ctx = mMainView.getContext();
@@ -343,61 +450,32 @@
}
/**
- * Default implementation turns on background for actions and applies different Ids to views so
- * that GuidedStepFragment could run transitions against two action lists. The method is called
- * by GuidedStepFragment, app may override this function when replacing default layout file
- * provided by {@link #onProvideLayoutId()}
+ * Choose the layout resource for button actions in {@link #onProvideLayoutId()}.
*/
public void setAsButtonActions() {
+ if (mMainView != null) {
+ throw new IllegalStateException("setAsButtonActions() must be called before creating "
+ + "views");
+ }
mButtonActions = true;
- mMainView.setId(R.id.guidedactions_root2);
- ViewCompat.setTransitionName(mMainView, "guidedactions_root2");
- if (mActionsGridView != null) {
- mActionsGridView.setId(R.id.guidedactions_list2);
- }
- if (mSelectorView != null) {
- mSelectorView.setId(R.id.guidedactions_selector2);
- ViewCompat.setTransitionName(mSelectorView, "guidedactions_selector2");
- }
- if (mContentView != null) {
- mContentView.setId(R.id.guidedactions_content2);
- ViewCompat.setTransitionName(mContentView, "guidedactions_content2");
- }
- if (mBgView != null) {
- mBgView.setId(R.id.guidedactions_list_background2);
- ViewCompat.setTransitionName(mBgView, "guidedactions_list_background2");
- mBgView.setVisibility(View.VISIBLE);
- }
}
/**
- * Returns true if {@link #setAsButtonActions()} was called, false otherwise.
- * @return True if {@link #setAsButtonActions()} was called, false otherwise.
+ * Returns true if it is button actions list, false for normal actions list.
+ * @return True if it is button actions list, false for normal actions list.
*/
public boolean isButtonActions() {
return mButtonActions;
}
- final ViewTreeObserver.OnGlobalFocusChangeListener mGlobalFocusChangeListener =
- new ViewTreeObserver.OnGlobalFocusChangeListener() {
-
- @Override
- public void onGlobalFocusChanged(View oldFocus, View newFocus) {
- updateSelectorView(false);
- }
- };
-
/**
* Called when destroy the View created by GuidedActionsStylist.
*/
public void onDestroyView() {
- if (mSelectorView != null) {
- mActionsGridView.getViewTreeObserver().removeOnGlobalFocusChangeListener(
- mGlobalFocusChangeListener);
- }
- endSelectorAnimator();
+ mExpandedAction = null;
+ mExpandTransition = null;
mActionsGridView = null;
- mSelectorView = null;
+ mSubActionsGridView = null;
mContentView = null;
mBgView = null;
mMainView = null;
@@ -412,16 +490,27 @@
}
/**
+ * Returns the VerticalGridView that displays the sub actions list of an expanded action.
+ * @return The VerticalGridView that displays the sub actions list of an expanded action.
+ */
+ public VerticalGridView getSubActionsGridView() {
+ return mSubActionsGridView;
+ }
+
+ /**
* Provides the resource ID of the layout defining the host view for the list of guided actions.
* Subclasses may override to provide their own customized layouts. The base implementation
- * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions}. If overridden, the
- * substituted layout should contain matching IDs for any views that should be managed by the
- * base class; this can be achieved by starting with a copy of the base layout file.
- * @return The resource ID of the layout to be inflated to define the host view for the list
- * of GuidedActions.
+ * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions} or
+ * {@link android.support.v17.leanback.R.layout#lb_guidedbuttonactions} if
+ * {@link #isButtonActions()} is true. If overridden, the substituted layout should contain
+ * matching IDs for any views that should be managed by the base class; this can be achieved by
+ * starting with a copy of the base layout file.
+ *
+ * @return The resource ID of the layout to be inflated to define the host view for the list of
+ * GuidedActions.
*/
public int onProvideLayoutId() {
- return R.layout.lb_guidedactions;
+ return mButtonActions ? R.layout.lb_guidedbuttonactions : R.layout.lb_guidedactions;
}
/**
@@ -431,6 +520,9 @@
* @return View type that used in {@link #onProvideItemLayoutId(int)}.
*/
public int getItemViewType(GuidedAction action) {
+ if (action instanceof GuidedDatePickerAction) {
+ return VIEW_TYPE_DATE_PICKER;
+ }
return VIEW_TYPE_DEFAULT;
}
@@ -454,19 +546,24 @@
/**
* Provides the resource ID of the layout defining the view for an individual guided actions.
* Subclasses may override to provide their own customized layouts. The base implementation
- * returns {@link android.support.v17.leanback.R.layout#lb_guidedactions_item}. If overridden,
- * the substituted layout should contain matching IDs for any views that should be managed by
- * the base class; this can be achieved by starting with a copy of the base layout file. Note
- * that in order for the item to support editing, the title view should both subclass {@link
- * android.widget.EditText} and implement {@link ImeKeyMonitor}; see {@link
- * GuidedActionEditText}.
+ * supports:
+ * <li>{@link android.support.v17.leanback.R.layout#lb_guidedactions_item}
+ * <li>{{@link android.support.v17.leanback.R.layout#lb_guidedactions_datepicker_item}. If
+ * overridden, the substituted layout should contain matching IDs for any views that should be
+ * managed by the base class; this can be achieved by starting with a copy of the base layout
+ * file. Note that in order for the item to support editing, the title view should both subclass
+ * {@link android.widget.EditText} and implement {@link ImeKeyMonitor}; see
+ * {@link GuidedActionEditText}.
+ *
* @param viewType View type returned by {@link #getItemViewType(GuidedAction)}
* @return The resource ID of the layout to be inflated to define the view to display an
- * individual GuidedAction.
+ * individual GuidedAction.
*/
public int onProvideItemLayoutId(int viewType) {
if (viewType == VIEW_TYPE_DEFAULT) {
return onProvideItemLayoutId();
+ } else if (viewType == VIEW_TYPE_DATE_PICKER) {
+ return R.layout.lb_guidedactions_datepicker_item;
} else {
throw new RuntimeException("ViewType " + viewType +
" not supported in GuidedActionsStylist");
@@ -486,7 +583,7 @@
public ViewHolder onCreateViewHolder(ViewGroup parent) {
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View v = inflater.inflate(onProvideItemLayoutId(), parent, false);
- return new ViewHolder(v);
+ return new ViewHolder(v, parent == mSubActionsGridView);
}
/**
@@ -505,7 +602,7 @@
}
LayoutInflater inflater = LayoutInflater.from(parent.getContext());
View v = inflater.inflate(onProvideItemLayoutId(viewType), parent, false);
- return new ViewHolder(v);
+ return new ViewHolder(v, parent == mSubActionsGridView);
}
/**
@@ -516,10 +613,13 @@
*/
public void onBindViewHolder(ViewHolder vh, GuidedAction action) {
+ vh.mAction = action;
if (vh.mTitleView != null) {
vh.mTitleView.setText(action.getTitle());
vh.mTitleView.setAlpha(action.isEnabled() ? mEnabledTextAlpha : mDisabledTextAlpha);
- vh.mTitleView.setFocusable(action.isEditable());
+ vh.mTitleView.setFocusable(false);
+ vh.mTitleView.setClickable(false);
+ vh.mTitleView.setLongClickable(false);
}
if (vh.mDescriptionView != null) {
vh.mDescriptionView.setText(action.getDescription());
@@ -527,46 +627,57 @@
View.GONE : View.VISIBLE);
vh.mDescriptionView.setAlpha(action.isEnabled() ? mEnabledDescriptionAlpha :
mDisabledDescriptionAlpha);
- vh.mDescriptionView.setFocusable(action.isDescriptionEditable());
+ vh.mDescriptionView.setFocusable(false);
+ vh.mDescriptionView.setClickable(false);
+ vh.mDescriptionView.setLongClickable(false);
}
// Clients might want the check mark view to be gone entirely, in which case, ignore it.
if (vh.mCheckmarkView != null) {
onBindCheckMarkView(vh, action);
}
-
- if (vh.mChevronView != null) {
- onBindChevronView(vh, action);
- }
+ setIcon(vh.mIconView, action);
if (action.hasMultilineDescription()) {
if (vh.mTitleView != null) {
- vh.mTitleView.setMaxLines(mTitleMaxLines);
+ setMaxLines(vh.mTitleView, mTitleMaxLines);
if (vh.mDescriptionView != null) {
- vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(vh.view.getContext(),
- vh.mTitleView));
+ vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(
+ vh.itemView.getContext(), vh.mTitleView));
}
}
} else {
if (vh.mTitleView != null) {
- vh.mTitleView.setMaxLines(mTitleMinLines);
+ setMaxLines(vh.mTitleView, mTitleMinLines);
}
if (vh.mDescriptionView != null) {
- vh.mDescriptionView.setMaxLines(mDescriptionMinLines);
+ setMaxLines(vh.mDescriptionView, mDescriptionMinLines);
}
}
+ if (vh.mActivatorView != null) {
+ onBindActivatorView(vh, action);
+ }
setEditingMode(vh, action, false);
if (action.isFocusable()) {
- vh.view.setFocusable(true);
- if (vh.view instanceof ViewGroup) {
- ((ViewGroup) vh.view).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
- }
+ vh.itemView.setFocusable(true);
+ ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);
} else {
- vh.view.setFocusable(false);
- if (vh.view instanceof ViewGroup) {
- ((ViewGroup) vh.view).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
- }
+ vh.itemView.setFocusable(false);
+ ((ViewGroup) vh.itemView).setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS);
}
setupImeOptions(vh, action);
+
+ updateChevronAndVisibility(vh);
+ }
+
+ private static void setMaxLines(TextView view, int maxLines) {
+ // setSingleLine must be called before setMaxLines because it resets maximum to
+ // Integer.MAX_VALUE.
+ if (maxLines == 1) {
+ view.setSingleLine(true);
+ } else {
+ view.setSingleLine(false);
+ view.setMaxLines(maxLines);
+ }
}
/**
@@ -587,13 +698,13 @@
}
public void setEditingMode(ViewHolder vh, GuidedAction action, boolean editing) {
- if (editing != vh.mInEditing) {
- vh.mInEditing = editing;
+ if (editing != vh.isInEditing() && !isInExpandTransition()) {
onEditingModeChange(vh, action, editing);
}
}
protected void onEditingModeChange(ViewHolder vh, GuidedAction action, boolean editing) {
+ action = vh.getAction();
TextView titleView = vh.getTitleView();
TextView descriptionView = vh.getDescriptionView();
if (editing) {
@@ -610,12 +721,15 @@
descriptionView.setVisibility(View.VISIBLE);
descriptionView.setInputType(action.getDescriptionEditInputType());
}
- vh.mInEditingDescription = true;
- } else {
- vh.mInEditingDescription = false;
+ vh.mEditingMode = EDITING_DESCRIPTION;
+ } else if (action.isEditable()){
if (titleView != null) {
titleView.setInputType(action.getEditInputType());
}
+ vh.mEditingMode = EDITING_TITLE;
+ } else if (vh.mActivatorView != null) {
+ onEditActivatorView(vh, action, editing);
+ vh.mEditingMode = EDITING_ACTIVATOR_VIEW;
}
} else {
if (titleView != null) {
@@ -624,18 +738,22 @@
if (descriptionView != null) {
descriptionView.setText(action.getDescription());
}
- if (vh.mInEditingDescription) {
+ if (vh.mEditingMode == EDITING_DESCRIPTION) {
if (descriptionView != null) {
descriptionView.setVisibility(TextUtils.isEmpty(action.getDescription()) ?
View.GONE : View.VISIBLE);
descriptionView.setInputType(action.getDescriptionInputType());
}
- vh.mInEditingDescription = false;
- } else {
+ } else if (vh.mEditingMode == EDITING_TITLE) {
if (titleView != null) {
titleView.setInputType(action.getInputType());
}
+ } else if (vh.mEditingMode == EDITING_ACTIVATOR_VIEW) {
+ if (vh.mActivatorView != null) {
+ onEditActivatorView(vh, action, editing);
+ }
}
+ vh.mEditingMode = EDITING_NONE;
}
}
@@ -659,7 +777,7 @@
public void onAnimateItemPressed(ViewHolder vh, boolean pressed) {
int attr = pressed ? R.attr.guidedActionPressedAnimation :
R.attr.guidedActionUnpressedAnimation;
- createAnimator(vh.view, attr).start();
+ createAnimator(vh.itemView, attr).start();
}
/**
@@ -667,7 +785,7 @@
* @param vh The view holder associated with the relevant action.
*/
public void onAnimateItemPressedCancelled(ViewHolder vh) {
- createAnimator(vh.view, R.attr.guidedActionUnpressedAnimation).end();
+ createAnimator(vh.itemView, R.attr.guidedActionUnpressedAnimation).end();
}
/**
@@ -722,6 +840,85 @@
}
/**
+ * Performs binding activator view value to action. Default implementation supports
+ * GuidedDatePickerAction, subclass may override to add support of other views.
+ * @param vh ViewHolder of activator view.
+ * @param action GuidedAction to bind.
+ */
+ public void onBindActivatorView(ViewHolder vh, GuidedAction action) {
+ if (action instanceof GuidedDatePickerAction) {
+ GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
+ DatePicker dateView = (DatePicker) vh.mActivatorView;
+ dateView.setDatePickerFormat(dateAction.getDatePickerFormat());
+ if (dateAction.getMinDate() != Long.MIN_VALUE) {
+ dateView.setMinDate(dateAction.getMinDate());
+ }
+ if (dateAction.getMaxDate() != Long.MAX_VALUE) {
+ dateView.setMaxDate(dateAction.getMaxDate());
+ }
+ Calendar c = Calendar.getInstance();
+ c.setTimeInMillis(dateAction.getDate());
+ dateView.updateDate(c.get(Calendar.YEAR), c.get(Calendar.MONTH),
+ c.get(Calendar.DAY_OF_MONTH), false);
+ }
+ }
+
+ /**
+ * Performs updating GuidedAction from activator view. Default implementation supports
+ * GuidedDatePickerAction, subclass may override to add support of other views.
+ * @param vh ViewHolder of activator view.
+ * @param action GuidedAction to update.
+ * @return True if value has been updated, false otherwise.
+ */
+ public boolean onUpdateActivatorView(ViewHolder vh, GuidedAction action) {
+ if (action instanceof GuidedDatePickerAction) {
+ GuidedDatePickerAction dateAction = (GuidedDatePickerAction) action;
+ DatePicker dateView = (DatePicker) vh.mActivatorView;
+ if (dateAction.getDate() != dateView.getDate()) {
+ dateAction.setDate(dateView.getDate());
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Sets listener for reporting view being edited.
+ * @hide
+ */
+ public void setEditListener(EditListener listener) {
+ mEditListener = listener;
+ }
+
+ void onEditActivatorView(final ViewHolder vh, final GuidedAction action,
+ boolean editing) {
+ if (editing) {
+ vh.itemView.setFocusable(false);
+ vh.mActivatorView.requestFocus();
+ setExpandedViewHolder(vh);
+ vh.mActivatorView.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (!isInExpandTransition()) {
+ setEditingMode(vh, action, false);
+ }
+ }
+ });
+ } else {
+ if (onUpdateActivatorView(vh, action)) {
+ if (mEditListener != null) {
+ mEditListener.onGuidedActionEditedAndProceed(action);
+ }
+ }
+ vh.itemView.setFocusable(true);
+ vh.itemView.requestFocus();
+ setExpandedViewHolder(null);
+ vh.mActivatorView.setOnClickListener(null);
+ vh.mActivatorView.setClickable(false);
+ }
+ }
+
+ /**
* Sets states of chevron view, called by {@link #onBindViewHolder(ViewHolder, GuidedAction)}.
* Subclass may override.
*
@@ -729,9 +926,267 @@
* @param action The GuidedAction object to bind to.
*/
public void onBindChevronView(ViewHolder vh, GuidedAction action) {
- vh.mChevronView.setVisibility(action.hasNext() ? View.VISIBLE : View.GONE);
- vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
- mDisabledChevronAlpha);
+ final boolean hasNext = action.hasNext();
+ final boolean hasSubActions = action.hasSubActions();
+ if (hasNext || hasSubActions) {
+ vh.mChevronView.setVisibility(View.VISIBLE);
+ vh.mChevronView.setAlpha(action.isEnabled() ? mEnabledChevronAlpha :
+ mDisabledChevronAlpha);
+ if (hasNext) {
+ float r = mMainView != null
+ && mMainView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL ? 180f : 0f;
+ vh.mChevronView.setRotation(r);
+ } else if (action == mExpandedAction) {
+ vh.mChevronView.setRotation(270);
+ } else {
+ vh.mChevronView.setRotation(90);
+ }
+ } else {
+ vh.mChevronView.setVisibility(View.GONE);
+
+ }
+ }
+
+ /**
+ * Expands or collapse the sub actions list view.
+ * @param avh When not null, fill sub actions list of this ViewHolder into sub actions list and
+ * hide the other items in main list. When null, collapse the sub actions list.
+ */
+ public void setExpandedViewHolder(ViewHolder avh) {
+ if (isInExpandTransition()) {
+ return;
+ }
+ if (isExpandTransitionSupported()) {
+ startExpandedTransition(avh);
+ } else {
+ onUpdateExpandedViewHolder(avh);
+ }
+ }
+
+ /**
+ * Returns true if it is running an expanding or collapsing transition, false otherwise.
+ * @return True if it is running an expanding or collapsing transition, false otherwise.
+ */
+ public boolean isInExpandTransition() {
+ return mExpandTransition != null;
+ }
+
+ /**
+ * Returns if expand/collapse animation is supported. When this method returns true,
+ * {@link #startExpandedTransition(ViewHolder)} will be used. When this method returns false,
+ * {@link #onUpdateExpandedViewHolder(ViewHolder)} will be called.
+ * @return True if it is running an expanding or collapsing transition, false otherwise.
+ */
+ public boolean isExpandTransitionSupported() {
+ return VERSION.SDK_INT >= 21;
+ }
+
+ /**
+ * Start transition to expand or collapse GuidedActionStylist.
+ * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null
+ * the GuidedActionStylist will collapse sub actions.
+ */
+ public void startExpandedTransition(ViewHolder avh) {
+ ViewHolder focusAvh = null; // expand / collapse view holder
+ final int count = mActionsGridView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ViewHolder vh = (ViewHolder) mActionsGridView
+ .getChildViewHolder(mActionsGridView.getChildAt(i));
+ if (avh == null && vh.itemView.getVisibility() == View.VISIBLE) {
+ // going to collapse this one.
+ focusAvh = vh;
+ break;
+ } else if (avh != null && vh.getAction() == avh.getAction()) {
+ // going to expand this one.
+ focusAvh = vh;
+ break;
+ }
+ }
+ if (focusAvh == null) {
+ // huh?
+ onUpdateExpandedViewHolder(avh);
+ return;
+ }
+ boolean isSubActionTransition = focusAvh.getAction().hasSubActions();
+ Object set = TransitionHelper.createTransitionSet(false);
+ float slideDistance = isSubActionTransition ? focusAvh.itemView.getHeight() :
+ focusAvh.itemView.getHeight() * 0.5f;
+ Object slideAndFade = TransitionHelper.createFadeAndShortSlide(Gravity.TOP | Gravity.BOTTOM,
+ slideDistance);
+ Object changeFocusItemTransform = TransitionHelper.createChangeTransform();
+ Object changeFocusItemBounds = TransitionHelper.createChangeBounds(false);
+ Object fade = TransitionHelper.createFadeTransition(TransitionHelper.FADE_IN |
+ TransitionHelper.FADE_OUT);
+ Object changeGridBounds = TransitionHelper.createChangeBounds(false);
+ if (avh == null) {
+ TransitionHelper.setStartDelay(slideAndFade, 150);
+ TransitionHelper.setStartDelay(changeFocusItemTransform, 100);
+ TransitionHelper.setStartDelay(changeFocusItemBounds, 100);
+ } else {
+ TransitionHelper.setStartDelay(fade, 100);
+ TransitionHelper.setStartDelay(changeGridBounds, 100);
+ TransitionHelper.setStartDelay(changeFocusItemTransform, 50);
+ TransitionHelper.setStartDelay(changeFocusItemBounds, 50);
+ }
+ for (int i = 0; i < count; i++) {
+ ViewHolder vh = (ViewHolder) mActionsGridView
+ .getChildViewHolder(mActionsGridView.getChildAt(i));
+ if (vh == focusAvh) {
+ // going to expand/collapse this one.
+ if (isSubActionTransition) {
+ TransitionHelper.include(changeFocusItemTransform, vh.itemView);
+ TransitionHelper.include(changeFocusItemBounds, vh.itemView);
+ }
+ } else {
+ // going to slide this item to top / bottom.
+ TransitionHelper.include(slideAndFade, vh.itemView);
+ TransitionHelper.exclude(fade, vh.itemView, true);
+ }
+ }
+ TransitionHelper.include(changeGridBounds, mSubActionsGridView);
+ TransitionHelper.addTransition(set, slideAndFade);
+ // note that we don't run ChangeBounds for activating view due to the rounding problem
+ // of multiple level views ChangeBounds animation causing vertical jittering.
+ if (isSubActionTransition) {
+ TransitionHelper.addTransition(set, changeFocusItemTransform);
+ TransitionHelper.addTransition(set, changeFocusItemBounds);
+ }
+ TransitionHelper.addTransition(set, fade);
+ TransitionHelper.addTransition(set, changeGridBounds);
+ mExpandTransition = set;
+ TransitionHelper.addTransitionListener(mExpandTransition, new TransitionListener() {
+ @Override
+ public void onTransitionEnd(Object transition) {
+ mExpandTransition = null;
+ }
+ });
+ if (avh != null && mSubActionsGridView.getTop() != avh.itemView.getTop()) {
+ // For expanding, set the initial position of subActionsGridView before running
+ // a ChangeBounds on it.
+ final ViewHolder toUpdate = avh;
+ mSubActionsGridView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
+ @Override
+ public void onLayoutChange(View v, int left, int top, int right, int bottom,
+ int oldLeft, int oldTop, int oldRight, int oldBottom) {
+ if (mSubActionsGridView == null) {
+ return;
+ }
+ mSubActionsGridView.removeOnLayoutChangeListener(this);
+ mMainView.post(new Runnable() {
+ public void run() {
+ if (mMainView == null) {
+ return;
+ }
+ TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
+ onUpdateExpandedViewHolder(toUpdate);
+ }
+ });
+ }
+ });
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
+ lp.topMargin = avh.itemView.getTop();
+ lp.height = 0;
+ mSubActionsGridView.setLayoutParams(lp);
+ return;
+ }
+ TransitionHelper.beginDelayedTransition(mMainView, mExpandTransition);
+ onUpdateExpandedViewHolder(avh);
+ }
+
+ /**
+ * @return True if sub actions list is expanded.
+ */
+ public boolean isSubActionsExpanded() {
+ return mExpandedAction != null;
+ }
+
+ /**
+ * @return Current expanded GuidedAction or null if not expanded.
+ */
+ public GuidedAction getExpandedAction() {
+ return mExpandedAction;
+ }
+
+ /**
+ * Expand or collapse GuidedActionStylist.
+ * @param avh When not null, the GuidedActionStylist expands the sub actions of avh. When null
+ * the GuidedActionStylist will collapse sub actions.
+ */
+ public void onUpdateExpandedViewHolder(ViewHolder avh) {
+
+ // Note about setting the prune child flag back & forth here: without this, the actions that
+ // go off the screen from the top or bottom become invisible forever. This is because once
+ // an action is expanded, it takes more space which in turn kicks out some other actions
+ // off of the screen. Once, this action is collapsed (after the second click) and the
+ // visibility flag is set back to true for all existing actions,
+ // the off-the-screen actions are pruned from the view, thus
+ // could not be accessed, had we not disabled pruning prior to this.
+ if (avh == null) {
+ mExpandedAction = null;
+ mActionsGridView.setPruneChild(true);
+ } else if (avh.getAction() != mExpandedAction) {
+ mExpandedAction = avh.getAction();
+ mActionsGridView.setPruneChild(false);
+ }
+ // In expanding mode, notifyItemChange on expanded item will reset the translationY by
+ // the default ItemAnimator. So disable ItemAnimation in expanding mode.
+ mActionsGridView.setAnimateChildLayout(false);
+ final int count = mActionsGridView.getChildCount();
+ for (int i = 0; i < count; i++) {
+ ViewHolder vh = (ViewHolder) mActionsGridView
+ .getChildViewHolder(mActionsGridView.getChildAt(i));
+ updateChevronAndVisibility(vh);
+ }
+ if (mSubActionsGridView != null) {
+ if (avh != null && avh.getAction().hasSubActions()) {
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
+ lp.topMargin = avh.itemView.getTop();
+ lp.height = ViewGroup.MarginLayoutParams.MATCH_PARENT;
+ mSubActionsGridView.setLayoutParams(lp);
+ mSubActionsGridView.setVisibility(View.VISIBLE);
+ mSubActionsGridView.requestFocus();
+ mSubActionsGridView.setSelectedPosition(0);
+ ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
+ .setActions(avh.getAction().getSubActions());
+ } else if (mSubActionsGridView.getVisibility() == View.VISIBLE) {
+ mSubActionsGridView.setVisibility(View.INVISIBLE);
+ ViewGroup.MarginLayoutParams lp =
+ (ViewGroup.MarginLayoutParams) mSubActionsGridView.getLayoutParams();
+ lp.height = 0;
+ mSubActionsGridView.setLayoutParams(lp);
+ ((GuidedActionAdapter) mSubActionsGridView.getAdapter())
+ .setActions(Collections.EMPTY_LIST);
+ mActionsGridView.requestFocus();
+ }
+ }
+ }
+
+ private void updateChevronAndVisibility(ViewHolder vh) {
+ if (!vh.isSubAction()) {
+ if (mExpandedAction == null) {
+ vh.itemView.setVisibility(View.VISIBLE);
+ vh.itemView.setTranslationY(0);
+ if (vh.mActivatorView != null) {
+ vh.setActivated(false);
+ }
+ } else if (vh.getAction() == mExpandedAction) {
+ vh.itemView.setVisibility(View.VISIBLE);
+ if (vh.getAction().hasSubActions()) {
+ vh.itemView.setTranslationY(- vh.itemView.getHeight());
+ } else if (vh.mActivatorView != null) {
+ vh.itemView.setTranslationY(0);
+ vh.setActivated(true);
+ }
+ } else {
+ vh.itemView.setVisibility(View.INVISIBLE);
+ vh.itemView.setTranslationY(0);
+ }
+ }
+ if (vh.mChevronView != null) {
+ onBindChevronView(vh, vh.getAction());
+ }
}
/*
@@ -745,7 +1200,6 @@
*/
@Override
public void onImeAppearing(@NonNull List<Animator> animators) {
- animators.add(createAnimator(mContentView, R.attr.guidedStepImeAppearingAnimation));
}
/**
@@ -753,7 +1207,6 @@
*/
@Override
public void onImeDisappearing(@NonNull List<Animator> animators) {
- animators.add(createAnimator(mContentView, R.attr.guidedStepImeDisappearingAnimation));
}
/*
@@ -815,45 +1268,4 @@
return (int)(mDisplayHeight - 2*mVerticalPadding - 2*mTitleMaxLines*title.getLineHeight());
}
- private void endSelectorAnimator() {
- if (mSelectorAnimator != null) {
- mSelectorAnimator.end();
- mSelectorAnimator = null;
- }
- }
-
- private void updateSelectorView(boolean animate) {
- if (mActionsGridView == null || mSelectorView == null || mSelectorView.getHeight() <= 0) {
- return;
- }
- final View focusedChild = mActionsGridView.getFocusedChild();
- endSelectorAnimator();
- if (focusedChild == null || !mActionsGridView.hasFocus()
- || mActionsGridView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
- if (animate) {
- mSelectorAnimator = createAnimator(mSelectorView,
- R.attr.guidedActionsSelectorHideAnimation);
- mSelectorAnimator.start();
- } else {
- mSelectorView.setAlpha(0f);
- }
- } else {
- final float scaleY = (float) focusedChild.getHeight() / mSelectorView.getHeight();
- Rect r = new Rect(0, 0, focusedChild.getWidth(), focusedChild.getHeight());
- mMainView.offsetDescendantRectToMyCoords(focusedChild, r);
- mMainView.offsetRectIntoDescendantCoords(mSelectorView, r);
- mSelectorView.setTranslationY(r.exactCenterY() - mSelectorView.getHeight() * 0.5f);
- if (animate) {
- mSelectorAnimator = createAnimator(mSelectorView,
- R.attr.guidedActionsSelectorShowAnimation);
- ((ObjectAnimator) ((AnimatorSet) mSelectorAnimator).getChildAnimations().get(1))
- .setFloatValues(scaleY);
- mSelectorAnimator.start();
- } else {
- mSelectorView.setAlpha(1f);
- mSelectorView.setScaleY(scaleY);
- }
- }
- }
-
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
new file mode 100644
index 0000000..9b9f29f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
@@ -0,0 +1,189 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.os.Bundle;
+import android.support.v17.leanback.widget.picker.DatePicker;
+
+import java.util.Calendar;
+import java.util.TimeZone;
+
+/**
+ * Subclass of GuidedAction that can choose a date. The Action is editable by default; to make it
+ * read only, call hasEditableActivatorView(false) on the Builder.
+ */
+public class GuidedDatePickerAction extends GuidedAction {
+
+ /**
+ * Base Builder class to build GuidedDatePickerAction. Subclass this BuilderBase when app needs
+ * to subclass GuidedDatePickerAction, implement your build() which should call
+ * {@link #applyDatePickerValues(GuidedDatePickerAction)}. When using GuidedDatePickerAction
+ * directly, use {@link Builder}.
+ */
+ public abstract static class BuilderBase<B extends BuilderBase>
+ extends GuidedAction.BuilderBase<B> {
+
+ private String mDatePickerFormat;
+ private long mDate;
+ private long mMinDate = Long.MIN_VALUE;
+ private long mMaxDate = Long.MAX_VALUE;
+
+ public BuilderBase(Context context) {
+ super(context);
+ Calendar c = Calendar.getInstance();
+ mDate = c.getTimeInMillis();
+ hasEditableActivatorView(true);
+ }
+
+ /**
+ * Sets format of date Picker or null for default. The format is a case insensitive String
+ * containing the day ('d'), month ('m'), and year ('y'). When the format is not specified,
+ * a default format of current locale will be used.
+ * @param format Format of showing Date, e.g. "YMD".
+ * @return This Builder object.
+ */
+ public B datePickerFormat(String format) {
+ mDatePickerFormat = format;
+ return (B) this;
+ }
+
+ /**
+ * Sets a Date for date picker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @return This Builder Object.
+ */
+ public B date(long date) {
+ mDate = date;
+ return (B) this;
+ }
+
+ /**
+ * Sets minimal Date for date picker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @return This Builder Object.
+ */
+ public B minDate(long minDate) {
+ mMinDate = minDate;
+ return (B) this;
+ }
+
+ /**
+ * Sets maximum Date for date picker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @return This Builder Object.
+ */
+ public B maxDate(long maxDate) {
+ mMaxDate = maxDate;
+ return (B) this;
+ }
+
+ /**
+ * Apply values to GuidedDatePickerAction.
+ * @param action GuidedDatePickerAction to apply values.
+ */
+ protected final void applyDatePickerValues(GuidedDatePickerAction action) {
+ super.applyValues(action);
+ action.mDatePickerFormat = mDatePickerFormat;
+ action.mDate = mDate;
+ if (mMinDate > mMaxDate) {
+ throw new IllegalArgumentException("MinDate cannot be larger than MaxDate");
+ }
+ action.mMinDate = mMinDate;
+ action.mMaxDate = mMaxDate;
+ }
+
+ }
+
+ /**
+ * Builder class to build a GuidedDatePickerAction.
+ */
+ public final static class Builder extends BuilderBase<Builder> {
+ public Builder(Context context) {
+ super(context);
+ }
+
+ /**
+ * Builds the GuidedDatePickerAction corresponding to this Builder.
+ * @return The GuidedDatePickerAction as configured through this Builder.
+ */
+ public GuidedDatePickerAction build() {
+ GuidedDatePickerAction action = new GuidedDatePickerAction();
+ applyDatePickerValues(action);
+ return action;
+ }
+ }
+
+ private String mDatePickerFormat;
+ private long mDate;
+ private long mMinDate = Long.MIN_VALUE;
+ private long mMaxDate = Long.MAX_VALUE;
+
+ /**
+ * Returns format of date Picker or null if not specified. The format is a case insensitive
+ * String containing the * day ('d'), month ('m'), and year ('y'). When the format is not
+ * specified, a default format of current locale will
+ * be used.
+ * @return Format of showing Date, e.g. "YMD". Returns null if using current locale's default.
+ */
+ public String getDatePickerFormat() {
+ return mDatePickerFormat;
+ }
+
+ /**
+ * Get current value of DatePicker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @return Current value of DatePicker Action.
+ */
+ public long getDate() {
+ return mDate;
+ }
+
+ /**
+ * Sets current value of DatePicker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @param date New value to update current value of DatePicker Action.
+ */
+ public void setDate(long date) {
+ mDate = date;
+ }
+
+ /**
+ * Get minimal value of DatePicker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone. -1 if not set.
+ * @return Minimal value of DatePicker Action or Long.MIN_VALUE if not set.
+ */
+ public long getMinDate() {
+ return mMinDate;
+ }
+
+ /**
+ * Get maximum value of DatePicker in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * @return Maximum value of DatePicker Action or Long.MAX_VALUE if not set.
+ */
+ public long getMaxDate() {
+ return mMaxDate;
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle bundle, String key) {
+ bundle.putLong(key, getDate());
+ }
+
+ @Override
+ public void onRestoreInstanceState(Bundle bundle, String key) {
+ setDate(bundle.getLong(key, getDate()));
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
index b5d56d2..f4e5954 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/HeaderItem.java
@@ -23,6 +23,7 @@
private final long mId;
private final String mName;
+ private CharSequence mContentDescription;
/**
* Create a header item. All fields are optional.
@@ -52,4 +53,21 @@
public final String getName() {
return mName;
}
+
+ /**
+ * Returns optional content description for the HeaderItem. When it is null, {@link #getName()}
+ * should be used for the content description.
+ * @return Content description for the HeaderItem.
+ */
+ public CharSequence getContentDescription() {
+ return mContentDescription;
+ }
+
+ /**
+ * Sets optional content description for the HeaderItem.
+ * @param contentDescription Content description sets on the HeaderItem.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
index 08eb617..9fc1f49 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ImageCardView.java
@@ -29,74 +29,64 @@
import android.widget.TextView;
/**
- * A subclass of {@link BaseCardView} with an {@link ImageView} as its main
- * region. The {@link ImageCardView} is highly customizable and can be used for
- * various use-cases by adjusting the ImageViewCard's type to any combination of
- * Title, Content, Badge or ImageOnly.
+ * A subclass of {@link BaseCardView} with an {@link ImageView} as its main region. The
+ * {@link ImageCardView} is highly customizable and can be used for various use-cases by adjusting
+ * the ImageViewCard's type to any combination of Title, Content, Badge or ImageOnly.
* <p>
- * <h3>Styling</h3> There are three different ways to style the ImageCardView.
- * <br>
- * No matter what way you use, all your styles applied to an ImageCardView have
- * to extend the style {@link R.style#Widget_Leanback_ImageCardViewStyle}.
+ * <h3>Styling</h3> There are two different ways to style the ImageCardView. <br>
+ * No matter what way you use, all your styles applied to an ImageCardView have to extend the style
+ * {@link R.style#Widget_Leanback_ImageCardViewStyle}.
* <p>
* <u>Example:</u><br>
*
* <pre>
- * {@code <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
+ * {@code
+ * <style name="CustomImageCardViewStyle" parent="Widget.Leanback.ImageCardViewStyle">
<item name="cardBackground">#F0F</item>
<item name="lbImageCardViewType">Title|Content</item>
- <item name="lbImageCardViewInfoAreaStyle">@style/ImageCardViewColoredInfoArea</item>
- <item name="lbImageCardViewTitleStyle">@style/ImageCardViewColoredTitle</item>
+ </style>
+ <style name="CustomImageCardTheme" parent="Theme.Leanback">
+ <item name="imageCardViewStyle">@style/CustomImageCardViewStyle</item>
+ <item name="imageCardViewInfoAreaStyle">@style/ImageCardViewColoredInfoArea</item>
+ <item name="imageCardViewTitleStyle">@style/ImageCardViewColoredTitle</item>
</style>}
* </pre>
* <p>
- * The first possibility is to set a custom Style in the Leanback Theme's
- * attribute <code>imageCardViewStyle</code>. The style set here, is the default
- * style for all ImageCardViews. The other two possibilities allow you to style
- * a particular ImageCardView. This is usefull if you want to create multiple
- * types of cards. E.g. you might want to display a card with only a title and
- * another one with title and content. Thus you need to define two different
- * <code>ImageCardViewStyles</code> and apply them to the ImageCardViews. You
- * can do this by either using a the {@link #ImageCardView(Context, int)}
- * constructor and passing a style as second argument or by setting the style in
- * a layout.
+ * The first possibility is to set custom Styles in the Leanback Theme's attributes
+ * <code>imageCardViewStyle</code>, <code>imageCardViewTitleStyle</code> etc. The styles set here,
+ * is the default style for all ImageCardViews.
+ * <p>
+ * The second possibility allows you to style a particular ImageCardView. This is useful if you
+ * want to create multiple types of cards. E.g. you might want to display a card with only a title
+ * and another one with title and content. Thus you need to define two different
+ * <code>ImageCardViewStyles</code> and two different themes and apply them to the ImageCardViews.
+ * You can do this by using a the {@link #ImageCardView(Context)} constructor and passing a
+ * ContextThemeWrapper with the custom ImageCardView theme id.
* <p>
* <u>Example (using constructor):</u><br>
*
* <pre>
* {@code
- * new ImageCardView(context, R.style.CustomImageCardViewStyle);
+ * new ImageCardView(new ContextThemeWrapper(context, R.style.CustomImageCardTheme));
* }
* </pre>
*
- * <u>Example (using style attribute in a layout):</u><br>
- *
- * <pre>
- * {@code <android.support.v17.leanback.widget.ImageCardView
- android:id="@+id/imageCardView"
- style="@style/CustomImageCardViewStyle"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content">
- </android.support.v17.leanback.widget.ImageCardView>}
- * </pre>
* <p>
- * You can style all ImageCardView's components such as the title, content,
- * badge, infoArea and the image itself by extending the corresponding style and
- * overriding the specific attribute in your custom
- * <code>ImageCardViewStyle</code>.
+ * You can style all ImageCardView's components such as the title, content, badge, infoArea and the
+ * image itself by extending the corresponding style and overriding the specific attribute in your
+ * custom ImageCardView theme.
*
- * <h3>Components</h3> The ImageCardView contains three components which can be
- * combined in any combination:
+ * <h3>Components</h3> The ImageCardView contains three components which can be combined in any
+ * combination:
* <ul>
* <li>Title: The card's title</li>
* <li>Content: A short description</li>
- * <li>Badge: An icon which can be displayed on the right or left side of the
- * card.</li>
+ * <li>Badge: An icon which can be displayed on the right or left side of the card.</li>
* </ul>
- * In order to choose the components you want to use in your ImageCardView, you
- * have to specify them in the <code>lbImageCardViewType</code> attribute of
- * your custom <code>ImageCardViewStyle</code>. You can combine the following
- * values: <code>Title, Content, IconOnRight, IconOnLeft, ImageOnly</code>.
+ * In order to choose the components you want to use in your ImageCardView, you have to specify them
+ * in the <code>lbImageCardViewType</code> attribute of your custom <code>ImageCardViewStyle</code>.
+ * You can combine the following values:
+ * <code>Title, Content, IconOnRight, IconOnLeft, ImageOnly</code>.
* <p>
* <u>Examples:</u><br>
*
@@ -118,11 +108,11 @@
*
* @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewStyle
* @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewType
- * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewTitleStyle
- * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewContentStyle
- * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewBadgeStyle
- * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewImageStyle
- * @attr ref android.support.v17.leanback.R.styleable#lbImageCardView_lbImageCardViewInfoAreaStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewTitleStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewContentStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewBadgeStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewImageStyle
+ * @attr ref android.support.v17.leanback.R.styleable#LeanbackTheme_imageCardViewInfoAreaStyle
*/
public class ImageCardView extends BaseCardView {
@@ -140,43 +130,50 @@
private boolean mAttachedToWindow;
/**
- * Create an ImageCardView using a given style for customization.
+ * Create an ImageCardView using a given theme for customization.
*
* @param context
* The Context the view is running in, through which it can
* access the current theme, resources, etc.
- * @param styleResId
- * The resourceId of the style you want to apply to the
- * ImageCardView. The style has to extend
- * {@link R.style#Widget_Leanback_ImageCardViewStyle}.
+ * @param themeResId
+ * The resourceId of the theme you want to apply to the ImageCardView. The theme
+ * includes attributes "imageCardViewStyle", "imageCardViewTitleStyle",
+ * "imageCardViewContentStyle" etc. to customize individual part of ImageCardView.
+ * @deprecated Calling this constructor inefficiently creates one ContextThemeWrapper per card,
+ * you should share it in card Presenter: wrapper = new ContextThemeWrapper(context, themResId);
+ * return new ImageCardView(wrapper);
*/
- public ImageCardView(Context context, int styleResId) {
- super(new ContextThemeWrapper(context, styleResId), null, 0);
- buildImageCardView(styleResId);
+ @Deprecated
+ public ImageCardView(Context context, int themeResId) {
+ this(new ContextThemeWrapper(context, themeResId));
}
/**
* @see #View(Context, AttributeSet, int)
*/
public ImageCardView(Context context, AttributeSet attrs, int defStyleAttr) {
- super(getStyledContext(context, attrs, defStyleAttr), attrs, defStyleAttr);
- buildImageCardView(getImageCardViewStyle(context, attrs, defStyleAttr));
+ super(context, attrs, defStyleAttr);
+ buildImageCardView(attrs, defStyleAttr, R.style.Widget_Leanback_ImageCardView);
}
- private void buildImageCardView(int styleResId) {
+ private void buildImageCardView(AttributeSet attrs, int defStyleAttr, int defStyle) {
// Make sure the ImageCardView is focusable.
setFocusable(true);
setFocusableInTouchMode(true);
LayoutInflater inflater = LayoutInflater.from(getContext());
inflater.inflate(R.layout.lb_image_card_view, this);
- TypedArray cardAttrs = getContext().obtainStyledAttributes(styleResId, R.styleable.lbImageCardView);
- int cardType = cardAttrs.getInt(R.styleable.lbImageCardView_lbImageCardViewType, CARD_TYPE_FLAG_IMAGE_ONLY);
+ TypedArray cardAttrs = getContext().obtainStyledAttributes(attrs,
+ R.styleable.lbImageCardView, defStyleAttr, defStyle);
+ int cardType = cardAttrs
+ .getInt(R.styleable.lbImageCardView_lbImageCardViewType, CARD_TYPE_FLAG_IMAGE_ONLY);
+
boolean hasImageOnly = cardType == CARD_TYPE_FLAG_IMAGE_ONLY;
boolean hasTitle = (cardType & CARD_TYPE_FLAG_TITLE) == CARD_TYPE_FLAG_TITLE;
boolean hasContent = (cardType & CARD_TYPE_FLAG_CONTENT) == CARD_TYPE_FLAG_CONTENT;
boolean hasIconRight = (cardType & CARD_TYPE_FLAG_ICON_RIGHT) == CARD_TYPE_FLAG_ICON_RIGHT;
- boolean hasIconLeft = !hasIconRight && (cardType & CARD_TYPE_FLAG_ICON_LEFT) == CARD_TYPE_FLAG_ICON_LEFT;
+ boolean hasIconLeft =
+ !hasIconRight && (cardType & CARD_TYPE_FLAG_ICON_LEFT) == CARD_TYPE_FLAG_ICON_LEFT;
mImageView = (ImageView) findViewById(R.id.main_image);
if (mImageView.getDrawable() == null) {
@@ -191,12 +188,14 @@
}
// Create children
if (hasTitle) {
- mTitleView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_title, mInfoArea, false);
+ mTitleView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_title,
+ mInfoArea, false);
mInfoArea.addView(mTitleView);
}
if (hasContent) {
- mContentView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_content, mInfoArea, false);
+ mContentView = (TextView) inflater.inflate(R.layout.lb_image_card_view_themed_content,
+ mInfoArea, false);
mInfoArea.addView(mContentView);
}
@@ -211,8 +210,8 @@
// Set up LayoutParams for children
if (hasTitle && !hasContent && mBadgeImage != null) {
- RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mTitleView
- .getLayoutParams();
+ RelativeLayout.LayoutParams relativeLayoutParams =
+ (RelativeLayout.LayoutParams) mTitleView.getLayoutParams();
// Adjust title TextView if there is an icon but no content
if (hasIconLeft) {
relativeLayoutParams.addRule(RelativeLayout.END_OF, mBadgeImage.getId());
@@ -224,8 +223,8 @@
// Set up LayoutParams for children
if (hasContent) {
- RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mContentView
- .getLayoutParams();
+ RelativeLayout.LayoutParams relativeLayoutParams =
+ (RelativeLayout.LayoutParams) mContentView.getLayoutParams();
if (!hasTitle) {
relativeLayoutParams.addRule(RelativeLayout.ALIGN_PARENT_TOP);
}
@@ -239,8 +238,8 @@
}
if (mBadgeImage != null) {
- RelativeLayout.LayoutParams relativeLayoutParams = (RelativeLayout.LayoutParams) mBadgeImage
- .getLayoutParams();
+ RelativeLayout.LayoutParams relativeLayoutParams =
+ (RelativeLayout.LayoutParams) mBadgeImage.getLayoutParams();
if (hasContent) {
relativeLayoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, mContentView.getId());
} else if (hasTitle) {
@@ -269,23 +268,6 @@
cardAttrs.recycle();
}
- private static Context getStyledContext(Context context, AttributeSet attrs, int defStyleAttr) {
- int style = getImageCardViewStyle(context, attrs, defStyleAttr);
- return new ContextThemeWrapper(context, style);
- }
-
- private static int getImageCardViewStyle(Context context, AttributeSet attrs, int defStyleAttr) {
- // Read style attribute defined in XML layout.
- int style = null == attrs ? 0 : attrs.getStyleAttribute();
- if (0 == style) {
- // Not found? Read global ImageCardView style from Theme attribute.
- TypedArray styledAttrs = context.obtainStyledAttributes(R.styleable.LeanbackTheme);
- style = styledAttrs.getResourceId(R.styleable.LeanbackTheme_imageCardViewStyle, 0);
- styledAttrs.recycle();
- }
- return style;
- }
-
/**
* @see #View(Context)
*/
@@ -476,8 +458,8 @@
private void fadeIn() {
mImageView.setAlpha(0f);
if (mAttachedToWindow) {
- mImageView.animate().alpha(1f)
- .setDuration(mImageView.getResources().getInteger(android.R.integer.config_shortAnimTime));
+ mImageView.animate().alpha(1f).setDuration(
+ mImageView.getResources().getInteger(android.R.integer.config_shortAnimTime));
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java
new file mode 100644
index 0000000..ff27d92
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/InvisibleRowPresenter.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.ViewGroup;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
+
+/**
+ * @hide
+ */
+public class InvisibleRowPresenter extends RowPresenter {
+
+ public InvisibleRowPresenter() {
+ setHeaderPresenter(null);
+ }
+
+ @Override
+ protected ViewHolder createRowViewHolder(ViewGroup parent) {
+ RelativeLayout root = new RelativeLayout(parent.getContext());
+ root.setLayoutParams(new RelativeLayout.LayoutParams(0, 0));
+ return new ViewHolder(root);
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
index 31bc893..cc51d54 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacet.java
@@ -52,6 +52,7 @@
int mOffset = 0;
float mOffsetPercent = 50f;
boolean mOffsetWithPadding = false;
+ private boolean mAlignToBaseline;
/**
* Sets number of pixels to offset. Can be negative for alignment from the high edge, or
@@ -140,6 +141,22 @@
public final int getItemAlignmentFocusViewId() {
return mFocusViewId != View.NO_ID ? mFocusViewId : mViewId;
}
+
+ /**
+ * Align to baseline if {@link #getItemAlignmentViewId()} is a TextView and
+ * alignToBaseline is true.
+ * @param alignToBaseline Boolean indicating whether to align the text to baseline.
+ */
+ public final void setAlignedToTextViewBaseline(boolean alignToBaseline) {
+ this.mAlignToBaseline = alignToBaseline;
+ }
+
+ /**
+ * Returns true when TextView should be aligned to the baseline.
+ */
+ public boolean isAlignedToTextViewBaseLine() {
+ return mAlignToBaseline;
+ }
}
private ItemAlignmentDef[] mAlignmentDefs = new ItemAlignmentDef[]{new ItemAlignmentDef()};
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
index 04559b9..3230848 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ItemAlignmentFacetHelper.java
@@ -16,10 +16,12 @@
import static android.support.v17.leanback.widget.ItemAlignmentFacet.ITEM_ALIGN_OFFSET_PERCENT_DISABLED;
import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import android.graphics.Paint;
import android.graphics.Rect;
import android.support.v17.leanback.widget.GridLayoutManager.LayoutParams;
import android.view.View;
import android.view.ViewGroup;
+import android.widget.TextView;
/**
* Helper class to handle ItemAlignmentFacet in a grid view.
@@ -80,6 +82,11 @@
((ViewGroup) itemView).offsetDescendantRectToMyCoords(view, sRect);
alignPos = sRect.top - p.getOpticalTopInset();
}
+ if (view instanceof TextView && facet.isAlignedToTextViewBaseLine()) {
+ Paint textPaint = ((TextView)view).getPaint();
+ int titleViewTextHeight = -textPaint.getFontMetricsInt().top;
+ alignPos += titleViewTextHeight;
+ }
}
return alignPos;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
index 962c80b..8f95c04 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRow.java
@@ -19,6 +19,7 @@
*/
public class ListRow extends Row {
private final ObjectAdapter mAdapter;
+ private CharSequence mContentDescription;
/**
* Returns the {@link ObjectAdapter} that represents a list of objects.
@@ -50,4 +51,36 @@
throw new IllegalArgumentException("ObjectAdapter cannot be null");
}
}
+
+ /**
+ * Returns content description for the ListRow. By default it returns
+ * {@link HeaderItem#getContentDescription()} or {@link HeaderItem#getName()},
+ * unless {@link #setContentDescription(CharSequence)} was explicitly called.
+ *
+ * @return Content description for the ListRow.
+ */
+ public CharSequence getContentDescription() {
+ if (mContentDescription != null) {
+ return mContentDescription;
+ }
+ final HeaderItem headerItem = getHeaderItem();
+ if (headerItem != null) {
+ CharSequence contentDescription = headerItem.getContentDescription();
+ if (contentDescription != null) {
+ return contentDescription;
+ }
+ return headerItem.getName();
+ }
+ return null;
+ }
+
+ /**
+ * Explicitly set content description for the ListRow, {@link #getContentDescription()} will
+ * ignore values from HeaderItem.
+ *
+ * @param contentDescription Content description sets on the ListRow.
+ */
+ public void setContentDescription(CharSequence contentDescription) {
+ mContentDescription = contentDescription;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
index 5540f78..39b4863 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java
@@ -15,15 +15,14 @@
import android.content.Context;
import android.content.res.TypedArray;
-import android.os.Build;
import android.support.v17.leanback.R;
import android.support.v17.leanback.system.Settings;
import android.support.v17.leanback.transition.TransitionHelper;
+import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewGroup.LayoutParams;
import java.util.HashMap;
@@ -77,17 +76,139 @@
mPaddingRight = mGridView.getPaddingRight();
}
+ /**
+ * Gets ListRowPresenter that creates this ViewHolder.
+ * @return ListRowPresenter that creates this ViewHolder.
+ */
public final ListRowPresenter getListRowPresenter() {
return mListRowPresenter;
}
+ /**
+ * Gets HorizontalGridView that shows a list of items.
+ * @return HorizontalGridView that shows a list of items.
+ */
public final HorizontalGridView getGridView() {
return mGridView;
}
+ /**
+ * Gets ItemBridgeAdapter that creates the list of items.
+ * @return ItemBridgeAdapter that creates the list of items.
+ */
public final ItemBridgeAdapter getBridgeAdapter() {
return mItemBridgeAdapter;
}
+
+ /**
+ * Gets selected item position in adapter.
+ * @return Selected item position in adapter.
+ */
+ public int getSelectedPosition() {
+ return mGridView.getSelectedPosition();
+ }
+
+ /**
+ * Gets ViewHolder at a position in adapter. Returns null if the item does not exist
+ * or the item is not bound to a view.
+ * @param position Position of the item in adapter.
+ * @return ViewHolder bounds to the item.
+ */
+ public Presenter.ViewHolder getItemViewHolder(int position) {
+ ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) mGridView
+ .findViewHolderForAdapterPosition(position);
+ if (ibvh == null) {
+ return null;
+ }
+ return ibvh.getViewHolder();
+ }
+ }
+
+ /**
+ * A task on the ListRowPresenter.ViewHolder that can select an item by position in the
+ * HorizontalGridView and perform an optional item task on it.
+ */
+ public static class SelectItemViewHolderTask extends Presenter.ViewHolderTask {
+
+ private int mItemPosition;
+ private boolean mSmoothScroll = true;
+ private Presenter.ViewHolderTask mItemTask;
+
+ public SelectItemViewHolderTask(int itemPosition) {
+ setItemPosition(itemPosition);
+ }
+
+ /**
+ * Sets the adapter position of item to select.
+ * @param itemPosition Position of the item in adapter.
+ */
+ public void setItemPosition(int itemPosition) {
+ mItemPosition = itemPosition;
+ }
+
+ /**
+ * Returns the adapter position of item to select.
+ * @return The adapter position of item to select.
+ */
+ public int getItemPosition() {
+ return mItemPosition;
+ }
+
+ /**
+ * Sets smooth scrolling to the item or jump to the item without scrolling. By default it is
+ * true.
+ * @param smoothScroll True for smooth scrolling to the item, false otherwise.
+ */
+ public void setSmoothScroll(boolean smoothScroll) {
+ mSmoothScroll = smoothScroll;
+ }
+
+ /**
+ * Returns true if smooth scrolling to the item false otherwise. By default it is true.
+ * @return True for smooth scrolling to the item, false otherwise.
+ */
+ public boolean isSmoothScroll() {
+ return mSmoothScroll;
+ }
+
+ /**
+ * Returns optional task to run when the item is selected, null for no task.
+ * @return Optional task to run when the item is selected, null for no task.
+ */
+ public Presenter.ViewHolderTask getItemTask() {
+ return mItemTask;
+ }
+
+ /**
+ * Sets task to run when the item is selected, null for no task.
+ * @param itemTask Optional task to run when the item is selected, null for no task.
+ */
+ public void setItemTask(Presenter.ViewHolderTask itemTask) {
+ mItemTask = itemTask;
+ }
+
+ @Override
+ public void run(Presenter.ViewHolder holder) {
+ if (holder instanceof ListRowPresenter.ViewHolder) {
+ HorizontalGridView gridView = ((ListRowPresenter.ViewHolder) holder).getGridView();
+ android.support.v17.leanback.widget.ViewHolderTask task = null;
+ if (mItemTask != null) {
+ task = new android.support.v17.leanback.widget.ViewHolderTask() {
+ final Presenter.ViewHolderTask itemTask = mItemTask;
+ @Override
+ public void run(RecyclerView.ViewHolder rvh) {
+ ItemBridgeAdapter.ViewHolder ibvh = (ItemBridgeAdapter.ViewHolder) rvh;
+ itemTask.run(ibvh.getViewHolder());
+ }
+ };
+ }
+ if (isSmoothScroll()) {
+ gridView.setSelectedPositionSmooth(mItemPosition, task);
+ } else {
+ gridView.setSelectedPosition(mItemPosition, task);
+ }
+ }
+ }
}
class ListRowPresenterItemBridgeAdapter extends ItemBridgeAdapter {
@@ -148,6 +269,7 @@
}
}
+ private int mNumRows = 1;
private int mRowHeight;
private int mExpandedRowHeight;
private PresenterSelector mHoverCardPresenterSelector;
@@ -268,6 +390,14 @@
return mUseFocusDimmer;
}
+ /**
+ * Sets the numbers of rows for rendering the list of items. By default, it is
+ * set to 1.
+ */
+ public void setNumRows(int numRows) {
+ this.mNumRows = numRows;
+ }
+
@Override
protected void initializeRowViewHolder(RowPresenter.ViewHolder holder) {
super.initializeRowViewHolder(holder);
@@ -315,6 +445,7 @@
return false;
}
});
+ rowViewHolder.mGridView.setNumRows(mNumRows);
}
final boolean needsDefaultListSelectEffect() {
@@ -355,7 +486,7 @@
*/
private void selectChildView(ViewHolder rowViewHolder, View view, boolean fireEvent) {
if (view != null) {
- if (rowViewHolder.mExpanded && rowViewHolder.mSelected) {
+ if (rowViewHolder.mSelected) {
ItemBridgeAdapter.ViewHolder ibh = (ItemBridgeAdapter.ViewHolder)
rowViewHolder.mGridView.getChildViewHolder(view);
@@ -515,6 +646,7 @@
ListRow rowItem = (ListRow) item;
vh.mItemBridgeAdapter.setAdapter(rowItem.getAdapter());
vh.mGridView.setAdapter(vh.mItemBridgeAdapter);
+ vh.mGridView.setContentDescription(rowItem.getContentDescription());
}
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
index c1a4795..528e9aa 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowView.java
@@ -43,6 +43,9 @@
inflater.inflate(R.layout.lb_list_row, this);
mGridView = (HorizontalGridView) findViewById(R.id.row_content);
+ // since we use WRAP_CONTENT for height in lb_list_row, we need set fixed size to false
+ mGridView.setHasFixedSize(false);
+
// Uncomment this to experiment with page-based scrolling.
// mGridView.setFocusScrollStrategy(HorizontalGridView.FOCUS_SCROLL_PAGE);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java
new file mode 100644
index 0000000..2cb8174
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/MediaItemActionPresenter.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.support.v17.leanback.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+
+/**
+ * The presenter displaying a custom action in {@link AbstractMediaItemPresenter}.
+ * This is the default presenter for actions in media rows if no action presenter is provided by the
+ * user.
+ *
+ * Binds to items of type {@link MultiActionsProvider.MultiAction}.
+ */
+class MediaItemActionPresenter extends Presenter {
+
+ MediaItemActionPresenter() {
+ }
+
+ static class ViewHolder extends Presenter.ViewHolder {
+ final ImageView mIcon;
+
+ public ViewHolder(View view) {
+ super(view);
+ mIcon = (ImageView) view.findViewById(R.id.actionIcon);
+ }
+
+ public ImageView getIcon() {
+ return mIcon;
+ }
+ }
+
+ @Override
+ public Presenter.ViewHolder onCreateViewHolder(ViewGroup parent) {
+ Context context = parent.getContext();
+ View actionView = LayoutInflater.from(context).
+ inflate(R.layout.lb_row_media_item_action, parent,
+ false);
+ return new ViewHolder(actionView);
+ }
+
+ @Override
+ public void onBindViewHolder(Presenter.ViewHolder viewHolder, Object item) {
+ ViewHolder actionViewHolder = (ViewHolder) viewHolder;
+ MultiActionsProvider.MultiAction action = (MultiActionsProvider.MultiAction) item;
+ actionViewHolder.getIcon().setImageDrawable(action.getCurrentDrawable());
+ }
+
+ @Override
+ public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java b/v17/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
new file mode 100644
index 0000000..6ec93f2
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/MediaRowFocusView.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.RectF;
+import android.util.AttributeSet;
+import android.view.View;
+import android.support.v17.leanback.R;
+
+/**
+ * Creates a view for a media item row in a playlist
+ */
+class MediaRowFocusView extends View {
+
+ private final Paint mPaint;
+ private final RectF mRoundRectF = new RectF();
+ private int mRoundRectRadius;
+
+ public MediaRowFocusView(Context context) {
+ super(context);
+ mPaint = createPaint(context);
+ }
+
+ public MediaRowFocusView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ mPaint = createPaint(context);
+ }
+
+ public MediaRowFocusView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ mPaint = createPaint(context);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mRoundRectRadius = getHeight() / 2;
+ int drawHeight = 2 * mRoundRectRadius;
+ int drawOffset = (drawHeight - getHeight()) / 2;
+ mRoundRectF.set(0, -drawOffset, getWidth(), getHeight() + drawOffset);
+ canvas.drawRoundRect(mRoundRectF, mRoundRectRadius, mRoundRectRadius, mPaint);
+ }
+
+ private Paint createPaint(Context context) {
+ Paint paint = new Paint();
+ paint.setColor(context.getResources().getColor(
+ R.color.lb_playback_media_row_highlight_color));
+ return paint;
+ }
+
+ public int getRoundRectRadius() {
+ return mRoundRectRadius;
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java b/v17/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java
new file mode 100644
index 0000000..8127012
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/MultiActionsProvider.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * An interface implemented by the user if they wish to provide actions for a media item row to
+ * be displayed by an {@link AbstractMediaItemPresenter}.
+ *
+ * A media row consists of media item details together with a number of custom actions,
+ * following the media item details. Classes implementing {@link MultiActionsProvider} can define
+ * their own media data model within their derived classes.
+ * <p>
+ * The actions are provided by overriding {@link MultiActionsProvider#getActions()}
+ * Provided actions should be instances of {@link MultiAction}.
+ * </p>
+ */
+public interface MultiActionsProvider {
+
+ /**
+ * MultiAction represents an action that can have multiple states. {@link #getIndex()} returns
+ * the current index within the drawables. Both list of drawables and index can be updated
+ * dynamically in the program, and the UI could be updated by notifying the listeners
+ * provided in {@link AbstractMediaItemPresenter.ViewHolder}.
+ */
+ public static class MultiAction {
+ private long mId;
+ private int mIndex;
+ private Drawable[] mDrawables;
+
+ public MultiAction(long id) {
+ mId = id;
+ mIndex = 0;
+ }
+
+ /**
+ * Sets the drawables used for displaying different states within this {@link MultiAction}.
+ * The size of drawables determines the set of states this action represents.
+ * @param drawables Array of drawables for different MultiAction states.
+ */
+ public void setDrawables(Drawable[] drawables) {
+ mDrawables = drawables;
+ if (mIndex > drawables.length - 1) {
+ mIndex = drawables.length - 1;
+ }
+ }
+
+ /**
+ * Returns the drawables used for displaying different states within this
+ * {@link MultiAction}.
+ * @return The drawables used for displaying different states within this
+ * {@link MultiAction}.
+ */
+ public Drawable[] getDrawables() {
+ return mDrawables;
+ }
+
+ /**
+ * Increments the index which this MultiAction currently represents. The index is wrapped
+ * around to zero when the end is reached.
+ */
+ public void incrementIndex() {
+ setIndex(mIndex < (mDrawables.length - 1) ? (mIndex + 1) : 0);
+ }
+
+ /**
+ * Sets the index which this MultiAction currently represents.
+ * @param index The current action index.
+ */
+ public void setIndex(int index) {
+ mIndex = index;
+ }
+
+ /**
+ * Returns the currently selected index in this MultiAction.
+ * @return The currently selected index in this MultiAction.
+ */
+ public int getIndex() {
+ return mIndex;
+ }
+
+ /**
+ * @return The icon drawable for the current state of this MultiAction.
+ */
+ public Drawable getCurrentDrawable() {
+ return mDrawables[mIndex];
+ }
+
+ /**
+ * @return The id for this MultiAction.
+ */
+ public long getId() {
+ return mId;
+ }
+ }
+
+ /**
+ * Should override this method in order to provide a custom set of actions for a media item row
+ * @return Array of MultiAction items to be displayed for this media item row.
+ */
+ public MultiAction[] getActions();
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java
new file mode 100644
index 0000000..52832e9
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java
@@ -0,0 +1,141 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build.VERSION;
+import android.util.AttributeSet;
+import android.widget.LinearLayout;
+
+/**
+ * Implements foreground drawable before M and falls back to M's foreground implementation.
+ * @hide
+ */
+class NonOverlappingLinearLayoutWithForeground extends LinearLayout {
+
+ private static final int VERSION_M = 23;
+
+ private Drawable mForeground;
+ private boolean mForegroundBoundsChanged;
+ private final Rect mSelfBounds = new Rect();
+
+ public NonOverlappingLinearLayoutWithForeground(Context context) {
+ this(context, null);
+ }
+
+ public NonOverlappingLinearLayoutWithForeground(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public NonOverlappingLinearLayoutWithForeground(Context context, AttributeSet attrs,
+ int defStyle) {
+ super(context, attrs, defStyle);
+ if (context.getApplicationInfo().targetSdkVersion >= VERSION_M
+ && VERSION.SDK_INT >= VERSION_M) {
+ // dont need do anything, base View constructor >=M already reads the foreground if
+ // targetSDK is >= M.
+ } else {
+ // in other cases, including M but targetSDK is less than M, we need setForeground in
+ // code.
+ TypedArray a = context.obtainStyledAttributes(attrs,
+ new int[] { android.R.attr.foreground });
+ Drawable d = a.getDrawable(0);
+ if (d != null) {
+ setForegroundCompat(d);
+ }
+ }
+ }
+
+ public void setForegroundCompat(Drawable d) {
+ if (VERSION.SDK_INT >= VERSION_M) {
+ // From M, foreground is naturally supported.
+ ForegroundHelper.getInstance().setForeground(this, d);
+ } else {
+ // before M, do our own customized foreground draw.
+ if (mForeground != d) {
+ mForeground = d;
+ mForegroundBoundsChanged = true;
+ setWillNotDraw(false);
+ mForeground.setCallback(this);
+ if (mForeground.isStateful()) {
+ mForeground.setState(getDrawableState());
+ }
+ }
+ }
+ }
+
+ public Drawable getForegroundCompat() {
+ if (VERSION.SDK_INT >= VERSION_M) {
+ return ForegroundHelper.getInstance().getForeground(this);
+ } else {
+ return mForeground;
+ }
+ }
+
+ @Override
+ public void draw(Canvas canvas) {
+ super.draw(canvas);
+ if (mForeground != null) {
+ final Drawable foreground = mForeground;
+ if (mForegroundBoundsChanged) {
+ mForegroundBoundsChanged = false;
+ final Rect selfBounds = mSelfBounds;
+ final int w = getRight() - getLeft();
+ final int h = getBottom() - getTop();
+ selfBounds.set(0, 0, w, h);
+ foreground.setBounds(selfBounds);
+ }
+ foreground.draw(canvas);
+ }
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mForegroundBoundsChanged |= changed;
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || (who == mForeground);
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ if (mForeground != null) {
+ mForeground.jumpToCurrentState();
+ }
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ if (mForeground != null && mForeground.isStateful()) {
+ mForeground.setState(getDrawableState());
+ }
+ }
+
+ /**
+ * Avoids creating a hardware layer when animating alpha.
+ */
+ @Override
+ public boolean hasOverlappingRendering() {
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
index c5dc25c..f4bf37d 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewClickedListener.java
@@ -14,11 +14,9 @@
package android.support.v17.leanback.widget;
/**
- * Interface for receiving notification when a item view holder is clicked.
+ * Interface for receiving notification when a item view holder is clicked. This interface expects
+ * row object to be sub class of {@link Row}.
*/
-public interface OnItemViewClickedListener {
-
- public void onItemClicked(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row);
+public interface OnItemViewClickedListener extends BaseOnItemViewClickedListener<Row> {
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
index 5e355bf..30fdb67 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/OnItemViewSelectedListener.java
@@ -17,32 +17,7 @@
* Interface for receiving notification when a row or item becomes selected. The concept of
* current selection is different than focus. A row or item can be selected without having focus;
* for example, when a row header view gains focus then the corresponding row view becomes selected.
+ * This interface expects row object to be sub class of {@link Row}.
*/
-public interface OnItemViewSelectedListener {
- /**
- * Called when the a row or a new item becomes selected.
- * <p>
- * For a non {@link ListRow} case, parameter item may be null. Event is fired when
- * selection changes between rows, regardless if row view has focus or not.
- * <p>
- * For a {@link ListRow} case, parameter item is null if the list row is empty.
- * </p>
- * <p>
- * In the case of a grid, the row parameter is always null.
- * </p>
- * <li>
- * Row has focus: event is fired when focus changes between children of the row.
- * </li>
- * <li>
- * No row has focus: the event is fired with the currently selected row and last
- * focused item in the row.
- * </li>
- *
- * @param itemViewHolder The view holder of the item that is currently selected.
- * @param item The item that is currently selected.
- * @param rowViewHolder The view holder of the row that is currently selected.
- * @param row The row that is currently selected.
- */
- public void onItemSelected(Presenter.ViewHolder itemViewHolder, Object item,
- RowPresenter.ViewHolder rowViewHolder, Row row);
+public interface OnItemViewSelectedListener extends BaseOnItemViewSelectedListener<Row> {
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java
new file mode 100644
index 0000000..c7765b6
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PageRow.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Used to represent content spanning full page.
+ */
+public class PageRow extends Row {
+
+ public PageRow(HeaderItem headerItem) {
+ super(headerItem);
+ }
+
+ @Override
+ final public boolean isRenderedAsRowView() {
+ return false;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
new file mode 100644
index 0000000..94ab8d8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
@@ -0,0 +1,506 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.animation.Animator;
+import android.animation.AnimatorSet;
+import android.animation.ObjectAnimator;
+import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Paint;
+import android.graphics.Rect;
+import android.support.annotation.ColorInt;
+import android.support.annotation.VisibleForTesting;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.util.Property;
+import android.view.View;
+import android.view.animation.DecelerateInterpolator;
+
+/**
+ * A page indicator with dots.
+ * @hide
+ */
+public class PagingIndicator extends View {
+ private static final long DURATION_ALPHA = 167;
+ private static final long DURATION_DIAMETER = 417;
+ private static final long DURATION_TRANSLATION_X = DURATION_DIAMETER;
+ private static final TimeInterpolator DECELERATE_INTERPOLATOR = new DecelerateInterpolator();
+
+ private static final Property<Dot, Float> DOT_ALPHA
+ = new Property<Dot, Float>(Float.class, "alpha") {
+ @Override
+ public Float get(Dot dot) {
+ return dot.getAlpha();
+ }
+
+ @Override
+ public void set(Dot dot, Float value) {
+ dot.setAlpha(value);
+ }
+ };
+
+ private static final Property<Dot, Float> DOT_DIAMETER
+ = new Property<Dot, Float>(Float.class, "diameter") {
+ @Override
+ public Float get(Dot dot) {
+ return dot.getDiameter();
+ }
+
+ @Override
+ public void set(Dot dot, Float value) {
+ dot.setDiameter(value);
+ }
+ };
+
+ private static final Property<Dot, Float> DOT_TRANSLATION_X
+ = new Property<Dot, Float>(Float.class, "translation_x") {
+ @Override
+ public Float get(Dot dot) {
+ return dot.getTranslationX();
+ }
+
+ @Override
+ public void set(Dot dot, Float value) {
+ dot.setTranslationX(value);
+ }
+ };
+
+ // attribute
+ private boolean mIsLtr;
+ private final int mDotDiameter;
+ private final int mDotRadius;
+ private final int mDotGap;
+ private final int mArrowDiameter;
+ private final int mArrowRadius;
+ private final int mArrowGap;
+ private final int mShadowRadius;
+ private Dot[] mDots;
+ // X position when the dot is selected.
+ private int[] mDotSelectedX;
+ // X position when the dot is located to the left of the selected dot.
+ private int[] mDotSelectedPrevX;
+ // X position when the dot is located to the right of the selected dot.
+ private int[] mDotSelectedNextX;
+ private int mDotCenterY;
+
+ // state
+ private int mPageCount;
+ private int mCurrentPage;
+ private int mPreviousPage;
+
+ // drawing
+ @ColorInt
+ private final int mDotFgSelectColor;
+ private final Paint mBgPaint;
+ private final Paint mFgPaint;
+ private final AnimatorSet mShowAnimator;
+ private final AnimatorSet mHideAnimator;
+ private final AnimatorSet mAnimator = new AnimatorSet();
+ private Bitmap mArrow;
+ private final Rect mArrowRect;
+ private final float mArrowToBgRatio;
+
+ public PagingIndicator(Context context) {
+ this(context, null, 0);
+ }
+
+ public PagingIndicator(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public PagingIndicator(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ Resources res = getResources();
+ TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PagingIndicator,
+ defStyle, 0);
+ mDotRadius = getDimensionFromTypedArray(typedArray, R.styleable.PagingIndicator_dotRadius,
+ R.dimen.lb_page_indicator_dot_radius);
+ mDotDiameter = mDotRadius * 2;
+ mArrowRadius = getDimensionFromTypedArray(typedArray,
+ R.styleable.PagingIndicator_arrowRadius, R.dimen.lb_page_indicator_arrow_radius);
+ mArrowDiameter = mArrowRadius * 2;
+ mDotGap = getDimensionFromTypedArray(typedArray, R.styleable.PagingIndicator_dotToDotGap,
+ R.dimen.lb_page_indicator_dot_gap);
+ mArrowGap = getDimensionFromTypedArray(typedArray,
+ R.styleable.PagingIndicator_dotToArrowGap, R.dimen.lb_page_indicator_arrow_gap);
+ int bgColor = getColorFromTypedArray(typedArray, R.styleable.PagingIndicator_dotBgColor,
+ R.color.lb_page_indicator_dot);
+ mBgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ mBgPaint.setColor(bgColor);
+ mDotFgSelectColor = getColorFromTypedArray(typedArray,
+ R.styleable.PagingIndicator_arrowBgColor,
+ R.color.lb_page_indicator_arrow_background);
+ typedArray.recycle();
+ mIsLtr = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
+ int shadowColor = res.getColor(R.color.lb_page_indicator_arrow_shadow);
+ mShadowRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_radius);
+ mFgPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
+ int shadowOffset = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_shadow_offset);
+ mFgPaint.setShadowLayer(mShadowRadius, shadowOffset, shadowOffset, shadowColor);
+ mArrow = loadArrow();
+ mArrowRect = new Rect(0, 0, mArrow.getWidth(), mArrow.getHeight());
+ mArrowToBgRatio = (float) mArrow.getWidth() / (float) mArrowDiameter;
+ // Initialize animations.
+ mShowAnimator = new AnimatorSet();
+ mShowAnimator.playTogether(createDotAlphaAnimator(0.0f, 1.0f),
+ createDotDiameterAnimator(mDotRadius * 2, mArrowRadius * 2),
+ createDotTranslationXAnimator());
+ mHideAnimator = new AnimatorSet();
+ mHideAnimator.playTogether(createDotAlphaAnimator(1.0f, 0.0f),
+ createDotDiameterAnimator(mArrowRadius * 2, mDotRadius * 2),
+ createDotTranslationXAnimator());
+ mAnimator.playTogether(mShowAnimator, mHideAnimator);
+ // Use software layer to show shadows.
+ setLayerType(View.LAYER_TYPE_SOFTWARE, null);
+ }
+
+ private int getDimensionFromTypedArray(TypedArray typedArray, int attr, int defaultId) {
+ return typedArray.getDimensionPixelOffset(attr,
+ getResources().getDimensionPixelOffset(defaultId));
+ }
+
+ private int getColorFromTypedArray(TypedArray typedArray, int attr, int defaultId) {
+ return typedArray.getColor(attr, getResources().getColor(defaultId));
+ }
+
+ private Bitmap loadArrow() {
+ Bitmap arrow = BitmapFactory.decodeResource(getResources(), R.drawable.lb_ic_nav_arrow);
+ if (mIsLtr) {
+ return arrow;
+ } else {
+ Matrix matrix = new Matrix();
+ matrix.preScale(-1, 1);
+ return Bitmap.createBitmap(arrow, 0, 0, arrow.getWidth(), arrow.getHeight(), matrix,
+ false);
+ }
+ }
+
+ private Animator createDotAlphaAnimator(float from, float to) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_ALPHA, from, to);
+ animator.setDuration(DURATION_ALPHA);
+ animator.setInterpolator(DECELERATE_INTERPOLATOR);
+ return animator;
+ }
+
+ private Animator createDotDiameterAnimator(float from, float to) {
+ ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_DIAMETER, from, to);
+ animator.setDuration(DURATION_DIAMETER);
+ animator.setInterpolator(DECELERATE_INTERPOLATOR);
+ return animator;
+ }
+
+ private Animator createDotTranslationXAnimator() {
+ // The direction is determined in the Dot.
+ ObjectAnimator animator = ObjectAnimator.ofFloat(null, DOT_TRANSLATION_X,
+ -mArrowGap + mDotGap, 0.0f);
+ animator.setDuration(DURATION_TRANSLATION_X);
+ animator.setInterpolator(DECELERATE_INTERPOLATOR);
+ return animator;
+ }
+
+ /**
+ * Sets the page count.
+ */
+ public void setPageCount(int pages) {
+ if (pages <= 0) {
+ throw new IllegalArgumentException("The page count should be a positive integer");
+ }
+ mPageCount = pages;
+ mDots = new Dot[mPageCount];
+ for (int i = 0; i < mPageCount; ++i) {
+ mDots[i] = new Dot();
+ }
+ calculateDotPositions();
+ setSelectedPage(0);
+ }
+
+ /**
+ * Called when the page has been selected.
+ */
+ public void onPageSelected(int pageIndex, boolean withAnimation) {
+ if (mCurrentPage == pageIndex) {
+ return;
+ }
+ if (mAnimator.isStarted()) {
+ mAnimator.end();
+ }
+ mPreviousPage = mCurrentPage;
+ if (withAnimation) {
+ mHideAnimator.setTarget(mDots[mPreviousPage]);
+ mShowAnimator.setTarget(mDots[pageIndex]);
+ mAnimator.start();
+ }
+ setSelectedPage(pageIndex);
+ }
+
+ private void calculateDotPositions() {
+ int left = getPaddingLeft();
+ int top = getPaddingTop();
+ int right = getWidth() - getPaddingRight();
+ int requiredWidth = getRequiredWidth();
+ int mid = (left + right) / 2;
+ mDotSelectedX = new int[mPageCount];
+ mDotSelectedPrevX = new int[mPageCount];
+ mDotSelectedNextX = new int[mPageCount];
+ if (mIsLtr) {
+ int startLeft = mid - requiredWidth / 2;
+ // mDotSelectedX[0] should be mDotSelectedPrevX[-1] + mArrowGap
+ mDotSelectedX[0] = startLeft + mDotRadius - mDotGap + mArrowGap;
+ mDotSelectedPrevX[0] = startLeft + mDotRadius;
+ mDotSelectedNextX[0] = startLeft + mDotRadius - 2 * mDotGap + 2 * mArrowGap;
+ for (int i = 1; i < mPageCount; i++) {
+ mDotSelectedX[i] = mDotSelectedPrevX[i - 1] + mArrowGap;
+ mDotSelectedPrevX[i] = mDotSelectedPrevX[i - 1] + mDotGap;
+ mDotSelectedNextX[i] = mDotSelectedX[i - 1] + mArrowGap;
+ }
+ } else {
+ int startRight = mid + requiredWidth / 2;
+ // mDotSelectedX[0] should be mDotSelectedPrevX[-1] - mArrowGap
+ mDotSelectedX[0] = startRight - mDotRadius + mDotGap - mArrowGap;
+ mDotSelectedPrevX[0] = startRight - mDotRadius;
+ mDotSelectedNextX[0] = startRight - mDotRadius + 2 * mDotGap - 2 * mArrowGap;
+ for (int i = 1; i < mPageCount; i++) {
+ mDotSelectedX[i] = mDotSelectedPrevX[i - 1] - mArrowGap;
+ mDotSelectedPrevX[i] = mDotSelectedPrevX[i - 1] - mDotGap;
+ mDotSelectedNextX[i] = mDotSelectedX[i - 1] - mArrowGap;
+ }
+ }
+ mDotCenterY = top + mArrowRadius;
+ adjustDotPosition();
+ }
+
+ @VisibleForTesting
+ int getPageCount() {
+ return mPageCount;
+ }
+
+ @VisibleForTesting
+ int[] getDotSelectedX() {
+ return mDotSelectedX;
+ }
+
+ @VisibleForTesting
+ int[] getDotSelectedLeftX() {
+ return mDotSelectedPrevX;
+ }
+
+ @VisibleForTesting
+ int[] getDotSelectedRightX() {
+ return mDotSelectedNextX;
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ int desiredHeight = getDesiredHeight();
+ int height;
+ switch (MeasureSpec.getMode(heightMeasureSpec)) {
+ case MeasureSpec.EXACTLY:
+ height = MeasureSpec.getSize(heightMeasureSpec);
+ break;
+ case MeasureSpec.AT_MOST:
+ height = Math.min(desiredHeight, MeasureSpec.getSize(heightMeasureSpec));
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ height = desiredHeight;
+ break;
+ }
+ int desiredWidth = getDesiredWidth();
+ int width;
+ switch (MeasureSpec.getMode(widthMeasureSpec)) {
+ case MeasureSpec.EXACTLY:
+ width = MeasureSpec.getSize(widthMeasureSpec);
+ break;
+ case MeasureSpec.AT_MOST:
+ width = Math.min(desiredWidth, MeasureSpec.getSize(widthMeasureSpec));
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ default:
+ width = desiredWidth;
+ break;
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ @Override
+ protected void onSizeChanged(int width, int height, int oldWidth, int oldHeight) {
+ setMeasuredDimension(width, height);
+ calculateDotPositions();
+ }
+
+ private int getDesiredHeight() {
+ return getPaddingTop() + mArrowDiameter + getPaddingBottom() + mShadowRadius;
+ }
+
+ private int getRequiredWidth() {
+ return 2 * mDotRadius + 2 * mArrowGap + (mPageCount - 3) * mDotGap;
+ }
+
+ private int getDesiredWidth() {
+ return getPaddingLeft() + getRequiredWidth() + getPaddingRight();
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ for (int i = 0; i < mPageCount; ++i) {
+ mDots[i].draw(canvas);
+ }
+ }
+
+ private void setSelectedPage(int now) {
+ if (now == mCurrentPage) {
+ return;
+ }
+
+ mCurrentPage = now;
+ adjustDotPosition();
+ }
+
+ private void adjustDotPosition() {
+ for (int i = 0; i < mCurrentPage; ++i) {
+ mDots[i].deselect();
+ mDots[i].mDirection = i == mPreviousPage ? Dot.LEFT : Dot.RIGHT;
+ mDots[i].mCenterX = mDotSelectedPrevX[i];
+ }
+ mDots[mCurrentPage].select();
+ mDots[mCurrentPage].mDirection = mPreviousPage < mCurrentPage ? Dot.LEFT : Dot.RIGHT;
+ mDots[mCurrentPage].mCenterX = mDotSelectedX[mCurrentPage];
+ for (int i = mCurrentPage + 1; i < mPageCount; ++i) {
+ mDots[i].deselect();
+ mDots[i].mDirection = Dot.RIGHT;
+ mDots[i].mCenterX = mDotSelectedNextX[i];
+ }
+ }
+
+ @Override
+ public void onRtlPropertiesChanged(int layoutDirection) {
+ super.onRtlPropertiesChanged(layoutDirection);
+ boolean isLtr = layoutDirection == View.LAYOUT_DIRECTION_LTR;
+ if (mIsLtr != isLtr) {
+ mIsLtr = isLtr;
+ mArrow = loadArrow();
+ if (mDots != null) {
+ for (Dot dot : mDots) {
+ dot.onRtlPropertiesChanged();
+ }
+ }
+ calculateDotPositions();
+ invalidate();
+ }
+ }
+
+ public class Dot {
+ static final float LEFT = -1;
+ static final float RIGHT = 1;
+ static final float LTR = 1;
+ static final float RTL = -1;
+
+ float mAlpha;
+ @ColorInt
+ int mFgColor;
+ float mTranslationX;
+ float mCenterX;
+ float mDiameter;
+ float mRadius;
+ float mArrowImageRadius;
+ float mDirection = RIGHT;
+ float mLayoutDirection = mIsLtr ? LTR : RTL;
+
+ void select() {
+ mTranslationX = 0.0f;
+ mCenterX = 0.0f;
+ mDiameter = mArrowDiameter;
+ mRadius = mArrowRadius;
+ mArrowImageRadius = mRadius * mArrowToBgRatio;
+ mAlpha = 1.0f;
+ adjustAlpha();
+ }
+
+ void deselect() {
+ mTranslationX = 0.0f;
+ mCenterX = 0.0f;
+ mDiameter = mDotDiameter;
+ mRadius = mDotRadius;
+ mArrowImageRadius = mRadius * mArrowToBgRatio;
+ mAlpha = 0.0f;
+ adjustAlpha();
+ }
+
+ public void adjustAlpha() {
+ int alpha = Math.round(0xFF * mAlpha);
+ int red = Color.red(mDotFgSelectColor);
+ int green = Color.green(mDotFgSelectColor);
+ int blue = Color.blue(mDotFgSelectColor);
+ mFgColor = Color.argb(alpha, red, green, blue);
+ }
+
+ public float getAlpha() {
+ return mAlpha;
+ }
+
+ public void setAlpha(float alpha) {
+ this.mAlpha = alpha;
+ adjustAlpha();
+ invalidate();
+ }
+
+ public float getTranslationX() {
+ return mTranslationX;
+ }
+
+ public void setTranslationX(float translationX) {
+ this.mTranslationX = translationX * mDirection * mLayoutDirection;
+ invalidate();
+ }
+
+ public float getDiameter() {
+ return mDiameter;
+ }
+
+ public void setDiameter(float diameter) {
+ this.mDiameter = diameter;
+ this.mRadius = diameter / 2;
+ this.mArrowImageRadius = diameter / 2 * mArrowToBgRatio;
+ invalidate();
+ }
+
+ void draw(Canvas canvas) {
+ float centerX = mCenterX + mTranslationX;
+ canvas.drawCircle(centerX, mDotCenterY, mRadius, mBgPaint);
+ if (mAlpha > 0) {
+ mFgPaint.setColor(mFgColor);
+ canvas.drawCircle(centerX, mDotCenterY, mRadius, mFgPaint);
+ canvas.drawBitmap(mArrow, mArrowRect, new Rect((int) (centerX - mArrowImageRadius),
+ (int) (mDotCenterY - mArrowImageRadius),
+ (int) (centerX + mArrowImageRadius),
+ (int) (mDotCenterY + mArrowImageRadius)), null);
+ }
+ }
+
+ void onRtlPropertiesChanged() {
+ mLayoutDirection = mIsLtr ? LTR : RTL;
+ }
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java b/v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
index 4ae1be0..eb9ada4 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PersistentFocusWrapper.java
@@ -167,6 +167,10 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
SavedState savedState = (SavedState) state;
mSelectedPosition = ((SavedState) state).mSelectedPosition;
if (DEBUG) Log.v(TAG, "onRestoreInstanceState mSelectedPosition " + mSelectedPosition);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
index 1ee77f2..b1cf2f9 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRow.java
@@ -321,6 +321,23 @@
}
/**
+ * An action displaying an icon for picture-in-picture.
+ */
+ public static class PictureInPictureAction extends Action {
+ /**
+ * Constructor
+ * @param context Context used for loading resources.
+ */
+ public PictureInPictureAction(Context context) {
+ super(R.id.lb_control_picture_in_picture);
+ setIcon(getStyledDrawable(context,
+ R.styleable.lbPlaybackControlsActionIcons_picture_in_picture));
+ setLabel1(context.getString(R.string.lb_playback_controls_picture_in_picture));
+ addKeyCode(KeyEvent.KEYCODE_WINDOW);
+ }
+ }
+
+ /**
* An action displaying an icon for "more actions".
*/
public static class MoreActions extends Action {
@@ -554,7 +571,7 @@
* @param highlightColor Color for the highlighted icon state.
*/
public ClosedCaptioningAction(Context context, int highlightColor) {
- super(R.id.lb_control_high_quality);
+ super(R.id.lb_control_closed_captioning);
BitmapDrawable uncoloredDrawable = (BitmapDrawable) getStyledDrawable(context,
R.styleable.lbPlaybackControlsActionIcons_closed_captioning);
Drawable[] drawables = new Drawable[2];
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
index b114dcf..16309bd 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Presenter.java
@@ -101,6 +101,18 @@
}
}
+ /**
+ * Base class to perform a task on Presenter.ViewHolder.
+ */
+ public static abstract class ViewHolderTask {
+ /**
+ * Called to perform a task on view holder.
+ * @param holder The view holder to perform task.
+ */
+ public void run(Presenter.ViewHolder holder) {
+ }
+ }
+
private Map<Class, Object> mFacets;
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Row.java b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
index 2fae1c7..ea52a82 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/Row.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Row.java
@@ -116,4 +116,14 @@
final int getFlags() {
return mFlags;
}
+
+ /**
+ * Returns true if this Row can be rendered in a visible row view, false otherwise. For example
+ * {@link ListRow} is rendered by {@link ListRowPresenter}. {@link PageRow},
+ * {@link SectionRow}, {@link DividerRow} are rendered as invisible row views.
+ * @return True if this Row can be rendered in a visible row view, false otherwise.
+ */
+ public boolean isRenderedAsRowView() {
+ return true;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
index c6c47e1..b7805cf 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowHeaderPresenter.java
@@ -30,6 +30,7 @@
private final int mLayoutResourceId;
private final Paint mFontMeasurePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
private boolean mNullItemVisibilityGone;
+ private final boolean mAnimateSelect;
public RowHeaderPresenter() {
this(R.layout.lb_row_header);
@@ -39,7 +40,15 @@
* @hide
*/
public RowHeaderPresenter(int layoutResourceId) {
+ this(layoutResourceId, true);
+ }
+
+ /**
+ * @hide
+ */
+ public RowHeaderPresenter(int layoutResourceId, boolean animateSelect) {
mLayoutResourceId = layoutResourceId;
+ mAnimateSelect = animateSelect;
}
/**
@@ -81,7 +90,9 @@
viewHolder.mOriginalTextColor = headerView.getCurrentTextColor();
viewHolder.mUnselectAlpha = parent.getResources().getFraction(
R.fraction.lb_browse_header_unselect_alpha, 1, 1);
- setSelectLevel(viewHolder, 0);
+ if (mAnimateSelect) {
+ setSelectLevel(viewHolder, 0);
+ }
return viewHolder;
}
@@ -90,19 +101,23 @@
HeaderItem headerItem = item == null ? null : ((Row) item).getHeaderItem();
if (headerItem == null) {
((RowHeaderView) viewHolder.view).setText(null);
+ viewHolder.view.setContentDescription(null);
if (mNullItemVisibilityGone) {
viewHolder.view.setVisibility(View.GONE);
}
} else {
viewHolder.view.setVisibility(View.VISIBLE);
((RowHeaderView) viewHolder.view).setText(headerItem.getName());
+ viewHolder.view.setContentDescription(headerItem.getContentDescription());
}
}
@Override
public void onUnbindViewHolder(Presenter.ViewHolder viewHolder) {
((RowHeaderView) viewHolder.view).setText(null);
- setSelectLevel((ViewHolder) viewHolder, 0);
+ if (mAnimateSelect) {
+ setSelectLevel((ViewHolder) viewHolder, 0);
+ }
}
/**
@@ -117,8 +132,10 @@
* Called when the select level changes. The default implementation sets the alpha on the view.
*/
protected void onSelectLevelChanged(ViewHolder holder) {
- holder.view.setAlpha(holder.mUnselectAlpha + holder.mSelectLevel *
- (1f - holder.mUnselectAlpha));
+ if (mAnimateSelect) {
+ holder.view.setAlpha(holder.mUnselectAlpha + holder.mSelectLevel *
+ (1f - holder.mUnselectAlpha));
+ }
}
/**
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
index cb1f2ac7..d53e38c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -19,7 +19,9 @@
import android.view.ViewGroup;
/**
- * An abstract {@link Presenter} that renders a {@link Row}.
+ * An abstract {@link Presenter} that renders an Object in RowsFragment, the object can be
+ * subclass {@link Row} or a generic one. When the object is not {@link Row} class,
+ * {@link ViewHolder#getRow()} returns null.
*
* <h3>Customize UI widgets</h3>
* When a subclass of RowPresenter adds UI widgets, it should subclass
@@ -143,6 +145,7 @@
ContainerViewHolder mContainerViewHolder;
RowHeaderPresenter.ViewHolder mHeaderViewHolder;
Row mRow;
+ Object mRowObject;
int mActivated = ACTIVATED_NOT_ASSIGNED;
boolean mSelected;
boolean mExpanded;
@@ -150,8 +153,8 @@
float mSelectLevel = 0f; // initially unselected
protected final ColorOverlayDimmer mColorDimmer;
private View.OnKeyListener mOnKeyListener;
- private OnItemViewSelectedListener mOnItemViewSelectedListener;
- private OnItemViewClickedListener mOnItemViewClickedListener;
+ private BaseOnItemViewSelectedListener mOnItemViewSelectedListener;
+ private BaseOnItemViewClickedListener mOnItemViewClickedListener;
/**
* Constructor for ViewHolder.
@@ -164,13 +167,24 @@
}
/**
- * Returns the Row bound to the View in this ViewHolder.
+ * Returns the row bound to this ViewHolder. Returns null if the row is not an instance of
+ * {@link Row}.
+ * @return The row bound to this ViewHolder. Returns null if the row is not an instance of
+ * {@link Row}.
*/
public final Row getRow() {
return mRow;
}
/**
+ * Returns the Row object bound to this ViewHolder.
+ * @return The row object bound to this ViewHolder.
+ */
+ public final Object getRowObject() {
+ return mRowObject;
+ }
+
+ /**
* Returns whether the Row is in its expanded state.
*
* @return true if the Row is expanded, false otherwise.
@@ -249,14 +263,14 @@
* event with null item. A subclass of RowPresenter e.g. {@link ListRowPresenter} may
* fire a selection event with selected item.
*/
- public final void setOnItemViewSelectedListener(OnItemViewSelectedListener listener) {
+ public final void setOnItemViewSelectedListener(BaseOnItemViewSelectedListener listener) {
mOnItemViewSelectedListener = listener;
}
/**
* Returns the listener for item or row selection.
*/
- public final OnItemViewSelectedListener getOnItemViewSelectedListener() {
+ public final BaseOnItemViewSelectedListener getOnItemViewSelectedListener() {
return mOnItemViewSelectedListener;
}
@@ -266,14 +280,14 @@
* OnItemViewClickedListener will override {@link View.OnClickListener} that
* item presenter sets during {@link Presenter#onCreateViewHolder(ViewGroup)}.
*/
- public final void setOnItemViewClickedListener(OnItemViewClickedListener listener) {
+ public final void setOnItemViewClickedListener(BaseOnItemViewClickedListener listener) {
mOnItemViewClickedListener = listener;
}
/**
* Returns the listener for item click event.
*/
- public final OnItemViewClickedListener getOnItemViewClickedListener() {
+ public final BaseOnItemViewClickedListener getOnItemViewClickedListener() {
return mOnItemViewClickedListener;
}
}
@@ -467,7 +481,7 @@
protected void dispatchItemSelectedListener(ViewHolder vh, boolean selected) {
if (selected) {
if (vh.mOnItemViewSelectedListener != null) {
- vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRow());
+ vh.mOnItemViewSelectedListener.onItemSelected(null, null, vh, vh.getRowObject());
}
}
}
@@ -572,10 +586,14 @@
/**
* Binds the given row object to the given ViewHolder.
+ * Derived classes of {@link RowPresenter} overriding
+ * {@link #onBindRowViewHolder(ViewHolder, Object)} must call through the super class's
+ * implementation of this method.
*/
protected void onBindRowViewHolder(ViewHolder vh, Object item) {
- vh.mRow = (Row) item;
- if (vh.mHeaderViewHolder != null) {
+ vh.mRowObject = item;
+ vh.mRow = item instanceof Row ? (Row) item : null;
+ if (vh.mHeaderViewHolder != null && vh.getRow() != null) {
mHeaderPresenter.onBindViewHolder(vh.mHeaderViewHolder, item);
}
}
@@ -587,12 +605,15 @@
/**
* Unbinds the given ViewHolder.
+ * Derived classes of {@link RowPresenter} overriding {@link #onUnbindRowViewHolder(ViewHolder)}
+ * must call through the super class's implementation of this method.
*/
protected void onUnbindRowViewHolder(ViewHolder vh) {
if (vh.mHeaderViewHolder != null) {
mHeaderPresenter.onUnbindViewHolder(vh.mHeaderViewHolder);
}
vh.mRow = null;
+ vh.mRowObject = null;
}
@Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
index 362744e..90616a2 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ScaleFrameLayout.java
@@ -18,6 +18,7 @@
import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
/**
@@ -31,6 +32,8 @@
private float mLayoutScaleX = 1f;
private float mLayoutScaleY = 1f;
+ private float mChildScale = 1f;
+
public ScaleFrameLayout(Context context) {
this(context ,null);
}
@@ -58,6 +61,34 @@
}
}
+ public void setChildScale(float scale) {
+ if (mChildScale != scale) {
+ mChildScale = scale;
+ for (int i = 0; i < getChildCount(); i++) {
+ getChildAt(i).setScaleX(scale);
+ getChildAt(i).setScaleY(scale);
+ }
+ }
+ }
+
+ @Override
+ public void addView(View child, int index, ViewGroup.LayoutParams params) {
+ super.addView(child, index, params);
+ child.setScaleX(mChildScale);
+ child.setScaleY(mChildScale);
+ }
+
+ @Override
+ protected boolean addViewInLayout (View child, int index, ViewGroup.LayoutParams params,
+ boolean preventRequestLayout) {
+ boolean ret = super.addViewInLayout(child, index, params, preventRequestLayout);
+ if (ret) {
+ child.setScaleX(mChildScale);
+ child.setScaleY(mChildScale);
+ }
+ return ret;
+ }
+
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
final int count = getChildCount();
@@ -170,4 +201,5 @@
public void setForeground(Drawable d) {
throw new UnsupportedOperationException();
}
+
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
index 1c3835f..ef2c094 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchBar.java
@@ -13,6 +13,7 @@
*/
package android.support.v17.leanback.widget;
+import android.Manifest;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -21,6 +22,7 @@
import android.graphics.drawable.Drawable;
import android.media.AudioManager;
import android.media.SoundPool;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
@@ -43,16 +45,22 @@
import android.widget.ImageView;
import android.view.inputmethod.InputMethodManager;
import android.widget.RelativeLayout;
-import android.support.v17.leanback.R;
import android.widget.TextView;
+import android.support.v17.leanback.R;
+
import java.util.ArrayList;
import java.util.List;
/**
* A search widget containing a search orb and a text entry view.
*
- * <p>Note: Your application will need to request android.permission.RECORD_AUDIO</p>
+ * <p>
+ * Note: When {@link SpeechRecognitionCallback} is not used, i.e. using {@link SpeechRecognizer},
+ * your application will need to declare android.permission.RECORD_AUDIO in manifest file.
+ * If your application target >= 23 and the device is running >= 23, it needs implement
+ * {@link SearchBarPermissionListener} where requests runtime permission.
+ * </p>
*/
public class SearchBar extends RelativeLayout {
private static final String TAG = SearchBar.class.getSimpleName();
@@ -92,6 +100,21 @@
* @param query The query set in the search bar at the time the IME is being dismissed.
*/
public void onKeyboardDismiss(String query);
+
+ }
+
+ /**
+ * Interface that handles runtime permissions requests. App sets listener on SearchBar via
+ * {@link #setPermissionListener(SearchBarPermissionListener)}.
+ */
+ public interface SearchBarPermissionListener {
+
+ /**
+ * Method invoked when SearchBar asks for "android.permission.RECORD_AUDIO" runtime
+ * permission.
+ */
+ void requestAudioPermission();
+
}
private AudioManager.OnAudioFocusChangeListener mAudioFocusChangeListener =
@@ -130,6 +153,7 @@
private boolean mRecognizing = false;
private final Context mContext;
private AudioManager mAudioManager;
+ private SearchBarPermissionListener mPermissionListener;
public SearchBar(Context context) {
this(context, null);
@@ -441,9 +465,6 @@
}
}
mSpeechRecognizer = recognizer;
- if (mSpeechRecognizer != null) {
- enforceAudioRecordPermission();
- }
if (mSpeechRecognitionCallback != null && mSpeechRecognizer != null) {
throw new IllegalStateException("Can't have speech recognizer and request");
}
@@ -508,6 +529,14 @@
}
/**
+ * Returns true if is not running Recognizer, false otherwise.
+ * @return True if is not running Recognizer, false otherwise.
+ */
+ public boolean isRecognizing() {
+ return mRecognizing;
+ }
+
+ /**
* Stops the speech recognition, if already started.
*/
public void stopRecognition() {
@@ -537,14 +566,18 @@
}
/**
- * Starts the voice recognition.
+ * Sets listener that handles runtime permission requests.
+ * @param listener Listener that handles runtime permission requests.
*/
+ public void setPermissionListener(SearchBarPermissionListener listener) {
+ mPermissionListener = listener;
+ }
+
public void startRecognition() {
if (DEBUG) Log.v(TAG, String.format("startRecognition (listening: %s, recognizing: %s)",
mListening, mRecognizing));
if (mRecognizing) return;
- mRecognizing = true;
if (!hasFocus()) {
requestFocus();
}
@@ -552,10 +585,22 @@
mSearchTextEditor.setText("");
mSearchTextEditor.setHint("");
mSpeechRecognitionCallback.recognizeSpeech();
+ mRecognizing = true;
return;
}
if (null == mSpeechRecognizer) return;
+ int res = getContext().checkCallingOrSelfPermission(Manifest.permission.RECORD_AUDIO);
+ if (PackageManager.PERMISSION_GRANTED != res) {
+ if (Build.VERSION.SDK_INT >= 23 && mPermissionListener != null) {
+ mPermissionListener.requestAudioPermission();
+ return;
+ } else {
+ throw new IllegalStateException(Manifest.permission.RECORD_AUDIO +
+ " required for search");
+ }
+ }
+ mRecognizing = true;
// Request audio focus
int result = mAudioManager.requestAudioFocus(mAudioFocusChangeListener,
// Use the music stream.
@@ -725,14 +770,6 @@
}
}
- private void enforceAudioRecordPermission() {
- String permission = "android.permission.RECORD_AUDIO";
- int res = getContext().checkCallingOrSelfPermission(permission);
- if (PackageManager.PERMISSION_GRANTED != res) {
- throw new IllegalStateException("android.permission.RECORD_AUDIO required for search");
- }
- }
-
private void loadSounds(Context context) {
int[] sounds = {
R.raw.lb_voice_failure,
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
index a060ec0..10b8727 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SearchOrbView.java
@@ -51,6 +51,8 @@
private final float mUnfocusedZ;
private final float mFocusedZ;
private ValueAnimator mColorAnimator;
+ private boolean mColorAnimationEnabled;
+ private boolean mAttachedToWindow;
/**
* A set of colors used to display the search orb.
@@ -342,11 +344,16 @@
* </p>
*/
public void enableOrbColorAnimation(boolean enable) {
+ mColorAnimationEnabled = enable;
+ updateColorAnimator();
+ }
+
+ private void updateColorAnimator() {
if (mColorAnimator != null) {
mColorAnimator.end();
mColorAnimator = null;
}
- if (enable) {
+ if (mColorAnimationEnabled && mAttachedToWindow) {
// TODO: set interpolator (material if available)
mColorAnimator = ValueAnimator.ofObject(mColorEvaluator,
mColors.color, mColors.brightColor, mColors.color);
@@ -364,9 +371,17 @@
}
@Override
+ protected void onAttachedToWindow() {
+ super.onAttachedToWindow();
+ mAttachedToWindow = true;
+ updateColorAnimator();
+ }
+
+ @Override
protected void onDetachedFromWindow() {
+ mAttachedToWindow = false;
// Must stop infinite animation to prevent activity leak
- enableOrbColorAnimation(false);
+ updateColorAnimator();
super.onDetachedFromWindow();
}
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/SectionRow.java b/v17/leanback/src/android/support/v17/leanback/widget/SectionRow.java
new file mode 100644
index 0000000..379508d
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/SectionRow.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+/**
+ * Used to represent section item in HeadersFragment. Unlike a normal Row, it's not focusable.
+ */
+public class SectionRow extends Row {
+
+ public SectionRow(HeaderItem headerItem) {
+ super(headerItem);
+ }
+
+ public SectionRow(long id, String name) {
+ super(new HeaderItem(id, name));
+ }
+
+ public SectionRow(String name) {
+ super(new HeaderItem(name));
+ }
+
+ @Override
+ final public boolean isRenderedAsRowView() {
+ return false;
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
index 9282bb1..6f76c0ec 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleHelper.java
@@ -30,7 +30,7 @@
public class TitleHelper {
private ViewGroup mSceneRoot;
- private TitleView mTitleView;
+ private View mTitleView;
private Object mTitleUpTransition;
private Object mTitleDownTransition;
private Object mSceneWithTitle;
@@ -55,7 +55,7 @@
}
};
- public TitleHelper(ViewGroup sceneRoot, TitleView titleView) {
+ public TitleHelper(ViewGroup sceneRoot, View titleView) {
if (sceneRoot == null || titleView == null) {
throw new IllegalArgumentException("Views may not be null");
}
@@ -104,7 +104,7 @@
/**
* Returns the {@link TitleView}
*/
- public TitleView getTitleView() {
+ public View getTitleView() {
return mTitleView;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
index bc23de2..f9baf71 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleView.java
@@ -23,14 +23,71 @@
import android.widget.ImageView;
import android.widget.TextView;
+import static android.support.v17.leanback.widget.TitleViewAdapter.BRANDING_VIEW_VISIBLE;
+import static android.support.v17.leanback.widget.TitleViewAdapter.SEARCH_VIEW_VISIBLE;
+import static android.support.v17.leanback.widget.TitleViewAdapter.FULL_VIEW_VISIBLE;
+
/**
* Title view for a leanback fragment.
*/
-public class TitleView extends FrameLayout {
+public class TitleView extends FrameLayout implements TitleViewAdapter.Provider {
private ImageView mBadgeView;
private TextView mTextView;
private SearchOrbView mSearchOrbView;
+ private int flags = FULL_VIEW_VISIBLE;
+
+ private final TitleViewAdapter mTitleViewAdapter = new TitleViewAdapter() {
+ @Override
+ public View getSearchAffordanceView() {
+ return TitleView.this.getSearchAffordanceView();
+ }
+
+ @Override
+ public void setOnSearchClickedListener(View.OnClickListener listener) {
+ TitleView.this.setOnSearchClickedListener(listener);
+ }
+
+ @Override
+ public void setAnimationEnabled(boolean enable) {
+ TitleView.this.enableAnimation(enable);
+ }
+
+ @Override
+ public Drawable getBadgeDrawable() {
+ return TitleView.this.getBadgeDrawable();
+ }
+
+ @Override
+ public SearchOrbView.Colors getSearchAffordanceColors() {
+ return TitleView.this.getSearchAffordanceColors();
+ }
+
+ @Override
+ public CharSequence getTitle() {
+ return TitleView.this.getTitle();
+ }
+
+ @Override
+ public void setBadgeDrawable(Drawable drawable) {
+ TitleView.this.setBadgeDrawable(drawable);
+ }
+
+ @Override
+ public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+ TitleView.this.setSearchAffordanceColors(colors);
+ }
+
+ @Override
+ public void setTitle(CharSequence titleText) {
+ TitleView.this.setTitle(titleText);
+ }
+
+ @Override
+ public void updateComponentsVisibility(int flags) {
+ TitleView.this.updateComponentsVisibility(flags);
+ }
+ };
public TitleView(Context context) {
this(context, null);
@@ -57,8 +114,9 @@
/**
* Sets the title text.
*/
- public void setTitle(String titleText) {
+ public void setTitle(CharSequence titleText) {
mTextView.setText(titleText);
+ updateBadgeVisibility();
}
/**
@@ -74,13 +132,7 @@
*/
public void setBadgeDrawable(Drawable drawable) {
mBadgeView.setImageDrawable(drawable);
- if (drawable != null) {
- mBadgeView.setVisibility(View.VISIBLE);
- mTextView.setVisibility(View.GONE);
- } else {
- mBadgeView.setVisibility(View.GONE);
- mTextView.setVisibility(View.VISIBLE);
- }
+ updateBadgeVisibility();
}
/**
@@ -124,4 +176,44 @@
public void enableAnimation(boolean enable) {
mSearchOrbView.enableOrbColorAnimation(enable && mSearchOrbView.hasFocus());
}
+
+ /**
+ * Based on the flag, it updates the visibility of the individual components -
+ * BadgeView, TextView and SearchView.
+ *
+ * @param flags integer representing the visibility of TitleView components.
+ * @see TitleViewAdapter#SEARCH_VIEW_VISIBLE
+ * @see TitleViewAdapter#BRANDING_VIEW_VISIBLE
+ * @see TitleViewAdapter#FULL_VIEW_VISIBLE
+ */
+ public void updateComponentsVisibility(int flags) {
+ this.flags = flags;
+
+ if ((flags & BRANDING_VIEW_VISIBLE) == BRANDING_VIEW_VISIBLE) {
+ updateBadgeVisibility();
+ } else {
+ mBadgeView.setVisibility(View.GONE);
+ mTextView.setVisibility(View.GONE);
+ }
+
+ int visibility = (flags & SEARCH_VIEW_VISIBLE) == SEARCH_VIEW_VISIBLE
+ ? View.VISIBLE : View.INVISIBLE;
+ mSearchOrbView.setVisibility(visibility);
+ }
+
+ private void updateBadgeVisibility() {
+ Drawable drawable = mBadgeView.getDrawable();
+ if (drawable != null) {
+ mBadgeView.setVisibility(View.VISIBLE);
+ mTextView.setVisibility(View.GONE);
+ } else {
+ mBadgeView.setVisibility(View.GONE);
+ mTextView.setVisibility(View.VISIBLE);
+ }
+ }
+
+ @Override
+ public TitleViewAdapter getTitleViewAdapter() {
+ return mTitleViewAdapter;
+ }
}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java
new file mode 100644
index 0000000..7156be2
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/TitleViewAdapter.java
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.drawable.Drawable;
+import android.support.v17.leanback.R;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.view.View;
+
+/**
+ * This class allows a customized widget class to implement {@link TitleViewAdapter.Provider}
+ * and expose {@link TitleViewAdapter} methods to containing fragment (e.g. BrowseFragment or
+ * DetailsFragment).
+ * The title view must have a search orb view ({@link #getSearchAffordanceView()} aligned to start
+ * and can typically have a branding Drawable and or title text aligned to end. The branding part
+ * is fully open to customization: not necessary to be a drawable or text.
+ */
+public abstract class TitleViewAdapter {
+
+ /**
+ * Interface to be implemented by a customized widget class to implement
+ * {@link TitleViewAdapter}.
+ */
+ public interface Provider {
+ /**
+ * Returns {@link TitleViewAdapter} to be implemented by the customized widget class.
+ * @return {@link TitleViewAdapter} to be implemented by the customized widget class.
+ */
+ TitleViewAdapter getTitleViewAdapter();
+ }
+
+ public static final int BRANDING_VIEW_VISIBLE = 0x02;
+ public static final int SEARCH_VIEW_VISIBLE = 0x04;
+ public static final int FULL_VIEW_VISIBLE = BRANDING_VIEW_VISIBLE | SEARCH_VIEW_VISIBLE;
+
+ /**
+ * Sets the title text.
+ * @param titleText The text to set as title.
+ */
+ public void setTitle(CharSequence titleText) {
+ }
+
+ /**
+ * Returns the title text.
+ * @return The title text.
+ */
+ public CharSequence getTitle() {
+ return null;
+ }
+
+ /**
+ * Sets the badge drawable.
+ * If non-null, the drawable is displayed instead of the title text.
+ * @param drawable The badge drawable to set on title view.
+ */
+ public void setBadgeDrawable(Drawable drawable) {
+ }
+
+ /**
+ * Returns the badge drawable.
+ * @return The badge drawable.
+ */
+ public Drawable getBadgeDrawable() {
+ return null;
+ }
+
+ /**
+ * Returns the view for the search affordance.
+ * @return The view for search affordance.
+ */
+ public abstract View getSearchAffordanceView();
+
+ /**
+ * Sets a click listener for the search affordance view.
+ *
+ * <p>The presence of a listener will change the visibility of the search
+ * affordance in the fragment title. When set to non-null, the title will
+ * contain an element that a user may click to begin a search.
+ *
+ * <p>The listener's {@link View.OnClickListener#onClick onClick} method
+ * will be invoked when the user clicks on the search element.
+ *
+ * @param listener The listener to call when the search element is clicked.
+ */
+ public void setOnSearchClickedListener(View.OnClickListener listener) {
+ }
+
+ /**
+ * Sets the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
+ * search affordance.
+ *
+ * @param colors Colors used to draw search affordance.
+ */
+ public void setSearchAffordanceColors(SearchOrbView.Colors colors) {
+ }
+
+ /**
+ * Returns the {@link android.support.v17.leanback.widget.SearchOrbView.Colors} used to draw the
+ * search affordance.
+ *
+ * @return Colors used to draw search affordance.
+ */
+ public SearchOrbView.Colors getSearchAffordanceColors() {
+ return null;
+ }
+
+ /**
+ * Enables or disables any view animations. This method is called to save CPU cycle for example
+ * stop search view breathing animation when containing fragment is paused.
+ * @param enable True to enable animation, false otherwise.
+ */
+ public void setAnimationEnabled(boolean enable) {
+ }
+
+ /**
+ * Based on the flag, it updates the visibility of the individual components -
+ * Branding views (badge drawable and/or title) and search affordance view.
+ *
+ * @param flags integer representing the visibility of TitleView components.
+ * @see #BRANDING_VIEW_VISIBLE
+ * @see #SEARCH_VIEW_VISIBLE
+ * @see #FULL_VIEW_VISIBLE
+ */
+ public void updateComponentsVisibility(int flags) {
+ }
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/Util.java b/v17/leanback/src/android/support/v17/leanback/widget/Util.java
new file mode 100644
index 0000000..de4b1c7
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/Util.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+package android.support.v17.leanback.widget;
+
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+/**
+ * @hide
+ */
+public class Util {
+
+ /**
+ * Returns true if child == parent or is descendant of the parent.
+ */
+ public static boolean isDescendant(ViewGroup parent, View child) {
+ while (child != null) {
+ if (child == parent) {
+ return true;
+ }
+ ViewParent p = child.getParent();
+ if (!(p instanceof View)) {
+ return false;
+ }
+ child = (View) p;
+ }
+ return false;
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java b/v17/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java
new file mode 100644
index 0000000..6103487
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/ViewHolderTask.java
@@ -0,0 +1,23 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.support.v7.widget.RecyclerView;
+
+/**
+ * Interface for schedule task on a ViewHolder.
+ */
+public interface ViewHolderTask {
+ public void run(RecyclerView.ViewHolder viewHolder);
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
new file mode 100644
index 0000000..06aa4fe
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/DatePicker.java
@@ -0,0 +1,407 @@
+/*
+ * 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.v17.leanback.widget.picker;
+
+import android.content.Context;
+import android.content.res.TypedArray;
+import android.support.v17.leanback.R;
+import android.text.TextUtils;
+import android.util.AttributeSet;
+import android.util.Log;
+
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Locale;
+import java.util.TimeZone;
+
+/**
+ * {@link DatePicker} is a directly subclass of {@link Picker}.
+ * This class is a widget for selecting a date. The date can be selected by a
+ * year, month, and day Columns. The "minDate" and "maxDate" from which dates to be selected
+ * can be customized. The columns can be customized by attribute "datePickerFormat" or
+ * {@link #setDatePickerFormat(String)}.
+ *
+ * @attr ref R.styleable#lbDatePicker_android_maxDate
+ * @attr ref R.styleable#lbDatePicker_android_minDate
+ * @attr ref R.styleable#lbDatePicker_datePickerFormat
+ * @hide
+ */
+public class DatePicker extends Picker {
+
+ static final String LOG_TAG = "DatePicker";
+
+ private String mDatePickerFormat;
+ PickerColumn mMonthColumn;
+ PickerColumn mDayColumn;
+ PickerColumn mYearColumn;
+ int mColMonthIndex;
+ int mColDayIndex;
+ int mColYearIndex;
+
+ final static String DATE_FORMAT = "MM/dd/yyyy";
+ final DateFormat mDateFormat = new SimpleDateFormat(DATE_FORMAT);
+ PickerConstant mConstant;
+
+ Calendar mMinDate;
+ Calendar mMaxDate;
+ Calendar mCurrentDate;
+ Calendar mTempDate;
+
+ public DatePicker(Context context, AttributeSet attrs) {
+ this(context, attrs, 0);
+ }
+
+ public DatePicker(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+
+ updateCurrentLocale();
+ setSeparator(mConstant.dateSeparator);
+
+ final TypedArray attributesArray = context.obtainStyledAttributes(attrs,
+ R.styleable.lbDatePicker);
+ String minDate = attributesArray.getString(R.styleable.lbDatePicker_android_minDate);
+ String maxDate = attributesArray.getString(R.styleable.lbDatePicker_android_maxDate);
+ mTempDate.clear();
+ if (!TextUtils.isEmpty(minDate)) {
+ if (!parseDate(minDate, mTempDate)) {
+ mTempDate.set(1900, 0, 1);
+ }
+ } else {
+ mTempDate.set(1900, 0, 1);
+ }
+ mMinDate.setTimeInMillis(mTempDate.getTimeInMillis());
+
+ mTempDate.clear();
+ if (!TextUtils.isEmpty(maxDate)) {
+ if (!parseDate(maxDate, mTempDate)) {
+ mTempDate.set(2100, 0, 1);
+ }
+ } else {
+ mTempDate.set(2100, 0, 1);
+ }
+ mMaxDate.setTimeInMillis(mTempDate.getTimeInMillis());
+
+ String datePickerFormat = attributesArray
+ .getString(R.styleable.lbDatePicker_datePickerFormat);
+ if (TextUtils.isEmpty(datePickerFormat)) {
+ datePickerFormat = new String(
+ android.text.format.DateFormat.getDateFormatOrder(context));
+ }
+ setDatePickerFormat(datePickerFormat);
+ }
+
+ private boolean parseDate(String date, Calendar outDate) {
+ try {
+ outDate.setTime(mDateFormat.parse(date));
+ return true;
+ } catch (ParseException e) {
+ Log.w(LOG_TAG, "Date: " + date + " not in format: " + DATE_FORMAT);
+ return false;
+ }
+ }
+
+ /**
+ * Changes format of showing dates. For example "YMD".
+ * @param datePickerFormat Format of showing dates.
+ */
+ public void setDatePickerFormat(String datePickerFormat) {
+ if (TextUtils.isEmpty(datePickerFormat)) {
+ datePickerFormat = new String(
+ android.text.format.DateFormat.getDateFormatOrder(getContext()));
+ }
+ datePickerFormat = datePickerFormat.toUpperCase();
+ if (TextUtils.equals(mDatePickerFormat, datePickerFormat)) {
+ return;
+ }
+ mDatePickerFormat = datePickerFormat;
+ mYearColumn = mMonthColumn = mDayColumn = null;
+ mColYearIndex = mColDayIndex = mColMonthIndex = -1;
+ ArrayList<PickerColumn> columns = new ArrayList<PickerColumn>(3);
+ for (int i = 0; i < datePickerFormat.length(); i++) {
+ switch (datePickerFormat.charAt(i)) {
+ case 'Y':
+ if (mYearColumn != null) {
+ throw new IllegalArgumentException("datePicker format error");
+ }
+ columns.add(mYearColumn = new PickerColumn());
+ mColYearIndex = i;
+ mYearColumn.setLabelFormat("%d");
+ break;
+ case 'M':
+ if (mMonthColumn != null) {
+ throw new IllegalArgumentException("datePicker format error");
+ }
+ columns.add(mMonthColumn = new PickerColumn());
+ mMonthColumn.setStaticLabels(mConstant.months);
+ mColMonthIndex = i;
+ break;
+ case 'D':
+ if (mDayColumn != null) {
+ throw new IllegalArgumentException("datePicker format error");
+ }
+ columns.add(mDayColumn = new PickerColumn());
+ mDayColumn.setLabelFormat("%02d");
+ mColDayIndex = i;
+ break;
+ default:
+ throw new IllegalArgumentException("datePicker format error");
+ }
+ }
+ setColumns(columns);
+ updateSpinners(false);
+ }
+
+ /**
+ * Get format of showing dates. For example "YMD". Default value is from
+ * {@link android.text.format.DateFormat#getDateFormatOrder(Context)}.
+ * @return Format of showing dates.
+ */
+ public String getDatePickerFormat() {
+ return mDatePickerFormat;
+ }
+
+ private Calendar getCalendarForLocale(Calendar oldCalendar, Locale locale) {
+ if (oldCalendar == null) {
+ return Calendar.getInstance(locale);
+ } else {
+ final long currentTimeMillis = oldCalendar.getTimeInMillis();
+ Calendar newCalendar = Calendar.getInstance(locale);
+ newCalendar.setTimeInMillis(currentTimeMillis);
+ return newCalendar;
+ }
+ }
+
+ private void updateCurrentLocale() {
+ mConstant = new PickerConstant(Locale.getDefault(), getContext().getResources());
+ mTempDate = getCalendarForLocale(mTempDate, mConstant.locale);
+ mMinDate = getCalendarForLocale(mMinDate, mConstant.locale);
+ mMaxDate = getCalendarForLocale(mMaxDate, mConstant.locale);
+ mCurrentDate = getCalendarForLocale(mCurrentDate, mConstant.locale);
+
+ if (mMonthColumn != null) {
+ mMonthColumn.setStaticLabels(mConstant.months);
+ setColumnAt(mColMonthIndex, mMonthColumn);
+ }
+ }
+
+ @Override
+ public final void onColumnValueChanged(int column, int newVal) {
+ mTempDate.setTimeInMillis(mCurrentDate.getTimeInMillis());
+ // take care of wrapping of days and months to update greater fields
+ int oldVal = getColumnAt(column).getCurrentValue();
+ if (column == mColDayIndex) {
+ mTempDate.add(Calendar.DAY_OF_MONTH, newVal - oldVal);
+ } else if (column == mColMonthIndex) {
+ mTempDate.add(Calendar.MONTH, newVal - oldVal);
+ } else if (column == mColYearIndex) {
+ mTempDate.add(Calendar.YEAR, newVal - oldVal);
+ } else {
+ throw new IllegalArgumentException();
+ }
+ setDate(mTempDate.get(Calendar.YEAR), mTempDate.get(Calendar.MONTH),
+ mTempDate.get(Calendar.DAY_OF_MONTH));
+ updateSpinners(false);
+ }
+
+
+ /**
+ * Sets the minimal date supported by this {@link DatePicker} in
+ * milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ *
+ * @param minDate The minimal supported date.
+ */
+ public void setMinDate(long minDate) {
+ mTempDate.setTimeInMillis(minDate);
+ if (mTempDate.get(Calendar.YEAR) == mMinDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMinDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ mMinDate.setTimeInMillis(minDate);
+ if (mCurrentDate.before(mMinDate)) {
+ mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+ }
+ updateSpinners(false);
+ }
+
+
+ /**
+ * Gets the minimal date supported by this {@link DatePicker} in
+ * milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * <p>
+ * Note: The default minimal date is 01/01/1900.
+ * <p>
+ *
+ * @return The minimal supported date.
+ */
+ public long getMinDate() {
+ return mMinDate.getTimeInMillis();
+ }
+
+ /**
+ * Sets the maximal date supported by this {@link DatePicker} in
+ * milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ *
+ * @param maxDate The maximal supported date.
+ */
+ public void setMaxDate(long maxDate) {
+ mTempDate.setTimeInMillis(maxDate);
+ if (mTempDate.get(Calendar.YEAR) == mMaxDate.get(Calendar.YEAR)
+ && mTempDate.get(Calendar.DAY_OF_YEAR) != mMaxDate.get(Calendar.DAY_OF_YEAR)) {
+ return;
+ }
+ mMaxDate.setTimeInMillis(maxDate);
+ if (mCurrentDate.after(mMaxDate)) {
+ mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+ }
+ updateSpinners(false);
+ }
+
+ /**
+ * Gets the maximal date supported by this {@link DatePicker} in
+ * milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ * <p>
+ * Note: The default maximal date is 12/31/2100.
+ * <p>
+ *
+ * @return The maximal supported date.
+ */
+ public long getMaxDate() {
+ return mMaxDate.getTimeInMillis();
+ }
+
+ /**
+ * Gets current date value in milliseconds since January 1, 1970 00:00:00 in
+ * {@link TimeZone#getDefault()} time zone.
+ *
+ * @return Current date values.
+ */
+ public long getDate() {
+ return mCurrentDate.getTimeInMillis();
+ }
+
+ private void setDate(int year, int month, int dayOfMonth) {
+ mCurrentDate.set(year, month, dayOfMonth);
+ if (mCurrentDate.before(mMinDate)) {
+ mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+ } else if (mCurrentDate.after(mMaxDate)) {
+ mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+ }
+ }
+
+ /**
+ * Update the current date.
+ *
+ * @param year The year.
+ * @param month The month which is <strong>starting from zero</strong>.
+ * @param dayOfMonth The day of the month.
+ * @param animation True to run animation to scroll the column.
+ */
+ public void updateDate(int year, int month, int dayOfMonth, boolean animation) {
+ if (!isNewDate(year, month, dayOfMonth)) {
+ return;
+ }
+ setDate(year, month, dayOfMonth);
+ updateSpinners(animation);
+ }
+
+ private boolean isNewDate(int year, int month, int dayOfMonth) {
+ return (mCurrentDate.get(Calendar.YEAR) != year
+ || mCurrentDate.get(Calendar.MONTH) != dayOfMonth
+ || mCurrentDate.get(Calendar.DAY_OF_MONTH) != month);
+ }
+
+ private static boolean updateMin(PickerColumn column, int value) {
+ if (value != column.getMinValue()) {
+ column.setMinValue(value);
+ return true;
+ }
+ return false;
+ }
+
+ private static boolean updateMax(PickerColumn column, int value) {
+ if (value != column.getMaxValue()) {
+ column.setMaxValue(value);
+ return true;
+ }
+ return false;
+ }
+
+ private static int[] DATE_FIELDS = {Calendar.DAY_OF_MONTH, Calendar.MONTH, Calendar.YEAR};
+
+ // Following implementation always keeps up-to-date date ranges (min & max values) no matter
+ // what the currently selected date is. This prevents the constant updating of date values while
+ // scrolling vertically and thus fixes the animation jumps that used to happen when we reached
+ // the endpoint date field values since the adapter values do not change while scrolling up
+ // & down across a single field.
+ private void updateSpinnersImpl(boolean animation) {
+ // set the spinner ranges respecting the min and max dates
+ int dateFieldIndices[] = {mColDayIndex, mColMonthIndex, mColYearIndex};
+
+ boolean allLargerDateFieldsHaveBeenEqualToMinDate = true;
+ boolean allLargerDateFieldsHaveBeenEqualToMaxDate = true;
+ for(int i = DATE_FIELDS.length - 1; i >= 0; i--) {
+ boolean dateFieldChanged = false;
+ if (dateFieldIndices[i] < 0)
+ continue;
+
+ int currField = DATE_FIELDS[i];
+ PickerColumn currPickerColumn = getColumnAt(dateFieldIndices[i]);
+
+ if (allLargerDateFieldsHaveBeenEqualToMinDate) {
+ dateFieldChanged |= updateMin(currPickerColumn,
+ mMinDate.get(currField));
+ } else {
+ dateFieldChanged |= updateMin(currPickerColumn,
+ mCurrentDate.getActualMinimum(currField));
+ }
+
+ if (allLargerDateFieldsHaveBeenEqualToMaxDate) {
+ dateFieldChanged |= updateMax(currPickerColumn,
+ mMaxDate.get(currField));
+ } else {
+ dateFieldChanged |= updateMax(currPickerColumn,
+ mCurrentDate.getActualMaximum(currField));
+ }
+
+ allLargerDateFieldsHaveBeenEqualToMinDate &=
+ (mCurrentDate.get(currField) == mMinDate.get(currField));
+ allLargerDateFieldsHaveBeenEqualToMaxDate &=
+ (mCurrentDate.get(currField) == mMaxDate.get(currField));
+
+ if (dateFieldChanged) {
+ setColumnAt(dateFieldIndices[i], currPickerColumn);
+ }
+ setColumnValue(dateFieldIndices[i], mCurrentDate.get(currField), animation);
+ }
+ }
+
+ private void updateSpinners(final boolean animation) {
+ // update range in a post call. The reason is that RV does not allow notifyDataSetChange()
+ // in scroll pass. UpdateSpinner can be called in a scroll pass, UpdateSpinner() may
+ // notifyDataSetChange to update the range.
+ post(new Runnable() {
+ public void run() {
+ updateSpinnersImpl(animation);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
new file mode 100644
index 0000000..1f35aae
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/Picker.java
@@ -0,0 +1,593 @@
+/*
+ * 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.v17.leanback.widget.picker;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.v17.leanback.R;
+import android.support.v17.leanback.widget.OnChildViewHolderSelectedListener;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.widget.RecyclerView;
+import android.util.AttributeSet;
+import android.util.TypedValue;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.animation.AccelerateInterpolator;
+import android.view.animation.DecelerateInterpolator;
+import android.view.animation.Interpolator;
+import android.widget.FrameLayout;
+import android.widget.TextView;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Picker is a widget showing multiple customized {@link PickerColumn}s. The PickerColumns are
+ * initialized in {@link #setColumns(List)}. Call {@link #setColumnAt(int, PickerColumn)} if the
+ * column value range or labels change. Call {@link #setColumnValue(int, int, boolean)} to update
+ * the current value of PickerColumn.
+ * <p>
+ * Picker has two states and will change height:
+ * <li>{@link #isActivated()} is true: Picker shows typically three items vertically (see
+ * {@link #getActivatedVisibleItemCount()}}. Columns other than {@link #getSelectedColumn()} still
+ * shows one item if the Picker is focused. On a touch screen device, the Picker will not get focus
+ * so it always show three items on all columns. On a non-touch device (a TV), the Picker will show
+ * three items only on currently activated column. If the Picker has focus, it will intercept DPAD
+ * directions and select activated column.
+ * <li>{@link #isActivated()} is false: Picker shows one item vertically (see
+ * {@link #getVisibleItemCount()}) on all columns. The size of Picker shrinks.
+ */
+public class Picker extends FrameLayout {
+
+ public interface PickerValueListener {
+ public void onValueChanged(Picker picker, int column);
+ }
+
+ private ViewGroup mRootView;
+ private ViewGroup mPickerView;
+ private final List<VerticalGridView> mColumnViews = new ArrayList<VerticalGridView>();
+ private ArrayList<PickerColumn> mColumns;
+
+ private float mUnfocusedAlpha;
+ private float mFocusedAlpha;
+ private float mVisibleColumnAlpha;
+ private float mInvisibleColumnAlpha;
+ private int mAlphaAnimDuration;
+ private Interpolator mDecelerateInterpolator;
+ private Interpolator mAccelerateInterpolator;
+ private ArrayList<PickerValueListener> mListeners;
+ private float mVisibleItemsActivated = 3;
+ private float mVisibleItems = 1;
+ private int mSelectedColumn = 0;
+
+ private CharSequence mSeparator;
+ private int mPickerItemLayoutId = R.layout.lb_picker_item;
+ private int mPickerItemTextViewId = 0;
+
+ /**
+ * Gets separator string between columns.
+ */
+ public final CharSequence getSeparator() {
+ return mSeparator;
+ }
+
+ /**
+ * Sets separator String between Picker columns.
+ * @param seperator Separator String between Picker columns.
+ */
+ public final void setSeparator(CharSequence seperator) {
+ mSeparator = seperator;
+ }
+
+ /**
+ * Classes extending {@link Picker} can choose to override this method to
+ * supply the {@link Picker}'s item's layout id
+ */
+ public final int getPickerItemLayoutId() {
+ return mPickerItemLayoutId;
+ }
+
+ /**
+ * Returns the {@link Picker}'s item's {@link TextView}'s id from within the
+ * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
+ * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
+ * TextView}.
+ */
+ public final int getPickerItemTextViewId() {
+ return mPickerItemTextViewId;
+ }
+
+ /**
+ * Sets the {@link Picker}'s item's {@link TextView}'s id from within the
+ * layout provided by {@link Picker#getPickerItemLayoutId()} or 0 if the
+ * layout provided by {@link Picker#getPickerItemLayoutId()} is a {link
+ * TextView}.
+ * @param textViewId View id of TextView inside a Picker item, or 0 if the Picker item is a
+ * TextView.
+ */
+ public final void setPickerItemTextViewId(int textViewId) {
+ mPickerItemTextViewId = textViewId;
+ }
+
+ /**
+ * Creates a Picker widget.
+ * @param context
+ * @param attrs
+ * @param defStyleAttr
+ */
+ public Picker(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ // Make it enabled and clickable to receive Click event.
+ setEnabled(true);
+
+ mFocusedAlpha = 1f; //getFloat(R.dimen.list_item_selected_title_text_alpha);
+ mUnfocusedAlpha = 1f; //getFloat(R.dimen.list_item_unselected_text_alpha);
+ mVisibleColumnAlpha = 0.5f; //getFloat(R.dimen.picker_item_visible_column_item_alpha);
+ mInvisibleColumnAlpha = 0f; //getFloat(R.dimen.picker_item_invisible_column_item_alpha);
+
+ mAlphaAnimDuration = 200; // mContext.getResources().getInteger(R.integer.dialog_animation_duration);
+
+ mDecelerateInterpolator = new DecelerateInterpolator(2.5F);
+ mAccelerateInterpolator = new AccelerateInterpolator(2.5F);
+
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ mRootView = (ViewGroup) inflater.inflate(R.layout.lb_picker, this, true);
+ mPickerView = (ViewGroup) mRootView.findViewById(R.id.picker);
+
+ }
+
+ /**
+ * Get nth PickerColumn.
+ * @param colIndex Index of PickerColumn.
+ * @return PickerColumn at colIndex or null if {@link #setColumns(List)} is not called yet.
+ */
+ public PickerColumn getColumnAt(int colIndex) {
+ if (mColumns == null) {
+ return null;
+ }
+ return mColumns.get(colIndex);
+ }
+
+ /**
+ * Get number of PickerColumns.
+ * @return Number of PickerColumns or 0 if {@link #setColumns(List)} is not called yet.
+ */
+ public int getColumnsCount() {
+ if (mColumns == null) {
+ return 0;
+ }
+ return mColumns.size();
+ }
+
+ /**
+ * Set columns and create Views.
+ * @param columns PickerColumns to be shown in the Picker.
+ */
+ public void setColumns(List<PickerColumn> columns) {
+ mColumnViews.clear();
+ mPickerView.removeAllViews();
+ mColumns = new ArrayList<PickerColumn>(columns);
+ if (mSelectedColumn > mColumns.size() - 1) {
+ mSelectedColumn = mColumns.size() - 1;
+ }
+ LayoutInflater inflater = LayoutInflater.from(getContext());
+ int totalCol = getColumnsCount();
+ for (int i = 0; i < totalCol; i++) {
+ final int colIndex = i;
+ final VerticalGridView columnView = (VerticalGridView) inflater.inflate(
+ R.layout.lb_picker_column, mPickerView, false);
+ // we dont want VerticalGridView to receive focus.
+ updateColumnSize(columnView);
+ // always center aligned, not aligning selected item on top/bottom edge.
+ columnView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_NO_EDGE);
+ // Width is dynamic, so has fixed size is false.
+ columnView.setHasFixedSize(false);
+ mColumnViews.add(columnView);
+
+ // add view to root
+ mPickerView.addView(columnView);
+
+ // add a separator if not the last element
+ if (i != totalCol - 1 && getSeparator() != null) {
+ TextView separator = (TextView) inflater.inflate(
+ R.layout.lb_picker_separator, mPickerView, false);
+ separator.setText(getSeparator());
+ mPickerView.addView(separator);
+ }
+
+ columnView.setAdapter(new PickerScrollArrayAdapter(getContext(),
+ getPickerItemLayoutId(), getPickerItemTextViewId(), colIndex));
+ columnView.setOnChildViewHolderSelectedListener(mColumnChangeListener);
+ }
+ }
+
+ /**
+ * When column labels change or column range changes, call this function to re-populate the
+ * selection list. Note this function cannot be called from RecyclerView layout/scroll pass.
+ * @param columnIndex Index of column to update.
+ * @param column New column to update.
+ */
+ public void setColumnAt(int columnIndex, PickerColumn column) {
+ mColumns.set(columnIndex, column);
+ VerticalGridView columnView = mColumnViews.get(columnIndex);
+ PickerScrollArrayAdapter adapter = (PickerScrollArrayAdapter) columnView.getAdapter();
+ if (adapter != null) {
+ adapter.notifyDataSetChanged();
+ }
+ columnView.setSelectedPosition(column.getCurrentValue() - column.getMinValue());
+ }
+
+ /**
+ * Manually set current value of a column. The function will update UI and notify listeners.
+ * @param columnIndex Index of column to update.
+ * @param value New value of the column.
+ * @param runAnimation True to scroll to the value or false otherwise.
+ */
+ public void setColumnValue(int columnIndex, int value, boolean runAnimation) {
+ PickerColumn column = mColumns.get(columnIndex);
+ if (column.getCurrentValue() != value) {
+ column.setCurrentValue(value);
+ notifyValueChanged(columnIndex);
+ VerticalGridView columnView = mColumnViews.get(columnIndex);
+ if (columnView != null) {
+ int position = value - mColumns.get(columnIndex).getMinValue();
+ if (runAnimation) {
+ columnView.setSelectedPositionSmooth(position);
+ } else {
+ columnView.setSelectedPosition(position);
+ }
+ }
+ }
+ }
+
+ private void notifyValueChanged(int columnIndex) {
+ if (mListeners != null) {
+ for (int i = mListeners.size() - 1; i >= 0; i--) {
+ mListeners.get(i).onValueChanged(this, columnIndex);
+ }
+ }
+ }
+
+ /**
+ * Register a callback to be invoked when the picker's value has changed.
+ * @param listener The callback to ad
+ */
+ public void addOnValueChangedListener(PickerValueListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<Picker.PickerValueListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Remove a previously installed value changed callback
+ * @param listener The callback to remove.
+ */
+ public void removeOnValueChangedListener(PickerValueListener listener) {
+ if (mListeners != null) {
+ mListeners.remove(listener);
+ }
+ }
+
+ private void updateColumnAlpha(int colIndex, boolean animate) {
+ VerticalGridView column = mColumnViews.get(colIndex);
+
+ int selected = column.getSelectedPosition();
+ View item;
+
+ for (int i = 0; i < column.getAdapter().getItemCount(); i++) {
+ item = column.getLayoutManager().findViewByPosition(i);
+ if (item != null) {
+ setOrAnimateAlpha(item, (selected == i), colIndex, animate);
+ }
+ }
+ }
+
+ private void setOrAnimateAlpha(View view, boolean selected, int colIndex,
+ boolean animate) {
+ boolean columnShownAsActivated = colIndex == mSelectedColumn || !hasFocus();
+ if (selected) {
+ // set alpha for main item (selected) in the column
+ if (columnShownAsActivated) {
+ setOrAnimateAlpha(view, animate, mFocusedAlpha, -1, mDecelerateInterpolator);
+ } else {
+ setOrAnimateAlpha(view, animate, mUnfocusedAlpha, -1, mDecelerateInterpolator);
+ }
+ } else {
+ // set alpha for remaining items in the column
+ if (columnShownAsActivated) {
+ setOrAnimateAlpha(view, animate, mVisibleColumnAlpha, -1, mDecelerateInterpolator);
+ } else {
+ setOrAnimateAlpha(view, animate, mInvisibleColumnAlpha, -1,
+ mDecelerateInterpolator);
+ }
+ }
+ }
+
+ private void setOrAnimateAlpha(View view, boolean animate, float destAlpha, float startAlpha,
+ Interpolator interpolator) {
+ view.animate().cancel();
+ if (!animate) {
+ view.setAlpha(destAlpha);
+ } else {
+ if (startAlpha >= 0.0f) {
+ // set a start alpha
+ view.setAlpha(startAlpha);
+ }
+ view.animate().alpha(destAlpha)
+ .setDuration(mAlphaAnimDuration).setInterpolator(interpolator)
+ .start();
+ }
+ }
+
+ /**
+ * Classes extending {@link Picker} can override this function to supply the
+ * behavior when a list has been scrolled. Subclass may call {@link #setColumnValue(int, int,
+ * boolean)} and or {@link #setColumnAt(int,PickerColumn)}. Subclass should not directly call
+ * {@link PickerColumn#setCurrentValue(int)} which does not update internal state or notify
+ * listeners.
+ * @param columnIndex index of which column was changed.
+ * @param newValue A new value desired to be set on the column.
+ */
+ public void onColumnValueChanged(int columnIndex, int newValue) {
+ PickerColumn column = mColumns.get(columnIndex);
+ if (column.getCurrentValue() != newValue) {
+ column.setCurrentValue(newValue);
+ notifyValueChanged(columnIndex);
+ }
+ }
+
+ private float getFloat(int resourceId) {
+ TypedValue buffer = new TypedValue();
+ getContext().getResources().getValue(resourceId, buffer, true);
+ return buffer.getFloat();
+ }
+
+ static class ViewHolder extends RecyclerView.ViewHolder {
+ final TextView textView;
+
+ ViewHolder(View v, TextView textView) {
+ super(v);
+ this.textView = textView;
+ }
+ }
+
+ class PickerScrollArrayAdapter extends RecyclerView.Adapter<ViewHolder> {
+
+ private final int mResource;
+ private final int mColIndex;
+ private final int mTextViewResourceId;
+ private PickerColumn mData;
+
+ PickerScrollArrayAdapter(Context context, int resource, int textViewResourceId,
+ int colIndex) {
+ mResource = resource;
+ mColIndex = colIndex;
+ mTextViewResourceId = textViewResourceId;
+ mData = mColumns.get(mColIndex);
+ }
+
+ @Override
+ public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ View v = inflater.inflate(mResource, parent, false);
+ TextView textView;
+ if (mTextViewResourceId != 0) {
+ textView = (TextView) v.findViewById(mTextViewResourceId);
+ } else {
+ textView = (TextView) v;
+ }
+ ViewHolder vh = new ViewHolder(v, textView);
+ return vh;
+ }
+
+ @Override
+ public void onBindViewHolder(ViewHolder holder, int position) {
+ if (holder.textView != null && mData != null) {
+ holder.textView.setText(mData.getLabelFor(mData.getMinValue() + position));
+ }
+ setOrAnimateAlpha(holder.itemView,
+ (mColumnViews.get(mColIndex).getSelectedPosition() == position),
+ mColIndex, false);
+ }
+
+ @Override
+ public void onViewAttachedToWindow(ViewHolder holder) {
+ holder.itemView.setFocusable(isActivated());
+ }
+
+ @Override
+ public int getItemCount() {
+ return mData == null ? 0 : mData.getCount();
+ }
+ }
+
+ private final OnChildViewHolderSelectedListener mColumnChangeListener = new
+ OnChildViewHolderSelectedListener() {
+
+ @Override
+ public void onChildViewHolderSelected(RecyclerView parent, RecyclerView.ViewHolder child,
+ int position, int subposition) {
+ PickerScrollArrayAdapter pickerScrollArrayAdapter = (PickerScrollArrayAdapter) parent
+ .getAdapter();
+
+ int colIndex = mColumnViews.indexOf(parent);
+ updateColumnAlpha(colIndex, true);
+ if (child != null) {
+ int newValue = mColumns.get(colIndex).getMinValue() + position;
+ onColumnValueChanged(colIndex, newValue);
+ }
+ }
+
+ };
+
+ @Override
+ public boolean dispatchKeyEvent(android.view.KeyEvent event) {
+ if (isActivated()) {
+ final int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ if (event.getAction() == KeyEvent.ACTION_UP) {
+ performClick();
+ }
+ break;
+ default:
+ return super.dispatchKeyEvent(event);
+ }
+ return true;
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ int column = getSelectedColumn();
+ if (column < mColumnViews.size()) {
+ return mColumnViews.get(column).requestFocus(direction, previouslyFocusedRect);
+ }
+ return false;
+ }
+
+ /**
+ * Classes extending {@link Picker} can choose to override this method to
+ * supply the {@link Picker}'s column's single item height in pixels.
+ */
+ protected int getPickerItemHeightPixels() {
+ return getContext().getResources().getDimensionPixelSize(R.dimen.picker_item_height);
+ }
+
+ private void updateColumnSize() {
+ for (int i = 0; i < getColumnsCount(); i++) {
+ updateColumnSize(mColumnViews.get(i));
+ }
+ }
+
+ private void updateColumnSize(VerticalGridView columnView) {
+ ViewGroup.LayoutParams lp = columnView.getLayoutParams();
+ lp.height = (int) (getPickerItemHeightPixels() * (isActivated() ?
+ getActivatedVisibleItemCount() : getVisibleItemCount()));
+ columnView.setLayoutParams(lp);
+ }
+
+ private void updateItemFocusable() {
+ final boolean activated = isActivated();
+ for (int i = 0; i < getColumnsCount(); i++) {
+ VerticalGridView grid = mColumnViews.get(i);
+ for (int j = 0; j < grid.getChildCount(); j++) {
+ View view = grid.getChildAt(j);
+ view.setFocusable(activated);
+ }
+ }
+ }
+ /**
+ * Returns number of visible items showing in a column when it's activated. The default value
+ * is 3.
+ * @return Number of visible items showing in a column when it's activated.
+ */
+ public float getActivatedVisibleItemCount() {
+ return mVisibleItemsActivated;
+ }
+
+ /**
+ * Changes number of visible items showing in a column when it's activated. The default value
+ * is 3.
+ * @param visiblePickerItems Number of visible items showing in a column when it's activated.
+ */
+ public void setActivatedVisibleItemCount(float visiblePickerItems) {
+ if (visiblePickerItems <= 0) {
+ throw new IllegalArgumentException();
+ }
+ if (mVisibleItemsActivated != visiblePickerItems) {
+ mVisibleItemsActivated = visiblePickerItems;
+ if (isActivated()) {
+ updateColumnSize();
+ }
+ }
+ }
+
+ /**
+ * Returns number of visible items showing in a column when it's not activated. The default
+ * value is 1.
+ * @return Number of visible items showing in a column when it's not activated.
+ */
+ public float getVisibleItemCount() {
+ return 1;
+ }
+
+ /**
+ * Changes number of visible items showing in a column when it's not activated. The default
+ * value is 1.
+ * @param pickerItems Number of visible items showing in a column when it's not activated.
+ */
+ public void setVisibleItemCount(float pickerItems) {
+ if (pickerItems <= 0) {
+ throw new IllegalArgumentException();
+ }
+ if (mVisibleItems != pickerItems) {
+ mVisibleItems = pickerItems;
+ if (!isActivated()) {
+ updateColumnSize();
+ }
+ }
+ }
+
+ @Override
+ public void setActivated(boolean activated) {
+ if (activated != isActivated()) {
+ super.setActivated(activated);
+ updateColumnSize();
+ updateItemFocusable();
+ } else {
+ super.setActivated(activated);
+ }
+ }
+
+ @Override
+ public void requestChildFocus(View child, View focused) {
+ super.requestChildFocus(child, focused);
+ for (int i = 0; i < mColumnViews.size(); i++) {
+ if (mColumnViews.get(i).hasFocus()) {
+ setSelectedColumn(i);
+ }
+ }
+ }
+
+ /**
+ * Change current selected column. Picker shows multiple items on selected column if Picker has
+ * focus. Picker shows multiple items on all column if Picker has no focus (e.g. a Touchscreen
+ * screen).
+ * @param columnIndex Index of column to activate.
+ */
+ public void setSelectedColumn(int columnIndex) {
+ if (mSelectedColumn != columnIndex) {
+ mSelectedColumn = columnIndex;
+ for (int i = 0; i < mColumnViews.size(); i++) {
+ updateColumnAlpha(i, true);
+ }
+ }
+ }
+
+ /**
+ * Get current activated column index.
+ * @return Current activated column index.
+ */
+ public int getSelectedColumn() {
+ return mSelectedColumn;
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
new file mode 100644
index 0000000..d2ee2f3
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerColumn.java
@@ -0,0 +1,140 @@
+/*
+ * 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.v17.leanback.widget.picker;
+
+/**
+ * Picker column class used by {@link Picker}, defines a contiguous value ranges and associated
+ * labels. A PickerColumn has a minValue and maxValue to choose between. The Picker column has
+ * a current value.
+ * The labels can be dynamically generated from value by {@link #setLabelFormat(String)} or
+ * a list of static labels set by {@link #setStaticLabels(CharSequence[])}.
+ */
+public class PickerColumn {
+
+ private int mCurrentValue;
+ private int mMinValue;
+ private int mMaxValue;
+ private CharSequence[] mStaticLabels;
+ private String mLabelFormat;
+
+ public PickerColumn() {
+ }
+
+ /**
+ * Set string format (see {@link String#format}) to display label for an
+ * integer value. {@link #setStaticLabels(CharSequence[])} overrides the format.
+ *
+ * @param labelFormat String format to display label for value between minValue and maxValue.
+ */
+ public void setLabelFormat(String labelFormat) {
+ mLabelFormat = labelFormat;
+ }
+
+ /**
+ * Return string format (see {@link String#format}) to display label for
+ * value.
+ * @return String format to display label for value.
+ */
+ public String getLabelFormat() {
+ return mLabelFormat;
+ }
+
+ /**
+ * Set static labels for each value, minValue maps to labels[0], maxValue maps to
+ * labels[labels.length - 1].
+ * @param labels Static labels for each value between minValue and maxValue.
+ */
+ public void setStaticLabels(CharSequence[] labels) {
+ mStaticLabels = labels;
+ }
+
+ /**
+ * Returns static labels for each value, minValue maps to labels[0], maxValue maps to
+ * labels[labels.length - 1]. When null, {@link #getLabelFormat()} will be used.
+ */
+ public CharSequence[] getStaticLabels() {
+ return mStaticLabels;
+ }
+
+ /**
+ * Get a label for value. The label can be static ({@link #setStaticLabels(CharSequence[])}
+ * or dynamically generated (@link {@link #setLabelFormat(String)} when static labels is null.
+ *
+ * @param value Value between minValue and maxValue.
+ * @return Label for the value.
+ */
+ public CharSequence getLabelFor(int value) {
+ if (mStaticLabels == null) {
+ return String.format(mLabelFormat, value);
+ }
+ return mStaticLabels[value];
+ }
+
+ /**
+ * Returns current value of the Column.
+ * @return Current value of the Column.
+ */
+ public int getCurrentValue() {
+ return mCurrentValue;
+ }
+
+ /**
+ * Sets current value of the Column.
+ */
+ public void setCurrentValue(int value) {
+ mCurrentValue = value;
+ }
+
+ /**
+ * Get total items count between minValue and maxValue.
+ * @return Total items count between minValue and maxValue.
+ */
+ public int getCount() {
+ return mMaxValue - mMinValue + 1;
+ }
+
+ /**
+ * Returns minimal value of the Column.
+ * @return Minimal value of the Column.
+ */
+ public int getMinValue() {
+ return mMinValue;
+ }
+
+ /**
+ * Returns maximum value of the Column.
+ * @return Maximum value of the Column.
+ */
+ public int getMaxValue() {
+ return mMaxValue;
+ }
+
+ /**
+ * Sets minimal value of the Column.
+ * @param minValue New minimal value to set.
+ */
+ public void setMinValue(int minValue) {
+ mMinValue = minValue;
+ }
+
+ /**
+ * Sets maximum value of the Column.
+ * @param maxValue New maximum value to set.
+ */
+ public void setMaxValue(int maxValue) {
+ mMaxValue = maxValue;
+ }
+
+}
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerConstant.java b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerConstant.java
new file mode 100644
index 0000000..cfb704f
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/picker/PickerConstant.java
@@ -0,0 +1,68 @@
+/*
+ * 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.v17.leanback.widget.picker;
+
+import android.content.res.Resources;
+import android.support.v17.leanback.R;
+
+import java.text.DateFormatSymbols;
+import java.util.Calendar;
+import java.util.Locale;
+
+/**
+ * Date/Time Picker related constants
+ */
+class PickerConstant {
+
+ public final String[] months;
+ public final String[] days;
+ public final String[] hours12;
+ public final String[] hours24;
+ public final String[] minutes;
+ public final String[] ampm;
+ public final String dateSeparator;
+ public final String timeSeparator;
+ public final Locale locale;
+
+ public PickerConstant(Locale locale, Resources resources) {
+ this.locale = locale;
+ DateFormatSymbols symbols = DateFormatSymbols.getInstance(locale);
+ months = symbols.getShortMonths();
+ Calendar calendar = Calendar.getInstance(locale);
+ days = createStringIntArrays(calendar.getMinimum(Calendar.DAY_OF_MONTH),
+ calendar.getMaximum(Calendar.DAY_OF_MONTH), "%02d");
+ hours12 = createStringIntArrays(1, 12, "%02d");
+ hours24 = createStringIntArrays(0, 23, "%02d");
+ minutes = createStringIntArrays(0, 59, "%02d");
+ ampm = symbols.getAmPmStrings();
+ dateSeparator = resources.getString(R.string.lb_date_separator);
+ timeSeparator = resources.getString(R.string.lb_time_separator);
+ }
+
+
+ public static String[] createStringIntArrays(int firstNumber, int lastNumber, String format) {
+ String[] array = new String[lastNumber - firstNumber + 1];
+ for (int i = firstNumber; i <= lastNumber; i++) {
+ if (format != null) {
+ array[i - firstNumber] = String.format(format, i);
+ } else {
+ array[i - firstNumber] = String.valueOf(i);
+ }
+ }
+ return array;
+ }
+
+
+}
\ No newline at end of file
diff --git a/v17/preference-leanback/Android.mk b/v17/preference-leanback/Android.mk
index 33e12da..bd36e32 100644
--- a/v17/preference-leanback/Android.mk
+++ b/v17/preference-leanback/Android.mk
@@ -14,25 +14,27 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
+# Android libraries referenced by this module's resources.
+resource_libs := \
+ android-support-v17-leanback \
+ android-support-v14-preference \
+ android-support-v7-preference \
+ android-support-v7-appcompat \
+ android-support-v7-recyclerview
+
+# Build the resources using the latest applicable SDK version.
# We do this here because the final static library must be compiled with an older
# SDK version than the resources. The resources library and the R class that it
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v17-preference-leanback-res
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := \
- frameworks/support/v7/appcompat/res \
- frameworks/support/v7/recyclerview/res \
- frameworks/support/v7/preference/res \
- frameworks/support/v14/preference/res \
- frameworks/support/v17/leanback/res \
- $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := $(resource_libs)
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_JAR_EXCLUDE_FILES := none
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files := $(LOCAL_SRC_FILES)
@@ -44,33 +46,45 @@
LOCAL_MODULE := android-support-v17-preference-leanback-api21
LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, api21)
-LOCAL_JAVA_LIBRARIES := android-support-v17-preference-leanback-res \
- android-support-v17-leanback
+LOCAL_JAVA_LIBRARIES := \
+ android-support-v17-preference-leanback-res \
+ android-support-v17-leanback
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v17-preference-leanback \
+# android-support-v17-leanback \
+# android-support-v14-preference \
+# android-support-v7-preference \
+# android-support-v7-appcompat \
+# android-support-v7-recyclerview \
+# android-support-v4 \
+# android-support-annotions
+#
# in their makefiles to include the resources in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v17-preference-leanback
LOCAL_SDK_VERSION := 17
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-v17-preference-leanback-api21
-LOCAL_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-v7-appcompat \
- android-support-v7-recyclerview \
- android-support-v7-preference \
- android-support-v14-preference \
- android-support-v17-leanback \
- android-support-annotations \
- android-support-v17-preference-leanback-res
+ android-support-v17-preference-leanback-api21
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-v17-preference-leanback-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ $(resource_libs) \
+ android-support-v4 \
+ android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v17/preference-leanback/api/23.1.1.txt b/v17/preference-leanback/api/23.1.1.txt
new file mode 100644
index 0000000..06316ab
--- /dev/null
+++ b/v17/preference-leanback/api/23.1.1.txt
@@ -0,0 +1,61 @@
+package android.support.v17.preference {
+
+ public abstract class BaseLeanbackPreferenceFragment extends android.support.v14.preference.PreferenceFragment {
+ ctor public BaseLeanbackPreferenceFragment();
+ }
+
+ public class LeanbackListPreferenceDialogFragment extends android.support.v17.preference.LeanbackPreferenceDialogFragment {
+ ctor public LeanbackListPreferenceDialogFragment();
+ method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceMulti(java.lang.String);
+ method public static android.support.v17.preference.LeanbackListPreferenceDialogFragment newInstanceSingle(java.lang.String);
+ method public android.support.v7.widget.RecyclerView.Adapter onCreateAdapter();
+ }
+
+ public class LeanbackListPreferenceDialogFragment.AdapterMulti extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+ ctor public LeanbackListPreferenceDialogFragment.AdapterMulti(java.lang.CharSequence[], java.lang.CharSequence[], java.util.Set<java.lang.String>);
+ method public int getItemCount();
+ method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
+ method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+ }
+
+ public class LeanbackListPreferenceDialogFragment.AdapterSingle extends android.support.v7.widget.RecyclerView.Adapter implements android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+ ctor public LeanbackListPreferenceDialogFragment.AdapterSingle(java.lang.CharSequence[], java.lang.CharSequence[], java.lang.CharSequence);
+ method public int getItemCount();
+ method public void onBindViewHolder(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder, int);
+ method public android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+ }
+
+ public static class LeanbackListPreferenceDialogFragment.ViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder implements android.view.View.OnClickListener {
+ ctor public LeanbackListPreferenceDialogFragment.ViewHolder(android.view.View, android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener);
+ method public android.view.ViewGroup getContainer();
+ method public android.widget.TextView getTitleView();
+ method public android.widget.Checkable getWidgetView();
+ method public void onClick(android.view.View);
+ }
+
+ public static abstract interface LeanbackListPreferenceDialogFragment.ViewHolder.OnItemClickListener {
+ method public abstract void onItemClick(android.support.v17.preference.LeanbackListPreferenceDialogFragment.ViewHolder);
+ }
+
+ public class LeanbackPreferenceDialogFragment extends android.app.Fragment {
+ ctor public LeanbackPreferenceDialogFragment();
+ method public android.support.v7.preference.DialogPreference getPreference();
+ field public static final java.lang.String ARG_KEY = "key";
+ }
+
+ public abstract class LeanbackPreferenceFragment extends android.support.v17.preference.BaseLeanbackPreferenceFragment {
+ ctor public LeanbackPreferenceFragment();
+ }
+
+ public abstract class LeanbackSettingsFragment extends android.app.Fragment {
+ ctor public LeanbackSettingsFragment();
+ method public boolean onPreferenceDisplayDialog(android.support.v14.preference.PreferenceFragment, android.support.v7.preference.Preference);
+ method public abstract void onPreferenceStartInitialScreen();
+ method public void startImmersiveFragment(android.app.Fragment);
+ method public void startPreferenceFragment(android.app.Fragment);
+ }
+
+}
+
diff --git a/v17/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java b/v17/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java
new file mode 100644
index 0000000..5df1fc8
--- /dev/null
+++ b/v17/preference-leanback/api21/android/support/v17/internal/widget/OutlineOnlyWithChildrenFrameLayout.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v17.internal.widget;
+
+import android.content.Context;
+import android.graphics.Outline;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewOutlineProvider;
+import android.widget.FrameLayout;
+
+/**
+ * {@link FrameLayout} subclass that provides an outline only when it has children, so that it does
+ * not cast a shadow when empty.
+ *
+ * @hide
+ */
+public class OutlineOnlyWithChildrenFrameLayout extends FrameLayout {
+
+ private ViewOutlineProvider mMagicalOutlineProvider;
+ private ViewOutlineProvider mInnerOutlineProvider;
+
+ public OutlineOnlyWithChildrenFrameLayout(Context context) {
+ super(context);
+ }
+
+ public OutlineOnlyWithChildrenFrameLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public OutlineOnlyWithChildrenFrameLayout(Context context, AttributeSet attrs,
+ int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public OutlineOnlyWithChildrenFrameLayout(Context context, AttributeSet attrs,
+ int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ invalidateOutline();
+ }
+
+ @Override
+ public void setOutlineProvider(ViewOutlineProvider provider) {
+ mInnerOutlineProvider = provider;
+ if (mMagicalOutlineProvider == null) {
+ // Can't initialize this directly because this method is called from the superclass's
+ // constructor.
+ mMagicalOutlineProvider = new ViewOutlineProvider() {
+ @Override
+ public void getOutline(View view, Outline outline) {
+ if (getChildCount() > 0) {
+ mInnerOutlineProvider.getOutline(view, outline);
+ } else {
+ ViewOutlineProvider.BACKGROUND.getOutline(view, outline);
+ }
+ }
+ };
+ }
+ super.setOutlineProvider(mMagicalOutlineProvider);
+ }
+}
diff --git a/v17/preference-leanback/build.gradle b/v17/preference-leanback/build.gradle
index 0ddcdd2..dbdce7e 100644
--- a/v17/preference-leanback/build.gradle
+++ b/v17/preference-leanback/build.gradle
@@ -16,7 +16,7 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'preference-leanback-v17'
@@ -30,7 +30,7 @@
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -57,6 +57,39 @@
}
}
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
+
uploadArchives {
repositories {
mavenDeployer {
diff --git a/v17/preference-leanback/res/layout-v21/leanback_settings_fragment.xml b/v17/preference-leanback/res/layout-v21/leanback_settings_fragment.xml
new file mode 100644
index 0000000..6eab4e9
--- /dev/null
+++ b/v17/preference-leanback/res/layout-v21/leanback_settings_fragment.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License
+ -->
+<android.support.v17.preference.LeanbackSettingsRootView xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/settings_dialog_container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <android.support.v17.internal.widget.OutlineOnlyWithChildrenFrameLayout
+ android:id="@+id/settings_preference_fragment_container"
+ android:layout_width="@dimen/lb_settings_pane_width"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:elevation="@dimen/lb_preference_decor_elevation"
+ android:outlineProvider="bounds" />
+</android.support.v17.preference.LeanbackSettingsRootView>
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml b/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
index 728ecff..e7f6f85 100644
--- a/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
+++ b/v17/preference-leanback/res/layout/leanback_list_preference_item_multi.xml
@@ -31,6 +31,8 @@
android:id="@+id/button"
android:layout_width="@dimen/lb_preference_item_icon_size"
android:layout_height="@dimen/lb_preference_item_icon_size"
+ android:focusable="false"
+ android:clickable="false"
android:layout_marginEnd="@dimen/lb_preference_item_icon_margin_end"
android:layout_gravity="center_vertical" />
diff --git a/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml b/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
index 354ca41..836b82e 100644
--- a/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
+++ b/v17/preference-leanback/res/layout/leanback_list_preference_item_single.xml
@@ -31,6 +31,8 @@
android:id="@+id/button"
android:layout_width="@dimen/lb_preference_item_icon_size"
android:layout_height="@dimen/lb_preference_item_icon_size"
+ android:focusable="false"
+ android:clickable="false"
android:layout_marginEnd="@dimen/lb_preference_item_icon_margin_end"
android:layout_gravity="center_vertical" />
diff --git a/v17/preference-leanback/res/layout/leanback_preference_category.xml b/v17/preference-leanback/res/layout/leanback_preference_category.xml
index 9b978f3..97bb3f0 100644
--- a/v17/preference-leanback/res/layout/leanback_preference_category.xml
+++ b/v17/preference-leanback/res/layout/leanback_preference_category.xml
@@ -26,7 +26,7 @@
android:id="@android:id/title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:layout_gravity="center_vertical"
+ android:layout_gravity="start|center_vertical"
android:fontFamily="sans-serif-condensed"
android:textColor="?android:attr/colorAccent"
android:textSize="@dimen/lb_preference_category_text_size"/>
diff --git a/v17/preference-leanback/res/layout/leanback_preference_fragment.xml b/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
index 199e0f7..8c0c055 100644
--- a/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
+++ b/v17/preference-leanback/res/layout/leanback_preference_fragment.xml
@@ -20,7 +20,6 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/lb_preference_decor_list_background"
- android:elevation="@dimen/lb_preference_decor_elevation"
android:orientation="vertical"
android:transitionGroup="false"
>
diff --git a/v17/preference-leanback/res/layout/leanback_preferences_list.xml b/v17/preference-leanback/res/layout/leanback_preferences_list.xml
index 21e4e4a..7b640d9 100644
--- a/v17/preference-leanback/res/layout/leanback_preferences_list.xml
+++ b/v17/preference-leanback/res/layout/leanback_preferences_list.xml
@@ -20,4 +20,5 @@
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
+ android:transitionGroup="true"
style="?attr/preferenceFragmentListStyle" />
diff --git a/v17/preference-leanback/res/layout/leanback_settings_fragment.xml b/v17/preference-leanback/res/layout/leanback_settings_fragment.xml
index e9c421e..010f8e9 100644
--- a/v17/preference-leanback/res/layout/leanback_settings_fragment.xml
+++ b/v17/preference-leanback/res/layout/leanback_settings_fragment.xml
@@ -22,5 +22,7 @@
android:id="@+id/settings_preference_fragment_container"
android:layout_width="@dimen/lb_settings_pane_width"
android:layout_height="match_parent"
- android:layout_gravity="end"/>
+ android:layout_gravity="end"
+ android:elevation="@dimen/lb_preference_decor_elevation"
+ android:outlineProvider="bounds" />
</android.support.v17.preference.LeanbackSettingsRootView>
diff --git a/v17/preference-leanback/res/values/styles.xml b/v17/preference-leanback/res/values/styles.xml
index 42d2b5b..fbb1ad0 100644
--- a/v17/preference-leanback/res/values/styles.xml
+++ b/v17/preference-leanback/res/values/styles.xml
@@ -69,6 +69,8 @@
<style name="PreferenceFragmentList.Leanback">
<item name="android:paddingStart">0dp</item>
<item name="android:paddingEnd">0dp</item>
+ <item name="android:paddingBottom">34dp</item>
+ <item name="android:clipToPadding">false</item>
</style>
</resources>
diff --git a/v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java b/v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
index 6fc1fa6..40743dc 100644
--- a/v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
+++ b/v17/preference-leanback/src/android/support/v17/preference/BaseLeanbackPreferenceFragment.java
@@ -20,6 +20,7 @@
import android.os.Bundle;
import android.support.v14.preference.PreferenceFragment;
import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v7.preference.PreferenceRecyclerViewAccessibilityDelegate;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.ViewGroup;
@@ -37,6 +38,8 @@
.inflate(R.layout.leanback_preferences_list, parent, false);
verticalGridView.setWindowAlignment(VerticalGridView.WINDOW_ALIGN_BOTH_EDGE);
verticalGridView.setFocusScrollStrategy(VerticalGridView.FOCUS_SCROLL_ALIGNED);
+ verticalGridView.setAccessibilityDelegateCompat(
+ new PreferenceRecyclerViewAccessibilityDelegate(verticalGridView));
return verticalGridView;
}
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java b/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
index a82f543..2273fb6 100644
--- a/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
+++ b/v17/preference-leanback/src/android/support/v17/preference/LeanbackListPreferenceDialogFragment.java
@@ -155,6 +155,9 @@
@Override
public void onItemClick(ViewHolder viewHolder) {
final int index = viewHolder.getAdapterPosition();
+ if (index == RecyclerView.NO_POSITION) {
+ return;
+ }
final CharSequence entry = mEntryValues[index];
final ListPreference preference = (ListPreference) getPreference();
if (index >= 0) {
@@ -207,6 +210,9 @@
@Override
public void onItemClick(ViewHolder viewHolder) {
final int index = viewHolder.getAdapterPosition();
+ if (index == RecyclerView.NO_POSITION) {
+ return;
+ }
final String entry = mEntryValues[index].toString();
if (mSelections.contains(entry)) {
mSelections.remove(entry);
diff --git a/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java b/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
index 2c03ace..546ae18 100644
--- a/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
+++ b/v17/preference-leanback/src/android/support/v17/preference/LeanbackSettingsFragment.java
@@ -40,14 +40,13 @@
private static final String PREFERENCE_FRAGMENT_TAG =
"android.support.v17.preference.LeanbackSettingsFragment.PREFERENCE_FRAGMENT";
+ private final RootViewOnKeyListener mRootViewOnKeyListener = new RootViewOnKeyListener();
+
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
final View v = inflater.inflate(R.layout.leanback_settings_fragment, container, false);
- // Trap back button presses
- ((LeanbackSettingsRootView) v).setOnBackKeyListener(new RootViewOnKeyListener());
-
return v;
}
@@ -60,6 +59,25 @@
}
@Override
+ public void onResume() {
+ super.onResume();
+ // Trap back button presses
+ final LeanbackSettingsRootView rootView = (LeanbackSettingsRootView) getView();
+ if (rootView != null) {
+ rootView.setOnBackKeyListener(mRootViewOnKeyListener);
+ }
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ final LeanbackSettingsRootView rootView = (LeanbackSettingsRootView) getView();
+ if (rootView != null) {
+ rootView.setOnBackKeyListener(null);
+ }
+ }
+
+ @Override
public boolean onPreferenceDisplayDialog(PreferenceFragment caller, Preference pref) {
final Fragment f;
if (pref instanceof ListPreference) {
diff --git a/v17/tests/Android.mk b/v17/tests/Android.mk
index cf446cd..eb08a62 100644
--- a/v17/tests/Android.mk
+++ b/v17/tests/Android.mk
@@ -18,7 +18,7 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
@@ -34,8 +34,8 @@
LOCAL_STATIC_JAVA_LIBRARIES := \
android-support-v4 \
android-support-v7-recyclerview \
- android-support-v17-leanback
-LOCAL_JAVA_LIBRARIES := android.test.runner
+ android-support-v17-leanback \
+ android-support-test
LOCAL_PACKAGE_NAME := AndroidLeanbackTests
diff --git a/v17/tests/AndroidManifest.xml b/v17/tests/AndroidManifest.xml
index f4dd293..d9d2909 100644
--- a/v17/tests/AndroidManifest.xml
+++ b/v17/tests/AndroidManifest.xml
@@ -33,13 +33,18 @@
<activity android:name="android.support.v17.leanback.widget.GridActivity"
android:exported="true" />
+ <activity android:name=
+ "android.support.v17.leanback.app.wizard.GuidedStepAttributesTestActivity"
+ android:theme="@style/Theme.Leanback.GuidedStep"
+ android:exported="true" />
+
<activity android:name="android.support.v17.leanback.app.BrowseFragmentTestActivity"
- android:theme="@style/Theme.Leanback.Browse"
- android:exported="true" />
+ android:theme="@style/Theme.Leanback.Browse"
+ android:exported="true" />
<activity android:name="android.support.v17.leanback.app.BrowseSupportFragmentTestActivity"
- android:theme="@style/Theme.Leanback.Browse"
- android:exported="true" />
+ android:theme="@style/Theme.Leanback.Browse"
+ android:exported="true" />
</application>
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/v17/tests/generatev4.py b/v17/tests/generatev4.py
index 8dc4dc2..fa8244b 100755
--- a/v17/tests/generatev4.py
+++ b/v17/tests/generatev4.py
@@ -52,6 +52,8 @@
outfile.write("/* This file is auto-generated from {}FrgamentTest.java. DO NOT MODIFY. */\n\n".format(w))
for line in file:
+ for w in cls:
+ line = line.replace('{}Fragment'.format(w), '{}SupportFragment'.format(w))
for w in testcls:
line = line.replace('{}FragmentTest'.format(w), '{}SupportFragmentTest'.format(w))
line = line.replace('{}FragmentTestActivity'.format(w), '{}SupportFragmentTestActivity'.format(w))
diff --git a/v17/tests/res/layout/horizontal_grid_wrap.xml b/v17/tests/res/layout/horizontal_grid_wrap.xml
new file mode 100644
index 0000000..e640b66
--- /dev/null
+++ b/v17/tests/res/layout/horizontal_grid_wrap.xml
@@ -0,0 +1,21 @@
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <android.support.v17.leanback.widget.HorizontalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:numberOfRows="1"
+ lb:rowHeight="wrap_content"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/horizontal_item.xml b/v17/tests/res/layout/horizontal_item.xml
new file mode 100644
index 0000000..01bb273
--- /dev/null
+++ b/v17/tests/res/layout/horizontal_item.xml
@@ -0,0 +1,13 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ >
+ <Button
+ android:id="@+id/item"
+ android:text="itemx"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content" />
+</LinearLayout>
diff --git a/v17/tests/res/layout/vertical_grid_ltr.xml b/v17/tests/res/layout/vertical_grid_ltr.xml
new file mode 100644
index 0000000..6a390a3
--- /dev/null
+++ b/v17/tests/res/layout/vertical_grid_ltr.xml
@@ -0,0 +1,33 @@
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutDirection="ltr"
+ >
+ <Button android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:text="button"
+ />
+ <android.support.v17.leanback.widget.VerticalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_toEndOf="@id/button"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:horizontalMargin="12dip"
+ lb:verticalMargin="24dip"
+ lb:numberOfColumns="1"
+ lb:columnWidth="150dip"
+ lb:focusOutSideStart="false"
+ lb:focusOutSideEnd="true"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/vertical_grid_rtl.xml b/v17/tests/res/layout/vertical_grid_rtl.xml
new file mode 100644
index 0000000..87b2054
--- /dev/null
+++ b/v17/tests/res/layout/vertical_grid_rtl.xml
@@ -0,0 +1,33 @@
+<RelativeLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layoutDirection="rtl"
+ >
+ <Button android:id="@+id/button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_alignParentStart="true"
+ android:text="button"
+ />
+ <android.support.v17.leanback.widget.VerticalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:layout_toEndOf="@id/button"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:horizontalMargin="12dip"
+ lb:verticalMargin="24dip"
+ lb:numberOfColumns="1"
+ lb:columnWidth="150dip"
+ lb:focusOutSideStart="false"
+ lb:focusOutSideEnd="true"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</RelativeLayout>
diff --git a/v17/tests/res/layout/vertical_linear_with_button.xml b/v17/tests/res/layout/vertical_linear_with_button.xml
new file mode 100644
index 0000000..f37bdc3
--- /dev/null
+++ b/v17/tests/res/layout/vertical_linear_with_button.xml
@@ -0,0 +1,31 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <Button android:focusable="true"
+ android:id="@+id/button"
+ android:text="button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <requestFocus />
+ </Button>
+ <android.support.v17.leanback.widget.VerticalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:horizontalMargin="12dip"
+ lb:verticalMargin="24dip"
+ lb:numberOfColumns="1"
+ lb:columnWidth="150dip"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</LinearLayout>
diff --git a/v17/tests/res/layout/vertical_linear_with_button_onleft.xml b/v17/tests/res/layout/vertical_linear_with_button_onleft.xml
new file mode 100644
index 0000000..6305de6
--- /dev/null
+++ b/v17/tests/res/layout/vertical_linear_with_button_onleft.xml
@@ -0,0 +1,30 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:orientation="horizontal"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <Button
+ android:id="@+id/button"
+ android:text="button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <requestFocus />
+ </Button>
+ <android.support.v17.leanback.widget.VerticalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:horizontalMargin="12dip"
+ lb:verticalMargin="4dip"
+ lb:numberOfColumns="1"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</LinearLayout>
diff --git a/v17/tests/res/values/strings.xml b/v17/tests/res/values/strings.xml
new file mode 100644
index 0000000..111449e
--- /dev/null
+++ b/v17/tests/res/values/strings.xml
@@ -0,0 +1,50 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <string name="search">Search</string>
+ <string name="search_description">SearchFragment test</string>
+ <string name="invert_title">Invert enabled actions</string>
+ <string name="revert_description">Changes enabled actions to disabled & vice versa</string>
+ <string name="focusable_test_error_message">An un-focusable action was incorrectly
+ selected for action title: %s</string>
+ <string name="enabled_test_wrong_focus_error_message">The wrong action was focused</string>
+ <string name="enabled_test_wrong_click_error_message">onClickListener for GuidedAction is
+ incorrectly called for a disabled action</string>
+ <string name="enabled_test_wrong_flag_error_message">isEnabled returns incorrect result
+ </string>
+ <string name="checkbox_title">Option</string>
+ <string name="checkbox_desc">Description of Option</string>
+ <string name="radio_actions_info_title">Radio Actions</string>
+ <string name="radio_actions_info_desc">Only one radio action could be
+ selected at a time</string>
+ <string name="checkbox_actions_info_title">CheckBox Actions</string>
+ <string name="checkbox_actions_info_desc">Multiple checkbox actions can be
+ checked at a time</string>
+ <string name="dropdown_action_title">Drop-down Action%d</string>
+ <string name="dropdown_action_desc">This action may or may not have subactions</string>
+ <string name="subaction_title">Subaction%d</string>
+ <string name="subaction_test_wrong_focus_error_message">The wrong action or sub-action
+ was focused</string>
+ <string name="subaction_test_wrong_click_error_message">The wrong action or sub-action
+ was clicked</string>
+ <string name="datepicker_test_wrong_day_value">The date picker day field shows incorrect value
+ </string>
+ <string name="datepicker_test_wrong_month_value">The date picker month field shows incorrect value
+ </string>
+ <string name="datepicker_test_wrong_year_value">The date picker year field shows incorrect value
+ </string>
+ <string name="datepicker_test_transition_error1">The last day for month %d is incorrect</string>
+ <string name="datepicker_test_transition_error2" formatted="false">
+ Day %d is outside the valid range for month %d</string>
+ <string name="datepicker_with_range_title" formatted="false">Date Picker\nMin: %s\nMax: %s</string>
+ <string name="info_action_very_long_title">Super-long title%d: This is the beginning
+ of one type of a very long
+ title. Make sure that you set multilineDescription flag on for these actions.
+ This allows the long title to be displayed properly.</string>
+ <string name="info_action_very_long_desc">Super-long description%d: This is the beginning
+ of one type of a very long
+ description. Make sure that you set multilineDescription flag on for these actions.
+ This allows the long description to be displayed properly.</string>
+ <string name="multiline_enabled_actions_info_title">Long actions with multiline enabled</string>
+ <string name="multiline_disabled_actions_info_title">
+ Long actions with multiline disabled</string>
+</resources>
diff --git a/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTest.java b/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTest.java
index e9b1063..37fc107 100644
--- a/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTest.java
+++ b/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTest.java
@@ -26,9 +26,8 @@
import android.view.ViewGroup;
import android.widget.TextView;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
-
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Parcelable;
@@ -46,10 +45,38 @@
ActivityInstrumentationTestCase2<BrowseFragmentTestActivity> {
static final long TRANSITION_LENGTH = 1000;
+ static final long HORIZONTAL_SCROLL_WAIT = 2000;
+ static final long TIMEOUT = 10000;
Instrumentation mInstrumentation;
BrowseFragmentTestActivity mActivity;
+ static class WaitLock {
+ final boolean[] finished = new boolean[1];
+ String message;
+ long timeout;
+ public WaitLock(long timeout, String message) {
+ this.message = message;
+ this.timeout = timeout;
+ }
+ public void waitForFinish() {
+ long totalSleep = 0;
+ try {
+ while (!finished[0]) {
+ if ((totalSleep += 100) >= timeout) {
+ assertTrue(message, false);
+ }
+ Thread.sleep(100);
+ }
+ } catch (InterruptedException ex) {
+ assertTrue("Interrupted during wait", false);
+ }
+ }
+ public void signalFinish() {
+ finished[0] = true;
+ }
+ }
+
public BrowseFragmentTest() {
super(BrowseFragmentTestActivity.class);
}
@@ -90,4 +117,35 @@
sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
}
+ public void testSelectCardOnARow() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), BrowseFragmentTestActivity.class);
+ intent.putExtra(BrowseFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, (long) 1000);
+ intent.putExtra(BrowseFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+ initActivity(intent);
+
+ final WaitLock waitLock = new WaitLock(TIMEOUT, "Timeout while waiting scroll to the row");
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getBrowseTestFragment().setSelectedPosition(10, true,
+ new ListRowPresenter.SelectItemViewHolderTask(20) {
+ @Override
+ public void run(Presenter.ViewHolder holder) {
+ super.run(holder);
+ waitLock.signalFinish();
+ }
+ });
+ }
+ });
+ waitLock.waitForFinish();
+
+ // wait for scrolling to the item.
+ Thread.sleep(HORIZONTAL_SCROLL_WAIT);
+ ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
+ .getBrowseTestFragment().getRowsFragment().getRowViewHolder(mActivity
+ .getBrowseTestFragment().getSelectedPosition());
+ assertEquals(20, row.getGridView().getSelectedPosition());
+ }
+
}
diff --git a/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTestActivity.java b/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
index ee433bd..5f6316f1 100644
--- a/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
+++ b/v17/tests/src/android/support/v17/leanback/app/BrowseFragmentTestActivity.java
@@ -55,4 +55,8 @@
}
ft.commit();
}
+
+ public BrowseTestFragment getBrowseTestFragment() {
+ return (BrowseTestFragment) getFragmentManager().findFragmentById(R.id.main_frame);
+ }
}
diff --git a/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTest.java b/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
index 0c901be..e41569e 100644
--- a/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
+++ b/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTest.java
@@ -28,9 +28,8 @@
import android.view.ViewGroup;
import android.widget.TextView;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
-
+import android.support.v17.leanback.widget.Presenter;
+import android.support.v17.leanback.widget.ListRowPresenter;
import android.app.Instrumentation;
import android.content.Intent;
import android.os.Parcelable;
@@ -48,10 +47,38 @@
ActivityInstrumentationTestCase2<BrowseSupportFragmentTestActivity> {
static final long TRANSITION_LENGTH = 1000;
+ static final long HORIZONTAL_SCROLL_WAIT = 2000;
+ static final long TIMEOUT = 10000;
Instrumentation mInstrumentation;
BrowseSupportFragmentTestActivity mActivity;
+ static class WaitLock {
+ final boolean[] finished = new boolean[1];
+ String message;
+ long timeout;
+ public WaitLock(long timeout, String message) {
+ this.message = message;
+ this.timeout = timeout;
+ }
+ public void waitForFinish() {
+ long totalSleep = 0;
+ try {
+ while (!finished[0]) {
+ if ((totalSleep += 100) >= timeout) {
+ assertTrue(message, false);
+ }
+ Thread.sleep(100);
+ }
+ } catch (InterruptedException ex) {
+ assertTrue("Interrupted during wait", false);
+ }
+ }
+ public void signalFinish() {
+ finished[0] = true;
+ }
+ }
+
public BrowseSupportFragmentTest() {
super(BrowseSupportFragmentTestActivity.class);
}
@@ -92,4 +119,35 @@
sendKeys(KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_BACK);
}
+ public void testSelectCardOnARow() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), BrowseSupportFragmentTestActivity.class);
+ intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_LOAD_DATA_DELAY, (long) 1000);
+ intent.putExtra(BrowseSupportFragmentTestActivity.EXTRA_ADD_TO_BACKSTACK , true);
+ initActivity(intent);
+
+ final WaitLock waitLock = new WaitLock(TIMEOUT, "Timeout while waiting scroll to the row");
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getBrowseTestSupportFragment().setSelectedPosition(10, true,
+ new ListRowPresenter.SelectItemViewHolderTask(20) {
+ @Override
+ public void run(Presenter.ViewHolder holder) {
+ super.run(holder);
+ waitLock.signalFinish();
+ }
+ });
+ }
+ });
+ waitLock.waitForFinish();
+
+ // wait for scrolling to the item.
+ Thread.sleep(HORIZONTAL_SCROLL_WAIT);
+ ListRowPresenter.ViewHolder row = (ListRowPresenter.ViewHolder) mActivity
+ .getBrowseTestSupportFragment().getRowsSupportFragment().getRowViewHolder(mActivity
+ .getBrowseTestSupportFragment().getSelectedPosition());
+ assertEquals(20, row.getGridView().getSelectedPosition());
+ }
+
}
diff --git a/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java b/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
index 036d254..10c27d1 100644
--- a/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
+++ b/v17/tests/src/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java
@@ -57,4 +57,8 @@
}
ft.commit();
}
+
+ public BrowseTestSupportFragment getBrowseTestSupportFragment() {
+ return (BrowseTestSupportFragment) getSupportFragmentManager().findFragmentById(R.id.main_frame);
+ }
}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
new file mode 100644
index 0000000..40c1e70
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedDatePickerTest.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app.wizard;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.tests.R;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedDatePickerAction;
+import android.support.v17.leanback.widget.VerticalGridView;
+import android.support.v17.leanback.widget.picker.DatePicker;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
+
+import java.text.SimpleDateFormat;
+import java.util.ArrayList;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.List;
+
+public class GuidedDatePickerTest extends
+ ActivityInstrumentationTestCase2<GuidedStepAttributesTestActivity> {
+
+ static final long TRANSITION_LENGTH = 1000;
+ static long VERTICAL_SCROLL_WAIT = 500;
+ static long HORIZONTAL_SCROLL_WAIT = 500;
+ static final long FINAL_WAIT = 3000;
+
+ static final String TAG = "GuidedDatePickerTest";
+
+ private static final int DAY_INDEX = 0;
+ private static final int MONTH_INDEX = 1;
+ private static final int YEAR_INDEX = 2;
+ Instrumentation mInstrumentation;
+ GuidedStepAttributesTestActivity mActivity;
+
+ public GuidedDatePickerTest() {
+ super(GuidedStepAttributesTestActivity.class);
+ }
+
+ private void initActivity(Intent intent) {
+
+ setActivityIntent(intent);
+ mActivity = getActivity();
+ try {
+ Thread.sleep(2000);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ private void scrollOnField(int field, int[] columnIndices, DatePicker mPickerView,
+ int SCROLL_DIR) throws Throwable {
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+
+ int mColDayIndex = columnIndices[0];
+ int mColMonthIndex = columnIndices[1];
+ int mColYearIndex = columnIndices[2];
+ int columnIndex = -1;
+ switch (field) {
+ case Calendar.DAY_OF_MONTH:
+ columnIndex = mColDayIndex;
+ break;
+ case Calendar.MONTH:
+ columnIndex = mColMonthIndex;
+ break;
+ case Calendar.YEAR:
+ columnIndex = mColYearIndex;
+ }
+
+
+ LinearLayout columnsLayout = (LinearLayout) mPickerView.getChildAt(0);
+
+ int focusedFieldPos = columnsLayout.indexOfChild(columnsLayout.getFocusedChild());
+ if (focusedFieldPos == -1) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ }
+ focusedFieldPos = columnsLayout.indexOfChild(columnsLayout.getFocusedChild());
+ assertTrue("Date field could not be focused!", (focusedFieldPos != -1));
+
+ // following is to skip the separator fields "/" which are unfocusable but counted as
+ // children of columnsLayout
+ switch (focusedFieldPos) {
+ case 0:
+ focusedFieldPos = 0;
+ break;
+ case 2:
+ focusedFieldPos = 1;
+ break;
+ case 4:
+ focusedFieldPos = 2;
+ }
+
+ // now scroll right or left to the corresponding date field as indicated by the input field
+ int horizontalScrollOffset = columnIndex - focusedFieldPos;
+
+ int horizontalScrollDir = KeyEvent.KEYCODE_DPAD_RIGHT;
+ if (horizontalScrollOffset < 0) {
+ horizontalScrollOffset = -horizontalScrollOffset;
+ horizontalScrollDir = KeyEvent.KEYCODE_DPAD_LEFT;
+ }
+ for(int i = 0; i < horizontalScrollOffset; i++) {
+ sendKeys(horizontalScrollDir);
+ Thread.sleep(HORIZONTAL_SCROLL_WAIT);
+ }
+
+
+ Calendar currentActionCal = Calendar.getInstance();
+ currentActionCal.setTimeInMillis(mPickerView.getDate());
+
+ Calendar minCal = Calendar.getInstance();
+ minCal.setTimeInMillis(mPickerView.getMinDate());
+
+ Calendar maxCal = Calendar.getInstance();
+ maxCal.setTimeInMillis(mPickerView.getMaxDate());
+
+
+ int prevColumnVal = -1;
+ int currentColumnVal = mPickerView.getColumnAt(columnIndex).getCurrentValue();
+ while( currentColumnVal != prevColumnVal ){
+ assertTrue(getActivity().getString(R.string.datepicker_test_wrong_day_value),
+ mPickerView.getColumnAt(mColDayIndex).getCurrentValue() ==
+ currentActionCal.get(Calendar.DAY_OF_MONTH)
+ );
+ assertTrue(getActivity().getString(R.string.datepicker_test_wrong_month_value),
+ mPickerView.getColumnAt(mColMonthIndex).getCurrentValue() ==
+ currentActionCal.get(Calendar.MONTH)
+ );
+ assertTrue(getActivity().getString(R.string.datepicker_test_wrong_year_value),
+ mPickerView.getColumnAt(mColYearIndex).getCurrentValue() ==
+ currentActionCal.get(Calendar.YEAR)
+ );
+
+ int offset = SCROLL_DIR == KeyEvent.KEYCODE_DPAD_DOWN ? 1 : -1;
+ addDate(currentActionCal, field, offset, minCal, maxCal);
+
+ sendKeys(SCROLL_DIR);
+ Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+ prevColumnVal = currentColumnVal;
+ currentColumnVal = mPickerView.getColumnAt(columnIndex).getCurrentValue();
+ }
+ }
+
+ private void addDate(Calendar mCurrentDate, int field, int offset,
+ Calendar mMinDate, Calendar mMaxDate) {
+ int maxOffset = -1;
+ int actualMinFieldValue, actualMaxFieldValue;
+
+ if ( field == Calendar.YEAR ) {
+ actualMinFieldValue = mMinDate.get(Calendar.YEAR);
+ actualMaxFieldValue = mMaxDate.get(Calendar.YEAR);
+ } else {
+ actualMinFieldValue = mCurrentDate.getActualMinimum(field);
+ actualMaxFieldValue = mCurrentDate.getActualMaximum(field);
+ }
+
+ if ( offset > 0 ) {
+ maxOffset = Math.min(
+ actualMaxFieldValue - mCurrentDate.get(field), offset);
+ mCurrentDate.add(field, maxOffset);
+ if (mCurrentDate.after(mMaxDate)) {
+ mCurrentDate.setTimeInMillis(mMaxDate.getTimeInMillis());
+ }
+ } else {
+ maxOffset = Math.max(
+ actualMinFieldValue - mCurrentDate.get(field), offset);
+ mCurrentDate.add(field, Math.max(offset, maxOffset));
+ if (mCurrentDate.before(mMinDate)) {
+ mCurrentDate.setTimeInMillis(mMinDate.getTimeInMillis());
+ }
+ }
+ }
+
+ public void testDifferentMonthLengths() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ final int NUM_DATE_ACTIONS = 1;
+
+ String title = "Date Picker Transition Test";
+ String breadcrumb = "Month Transition Test Demo";
+ String description = "Testing the transition between longer to shorter months";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+
+ Calendar cal = Calendar.getInstance();
+
+ cal.set(Calendar.YEAR, 2016);
+ cal.set(Calendar.MONTH, Calendar.JANUARY);
+ cal.set(Calendar.DAY_OF_MONTH, 30);
+ Date initialDate = cal.getTime();
+
+ GuidedDatePickerAction action = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(0)
+ .title("Date")
+ .date(initialDate.getTime())
+ .datePickerFormat("DMY")
+ .build();
+
+ actionList.add(action);
+
+ GuidedStepAttributesTestFragment.clear();
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+ initActivity(intent);
+
+ DatePicker mPickerView = (DatePicker) mActivity.findViewById(
+ R.id.guidedactions_activator_item);
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+ getGuidedStepTestFragment();
+ traverseMonths(mPickerView, (GuidedDatePickerAction) actionList.get(0));
+ Thread.sleep(FINAL_WAIT);
+ }
+
+ private void traverseMonths(DatePicker mPickerView, GuidedDatePickerAction dateAction)
+ throws Throwable{
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+
+ Calendar currentActionCal = Calendar.getInstance();
+ currentActionCal.setTimeInMillis(dateAction.getDate());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+
+ int prevMonth = -1;
+ int currentMonth = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+ while (currentMonth != prevMonth) {
+ int prevDayOfMonth = -1;
+ int currentDayOfMonth = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+ // scroll down the days till reaching the last day of month
+ while (currentDayOfMonth != prevDayOfMonth) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(VERTICAL_SCROLL_WAIT);
+ prevDayOfMonth = currentDayOfMonth;
+ currentDayOfMonth = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+ }
+ int oldDayValue = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+ int oldMonthValue = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+ // increment the month
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+
+ int newDayValue = mPickerView.getColumnAt(DAY_INDEX).getCurrentValue();
+ int newMonthValue = mPickerView.getColumnAt(MONTH_INDEX).getCurrentValue();
+ verifyMonthTransition(currentActionCal,
+ oldDayValue, oldMonthValue, newDayValue, newMonthValue);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(TRANSITION_LENGTH);
+ prevMonth = currentMonth;
+ currentMonth = newMonthValue;
+ }
+
+ }
+
+ private void verifyMonthTransition(Calendar currentCal, int oldDayValue, int oldMonthValue,
+ int newDayValue, int newMonthValue) {
+
+ if (oldMonthValue == newMonthValue)
+ return;
+
+ currentCal.set(Calendar.DAY_OF_MONTH, 1);
+ currentCal.set(Calendar.MONTH, oldMonthValue);
+ int expectedOldDayValue = currentCal.getActualMaximum(Calendar.DAY_OF_MONTH);
+ currentCal.set(Calendar.MONTH, newMonthValue);
+ int numDaysInNewMonth = currentCal.getActualMaximum(Calendar.DAY_OF_MONTH);
+ int expectedNewDayValue = (expectedOldDayValue <= numDaysInNewMonth) ?
+ expectedOldDayValue : numDaysInNewMonth;
+
+ assertTrue(getActivity().getString(
+ R.string.datepicker_test_transition_error1, oldMonthValue),
+ oldDayValue == expectedOldDayValue
+ );
+ assertTrue(getActivity().getString(
+ R.string.datepicker_test_transition_error2, newDayValue, newMonthValue),
+ newDayValue == expectedNewDayValue
+ );
+ }
+
+ public void testDateRanges() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ SimpleDateFormat dateFormat = new SimpleDateFormat("dd/MM/yyyy");
+
+ Calendar currCal = Calendar.getInstance();
+ currCal.set(Calendar.YEAR, 2016);
+ currCal.set(Calendar.MONTH, Calendar.JULY);
+ currCal.set(Calendar.DAY_OF_MONTH, 15);
+
+ Calendar minCal = Calendar.getInstance();
+ minCal.set(Calendar.YEAR, 2014);
+ minCal.set(Calendar.MONTH, Calendar.OCTOBER);
+ minCal.set(Calendar.DAY_OF_MONTH, 20);
+
+ Calendar maxCal = Calendar.getInstance();
+ maxCal.set(Calendar.YEAR, 2018);
+ maxCal.set(Calendar.MONTH, Calendar.FEBRUARY);
+ maxCal.set(Calendar.DAY_OF_MONTH, 10);
+
+ String title = "Date Picker Range Test";
+ String breadcrumb = "Date Picker Range Test Demo";
+ String description = "";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+
+ // testing different date formats and the correctness of range changes as we scroll
+ GuidedDatePickerAction dateAction1 = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(0)
+ .title(res.getString(R.string.datepicker_with_range_title,
+ dateFormat.format(minCal.getTime()),
+ dateFormat.format(maxCal.getTime())))
+ .multilineDescription(true)
+ .date(currCal.getTimeInMillis())
+ .datePickerFormat("MDY")
+ .minDate(minCal.getTimeInMillis())
+ .maxDate(maxCal.getTimeInMillis())
+ .build();
+
+ GuidedDatePickerAction dateAction2 = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(1)
+ .title(res.getString(R.string.datepicker_with_range_title,
+ dateFormat.format(minCal.getTimeInMillis()),
+ dateFormat.format(maxCal.getTimeInMillis())))
+ .multilineDescription(true)
+ .date(currCal.getTimeInMillis())
+ .datePickerFormat("DMY")
+ .minDate(minCal.getTimeInMillis())
+ .maxDate(maxCal.getTimeInMillis())
+ .build();
+
+ // testing date ranges when Year is equal
+ minCal.set(Calendar.YEAR, maxCal.get(Calendar.YEAR));
+ int minMonth = Math.min(minCal.get(Calendar.MONTH), maxCal.get(Calendar.MONTH));
+ int maxMonth = Math.max(minCal.get(Calendar.MONTH), maxCal.get(Calendar.MONTH));
+ minCal.set(Calendar.MONTH, minMonth);
+ maxCal.set(Calendar.MONTH, maxMonth);
+
+ GuidedDatePickerAction dateAction3 = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(2)
+ .title(res.getString(R.string.datepicker_with_range_title,
+ dateFormat.format(minCal.getTimeInMillis()),
+ dateFormat.format(maxCal.getTimeInMillis())))
+ .multilineDescription(true)
+ .date(currCal.getTimeInMillis())
+ .datePickerFormat("DMY")
+ .minDate(minCal.getTimeInMillis())
+ .maxDate(maxCal.getTimeInMillis())
+ .build();
+
+
+ // testing date ranges when both Month and Year are equal
+ minCal.set(Calendar.MONTH, maxCal.get(Calendar.MONTH));
+ int minDay = Math.min(minCal.get(Calendar.DAY_OF_MONTH), maxCal.get(Calendar.DAY_OF_MONTH));
+ int maxDay = Math.max(minCal.get(Calendar.DAY_OF_MONTH), maxCal.get(Calendar.DAY_OF_MONTH));
+ minCal.set(Calendar.DAY_OF_MONTH, minDay);
+ maxCal.set(Calendar.DAY_OF_MONTH, maxDay);
+
+ GuidedDatePickerAction dateAction4 = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(3)
+ .title(res.getString(R.string.datepicker_with_range_title,
+ dateFormat.format(minCal.getTimeInMillis()),
+ dateFormat.format(maxCal.getTimeInMillis())))
+ .multilineDescription(true)
+ .date(currCal.getTimeInMillis())
+ .datePickerFormat("DMY")
+ .minDate(minCal.getTimeInMillis())
+ .maxDate(maxCal.getTimeInMillis())
+ .build();
+
+
+ // testing date ranges when all fields are equal
+ minCal.set(Calendar.DAY_OF_MONTH, maxCal.get(Calendar.DAY_OF_MONTH));
+
+ GuidedDatePickerAction dateAction5 = new GuidedDatePickerAction.Builder(
+ mInstrumentation.getContext())
+ .id(4)
+ .title(res.getString(R.string.datepicker_with_range_title,
+ dateFormat.format(minCal.getTimeInMillis()),
+ dateFormat.format(maxCal.getTimeInMillis())))
+ .multilineDescription(true)
+ .date(currCal.getTimeInMillis())
+ .datePickerFormat("DMY")
+ .minDate(minCal.getTimeInMillis())
+ .maxDate(maxCal.getTimeInMillis())
+ .build();
+
+ actionList.add(dateAction1);
+ actionList.add(dateAction2);
+ actionList.add(dateAction3);
+ actionList.add(dateAction4);
+ actionList.add(dateAction5);
+
+ GuidedStepAttributesTestFragment.clear();
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+ initActivity(intent);
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+ getGuidedStepTestFragment();
+
+ scrollToMinAndMaxDates(new int[] {1, 0, 2}, dateAction1);
+ scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction2);
+ scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction3);
+ scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction4);
+ scrollToMinAndMaxDates(new int[] {0, 1, 2}, dateAction5);
+
+ Thread.sleep(FINAL_WAIT);
+ }
+
+ private void scrollToMinAndMaxDates(int[] columnIndices, GuidedDatePickerAction dateAction)
+ throws Throwable{
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+
+ VerticalGridView guidedActionsList = (VerticalGridView)
+ mActivity.findViewById(R.id.guidedactions_list);
+
+ int currSelectedAction = mFragment.getSelectedActionPosition();
+ // scroll up/down to the requested action
+ long verticalScrollOffset = dateAction.getId() - currSelectedAction;
+
+ int verticalScrollDir = KeyEvent.KEYCODE_DPAD_DOWN;
+ if (verticalScrollOffset < 0) {
+ verticalScrollOffset= -verticalScrollOffset;
+ verticalScrollDir = KeyEvent.KEYCODE_DPAD_UP;
+ }
+ for(int i = 0; i < verticalScrollOffset; i++) {
+ sendKeys(verticalScrollDir);
+ Thread.sleep(TRANSITION_LENGTH);
+ }
+
+ assertTrue("The wrong action was selected!", mFragment.getSelectedActionPosition() ==
+ dateAction.getId());
+ DatePicker mPickerView = (DatePicker) mFragment.getActionItemView((int) dateAction.getId())
+ .findViewById(R.id.guidedactions_activator_item);
+
+ Calendar currentActionCal = Calendar.getInstance();
+ currentActionCal.setTimeInMillis(dateAction.getDate());
+
+
+ // scrolling to the minimum date
+
+ scrollOnField(Calendar.YEAR, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+ dateAction.setDate(mPickerView.getDate());
+
+ scrollOnField(Calendar.MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+ dateAction.setDate(mPickerView.getDate());
+
+ scrollOnField(Calendar.DAY_OF_MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_UP);
+ dateAction.setDate(mPickerView.getDate());
+
+ Thread.sleep(VERTICAL_SCROLL_WAIT);
+
+ // now scrolling to the maximum date
+
+ scrollOnField(Calendar.YEAR, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+ dateAction.setDate(mPickerView.getDate());
+
+ scrollOnField(Calendar.MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+ dateAction.setDate(mPickerView.getDate());
+
+ scrollOnField(Calendar.DAY_OF_MONTH, columnIndices, mPickerView, KeyEvent.KEYCODE_DPAD_DOWN);
+ dateAction.setDate(mPickerView.getDate());
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ }
+
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
new file mode 100644
index 0000000..d7025157
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTest.java
@@ -0,0 +1,623 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app.wizard;
+
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.tests.R;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.test.ActivityInstrumentationTestCase2;
+import android.util.Log;
+import android.view.KeyEvent;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+public class GuidedStepAttributesTest extends
+ ActivityInstrumentationTestCase2<GuidedStepAttributesTestActivity>
+{
+ static final long TRANSITION_LENGTH = 1000;
+
+ static final String TAG = "GuidedStepAttributesTest";
+
+ Instrumentation mInstrumentation;
+ GuidedStepAttributesTestActivity mActivity;
+
+ public GuidedStepAttributesTest() {
+ super(GuidedStepAttributesTestActivity.class);
+ }
+
+ private void initActivity(Intent intent) {
+
+ setActivityIntent(intent);
+ mActivity = getActivity();
+ try {
+ Thread.sleep(2000);
+ } catch(InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void testFocusDisabledOnActions() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ final int NUM_SEARCH_ACTIONS = 10;
+ final List<Integer> ACTIONS_WITH_DISABLED_FOCUS = new ArrayList<>(
+ Arrays.asList(1, 3, 4, 5, 8));
+ final int ACTION_ID_SEARCH = 1;
+ List<Integer> EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT = new ArrayList<>();
+
+ // we will traverse actions from top to bottom and then back to the top
+ for(int i = 0; i < NUM_SEARCH_ACTIONS; i++) {
+ if (!ACTIONS_WITH_DISABLED_FOCUS.contains(i))
+ EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.add(i);
+ }
+ for(int i = EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.size(); i-- != 0;) {
+ EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.add(EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(i));
+ }
+
+
+ String title = "Guided Actions Focusable Test";
+ String breadcrumb = "Focusable Test Demo";
+ String description = "";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+ for (int i = 0; i < NUM_SEARCH_ACTIONS; i++ ) {
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .id(ACTION_ID_SEARCH)
+ .title(res.getString(R.string.search) + "" + i)
+ .description(res.getString(R.string.search_description) + i)
+ .build()
+ );
+ }
+ for(int action_id : ACTIONS_WITH_DISABLED_FOCUS )
+ actionList.get(action_id).setFocusable(false);
+
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+ initActivity(intent);
+
+ int lastSelectedActionId = -1;
+ int selectIndex = 0;
+ GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.getGuidedStepTestFragment();
+ int prevSelectedActionPosition = -1;
+ int nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+ while ( nextSelectedActionPosition != prevSelectedActionPosition ) {
+ lastSelectedActionId = mFragment.getSelectedActionPosition();
+ assertTrue(res.getString(R.string.focusable_test_error_message,
+ actionList.get(lastSelectedActionId).getTitle()),
+ lastSelectedActionId == EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(selectIndex));
+ selectIndex++;
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ prevSelectedActionPosition = nextSelectedActionPosition;
+ nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+ Thread.sleep(TRANSITION_LENGTH);
+ }
+
+ prevSelectedActionPosition = -1;
+ while ( nextSelectedActionPosition != prevSelectedActionPosition ) {
+ lastSelectedActionId = mFragment.getSelectedActionPosition();
+ assertTrue(res.getString(R.string.focusable_test_error_message,
+ actionList.get(lastSelectedActionId).getTitle()),
+ lastSelectedActionId == EXPECTED_ACTIONS_ID_AFTER_EACH_SELECT.get(selectIndex));
+ selectIndex++;
+ sendKeys(KeyEvent.KEYCODE_DPAD_UP);
+ prevSelectedActionPosition = nextSelectedActionPosition;
+ nextSelectedActionPosition = mFragment.getSelectedActionPosition();
+ Thread.sleep(TRANSITION_LENGTH);
+ }
+
+ }
+
+ // Note: do not remove final or sRevertCallback gets null from 2nd test on!
+ static final GuidedStepAttributesTestFragment.Callback sRevertCallback = new
+ GuidedStepAttributesTestFragment.Callback() {
+ @Override
+ public void onActionClicked(GuidedStepFragment fragment, long id) {
+ List<GuidedAction> allActions = fragment.getActions();
+ for(int i = 1; i < allActions.size(); i++) {
+ GuidedAction action = allActions.get(i);
+ action.setEnabled(!action.isEnabled());
+ fragment.notifyActionChanged(fragment.findActionPositionById(action.getId()));
+ }
+ }
+ };
+
+ public void testDisabledActions() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ final int NUM_SEARCH_ACTIONS = 10;
+ final List<Integer> DISABLED_ACTIONS = new ArrayList<>(
+ Arrays.asList(1, 3, 5, 7));
+ final int ACTION_ID_REVERT_BUTTON = 0;
+ final int ACTION_ID_SEARCH_BEGIN = ACTION_ID_REVERT_BUTTON + 1;
+ int ACTION_ID_SEARCH_END = ACTION_ID_SEARCH_BEGIN;
+
+ // sequence of clicked actions simulated in the test
+ List<Integer> CLICK_SEQUENCE = new ArrayList<>();
+
+ // Expected Clicked sequence can be different from focused ones since some of the actions
+ // are disabled hence not clickable
+ List<Integer> EXPECTED_FOCUSED_SEQUENCE = new ArrayList<>();
+ List<Integer> EXPECTED_CLICKED_SEQUENCE = new ArrayList<>();
+ // Expected actions state according to list of DISABLED_ACTIONS: false for disabled actions
+ List<Boolean> EXPECTED_ACTIONS_STATE = new ArrayList<>(
+ Arrays.asList(new Boolean[NUM_SEARCH_ACTIONS])
+ );
+ Collections.fill(EXPECTED_ACTIONS_STATE, Boolean.TRUE);
+
+ for(int i = 0; i < NUM_SEARCH_ACTIONS; i++) {
+ CLICK_SEQUENCE.add(i + 1);
+ }
+ for(int clickedActionId : CLICK_SEQUENCE) {
+ EXPECTED_FOCUSED_SEQUENCE.add(clickedActionId);
+ if (!DISABLED_ACTIONS.contains(clickedActionId - 1))
+ EXPECTED_CLICKED_SEQUENCE.add(clickedActionId);
+ else
+ EXPECTED_CLICKED_SEQUENCE.add(-1);
+ }
+
+ String title = "Guided Actions Enabled Test";
+ String breadcrumb = "Enabled Test Demo";
+ String description = "";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .id(ACTION_ID_REVERT_BUTTON)
+ .title(res.getString(R.string.invert_title))
+ .description(res.getString(R.string.revert_description))
+ .build()
+ );
+
+ for (int i = 0; i < NUM_SEARCH_ACTIONS; i++ ) {
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .id(ACTION_ID_SEARCH_END++)
+ .title(res.getString(R.string.search) + "" + i)
+ .description(res.getString(R.string.search_description) + i)
+ .build()
+ );
+ }
+ for(int action_id : DISABLED_ACTIONS ) {
+ if ( action_id >= 0 && action_id < NUM_SEARCH_ACTIONS ) {
+ actionList.get(action_id + 1).setEnabled(false);
+ EXPECTED_ACTIONS_STATE.set(action_id, Boolean.FALSE);
+ }
+ }
+
+ GuidedStepAttributesTestFragment.clear();
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+ GuidedStepAttributesTestFragment.setActionClickCallback(ACTION_ID_REVERT_BUTTON,
+ sRevertCallback);
+
+ initActivity(intent);
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+
+ examineEnabledAndDisabledActions(actionList, CLICK_SEQUENCE, EXPECTED_FOCUSED_SEQUENCE,
+ EXPECTED_CLICKED_SEQUENCE);
+ // now toggling all enabled/disabled actions to disabled/enabled and running the test again
+ Log.d(TAG, "Toggling actions...");
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mFragment.setSelectedActionPosition(0);
+ }
+ });
+ Thread.sleep(TRANSITION_LENGTH);
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ for(int i = 0; i < EXPECTED_CLICKED_SEQUENCE.size(); i++) {
+ if (EXPECTED_CLICKED_SEQUENCE.get(i) == -1)
+ EXPECTED_CLICKED_SEQUENCE.set(i, CLICK_SEQUENCE.get(i));
+ else
+ EXPECTED_CLICKED_SEQUENCE.set(i, -1);
+ }
+
+ examineEnabledAndDisabledActions(actionList, CLICK_SEQUENCE, EXPECTED_FOCUSED_SEQUENCE,
+ EXPECTED_CLICKED_SEQUENCE);
+
+ }
+
+ private void examineEnabledAndDisabledActions(
+ List<GuidedAction> actionList, List<Integer> CLICK_SEQUENCE,
+ List<Integer> EXPECTED_FOCUSED_SEQUENCE,
+ List<Integer> EXPECTED_CLICKED_SEQUENCE)
+ throws Throwable {
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+
+ for(int i = 0; i < CLICK_SEQUENCE.size(); i++) {
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID =
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID = -1;
+ final int id = CLICK_SEQUENCE.get(i);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mFragment.setSelectedActionPosition(id);
+ }
+ });
+ Thread.sleep(TRANSITION_LENGTH);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.enabled_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(i)
+ );
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.enabled_test_wrong_click_error_message),
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+ EXPECTED_CLICKED_SEQUENCE.get(i)
+ );
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.enabled_test_wrong_flag_error_message),
+ (GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID == -1) ?
+ !actionList.get(id).isEnabled() :
+ actionList.get(id).isEnabled()
+ );
+ }
+ }
+
+ public void testCheckedActions() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ final int NUM_RADIO_ACTIONS = 3;
+ final int NUM_CHECK_BOX_ACTIONS = 3;
+ final int INITIALLY_CHECKED_RADIO_ACTION = 0;
+ final List<Integer> INITIALLY_CHECKED_CHECKBOX_ACTIONS = new ArrayList<>(
+ Arrays.asList(1, 2)
+ );
+
+ List<Integer> CLICK_SEQUENCE = new ArrayList<>();
+ for(int i = 0; i < NUM_RADIO_ACTIONS + NUM_CHECK_BOX_ACTIONS; i++) {
+ CLICK_SEQUENCE.add(i);
+ }
+
+ List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK = new ArrayList<>(
+ Arrays.asList(new Boolean[CLICK_SEQUENCE.size()])
+ );
+ Collections.fill(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK, Boolean.FALSE);
+
+ // initial state of actions before any clicks happen
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(INITIALLY_CHECKED_RADIO_ACTION, true);
+ for(int checkedCheckBox : INITIALLY_CHECKED_CHECKBOX_ACTIONS) {
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(NUM_RADIO_ACTIONS + checkedCheckBox, true);
+ }
+
+ String title = "Guided Actions Checked Test";
+ String breadcrumb = "Checked Test Demo";
+ String description = "";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .title(res.getString(R.string.radio_actions_info_title))
+ .description(res.getString(R.string.radio_actions_info_desc))
+ .infoOnly(true)
+ .enabled(true)
+ .focusable(false)
+ .build()
+ );
+
+ int firstRadioActionIndex = actionList.size();
+ for(int i = 0; i < NUM_RADIO_ACTIONS; i++) {
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .title(res.getString(R.string.checkbox_title) + i)
+ .description(res.getString(R.string.checkbox_desc) + i)
+ .checkSetId(GuidedAction.DEFAULT_CHECK_SET_ID)
+ .build()
+ );
+ if (i == INITIALLY_CHECKED_RADIO_ACTION)
+ actionList.get(firstRadioActionIndex + i).setChecked(true);
+ }
+
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .title(res.getString(R.string.checkbox_actions_info_title))
+ .description(res.getString(R.string.checkbox_actions_info_desc))
+ .infoOnly(true)
+ .enabled(true)
+ .focusable(false)
+ .build()
+ );
+ int firstCheckBoxActionIndex = actionList.size();
+ for(int i = 0; i < NUM_CHECK_BOX_ACTIONS; i++) {
+ actionList.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .title(res.getString(R.string.checkbox_title) + i)
+ .description(res.getString(R.string.checkbox_desc) + i)
+ .checkSetId(GuidedAction.CHECKBOX_CHECK_SET_ID)
+ .build()
+ );
+ }
+ for(int i = 0; i < INITIALLY_CHECKED_CHECKBOX_ACTIONS.size(); i++ ) {
+ actionList.get(firstCheckBoxActionIndex + INITIALLY_CHECKED_CHECKBOX_ACTIONS.get(i))
+ .setChecked(true);
+ }
+
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+ initActivity(intent);
+
+ examineCheckedAndUncheckedActions(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+ NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS);
+ }
+
+ private void updateExpectedActionsStateAfterClick(
+ List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK, int NUM_RADIO_ACTIONS,
+ int NUM_CHECK_BOX_ACTIONS, int clickedActionIndex) {
+
+ if (clickedActionIndex < NUM_RADIO_ACTIONS) {
+ for(int i = 0; i < NUM_RADIO_ACTIONS; i++)
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(i, false);
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(clickedActionIndex, true);
+ }
+ else if (clickedActionIndex < NUM_RADIO_ACTIONS + NUM_CHECK_BOX_ACTIONS) {
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.set(clickedActionIndex,
+ !EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.get(clickedActionIndex));
+ }
+ }
+
+ private void verifyIfActionsStateIsCorrect(List<GuidedAction> actionList,
+ List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK) {
+
+ int actionIndex = 0;
+ for(GuidedAction checkAction : actionList) {
+ if (checkAction.infoOnly())
+ continue;
+ assertTrue("Action " + actionIndex + " is " +
+ (!checkAction.isChecked() ? "un-" : "") +
+ "checked while it shouldn't be!",
+ checkAction.isChecked() ==
+ EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK.get(actionIndex));
+ actionIndex++;
+ }
+ }
+
+ private void examineCheckedAndUncheckedActions(List<GuidedAction> actionList,
+ List<Boolean> EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+ int NUM_RADIO_ACTIONS,
+ int NUM_CHECK_BOX_ACTIONS) throws Throwable {
+
+ final GuidedStepFragment guidedStepCheckedFragment = (GuidedStepFragment)
+ mActivity.getGuidedStepTestFragment();
+ final int firstRadioActionIndex = 1;
+ final int firstCheckBoxActionIndex = firstRadioActionIndex + NUM_RADIO_ACTIONS + 1;
+ for(int actionId = 0; actionId < NUM_RADIO_ACTIONS; actionId++) {
+ final int id = actionId;
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ guidedStepCheckedFragment
+ .setSelectedActionPosition(firstRadioActionIndex + id);
+ }
+ });
+ Thread.sleep(TRANSITION_LENGTH);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ updateExpectedActionsStateAfterClick(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+ NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS, actionId);
+ verifyIfActionsStateIsCorrect(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK);
+ }
+
+ for(int actionId = 0; actionId < NUM_CHECK_BOX_ACTIONS; actionId++) {
+ final int id = actionId;
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ guidedStepCheckedFragment
+ .setSelectedActionPosition(firstCheckBoxActionIndex + id);
+ }
+ });
+ Thread.sleep(TRANSITION_LENGTH);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ updateExpectedActionsStateAfterClick(EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK,
+ NUM_RADIO_ACTIONS, NUM_CHECK_BOX_ACTIONS, NUM_RADIO_ACTIONS + actionId);
+ verifyIfActionsStateIsCorrect(actionList, EXPECTED_ACTIONS_STATE_AFTER_EACH_CLICK);
+ }
+ }
+
+ public void testSubActions() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(),
+ GuidedStepAttributesTestActivity.class);
+ Resources res = mInstrumentation.getContext().getResources();
+
+ String TAG = "testSubActions";
+ final int NUM_REGULAR_ACTIONS = 4;
+ final int[] NUM_SUBACTIONS_PER_ACTION = {2, 1, 0, 3};
+ final int[] REGULAR_ACTIONS_INDEX = new int[NUM_REGULAR_ACTIONS];
+ final int[] BEGIN_SUBACTION_INDEX_PER_ACTION = new int[NUM_REGULAR_ACTIONS];
+ final int[] END_SUBACTION_INDEX_PER_ACTION = new int[NUM_REGULAR_ACTIONS];
+ // Actions and Subactions are assigned unique sequential IDs
+ int lastIndex = 0;
+ for(int i = 0; i < NUM_REGULAR_ACTIONS; i++) {
+ REGULAR_ACTIONS_INDEX[i] = lastIndex;
+ lastIndex++;
+ BEGIN_SUBACTION_INDEX_PER_ACTION[i] = lastIndex;
+ END_SUBACTION_INDEX_PER_ACTION[i] = (lastIndex += NUM_SUBACTIONS_PER_ACTION[i]);
+ }
+
+ // Sample click sequence for the main action list (not subactions)
+ List<Integer> ACTION_CLICK_SEQUENCE = new ArrayList<>(Arrays.asList(
+ 3, 2, 1, 0
+ ));
+ List<Integer> EXPECTED_FOCUSED_SEQUENCE = new ArrayList<>();
+ List<Integer> EXPECTED_CLICKED_SEQUENCE = new ArrayList<>();
+
+ for(int clickedActionId : ACTION_CLICK_SEQUENCE) {
+ EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+ if (NUM_SUBACTIONS_PER_ACTION[clickedActionId] > 0) {
+ for (int i = BEGIN_SUBACTION_INDEX_PER_ACTION[clickedActionId]; i <
+ END_SUBACTION_INDEX_PER_ACTION[clickedActionId]; i++) {
+ EXPECTED_CLICKED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+ for (int j = BEGIN_SUBACTION_INDEX_PER_ACTION[clickedActionId]; j <= i; j++) {
+ EXPECTED_FOCUSED_SEQUENCE.add(j);
+ }
+ EXPECTED_CLICKED_SEQUENCE.add(i);
+ EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+ }
+ } else {
+ EXPECTED_CLICKED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+ EXPECTED_FOCUSED_SEQUENCE.add(REGULAR_ACTIONS_INDEX[clickedActionId]);
+ }
+ }
+
+ String title = "Guided SubActions Test";
+ String breadcrumb = "SubActions Test Demo";
+ String description = "";
+ GuidanceStylist.Guidance guidance = new GuidanceStylist.Guidance(title, description,
+ breadcrumb, null);
+
+ List<GuidedAction> actionList = new ArrayList<>();
+
+ lastIndex = 0;
+ for (int i = 0; i < NUM_REGULAR_ACTIONS; i++ ) {
+ GuidedAction action = new GuidedAction.Builder(mInstrumentation.getContext())
+ .id(lastIndex++)
+ .title(res.getString(R.string.dropdown_action_title, i))
+ .description(res.getString(R.string.dropdown_action_desc, i))
+ .build();
+ if (NUM_SUBACTIONS_PER_ACTION[i] > 0) {
+ List<GuidedAction> subActions = new ArrayList<>();
+ action.setSubActions(subActions);
+ for(int j = 0; j < NUM_SUBACTIONS_PER_ACTION[i]; j++) {
+ subActions.add(new GuidedAction.Builder(mInstrumentation.getContext())
+ .id(lastIndex++)
+ .title(res.getString(R.string.subaction_title, j))
+ .description("")
+ .build()
+ );
+ }
+ }
+ actionList.add(action);
+ }
+
+ GuidedStepAttributesTestFragment.clear();
+ GuidedStepAttributesTestFragment.GUIDANCE = guidance;
+ GuidedStepAttributesTestFragment.ACTION_LIST = actionList;
+
+ initActivity(intent);
+
+ final GuidedStepFragment mFragment = (GuidedStepFragment) mActivity.
+ getGuidedStepTestFragment();
+
+ int focusStep = 0, clickStep = 0;
+ for(int i = 0; i < ACTION_CLICK_SEQUENCE.size(); i++) {
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID =
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID = -1;
+ final int id = ACTION_CLICK_SEQUENCE.get(i);
+ final GuidedAction selectedAction = actionList.get(id);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mFragment.setSelectedActionPosition(id);
+ }
+ });
+ Thread.sleep(TRANSITION_LENGTH);
+
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+ );
+
+ if (selectedAction.hasSubActions()) {
+ // Following for loop clicks on a specific action and scrolls & clicks through
+ // all its subactions
+ for (int j = 0; j < selectedAction.getSubActions().size(); j++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+ );
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_click_error_message),
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+ EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+ );
+ for (int k = 0; k < j; k++) {
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ Thread.sleep(TRANSITION_LENGTH);
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+ );
+ }
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+ );
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_click_error_message),
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+ EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+ );
+ }
+ } else {
+ sendKeys(KeyEvent.KEYCODE_DPAD_CENTER);
+ Thread.sleep(TRANSITION_LENGTH);
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_focus_error_message),
+ GuidedStepAttributesTestFragment.LAST_SELECTED_ACTION_ID ==
+ EXPECTED_FOCUSED_SEQUENCE.get(focusStep++)
+ );
+ assertTrue(mInstrumentation.getContext().getResources().getString(
+ R.string.subaction_test_wrong_click_error_message),
+ GuidedStepAttributesTestFragment.LAST_CLICKED_ACTION_ID ==
+ EXPECTED_CLICKED_SEQUENCE.get(clickStep++)
+ );
+ }
+ }
+
+ }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
new file mode 100644
index 0000000..a0433cc
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestActivity.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app.wizard;
+
+import android.app.Activity;
+import android.app.Fragment;
+import android.content.Intent;
+import android.os.Bundle;
+import android.support.v17.leanback.app.GuidedStepFragment;
+
+public class GuidedStepAttributesTestActivity extends Activity {
+
+ private GuidedStepAttributesTestFragment mGuidedStepAttributesTestFragment;
+
+ public static String EXTRA_GUIDANCE = "guidance";
+ public static String EXTRA_ACTION_LIST = "actionList";
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ Intent intent = getIntent();
+
+ mGuidedStepAttributesTestFragment = new GuidedStepAttributesTestFragment();
+ GuidedStepFragment.addAsRoot(this, mGuidedStepAttributesTestFragment, android.R.id.content);
+ }
+ public Fragment getGuidedStepTestFragment() {
+ return getFragmentManager().findFragmentById(android.R.id.content);
+ }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
new file mode 100644
index 0000000..3819dac
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/app/wizard/GuidedStepAttributesTestFragment.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software distributed under the License
+ * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+ * or implied. See the License for the specific language governing permissions and limitations under
+ * the License.
+ */
+
+package android.support.v17.leanback.app.wizard;
+
+import android.os.Bundle;
+import android.support.v17.leanback.app.GuidedStepFragment;
+import android.support.v17.leanback.widget.GuidanceStylist;
+import android.support.v17.leanback.widget.GuidedAction;
+import android.util.Log;
+
+import java.util.HashMap;
+import java.util.List;
+
+public class GuidedStepAttributesTestFragment extends GuidedStepFragment {
+
+ private static String TAG = "GuidedStepAttributesTestFragment";
+ static class Callback {
+ public void onActionClicked(GuidedStepFragment fragment, long id) {
+ }
+ }
+
+ static HashMap<Long, Callback> sCallbacks = new HashMap();
+ public static GuidanceStylist.Guidance GUIDANCE = null;
+ public static List<GuidedAction> ACTION_LIST = null;
+ public static long LAST_CLICKED_ACTION_ID = -1;
+ public static long LAST_SELECTED_ACTION_ID = -1;
+
+ @Override
+ public GuidanceStylist.Guidance onCreateGuidance(Bundle savedInstanceState) {
+ if (GUIDANCE == null ) {
+ return new GuidanceStylist.Guidance("", "", "", null);
+ }
+ return GUIDANCE;
+ }
+
+ @Override
+ public void onCreateActions(List<GuidedAction> actions, Bundle savedInstanceState) {
+ if (ACTION_LIST == null)
+ return;
+ actions.addAll(ACTION_LIST);
+ }
+
+ @Override
+ public void onGuidedActionClicked(GuidedAction action) {
+ super.onGuidedActionFocused(action);
+ Callback callback = sCallbacks.get(action.getId());
+ if (callback != null) {
+ callback.onActionClicked(this, action.getId());
+ } else {
+ LAST_CLICKED_ACTION_ID = action.getId();
+ }
+ }
+
+ @Override
+ public void onGuidedActionFocused(GuidedAction action) {
+ super.onGuidedActionFocused(action);
+ LAST_SELECTED_ACTION_ID = action.getId();
+ }
+
+ @Override
+ public boolean onSubGuidedActionClicked(GuidedAction action) {
+ super.onSubGuidedActionClicked(action);
+ LAST_CLICKED_ACTION_ID = action.getId();
+ return true;
+ }
+
+ @Override
+ public long onGuidedActionEditedAndProceed(GuidedAction action) {
+
+ Callback callback = sCallbacks.get(action.getId());
+ if (callback != null) {
+ callback.onActionClicked(this, action.getId());
+ } else {
+ super.onGuidedActionEditedAndProceed(action);
+ }
+ return GuidedAction.ACTION_ID_CURRENT;
+ }
+
+ public static void setActionClickCallback(long id, Callback callback) {
+ sCallbacks.put(id, callback);
+ }
+
+ public static void clear() {
+ LAST_CLICKED_ACTION_ID = -1;
+ LAST_SELECTED_ACTION_ID = -1;
+ sCallbacks.clear();
+ }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
index 902a665..27c5ad7 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
@@ -42,6 +42,10 @@
private static final String TAG = "GridActivity";
+ interface AdapterListener {
+ void onBind(RecyclerView.ViewHolder vh, int position);
+ }
+
public static final String EXTRA_LAYOUT_RESOURCE_ID = "layoutResourceId";
public static final String EXTRA_NUM_ITEMS = "numItems";
public static final String EXTRA_ITEMS = "items";
@@ -51,6 +55,10 @@
public static final String EXTRA_REQUEST_FOCUS_ONLAYOUT = "requstFocusOnLayout";
public static final String EXTRA_CHILD_LAYOUT_ID = "childLayoutId";
public static final String EXTRA_SECONDARY_SIZE_ZERO = "secondarySizeZero";
+ public static final String EXTRA_UPDATE_SIZE = "updateSize";
+ public static final String EXTRA_LAYOUT_MARGINS = "layoutMargins";
+ public static final String EXTRA_NINEPATCH_SHADOW = "NINEPATCH_SHADOW";
+
/**
* Class that implements GridWidgetTest.ViewTypeProvider for creating different
* view types for each position.
@@ -87,11 +95,15 @@
GridWidgetTest.ViewTypeProvider mViewTypeProvider;
GridWidgetTest.ItemAlignmentFacetProvider mAlignmentProvider;
GridWidgetTest.ItemAlignmentFacetProvider mAlignmentViewTypeProvider;
+ AdapterListener mAdapterListener;
+ boolean mUpdateSize = true;
int[] mGridViewLayoutSize;
BaseGridView mGridView;
int[] mItemLengths;
boolean[] mItemFocusables;
+ int[] mLayoutMargins;
+ int mNinePatchShadow;
private int mBoundCount;
@@ -109,6 +121,9 @@
if (DEBUG) Log.d(TAG, "onChildSelected position=" + position + " id="+id);
}
});
+ if (mNinePatchShadow != 0) {
+ mGridView.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+ }
return view;
}
@@ -123,13 +138,16 @@
DEFAULT_REQUEST_LAYOUT_ONFOCUS);
mRequestFocusOnLayout = intent.getBooleanExtra(EXTRA_REQUEST_FOCUS_ONLAYOUT,
DEFAULT_REQUEST_FOCUS_ONLAYOUT);
+ mUpdateSize = intent.getBooleanExtra(EXTRA_UPDATE_SIZE, true);
mSecondarySizeZero = intent.getBooleanExtra(EXTRA_SECONDARY_SIZE_ZERO, false);
mItemLengths = intent.getIntArrayExtra(EXTRA_ITEMS);
mItemFocusables = intent.getBooleanArrayExtra(EXTRA_ITEMS_FOCUSABLE);
+ mLayoutMargins = intent.getIntArrayExtra(EXTRA_LAYOUT_MARGINS);
String alignmentClass = intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_CLASS);
String alignmentViewTypeClass =
intent.getStringExtra(EXTRA_ITEMALIGNMENTPROVIDER_VIEWTYPE_CLASS);
String viewTypeClass = intent.getStringExtra(EXTRA_VIEWTYPEPROVIDER_CLASS);
+ mNinePatchShadow = intent.getIntExtra(EXTRA_NINEPATCH_SHADOW, 0);
try {
if (alignmentClass != null) {
mAlignmentProvider = (GridWidgetTest.ItemAlignmentFacetProvider)
@@ -172,7 +190,7 @@
mNumItems = mItemLengths.length;
}
- mGridView.setAdapter(new MyAdapter());
+ mGridView.setAdapter(adapter);
setContentView(view);
}
@@ -255,10 +273,19 @@
System.arraycopy(mItemLengths, index + length, mItemLengths, index,
mNumItems - index - length);
mNumItems -= length;
- mGridView.getAdapter().notifyItemRangeRemoved(index, length);
+ if (mGridView.getAdapter() != null) {
+ mGridView.getAdapter().notifyItemRangeRemoved(index, length);
+ }
return removed;
}
+ void attachToNewAdapter(int[] items) {
+ mItemLengths = items;
+ mNumItems = items.length;
+ mGridView.setAdapter(new MyAdapter());
+ }
+
+
void addItems(int index, int[] items) {
int length = items.length;
if (mItemLengths.length < mNumItems + length) {
@@ -269,7 +296,9 @@
System.arraycopy(mItemLengths, index, mItemLengths, index + length, mNumItems - index);
System.arraycopy(items, 0, mItemLengths, index, length);
mNumItems += length;
- mGridView.getAdapter().notifyItemRangeInserted(index, length);
+ if (mGridView.getAdapter() != null) {
+ mGridView.getAdapter().notifyItemRangeInserted(index, length);
+ }
}
class MyAdapter extends RecyclerView.Adapter implements FacetProviderAdapter {
@@ -303,8 +332,9 @@
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (DEBUG) Log.v(TAG, "createViewHolder " + viewType);
+ View itemView;
if (mChildLayout != -1) {
- final View view = getLayoutInflater().inflate(mChildLayout, null, false);
+ final View view = getLayoutInflater().inflate(mChildLayout, parent, false);
ArrayList<View> focusables = new ArrayList<View>();
view.addFocusables(focusables, View.FOCUS_UP);
for (int i = 0; i < focusables.size(); i++) {
@@ -329,24 +359,46 @@
}
});
}
- ViewHolder holder = new ViewHolder(view);
- return holder;
- }
- TextView textView = new TextView(parent.getContext()) {
- @Override
- protected void onLayout(boolean change, int left, int top, int right, int bottom) {
- super.onLayout(change, left, top, right, bottom);
- if (mRequestFocusOnLayout) {
- if (hasFocus()) {
- clearFocus();
- requestFocus();
+ itemView = view;
+ } else {
+ TextView textView = new TextView(parent.getContext()) {
+ @Override
+ protected void onLayout(boolean change, int left, int top, int right,
+ int bottom) {
+ super.onLayout(change, left, top, right, bottom);
+ if (mRequestFocusOnLayout) {
+ if (hasFocus()) {
+ clearFocus();
+ requestFocus();
+ }
}
}
+ };
+ textView.setTextColor(Color.BLACK);
+ textView.setOnFocusChangeListener(mItemFocusChangeListener);
+ itemView = textView;
+ }
+ if (mLayoutMargins != null) {
+ ViewGroup.MarginLayoutParams lp = (ViewGroup.MarginLayoutParams)
+ itemView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.MarginLayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
}
- };
- textView.setTextColor(Color.BLACK);
- textView.setOnFocusChangeListener(mItemFocusChangeListener);
- return new ViewHolder(textView);
+ lp.leftMargin = mLayoutMargins[0];
+ lp.topMargin = mLayoutMargins[1];
+ lp.rightMargin = mLayoutMargins[2];
+ lp.bottomMargin = mLayoutMargins[3];
+ itemView.setLayoutParams(lp);
+ }
+ if (mNinePatchShadow != 0) {
+ ViewGroup viewGroup = (ViewGroup) itemView;
+ View shadow = new View(viewGroup.getContext());
+ shadow.setBackgroundResource(mNinePatchShadow);
+ viewGroup.addView(shadow);
+ viewGroup.setLayoutMode(ViewGroup.LAYOUT_MODE_OPTICAL_BOUNDS);
+ }
+ return new ViewHolder(itemView);
}
@Override
@@ -376,6 +428,9 @@
}
}
updateSize(holder.itemView, position);
+ if (mAdapterListener != null) {
+ mAdapterListener.onBind(baseHolder, position);
+ }
}
@Override
@@ -386,6 +441,9 @@
}
void updateSize(View view, int position) {
+ if (!mUpdateSize) {
+ return;
+ }
ViewGroup.LayoutParams p = view.getLayoutParams();
if (p == null) {
p = new ViewGroup.LayoutParams(0, 0);
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
index 335f449..91a114f 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -15,11 +15,20 @@
*/
package android.support.v17.leanback.widget;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Parcelable;
import android.support.v17.leanback.tests.R;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
import android.test.ActivityInstrumentationTestCase2;
import android.text.Selection;
import android.text.Spannable;
-import android.util.Log;
import android.util.SparseArray;
import android.util.SparseIntArray;
import android.view.KeyEvent;
@@ -27,18 +36,10 @@
import android.view.ViewGroup;
import android.widget.TextView;
-import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
-import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
-
-import android.app.Instrumentation;
-import android.content.Intent;
-import android.os.Parcelable;
-
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.HashMap;
-import java.util.Iterator;
/**
* @hide from javadoc
@@ -384,6 +385,175 @@
verifyBeginAligned();
}
+ static class DividerDecoration extends RecyclerView.ItemDecoration {
+
+ private ColorDrawable mTopDivider;
+ private ColorDrawable mBottomDivider;
+ private int mLeftOffset;
+ private int mRightOffset;
+ private int mTopOffset;
+ private int mBottomOffset;
+
+ DividerDecoration(int leftOffset, int topOffset, int rightOffset, int bottomOffset) {
+ mLeftOffset = leftOffset;
+ mTopOffset = topOffset;
+ mRightOffset = rightOffset;
+ mBottomOffset = bottomOffset;
+ }
+
+ @Override
+ public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
+ if (mTopDivider == null) {
+ mTopDivider = new ColorDrawable(Color.RED);
+ }
+ if (mBottomDivider == null) {
+ mBottomDivider = new ColorDrawable(Color.BLUE);
+ }
+ final int childCount = parent.getChildCount();
+ final int width = parent.getWidth();
+ for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
+ final View view = parent.getChildAt(childViewIndex);
+ mTopDivider.setBounds(0, (int) view.getY() - mTopOffset, width, (int) view.getY());
+ mTopDivider.draw(c);
+ mBottomDivider.setBounds(0, (int) view.getY() + view.getHeight(), width,
+ (int) view.getY() + view.getHeight() + mBottomOffset);
+ mBottomDivider.draw(c);
+ }
+ }
+
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.left = mLeftOffset;
+ outRect.top = mTopOffset;
+ outRect.right = mRightOffset;
+ outRect.bottom = mBottomOffset;
+ }
+ }
+
+ public void testItemDecorationAndMargins() throws Throwable {
+
+ final int leftMargin = 3;
+ final int topMargin = 4;
+ final int rightMargin = 7;
+ final int bottomMargin = 8;
+ final int itemHeight = 100;
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+ intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
+ new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ final int paddingLeft = mGridView.getPaddingLeft();
+ final int paddingTop = mGridView.getPaddingTop();
+ final int verticalSpace = mGridView.getVerticalMargin();
+ final int decorationLeft = 17;
+ final int decorationTop = 1;
+ final int decorationRight = 19;
+ final int decorationBottom = 2;
+
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
+ decorationRight, decorationBottom));
+ }
+ });
+ waitForScrollIdle();
+
+ View child0 = mGridView.getChildAt(0);
+ View child1 = mGridView.getChildAt(1);
+ View child2 = mGridView.getChildAt(2);
+
+ assertEquals(itemHeight, child0.getBottom() - child0.getTop());
+
+ // verify left margins
+ assertEquals(paddingLeft + leftMargin + decorationLeft, child0.getLeft());
+ assertEquals(paddingLeft + leftMargin + decorationLeft, child1.getLeft());
+ assertEquals(paddingLeft + leftMargin + decorationLeft, child2.getLeft());
+ // verify top bottom margins and decoration offset
+ assertEquals(paddingTop + topMargin + decorationTop, child0.getTop());
+ assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+ child1.getTop() - child0.getBottom());
+ assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+ child2.getTop() - child1.getBottom());
+
+ }
+
+ public void testItemDecorationAndMarginsAndOpticalBounds() throws Throwable {
+ final int leftMargin = 3;
+ final int topMargin = 4;
+ final int rightMargin = 7;
+ final int bottomMargin = 8;
+ final int itemHeight = 100;
+ final int ninePatchDrawableResourceId = R.drawable.lb_card_shadow_focused;
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_linear);
+ intent.putExtra(GridActivity.EXTRA_ITEMS, new int[]{itemHeight, itemHeight, itemHeight});
+ intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.relative_layout);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_MARGINS,
+ new int[]{leftMargin, topMargin, rightMargin, bottomMargin});
+ intent.putExtra(GridActivity.EXTRA_NINEPATCH_SHADOW, ninePatchDrawableResourceId);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ final int paddingLeft = mGridView.getPaddingLeft();
+ final int paddingTop = mGridView.getPaddingTop();
+ final int verticalSpace = mGridView.getVerticalMargin();
+ final int decorationLeft = 17;
+ final int decorationTop = 1;
+ final int decorationRight = 19;
+ final int decorationBottom = 2;
+
+ final Rect opticalPaddings = new Rect();
+ mGridView.getContext().getDrawable(ninePatchDrawableResourceId).getPadding(opticalPaddings);
+ final int opticalInsetsLeft = opticalPaddings.left;
+ final int opticalInsetsTop = opticalPaddings.top;
+ final int opticalInsetsRight = opticalPaddings.right;
+ final int opticalInsetsBottom = opticalPaddings.bottom;
+ assertTrue(opticalInsetsLeft > 0);
+ assertTrue(opticalInsetsTop > 0);
+ assertTrue(opticalInsetsRight > 0);
+ assertTrue(opticalInsetsBottom > 0);
+
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.addItemDecoration(new DividerDecoration(decorationLeft, decorationTop,
+ decorationRight, decorationBottom));
+ }
+ });
+ waitForScrollIdle();
+
+ View child0 = mGridView.getChildAt(0);
+ View child1 = mGridView.getChildAt(1);
+ View child2 = mGridView.getChildAt(2);
+
+ assertEquals(itemHeight + opticalInsetsTop + opticalInsetsBottom,
+ child0.getBottom() - child0.getTop());
+
+ // verify left margins decoration and optical insets
+ assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+ child0.getLeft());
+ assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+ child1.getLeft());
+ assertEquals(paddingLeft + leftMargin + decorationLeft - opticalInsetsLeft,
+ child2.getLeft());
+ // verify top bottom margins decoration offset and optical insets
+ assertEquals(paddingTop + topMargin + decorationTop, child0.getTop() + opticalInsetsTop);
+ assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+ (child1.getTop() + opticalInsetsTop) - (child0.getBottom() - opticalInsetsBottom));
+ assertEquals(bottomMargin + decorationBottom + verticalSpace + decorationTop + topMargin,
+ (child2.getTop() + opticalInsetsTop) - (child1.getBottom() - opticalInsetsBottom));
+
+ }
+
public void testThreeColumnVerticalBasic() throws Throwable {
mInstrumentation = getInstrumentation();
@@ -555,6 +725,63 @@
verifyBeginAligned();
}
+ public void testSetSelectedPositionDetached() throws Throwable {
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_linear);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 50);
+ initActivity(intent);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ final int focusToIndex = 49;
+ final ViewGroup parent = (ViewGroup) mGridView.getParent();
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ parent.removeView(mGridView);
+ }
+ });
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelectedPositionSmooth(focusToIndex);
+ }
+ });
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ parent.addView(mGridView);
+ mGridView.requestFocus();
+ }
+ });
+ waitForTransientStateGone(null);
+ waitForScrollIdle();
+ assertEquals(mGridView.getSelectedPosition(), focusToIndex);
+ assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex).hasFocus());
+
+ final int focusToIndex2 = 0;
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ parent.removeView(mGridView);
+ }
+ });
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mGridView.setSelectedPosition(focusToIndex2);
+ }
+ });
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ parent.addView(mGridView);
+ mGridView.requestFocus();
+ }
+ });
+ assertEquals(mGridView.getSelectedPosition(), focusToIndex2);
+ waitForTransientStateGone(null);
+ waitForScrollIdle();
+ assertTrue(mGridView.getLayoutManager().findViewByPosition(focusToIndex2).hasFocus());
+ }
+
public void testBug22209986() throws Throwable {
mInstrumentation = getInstrumentation();
@@ -1100,6 +1327,58 @@
}
+ public void testLtrFocusOutStartDisabled() throws Throwable {
+ final int numItems = 200;
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_ltr);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+ initActivity(intent);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.requestFocus();
+ mGridView.setSelectedPositionSmooth(0);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ waitForScrollIdle(mVerifyLayout);
+ assertTrue(mGridView.hasFocus());
+ }
+
+ public void testRtlFocusOutStartDisabled() throws Throwable {
+ final int numItems = 200;
+
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.vertical_grid_rtl);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, numItems);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+ initActivity(intent);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.requestFocus();
+ mGridView.setSelectedPositionSmooth(0);
+ }
+ });
+ waitForScrollIdle(mVerifyLayout);
+
+ sendKeys(KeyEvent.KEYCODE_DPAD_RIGHT);
+ waitForScrollIdle(mVerifyLayout);
+ assertTrue(mGridView.hasFocus());
+ }
+
public void testTransferFocusable() throws Throwable {
final int numItems = 200;
final int numColumns = 3;
@@ -1720,6 +1999,29 @@
verifyMargin();
}
+ public void testWrapContent() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.horizontal_grid_wrap);
+ int[] items = new int[200];
+ for (int i = 0; i < items.length; i++) {
+ items[i] = 300;
+ }
+ intent.putExtra(GridActivity.EXTRA_ITEMS, items);
+ mOrientation = BaseGridView.HORIZONTAL;
+ mNumRows = 1;
+
+ initActivity(intent);
+
+ runTestOnUiThread(new Runnable() {
+ public void run() {
+ mActivity.attachToNewAdapter(new int[0]);
+ }
+ });
+
+ }
+
public void testZeroFixedSecondarySize() throws Throwable {
mInstrumentation = getInstrumentation();
@@ -2215,6 +2517,176 @@
assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
}
+ public void testFocusFinder() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ // test focus from button to vertical grid view
+ final View button = mActivity.findViewById(R.id.button);
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertFalse(mGridView.isFocused());
+ assertTrue(mGridView.hasFocus());
+
+ // FocusFinder should find last focused(2nd) item on DPAD_DOWN
+ final View secondChild = mGridView.getChildAt(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ secondChild.requestFocus();
+ button.requestFocus();
+ }
+ });
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(secondChild.isFocused());
+
+ // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
+ // (2nd) item on DPAD_DOWN.
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ button.requestFocus();
+ }
+ });
+ mGridView.setFocusable(false);
+ mGridView.setFocusableInTouchMode(false);
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(secondChild.isFocused());
+ }
+
+ public void testRestoreIndexAndAddItems() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear);
+ intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 4);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ assertEquals(mGridView.getSelectedPosition(), 0);
+ final SparseArray states = new SparseArray();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.saveHierarchyState(states);
+ mGridView.setAdapter(null);
+ }
+
+ });
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.restoreHierarchyState(states);
+ mActivity.attachToNewAdapter(new int[0]);
+ mActivity.addItems(0, new int[]{100, 100, 100, 100});
+ }
+
+ });
+ waitForTransientStateGone(null);
+ assertEquals(mGridView.getSelectedPosition(), 0);
+ }
+
+ public void test27766012() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button_onleft);
+ intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 2);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ // set remove animator two seconds
+ mGridView.getItemAnimator().setRemoveDuration(2000);
+ final View view = mGridView.getChildAt(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.requestFocus();
+ }
+ });
+ assertTrue(view.hasFocus());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.removeItems(0, 2);
+ }
+
+ });
+ // wait one second, removing second view is still attached to parent
+ Thread.sleep(1000);
+ assertSame(view.getParent(), mGridView);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ // refocus to the removed item and do a focus search.
+ view.requestFocus();
+ view.focusSearch(View.FOCUS_UP);
+ }
+
+ });
+ }
+
+ public void testBug27258366() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button_onleft);
+ intent.putExtra(GridActivity.EXTRA_CHILD_LAYOUT_ID, R.layout.horizontal_item);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 10);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ intent.putExtra(GridActivity.EXTRA_UPDATE_SIZE, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ // move item1 500 pixels right, when focus is on item1, default focus finder will pick
+ // item0 and item2 for the best match of focusSearch(FOCUS_LEFT). The grid widget
+ // must override default addFocusables(), not to add item0 or item2.
+ mActivity.mAdapterListener = new GridActivity.AdapterListener() {
+ public void onBind(RecyclerView.ViewHolder vh, int position) {
+ if (position == 1) {
+ vh.itemView.setPaddingRelative(500, 0, 0, 0);
+ } else {
+ vh.itemView.setPaddingRelative(0, 0, 0, 0);
+ }
+ }
+ };
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mGridView.getAdapter().notifyDataSetChanged();
+ }
+ });
+ Thread.sleep(100);
+
+ final ViewGroup secondChild = (ViewGroup) mGridView.getChildAt(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ secondChild.requestFocus();
+ }
+ });
+ sendKeys(KeyEvent.KEYCODE_DPAD_LEFT);
+ Thread.sleep(100);
+ final View button = mActivity.findViewById(R.id.button);
+ assertTrue(button.isFocused());
+ }
+
public void testAccessibility() throws Throwable {
mInstrumentation = getInstrumentation();
Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
diff --git a/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java b/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java
new file mode 100644
index 0000000..a2ca45c
--- /dev/null
+++ b/v17/tests/src/android/support/v17/leanback/widget/PagingIndicatorTest.java
@@ -0,0 +1,88 @@
+/*
+ * 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.v17.leanback.widget;
+
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+
+/**
+ * Tests for {@link PagingIndicator}.
+ * @hide
+ */
+@SmallTest
+public class PagingIndicatorTest extends AndroidTestCase {
+ private PagingIndicator mIndicator;
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mIndicator = new PagingIndicator(getContext());
+ }
+
+ public void testDotPosition() {
+ mIndicator.setPageCount(3);
+ assertDotPosition();
+ mIndicator.setPageCount(6);
+ assertDotPosition();
+ mIndicator.setPageCount(9);
+ assertDotPosition();
+ }
+
+ private void assertDotPosition() {
+ assertSymmetry();
+ assertDistance();
+ }
+
+ private void assertSymmetry() {
+ int pageCount = mIndicator.getPageCount();
+ int mid = pageCount / 2;
+ int[] selectedX = mIndicator.getDotSelectedX();
+ int sum = selectedX[0] + selectedX[pageCount - 1];
+ for (int i = 1; i <= mid; ++i) {
+ assertEquals("Selected dots are not symmetric", sum,
+ selectedX[i] + selectedX[pageCount - i - 1]);
+ }
+ int[] leftX = mIndicator.getDotSelectedLeftX();
+ int[] rightX = mIndicator.getDotSelectedRightX();
+ sum = leftX[0] + rightX[pageCount - 1];
+ for (int i = 1; i < pageCount - 1; ++i) {
+ assertEquals("Deselected dots are not symmetric", sum,
+ leftX[i] + rightX[pageCount - i - 1]);
+ }
+ }
+
+ private void assertDistance() {
+ int pageCount = mIndicator.getPageCount();
+ int[] selectedX = mIndicator.getDotSelectedX();
+ int[] leftX = mIndicator.getDotSelectedLeftX();
+ int[] rightX = mIndicator.getDotSelectedRightX();
+ int distance = selectedX[1] - selectedX[0];
+ for (int i = 2; i < pageCount; ++i) {
+ assertEquals("Gaps between selected dots are not even", distance,
+ selectedX[i] - selectedX[i - 1]);
+ }
+ distance = leftX[1] - leftX[0];
+ for (int i = 2; i < pageCount - 1; ++i) {
+ assertEquals("Gaps between left dots are not even", distance,
+ leftX[i] - leftX[i - 1]);
+ }
+ distance = rightX[2] - rightX[1];
+ for (int i = 3; i < pageCount; ++i) {
+ assertEquals("Gaps between right dots are not even", distance,
+ rightX[i] - rightX[i - 1]);
+ }
+ }
+}
diff --git a/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java b/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java
index fe897da..e0d42a1 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/PresenterTest.java
@@ -19,6 +19,7 @@
import android.support.v17.leanback.app.HeadersFragment;
import android.support.v17.leanback.R;
import android.test.AndroidTestCase;
+import android.view.ContextThemeWrapper;
import android.widget.FrameLayout;
import android.view.View;
import android.view.ViewGroup.LayoutParams;
@@ -55,13 +56,25 @@
public void testHeaderPresenter() throws Throwable {
HeadersFragment hf = new HeadersFragment();
PresenterSelector ps = hf.getPresenterSelector();
- Presenter p = ps.getPresenter(new Object());
+
+ Presenter p = ps.getPresenter(new Row());
assertTrue("Row header instance",
p instanceof RowHeaderPresenter);
assertFalse("isNullItemVisibilityGone",
((RowHeaderPresenter) p).isNullItemVisibilityGone());
testHeaderPresenter((RowHeaderPresenter) p);
+ p = ps.getPresenter(new SectionRow("Section Name"));
+ assertTrue("Row header instance",
+ p instanceof RowHeaderPresenter);
+ assertFalse("isNullItemVisibilityGone",
+ ((RowHeaderPresenter) p).isNullItemVisibilityGone());
+ testHeaderPresenter((RowHeaderPresenter) p);
+
+ p = ps.getPresenter(new DividerRow());
+ assertTrue("Row header instance",
+ p instanceof DividerPresenter);
+
ListRowPresenter lrp = new ListRowPresenter();
assertTrue("Row header instance",
lrp.getHeaderPresenter() instanceof RowHeaderPresenter);
@@ -72,6 +85,7 @@
}
public void testPlaybackControlsRowPresenter() {
+ setContext(new ContextThemeWrapper(getContext(), R.style.Theme_Leanback));
Presenter detailsPresenter = new AbstractDetailsDescriptionPresenter() {
@Override
protected void onBindDescription(ViewHolder vh, Object item) {
diff --git a/v4/Android.mk b/v4/Android.mk
index 89a9924..1e8adad 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -210,24 +210,11 @@
# -----------------------------------------------------------------------
-# A helper sub-library that allows to use Lollipop internal APIs.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v4-api21-internal
-LOCAL_SDK_VERSION := 21
-LOCAL_SRC_FILES := \
- $(call all-java-files-under, api21/android/content/pm) \
- $(call all-java-files-under, api21/android/service/media)
-LOCAL_MODULE_TAGS := optional
-include $(BUILD_JAVA_LIBRARY)
-
-# -----------------------------------------------------------------------
-
# A helper sub-library that makes direct use of Lollipop APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4-api21
LOCAL_SDK_VERSION := 21
-LOCAL_SRC_FILES := $(call all-java-files-under, api21/android/support)
-LOCAL_JAVA_LIBRARIES := android-support-v4-api21-internal
+LOCAL_SRC_FILES := $(call all-java-files-under, api21)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api20
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -239,7 +226,7 @@
# A helper sub-library that makes direct use of V22 APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4-api22
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 22
LOCAL_SRC_FILES := $(call all-java-files-under, api22)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api21
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
@@ -252,9 +239,8 @@
# A helper sub-library that makes direct use of V23 APIs.
include $(CLEAR_VARS)
LOCAL_MODULE := android-support-v4-api23
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := 23
LOCAL_SRC_FILES := $(call all-java-files-under, api23)
-LOCAL_JAVA_LIBRARIES := android-support-v4-api21-internal
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api22
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -263,14 +249,30 @@
# -----------------------------------------------------------------------
+# A helper sub-library that makes direct use of V24 APIs.
+include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v4-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api23
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# -----------------------------------------------------------------------
+
# Here is the final static library that apps can link against.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v4
LOCAL_SDK_VERSION := 4
LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
LOCAL_SRC_FILES := $(call all-java-files-under, java) \
$(call all-Iaidl-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api23
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api24
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v4/AndroidManifest.xml b/v4/AndroidManifest.xml
index a21d926..01eaccf 100644
--- a/v4/AndroidManifest.xml
+++ b/v4/AndroidManifest.xml
@@ -14,7 +14,8 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="android.support.v4">
- <uses-sdk android:minSdkVersion="4"/>
+ <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="android.support.v4"/>
<application />
</manifest>
diff --git a/v4/NOTICES.md b/v4/NOTICES.md
new file mode 100644
index 0000000..8665d5d
--- /dev/null
+++ b/v4/NOTICES.md
@@ -0,0 +1,12 @@
+# Change Log
+
+## [23.0.1](https://android.googlesource.com/platform/frameworks/support/+/refs/heads/master/v4/) (2015-09-24)
+
+**Breakage and deprecation notices:**
+
+- ExploreByTouchHelper
+ - Several public methods that are only meant to be called by app developers (and not internally by
+ the helper itself) became final. Any code that depends on overriding these methods should be
+ moved elsewhere.
+ - The concept of keyboard and accessibility focus have been clarified. As a result, the
+ getFocusedVirtualView() method has been deprecated and will be removed in a subsequent release.
diff --git a/v4/api/23.1.1.txt b/v4/api/23.1.1.txt
new file mode 100644
index 0000000..5b4c450
--- /dev/null
+++ b/v4/api/23.1.1.txt
@@ -0,0 +1,3487 @@
+package android.support.v4.accessibilityservice {
+
+ public class AccessibilityServiceInfoCompat {
+ method public static java.lang.String capabilityToString(int);
+ method public static java.lang.String feedbackTypeToString(int);
+ method public static java.lang.String flagToString(int);
+ method public static boolean getCanRetrieveWindowContent(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static int getCapabilities(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static java.lang.String getDescription(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static java.lang.String getId(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static android.content.pm.ResolveInfo getResolveInfo(android.accessibilityservice.AccessibilityServiceInfo);
+ method public static java.lang.String getSettingsActivityName(android.accessibilityservice.AccessibilityServiceInfo);
+ field public static final int CAPABILITY_CAN_FILTER_KEY_EVENTS = 8; // 0x8
+ field public static final int CAPABILITY_CAN_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 4; // 0x4
+ field public static final int CAPABILITY_CAN_REQUEST_TOUCH_EXPLORATION = 2; // 0x2
+ field public static final int CAPABILITY_CAN_RETRIEVE_WINDOW_CONTENT = 1; // 0x1
+ field public static final int DEFAULT = 1; // 0x1
+ field public static final int FEEDBACK_ALL_MASK = -1; // 0xffffffff
+ field public static final int FEEDBACK_BRAILLE = 32; // 0x20
+ field public static final int FLAG_INCLUDE_NOT_IMPORTANT_VIEWS = 2; // 0x2
+ field public static final int FLAG_REPORT_VIEW_IDS = 16; // 0x10
+ field public static final int FLAG_REQUEST_ENHANCED_WEB_ACCESSIBILITY = 8; // 0x8
+ field public static final int FLAG_REQUEST_FILTER_KEY_EVENTS = 32; // 0x20
+ field public static final int FLAG_REQUEST_TOUCH_EXPLORATION_MODE = 4; // 0x4
+ }
+
+}
+
+package android.support.v4.app {
+
+ public deprecated class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, int, int, int);
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, boolean, int, int, int);
+ method public boolean isDrawerIndicatorEnabled();
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public void onDrawerClosed(android.view.View);
+ method public void onDrawerOpened(android.view.View);
+ method public void onDrawerSlide(android.view.View, float);
+ method public void onDrawerStateChanged(int);
+ method public boolean onOptionsItemSelected(android.view.MenuItem);
+ method public void setDrawerIndicatorEnabled(boolean);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
+ method public void setHomeAsUpIndicator(int);
+ method public void syncState();
+ }
+
+ public static abstract interface ActionBarDrawerToggle.Delegate {
+ method public abstract android.graphics.drawable.Drawable getThemeUpIndicator();
+ method public abstract void setActionBarDescription(int);
+ method public abstract void setActionBarUpIndicator(android.graphics.drawable.Drawable, int);
+ }
+
+ public static abstract interface ActionBarDrawerToggle.DelegateProvider {
+ method public abstract android.support.v4.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ }
+
+ public class ActivityCompat extends android.support.v4.content.ContextCompat {
+ ctor public ActivityCompat();
+ method public static void finishAffinity(android.app.Activity);
+ method public static void finishAfterTransition(android.app.Activity);
+ method public android.net.Uri getReferrer(android.app.Activity);
+ method public static boolean invalidateOptionsMenu(android.app.Activity);
+ method public static void postponeEnterTransition(android.app.Activity);
+ method public static void requestPermissions(android.app.Activity, java.lang.String[], int);
+ method public static void setEnterSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
+ method public static void setExitSharedElementCallback(android.app.Activity, android.support.v4.app.SharedElementCallback);
+ method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String);
+ method public static void startActivity(android.app.Activity, android.content.Intent, android.os.Bundle);
+ method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle);
+ method public static void startPostponedEnterTransition(android.app.Activity);
+ }
+
+ public static abstract interface ActivityCompat.OnRequestPermissionsResultCallback {
+ method public abstract void onRequestPermissionsResult(int, java.lang.String[], int[]);
+ }
+
+ public final class ActivityManagerCompat {
+ method public static boolean isLowRamDevice(android.app.ActivityManager);
+ }
+
+ public class ActivityOptionsCompat {
+ ctor protected ActivityOptionsCompat();
+ method public static android.support.v4.app.ActivityOptionsCompat makeCustomAnimation(android.content.Context, int, int);
+ method public static android.support.v4.app.ActivityOptionsCompat makeScaleUpAnimation(android.view.View, int, int, int, int);
+ method public static android.support.v4.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, android.view.View, java.lang.String);
+ method public static android.support.v4.app.ActivityOptionsCompat makeSceneTransitionAnimation(android.app.Activity, android.support.v4.util.Pair<android.view.View, java.lang.String>...);
+ method public static android.support.v4.app.ActivityOptionsCompat makeThumbnailScaleUpAnimation(android.view.View, android.graphics.Bitmap, int, int);
+ method public android.os.Bundle toBundle();
+ method public void update(android.support.v4.app.ActivityOptionsCompat);
+ }
+
+ public class AppOpsManagerCompat {
+ ctor public AppOpsManagerCompat();
+ method public static int noteOp(android.content.Context, java.lang.String, int, java.lang.String);
+ method public static int noteProxyOp(android.content.Context, java.lang.String, java.lang.String);
+ method public static java.lang.String permissionToOp(java.lang.String);
+ field public static final int MODE_ALLOWED = 0; // 0x0
+ field public static final int MODE_DEFAULT = 3; // 0x3
+ field public static final int MODE_IGNORED = 1; // 0x1
+ }
+
+ abstract class BaseFragmentActivityDonut extends android.app.Activity {
+ }
+
+ abstract class BaseFragmentActivityHoneycomb extends android.support.v4.app.BaseFragmentActivityDonut {
+ }
+
+ public class BundleCompat {
+ ctor public BundleCompat();
+ method public static android.os.IBinder getBinder(android.os.Bundle, java.lang.String);
+ method public static void putBinder(android.os.Bundle, java.lang.String, android.os.IBinder);
+ }
+
+ public class DialogFragment extends android.support.v4.app.Fragment implements android.content.DialogInterface.OnCancelListener android.content.DialogInterface.OnDismissListener {
+ ctor public DialogFragment();
+ method public void dismiss();
+ method public void dismissAllowingStateLoss();
+ method public android.app.Dialog getDialog();
+ method public boolean getShowsDialog();
+ method public int getTheme();
+ method public boolean isCancelable();
+ method public void onCancel(android.content.DialogInterface);
+ method public android.app.Dialog onCreateDialog(android.os.Bundle);
+ method public void onDismiss(android.content.DialogInterface);
+ method public void setCancelable(boolean);
+ method public void setShowsDialog(boolean);
+ method public void setStyle(int, int);
+ method public void show(android.support.v4.app.FragmentManager, java.lang.String);
+ method public int show(android.support.v4.app.FragmentTransaction, java.lang.String);
+ field public static final int STYLE_NORMAL = 0; // 0x0
+ field public static final int STYLE_NO_FRAME = 2; // 0x2
+ field public static final int STYLE_NO_INPUT = 3; // 0x3
+ field public static final int STYLE_NO_TITLE = 1; // 0x1
+ }
+
+ public class Fragment implements android.content.ComponentCallbacks android.view.View.OnCreateContextMenuListener {
+ ctor public Fragment();
+ method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public final boolean equals(java.lang.Object);
+ method public final android.support.v4.app.FragmentActivity getActivity();
+ method public boolean getAllowEnterTransitionOverlap();
+ method public boolean getAllowReturnTransitionOverlap();
+ method public final android.os.Bundle getArguments();
+ method public final android.support.v4.app.FragmentManager getChildFragmentManager();
+ method public android.content.Context getContext();
+ method public java.lang.Object getEnterTransition();
+ method public java.lang.Object getExitTransition();
+ method public final android.support.v4.app.FragmentManager getFragmentManager();
+ method public final java.lang.Object getHost();
+ method public final int getId();
+ method public android.support.v4.app.LoaderManager getLoaderManager();
+ method public final android.support.v4.app.Fragment getParentFragment();
+ method public java.lang.Object getReenterTransition();
+ method public final android.content.res.Resources getResources();
+ method public final boolean getRetainInstance();
+ method public java.lang.Object getReturnTransition();
+ method public java.lang.Object getSharedElementEnterTransition();
+ method public java.lang.Object getSharedElementReturnTransition();
+ method public final java.lang.String getString(int);
+ method public final java.lang.String getString(int, java.lang.Object...);
+ method public final java.lang.String getTag();
+ method public final android.support.v4.app.Fragment getTargetFragment();
+ method public final int getTargetRequestCode();
+ method public final java.lang.CharSequence getText(int);
+ method public boolean getUserVisibleHint();
+ method public android.view.View getView();
+ method public final int hashCode();
+ method public static android.support.v4.app.Fragment instantiate(android.content.Context, java.lang.String);
+ method public static android.support.v4.app.Fragment instantiate(android.content.Context, java.lang.String, android.os.Bundle);
+ method public final boolean isAdded();
+ method public final boolean isDetached();
+ method public final boolean isHidden();
+ method public final boolean isInLayout();
+ method public final boolean isRemoving();
+ method public final boolean isResumed();
+ method public final boolean isVisible();
+ method public void onActivityCreated(android.os.Bundle);
+ method public void onActivityResult(int, int, android.content.Intent);
+ method public void onAttach(android.content.Context);
+ method public deprecated void onAttach(android.app.Activity);
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public boolean onContextItemSelected(android.view.MenuItem);
+ method public void onCreate(android.os.Bundle);
+ method public android.view.animation.Animation onCreateAnimation(int, boolean, int);
+ method public void onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo);
+ method public void onCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method public android.view.View onCreateView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public void onDestroy();
+ method public void onDestroyOptionsMenu();
+ method public void onDestroyView();
+ method public void onDetach();
+ method public void onHiddenChanged(boolean);
+ method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
+ method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
+ method public void onLowMemory();
+ method public boolean onOptionsItemSelected(android.view.MenuItem);
+ method public void onOptionsMenuClosed(android.view.Menu);
+ method public void onPause();
+ method public void onPrepareOptionsMenu(android.view.Menu);
+ method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
+ method public void onResume();
+ method public void onSaveInstanceState(android.os.Bundle);
+ method public void onStart();
+ method public void onStop();
+ method public void onViewCreated(android.view.View, android.os.Bundle);
+ method public void onViewStateRestored(android.os.Bundle);
+ method public void registerForContextMenu(android.view.View);
+ method public final void requestPermissions(java.lang.String[], int);
+ method public void setAllowEnterTransitionOverlap(boolean);
+ method public void setAllowReturnTransitionOverlap(boolean);
+ method public void setArguments(android.os.Bundle);
+ method public void setEnterSharedElementCallback(android.support.v4.app.SharedElementCallback);
+ method public void setEnterTransition(java.lang.Object);
+ method public void setExitSharedElementCallback(android.support.v4.app.SharedElementCallback);
+ method public void setExitTransition(java.lang.Object);
+ method public void setHasOptionsMenu(boolean);
+ method public void setInitialSavedState(android.support.v4.app.Fragment.SavedState);
+ method public void setMenuVisibility(boolean);
+ method public void setReenterTransition(java.lang.Object);
+ method public void setRetainInstance(boolean);
+ method public void setReturnTransition(java.lang.Object);
+ method public void setSharedElementEnterTransition(java.lang.Object);
+ method public void setSharedElementReturnTransition(java.lang.Object);
+ method public void setTargetFragment(android.support.v4.app.Fragment, int);
+ method public void setUserVisibleHint(boolean);
+ method public boolean shouldShowRequestPermissionRationale(java.lang.String);
+ method public void startActivity(android.content.Intent);
+ method public void startActivityForResult(android.content.Intent, int);
+ method public void unregisterForContextMenu(android.view.View);
+ }
+
+ public static class Fragment.InstantiationException extends java.lang.RuntimeException {
+ ctor public Fragment.InstantiationException(java.lang.String, java.lang.Exception);
+ }
+
+ public static class Fragment.SavedState implements android.os.Parcelable {
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.app.Fragment.SavedState> CREATOR;
+ }
+
+ public class FragmentActivity extends android.support.v4.app.BaseFragmentActivityHoneycomb implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
+ ctor public FragmentActivity();
+ method public java.lang.Object getLastCustomNonConfigurationInstance();
+ method public android.support.v4.app.FragmentManager getSupportFragmentManager();
+ method public android.support.v4.app.LoaderManager getSupportLoaderManager();
+ method public final android.support.v4.media.session.MediaControllerCompat getSupportMediaController();
+ method public void onAttachFragment(android.support.v4.app.Fragment);
+ method protected void onResumeFragments();
+ method public java.lang.Object onRetainCustomNonConfigurationInstance();
+ method public final java.lang.Object onRetainNonConfigurationInstance();
+ method public void setEnterSharedElementCallback(android.support.v4.app.SharedElementCallback);
+ method public void setExitSharedElementCallback(android.support.v4.app.SharedElementCallback);
+ method public final void setSupportMediaController(android.support.v4.media.session.MediaControllerCompat);
+ method public void startActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int);
+ method public void supportFinishAfterTransition();
+ method public void supportInvalidateOptionsMenu();
+ method public void supportPostponeEnterTransition();
+ method public void supportStartPostponedEnterTransition();
+ method public final void validateRequestPermissionsRequestCode(int);
+ }
+
+ public abstract class FragmentContainer {
+ ctor public FragmentContainer();
+ method public abstract android.view.View onFindViewById(int);
+ method public abstract boolean onHasView();
+ }
+
+ public class FragmentController {
+ method public void attachHost(android.support.v4.app.Fragment);
+ method public static final android.support.v4.app.FragmentController createController(android.support.v4.app.FragmentHostCallback<?>);
+ method public void dispatchActivityCreated();
+ method public void dispatchConfigurationChanged(android.content.res.Configuration);
+ method public boolean dispatchContextItemSelected(android.view.MenuItem);
+ method public void dispatchCreate();
+ method public boolean dispatchCreateOptionsMenu(android.view.Menu, android.view.MenuInflater);
+ method public void dispatchDestroy();
+ method public void dispatchDestroyView();
+ method public void dispatchLowMemory();
+ method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
+ method public void dispatchOptionsMenuClosed(android.view.Menu);
+ method public void dispatchPause();
+ method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
+ method public void dispatchReallyStop();
+ method public void dispatchResume();
+ method public void dispatchStart();
+ method public void dispatchStop();
+ method public void doLoaderDestroy();
+ method public void doLoaderRetain();
+ method public void doLoaderStart();
+ method public void doLoaderStop(boolean);
+ method public void dumpLoaders(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public boolean execPendingActions();
+ method public java.util.List<android.support.v4.app.Fragment> getActiveFragments(java.util.List<android.support.v4.app.Fragment>);
+ method public int getActiveFragmentsCount();
+ method public android.support.v4.app.FragmentManager getSupportFragmentManager();
+ method public android.support.v4.app.LoaderManager getSupportLoaderManager();
+ method public void noteStateNotSaved();
+ method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ method public void reportLoaderStart();
+ method public void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+ method public void restoreLoaderNonConfig(android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager>);
+ method public android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager> retainLoaderNonConfig();
+ method public java.util.List<android.support.v4.app.Fragment> retainNonConfig();
+ method public android.os.Parcelable saveAllState();
+ }
+
+ public abstract class FragmentHostCallback extends android.support.v4.app.FragmentContainer {
+ ctor public FragmentHostCallback(android.content.Context, android.os.Handler, int);
+ method public void onDump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public android.view.View onFindViewById(int);
+ method public abstract E onGetHost();
+ method public android.view.LayoutInflater onGetLayoutInflater();
+ method public int onGetWindowAnimations();
+ method public boolean onHasView();
+ method public boolean onHasWindowAnimations();
+ method public void onRequestPermissionsFromFragment(android.support.v4.app.Fragment, java.lang.String[], int);
+ method public boolean onShouldSaveFragmentState(android.support.v4.app.Fragment);
+ method public boolean onShouldShowRequestPermissionRationale(java.lang.String);
+ method public void onStartActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int);
+ method public void onSupportInvalidateOptionsMenu();
+ }
+
+ public abstract class FragmentManager {
+ ctor public FragmentManager();
+ method public abstract void addOnBackStackChangedListener(android.support.v4.app.FragmentManager.OnBackStackChangedListener);
+ method public abstract android.support.v4.app.FragmentTransaction beginTransaction();
+ method public abstract void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public static void enableDebugLogging(boolean);
+ method public abstract boolean executePendingTransactions();
+ method public abstract android.support.v4.app.Fragment findFragmentById(int);
+ method public abstract android.support.v4.app.Fragment findFragmentByTag(java.lang.String);
+ method public abstract android.support.v4.app.FragmentManager.BackStackEntry getBackStackEntryAt(int);
+ method public abstract int getBackStackEntryCount();
+ method public abstract android.support.v4.app.Fragment getFragment(android.os.Bundle, java.lang.String);
+ method public abstract boolean isDestroyed();
+ method public abstract void popBackStack();
+ method public abstract void popBackStack(java.lang.String, int);
+ method public abstract void popBackStack(int, int);
+ method public abstract boolean popBackStackImmediate();
+ method public abstract boolean popBackStackImmediate(java.lang.String, int);
+ method public abstract boolean popBackStackImmediate(int, int);
+ method public abstract void putFragment(android.os.Bundle, java.lang.String, android.support.v4.app.Fragment);
+ method public abstract void removeOnBackStackChangedListener(android.support.v4.app.FragmentManager.OnBackStackChangedListener);
+ method public abstract android.support.v4.app.Fragment.SavedState saveFragmentInstanceState(android.support.v4.app.Fragment);
+ field public static final int POP_BACK_STACK_INCLUSIVE = 1; // 0x1
+ }
+
+ public static abstract interface FragmentManager.BackStackEntry {
+ method public abstract java.lang.CharSequence getBreadCrumbShortTitle();
+ method public abstract int getBreadCrumbShortTitleRes();
+ method public abstract java.lang.CharSequence getBreadCrumbTitle();
+ method public abstract int getBreadCrumbTitleRes();
+ method public abstract int getId();
+ method public abstract java.lang.String getName();
+ }
+
+ public static abstract interface FragmentManager.OnBackStackChangedListener {
+ method public abstract void onBackStackChanged();
+ }
+
+ public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public FragmentPagerAdapter(android.support.v4.app.FragmentManager);
+ method public abstract android.support.v4.app.Fragment getItem(int);
+ method public long getItemId(int);
+ method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ }
+
+ public abstract class FragmentStatePagerAdapter extends android.support.v4.view.PagerAdapter {
+ ctor public FragmentStatePagerAdapter(android.support.v4.app.FragmentManager);
+ method public abstract android.support.v4.app.Fragment getItem(int);
+ method public boolean isViewFromObject(android.view.View, java.lang.Object);
+ }
+
+ public class FragmentTabHost extends android.widget.TabHost implements android.widget.TabHost.OnTabChangeListener {
+ ctor public FragmentTabHost(android.content.Context);
+ ctor public FragmentTabHost(android.content.Context, android.util.AttributeSet);
+ method public void addTab(android.widget.TabHost.TabSpec, java.lang.Class<?>, android.os.Bundle);
+ method public void onTabChanged(java.lang.String);
+ method public void setup(android.content.Context, android.support.v4.app.FragmentManager);
+ method public void setup(android.content.Context, android.support.v4.app.FragmentManager, int);
+ }
+
+ public abstract class FragmentTransaction {
+ ctor public FragmentTransaction();
+ method public abstract android.support.v4.app.FragmentTransaction add(android.support.v4.app.Fragment, java.lang.String);
+ method public abstract android.support.v4.app.FragmentTransaction add(int, android.support.v4.app.Fragment);
+ method public abstract android.support.v4.app.FragmentTransaction add(int, android.support.v4.app.Fragment, java.lang.String);
+ method public abstract android.support.v4.app.FragmentTransaction addSharedElement(android.view.View, java.lang.String);
+ method public abstract android.support.v4.app.FragmentTransaction addToBackStack(java.lang.String);
+ method public abstract android.support.v4.app.FragmentTransaction attach(android.support.v4.app.Fragment);
+ method public abstract int commit();
+ method public abstract int commitAllowingStateLoss();
+ method public abstract android.support.v4.app.FragmentTransaction detach(android.support.v4.app.Fragment);
+ method public abstract android.support.v4.app.FragmentTransaction disallowAddToBackStack();
+ method public abstract android.support.v4.app.FragmentTransaction hide(android.support.v4.app.Fragment);
+ method public abstract boolean isAddToBackStackAllowed();
+ method public abstract boolean isEmpty();
+ method public abstract android.support.v4.app.FragmentTransaction remove(android.support.v4.app.Fragment);
+ method public abstract android.support.v4.app.FragmentTransaction replace(int, android.support.v4.app.Fragment);
+ method public abstract android.support.v4.app.FragmentTransaction replace(int, android.support.v4.app.Fragment, java.lang.String);
+ method public abstract android.support.v4.app.FragmentTransaction setBreadCrumbShortTitle(int);
+ method public abstract android.support.v4.app.FragmentTransaction setBreadCrumbShortTitle(java.lang.CharSequence);
+ method public abstract android.support.v4.app.FragmentTransaction setBreadCrumbTitle(int);
+ method public abstract android.support.v4.app.FragmentTransaction setBreadCrumbTitle(java.lang.CharSequence);
+ method public abstract android.support.v4.app.FragmentTransaction setCustomAnimations(int, int);
+ method public abstract android.support.v4.app.FragmentTransaction setCustomAnimations(int, int, int, int);
+ method public abstract android.support.v4.app.FragmentTransaction setTransition(int);
+ method public abstract android.support.v4.app.FragmentTransaction setTransitionStyle(int);
+ method public abstract android.support.v4.app.FragmentTransaction show(android.support.v4.app.Fragment);
+ field public static final int TRANSIT_ENTER_MASK = 4096; // 0x1000
+ field public static final int TRANSIT_EXIT_MASK = 8192; // 0x2000
+ field public static final int TRANSIT_FRAGMENT_CLOSE = 8194; // 0x2002
+ field public static final int TRANSIT_FRAGMENT_FADE = 4099; // 0x1003
+ field public static final int TRANSIT_FRAGMENT_OPEN = 4097; // 0x1001
+ field public static final int TRANSIT_NONE = 0; // 0x0
+ field public static final int TRANSIT_UNSET = -1; // 0xffffffff
+ }
+
+ public class ListFragment extends android.support.v4.app.Fragment {
+ ctor public ListFragment();
+ method public android.widget.ListAdapter getListAdapter();
+ method public android.widget.ListView getListView();
+ method public long getSelectedItemId();
+ method public int getSelectedItemPosition();
+ method public void onListItemClick(android.widget.ListView, android.view.View, int, long);
+ method public void setEmptyText(java.lang.CharSequence);
+ method public void setListAdapter(android.widget.ListAdapter);
+ method public void setListShown(boolean);
+ method public void setListShownNoAnimation(boolean);
+ method public void setSelection(int);
+ }
+
+ public abstract class LoaderManager {
+ ctor public LoaderManager();
+ method public abstract void destroyLoader(int);
+ method public abstract void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public static void enableDebugLogging(boolean);
+ method public abstract android.support.v4.content.Loader<D> getLoader(int);
+ method public boolean hasRunningLoaders();
+ method public abstract android.support.v4.content.Loader<D> initLoader(int, android.os.Bundle, android.support.v4.app.LoaderManager.LoaderCallbacks<D>);
+ method public abstract android.support.v4.content.Loader<D> restartLoader(int, android.os.Bundle, android.support.v4.app.LoaderManager.LoaderCallbacks<D>);
+ }
+
+ public static abstract interface LoaderManager.LoaderCallbacks {
+ method public abstract android.support.v4.content.Loader<D> onCreateLoader(int, android.os.Bundle);
+ method public abstract void onLoadFinished(android.support.v4.content.Loader<D>, D);
+ method public abstract void onLoaderReset(android.support.v4.content.Loader<D>);
+ }
+
+ public class NavUtils {
+ method public static android.content.Intent getParentActivityIntent(android.app.Activity);
+ method public static android.content.Intent getParentActivityIntent(android.content.Context, java.lang.Class<?>) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static android.content.Intent getParentActivityIntent(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static java.lang.String getParentActivityName(android.app.Activity);
+ method public static java.lang.String getParentActivityName(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
+ method public static void navigateUpFromSameTask(android.app.Activity);
+ method public static void navigateUpTo(android.app.Activity, android.content.Intent);
+ method public static boolean shouldUpRecreateTask(android.app.Activity, android.content.Intent);
+ field public static final java.lang.String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY";
+ }
+
+ public class NotificationCompat {
+ ctor public NotificationCompat();
+ method public static android.support.v4.app.NotificationCompat.Action getAction(android.app.Notification, int);
+ method public static int getActionCount(android.app.Notification);
+ method public static java.lang.String getCategory(android.app.Notification);
+ method public static android.os.Bundle getExtras(android.app.Notification);
+ method public static java.lang.String getGroup(android.app.Notification);
+ method public static boolean getLocalOnly(android.app.Notification);
+ method public static java.lang.String getSortKey(android.app.Notification);
+ method public static boolean isGroupSummary(android.app.Notification);
+ field public static final java.lang.String CATEGORY_ALARM = "alarm";
+ field public static final java.lang.String CATEGORY_CALL = "call";
+ field public static final java.lang.String CATEGORY_EMAIL = "email";
+ field public static final java.lang.String CATEGORY_ERROR = "err";
+ field public static final java.lang.String CATEGORY_EVENT = "event";
+ field public static final java.lang.String CATEGORY_MESSAGE = "msg";
+ field public static final java.lang.String CATEGORY_PROGRESS = "progress";
+ field public static final java.lang.String CATEGORY_PROMO = "promo";
+ field public static final java.lang.String CATEGORY_RECOMMENDATION = "recommendation";
+ field public static final java.lang.String CATEGORY_SERVICE = "service";
+ field public static final java.lang.String CATEGORY_SOCIAL = "social";
+ field public static final java.lang.String CATEGORY_STATUS = "status";
+ field public static final java.lang.String CATEGORY_SYSTEM = "sys";
+ field public static final java.lang.String CATEGORY_TRANSPORT = "transport";
+ field public static final int COLOR_DEFAULT = 0; // 0x0
+ field public static final int DEFAULT_ALL = -1; // 0xffffffff
+ field public static final int DEFAULT_LIGHTS = 4; // 0x4
+ field public static final int DEFAULT_SOUND = 1; // 0x1
+ field public static final int DEFAULT_VIBRATE = 2; // 0x2
+ field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
+ field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
+ field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+ field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
+ field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
+ field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
+ field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
+ field public static final java.lang.String EXTRA_PEOPLE = "android.people";
+ field public static final java.lang.String EXTRA_PICTURE = "android.picture";
+ field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
+ field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
+ field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
+ field public static final java.lang.String EXTRA_SHOW_WHEN = "android.showWhen";
+ field public static final java.lang.String EXTRA_SMALL_ICON = "android.icon";
+ field public static final java.lang.String EXTRA_SUB_TEXT = "android.subText";
+ field public static final java.lang.String EXTRA_SUMMARY_TEXT = "android.summaryText";
+ field public static final java.lang.String EXTRA_TEMPLATE = "android.template";
+ field public static final java.lang.String EXTRA_TEXT = "android.text";
+ field public static final java.lang.String EXTRA_TEXT_LINES = "android.textLines";
+ field public static final java.lang.String EXTRA_TITLE = "android.title";
+ field public static final java.lang.String EXTRA_TITLE_BIG = "android.title.big";
+ field public static final int FLAG_AUTO_CANCEL = 16; // 0x10
+ field public static final int FLAG_FOREGROUND_SERVICE = 64; // 0x40
+ field public static final int FLAG_GROUP_SUMMARY = 512; // 0x200
+ field public static final deprecated int FLAG_HIGH_PRIORITY = 128; // 0x80
+ field public static final int FLAG_INSISTENT = 4; // 0x4
+ field public static final int FLAG_LOCAL_ONLY = 256; // 0x100
+ field public static final int FLAG_NO_CLEAR = 32; // 0x20
+ field public static final int FLAG_ONGOING_EVENT = 2; // 0x2
+ field public static final int FLAG_ONLY_ALERT_ONCE = 8; // 0x8
+ field public static final int FLAG_SHOW_LIGHTS = 1; // 0x1
+ field public static final int PRIORITY_DEFAULT = 0; // 0x0
+ field public static final int PRIORITY_HIGH = 1; // 0x1
+ field public static final int PRIORITY_LOW = -1; // 0xffffffff
+ field public static final int PRIORITY_MAX = 2; // 0x2
+ field public static final int PRIORITY_MIN = -2; // 0xfffffffe
+ field public static final int STREAM_DEFAULT = -1; // 0xffffffff
+ field public static final int VISIBILITY_PRIVATE = 0; // 0x0
+ field public static final int VISIBILITY_PUBLIC = 1; // 0x1
+ field public static final int VISIBILITY_SECRET = -1; // 0xffffffff
+ }
+
+ public static class NotificationCompat.Action {
+ ctor public NotificationCompat.Action(int, java.lang.CharSequence, android.app.PendingIntent);
+ method public android.app.PendingIntent getActionIntent();
+ method public android.os.Bundle getExtras();
+ method public int getIcon();
+ method public android.support.v4.app.RemoteInput[] getRemoteInputs();
+ method public java.lang.CharSequence getTitle();
+ field public android.app.PendingIntent actionIntent;
+ field public int icon;
+ field public java.lang.CharSequence title;
+ }
+
+ public static final class NotificationCompat.Action.Builder {
+ ctor public NotificationCompat.Action.Builder(int, java.lang.CharSequence, android.app.PendingIntent);
+ ctor public NotificationCompat.Action.Builder(android.support.v4.app.NotificationCompat.Action);
+ method public android.support.v4.app.NotificationCompat.Action.Builder addExtras(android.os.Bundle);
+ method public android.support.v4.app.NotificationCompat.Action.Builder addRemoteInput(android.support.v4.app.RemoteInput);
+ method public android.support.v4.app.NotificationCompat.Action build();
+ method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Extender);
+ method public android.os.Bundle getExtras();
+ }
+
+ public static abstract interface NotificationCompat.Action.Extender {
+ method public abstract android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
+ }
+
+ public static final class NotificationCompat.Action.WearableExtender implements android.support.v4.app.NotificationCompat.Action.Extender {
+ ctor public NotificationCompat.Action.WearableExtender();
+ ctor public NotificationCompat.Action.WearableExtender(android.support.v4.app.NotificationCompat.Action);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender clone();
+ method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
+ method public java.lang.CharSequence getCancelLabel();
+ method public java.lang.CharSequence getConfirmLabel();
+ method public java.lang.CharSequence getInProgressLabel();
+ method public boolean isAvailableOffline();
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
+ }
+
+ public static class NotificationCompat.BigPictureStyle extends android.support.v4.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigPictureStyle();
+ ctor public NotificationCompat.BigPictureStyle(android.support.v4.app.NotificationCompat.Builder);
+ method public android.support.v4.app.NotificationCompat.BigPictureStyle bigLargeIcon(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.BigPictureStyle bigPicture(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.BigPictureStyle setBigContentTitle(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.BigPictureStyle setSummaryText(java.lang.CharSequence);
+ }
+
+ public static class NotificationCompat.BigTextStyle extends android.support.v4.app.NotificationCompat.Style {
+ ctor public NotificationCompat.BigTextStyle();
+ ctor public NotificationCompat.BigTextStyle(android.support.v4.app.NotificationCompat.Builder);
+ method public android.support.v4.app.NotificationCompat.BigTextStyle bigText(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.BigTextStyle setBigContentTitle(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.BigTextStyle setSummaryText(java.lang.CharSequence);
+ }
+
+ public static class NotificationCompat.Builder {
+ ctor public NotificationCompat.Builder(android.content.Context);
+ method public android.support.v4.app.NotificationCompat.Builder addAction(int, java.lang.CharSequence, android.app.PendingIntent);
+ method public android.support.v4.app.NotificationCompat.Builder addAction(android.support.v4.app.NotificationCompat.Action);
+ method public android.support.v4.app.NotificationCompat.Builder addExtras(android.os.Bundle);
+ method public android.support.v4.app.NotificationCompat.Builder addPerson(java.lang.String);
+ method public android.app.Notification build();
+ method public android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Extender);
+ method public android.os.Bundle getExtras();
+ method public deprecated android.app.Notification getNotification();
+ method protected static java.lang.CharSequence limitCharSequenceLength(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setAutoCancel(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setCategory(java.lang.String);
+ method public android.support.v4.app.NotificationCompat.Builder setColor(int);
+ method public android.support.v4.app.NotificationCompat.Builder setContent(android.widget.RemoteViews);
+ method public android.support.v4.app.NotificationCompat.Builder setContentInfo(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setContentIntent(android.app.PendingIntent);
+ method public android.support.v4.app.NotificationCompat.Builder setContentText(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setContentTitle(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setDefaults(int);
+ method public android.support.v4.app.NotificationCompat.Builder setDeleteIntent(android.app.PendingIntent);
+ method public android.support.v4.app.NotificationCompat.Builder setExtras(android.os.Bundle);
+ method public android.support.v4.app.NotificationCompat.Builder setFullScreenIntent(android.app.PendingIntent, boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setGroup(java.lang.String);
+ method public android.support.v4.app.NotificationCompat.Builder setGroupSummary(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setLargeIcon(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.Builder setLights(int, int, int);
+ method public android.support.v4.app.NotificationCompat.Builder setLocalOnly(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setNumber(int);
+ method public android.support.v4.app.NotificationCompat.Builder setOngoing(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setOnlyAlertOnce(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setPriority(int);
+ method public android.support.v4.app.NotificationCompat.Builder setProgress(int, int, boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setPublicVersion(android.app.Notification);
+ method public android.support.v4.app.NotificationCompat.Builder setShowWhen(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setSmallIcon(int);
+ method public android.support.v4.app.NotificationCompat.Builder setSmallIcon(int, int);
+ method public android.support.v4.app.NotificationCompat.Builder setSortKey(java.lang.String);
+ method public android.support.v4.app.NotificationCompat.Builder setSound(android.net.Uri);
+ method public android.support.v4.app.NotificationCompat.Builder setSound(android.net.Uri, int);
+ method public android.support.v4.app.NotificationCompat.Builder setStyle(android.support.v4.app.NotificationCompat.Style);
+ method public android.support.v4.app.NotificationCompat.Builder setSubText(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setTicker(java.lang.CharSequence, android.widget.RemoteViews);
+ method public android.support.v4.app.NotificationCompat.Builder setUsesChronometer(boolean);
+ method public android.support.v4.app.NotificationCompat.Builder setVibrate(long[]);
+ method public android.support.v4.app.NotificationCompat.Builder setVisibility(int);
+ method public android.support.v4.app.NotificationCompat.Builder setWhen(long);
+ field public java.util.ArrayList<java.lang.String> mPeople;
+ }
+
+ public static final class NotificationCompat.CarExtender implements android.support.v4.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.CarExtender();
+ ctor public NotificationCompat.CarExtender(android.app.Notification);
+ method public android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Builder);
+ method public int getColor();
+ method public android.graphics.Bitmap getLargeIcon();
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation getUnreadConversation();
+ method public android.support.v4.app.NotificationCompat.CarExtender setColor(int);
+ method public android.support.v4.app.NotificationCompat.CarExtender setLargeIcon(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.CarExtender setUnreadConversation(android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation);
+ }
+
+ public static class NotificationCompat.CarExtender.UnreadConversation {
+ method public long getLatestTimestamp();
+ method public java.lang.String[] getMessages();
+ method public java.lang.String getParticipant();
+ method public java.lang.String[] getParticipants();
+ method public android.app.PendingIntent getReadPendingIntent();
+ method public android.support.v4.app.RemoteInput getRemoteInput();
+ method public android.app.PendingIntent getReplyPendingIntent();
+ }
+
+ public static class NotificationCompat.CarExtender.UnreadConversation.Builder {
+ ctor public NotificationCompat.CarExtender.UnreadConversation.Builder(java.lang.String);
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder addMessage(java.lang.String);
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation build();
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder setLatestTimestamp(long);
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReadPendingIntent(android.app.PendingIntent);
+ method public android.support.v4.app.NotificationCompat.CarExtender.UnreadConversation.Builder setReplyAction(android.app.PendingIntent, android.support.v4.app.RemoteInput);
+ }
+
+ public static abstract interface NotificationCompat.Extender {
+ method public abstract android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Builder);
+ }
+
+ public static class NotificationCompat.InboxStyle extends android.support.v4.app.NotificationCompat.Style {
+ ctor public NotificationCompat.InboxStyle();
+ ctor public NotificationCompat.InboxStyle(android.support.v4.app.NotificationCompat.Builder);
+ method public android.support.v4.app.NotificationCompat.InboxStyle addLine(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.InboxStyle setBigContentTitle(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.InboxStyle setSummaryText(java.lang.CharSequence);
+ }
+
+ public static abstract class NotificationCompat.Style {
+ ctor public NotificationCompat.Style();
+ method public android.app.Notification build();
+ method public void setBuilder(android.support.v4.app.NotificationCompat.Builder);
+ }
+
+ public static final class NotificationCompat.WearableExtender implements android.support.v4.app.NotificationCompat.Extender {
+ ctor public NotificationCompat.WearableExtender();
+ ctor public NotificationCompat.WearableExtender(android.app.Notification);
+ method public android.support.v4.app.NotificationCompat.WearableExtender addAction(android.support.v4.app.NotificationCompat.Action);
+ method public android.support.v4.app.NotificationCompat.WearableExtender addActions(java.util.List<android.support.v4.app.NotificationCompat.Action>);
+ method public android.support.v4.app.NotificationCompat.WearableExtender addPage(android.app.Notification);
+ method public android.support.v4.app.NotificationCompat.WearableExtender addPages(java.util.List<android.app.Notification>);
+ method public android.support.v4.app.NotificationCompat.WearableExtender clearActions();
+ method public android.support.v4.app.NotificationCompat.WearableExtender clearPages();
+ method public android.support.v4.app.NotificationCompat.WearableExtender clone();
+ method public android.support.v4.app.NotificationCompat.Builder extend(android.support.v4.app.NotificationCompat.Builder);
+ method public java.util.List<android.support.v4.app.NotificationCompat.Action> getActions();
+ method public android.graphics.Bitmap getBackground();
+ method public int getContentAction();
+ method public int getContentIcon();
+ method public int getContentIconGravity();
+ method public boolean getContentIntentAvailableOffline();
+ method public int getCustomContentHeight();
+ method public int getCustomSizePreset();
+ method public android.app.PendingIntent getDisplayIntent();
+ method public int getGravity();
+ method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintHideIcon();
+ method public int getHintScreenTimeout();
+ method public boolean getHintShowBackgroundOnly();
+ method public java.util.List<android.app.Notification> getPages();
+ method public boolean getStartScrollBottom();
+ method public android.support.v4.app.NotificationCompat.WearableExtender setBackground(android.graphics.Bitmap);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setContentAction(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setContentIcon(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setContentIconGravity(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setContentIntentAvailableOffline(boolean);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setCustomContentHeight(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setCustomSizePreset(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setDisplayIntent(android.app.PendingIntent);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setGravity(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintHideIcon(boolean);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintScreenTimeout(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintShowBackgroundOnly(boolean);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setStartScrollBottom(boolean);
+ field public static final int SCREEN_TIMEOUT_LONG = -1; // 0xffffffff
+ field public static final int SCREEN_TIMEOUT_SHORT = 0; // 0x0
+ field public static final int SIZE_DEFAULT = 0; // 0x0
+ field public static final int SIZE_FULL_SCREEN = 5; // 0x5
+ field public static final int SIZE_LARGE = 4; // 0x4
+ field public static final int SIZE_MEDIUM = 3; // 0x3
+ field public static final int SIZE_SMALL = 2; // 0x2
+ field public static final int SIZE_XSMALL = 1; // 0x1
+ field public static final int UNSET_ACTION_INDEX = -1; // 0xffffffff
+ }
+
+ public final class NotificationCompatExtras {
+ field public static final java.lang.String EXTRA_ACTION_EXTRAS = "android.support.actionExtras";
+ field public static final java.lang.String EXTRA_GROUP_KEY = "android.support.groupKey";
+ field public static final java.lang.String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
+ field public static final java.lang.String EXTRA_LOCAL_ONLY = "android.support.localOnly";
+ field public static final java.lang.String EXTRA_REMOTE_INPUTS = "android.support.remoteInputs";
+ field public static final java.lang.String EXTRA_SORT_KEY = "android.support.sortKey";
+ }
+
+ public abstract class NotificationCompatSideChannelService extends android.app.Service {
+ ctor public NotificationCompatSideChannelService();
+ method public abstract void cancel(java.lang.String, int, java.lang.String);
+ method public abstract void cancelAll(java.lang.String);
+ method public abstract void notify(java.lang.String, int, java.lang.String, android.app.Notification);
+ method public android.os.IBinder onBind(android.content.Intent);
+ }
+
+ public class NotificationManagerCompat {
+ method public void cancel(int);
+ method public void cancel(java.lang.String, int);
+ method public void cancelAll();
+ method public static android.support.v4.app.NotificationManagerCompat from(android.content.Context);
+ method public static java.util.Set<java.lang.String> getEnabledListenerPackages(android.content.Context);
+ method public void notify(int, android.app.Notification);
+ method public void notify(java.lang.String, int, android.app.Notification);
+ field public static final java.lang.String ACTION_BIND_SIDE_CHANNEL = "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
+ field public static final java.lang.String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ }
+
+ public class RemoteInput extends android.support.v4.app.RemoteInputCompatBase.RemoteInput {
+ method public static void addResultsToIntent(android.support.v4.app.RemoteInput[], android.content.Intent, android.os.Bundle);
+ method public boolean getAllowFreeFormInput();
+ method public java.lang.CharSequence[] getChoices();
+ method public android.os.Bundle getExtras();
+ method public java.lang.CharSequence getLabel();
+ method public java.lang.String getResultKey();
+ method public static android.os.Bundle getResultsFromIntent(android.content.Intent);
+ field public static final java.lang.String EXTRA_RESULTS_DATA = "android.remoteinput.resultsData";
+ field public static final java.lang.String RESULTS_CLIP_LABEL = "android.remoteinput.results";
+ }
+
+ public static final class RemoteInput.Builder {
+ ctor public RemoteInput.Builder(java.lang.String);
+ method public android.support.v4.app.RemoteInput.Builder addExtras(android.os.Bundle);
+ method public android.support.v4.app.RemoteInput build();
+ method public android.os.Bundle getExtras();
+ method public android.support.v4.app.RemoteInput.Builder setAllowFreeFormInput(boolean);
+ method public android.support.v4.app.RemoteInput.Builder setChoices(java.lang.CharSequence[]);
+ method public android.support.v4.app.RemoteInput.Builder setLabel(java.lang.CharSequence);
+ }
+
+ class RemoteInputCompatBase {
+ }
+
+ public static abstract class RemoteInputCompatBase.RemoteInput {
+ ctor public RemoteInputCompatBase.RemoteInput();
+ method protected abstract boolean getAllowFreeFormInput();
+ method protected abstract java.lang.CharSequence[] getChoices();
+ method protected abstract android.os.Bundle getExtras();
+ method protected abstract java.lang.CharSequence getLabel();
+ method protected abstract java.lang.String getResultKey();
+ }
+
+ public class ServiceCompat {
+ field public static final int START_STICKY = 1; // 0x1
+ }
+
+ public class ShareCompat {
+ ctor public ShareCompat();
+ method public static void configureMenuItem(android.view.MenuItem, android.support.v4.app.ShareCompat.IntentBuilder);
+ method public static void configureMenuItem(android.view.Menu, int, android.support.v4.app.ShareCompat.IntentBuilder);
+ method public static android.content.ComponentName getCallingActivity(android.app.Activity);
+ method public static java.lang.String getCallingPackage(android.app.Activity);
+ field public static final java.lang.String EXTRA_CALLING_ACTIVITY = "android.support.v4.app.EXTRA_CALLING_ACTIVITY";
+ field public static final java.lang.String EXTRA_CALLING_PACKAGE = "android.support.v4.app.EXTRA_CALLING_PACKAGE";
+ }
+
+ public static class ShareCompat.IntentBuilder {
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailBcc(java.lang.String);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailBcc(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailCc(java.lang.String);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailCc(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailTo(java.lang.String);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addEmailTo(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder addStream(android.net.Uri);
+ method public android.content.Intent createChooserIntent();
+ method public static android.support.v4.app.ShareCompat.IntentBuilder from(android.app.Activity);
+ method public android.content.Intent getIntent();
+ method public android.support.v4.app.ShareCompat.IntentBuilder setChooserTitle(java.lang.CharSequence);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setChooserTitle(int);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setEmailBcc(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setEmailCc(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setEmailTo(java.lang.String[]);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setHtmlText(java.lang.String);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setStream(android.net.Uri);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setSubject(java.lang.String);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setText(java.lang.CharSequence);
+ method public android.support.v4.app.ShareCompat.IntentBuilder setType(java.lang.String);
+ method public void startChooser();
+ }
+
+ public static class ShareCompat.IntentReader {
+ method public static android.support.v4.app.ShareCompat.IntentReader from(android.app.Activity);
+ method public android.content.ComponentName getCallingActivity();
+ method public android.graphics.drawable.Drawable getCallingActivityIcon();
+ method public android.graphics.drawable.Drawable getCallingApplicationIcon();
+ method public java.lang.CharSequence getCallingApplicationLabel();
+ method public java.lang.String getCallingPackage();
+ method public java.lang.String[] getEmailBcc();
+ method public java.lang.String[] getEmailCc();
+ method public java.lang.String[] getEmailTo();
+ method public java.lang.String getHtmlText();
+ method public android.net.Uri getStream();
+ method public android.net.Uri getStream(int);
+ method public int getStreamCount();
+ method public java.lang.String getSubject();
+ method public java.lang.CharSequence getText();
+ method public java.lang.String getType();
+ method public boolean isMultipleShare();
+ method public boolean isShareIntent();
+ method public boolean isSingleShare();
+ }
+
+ public abstract class SharedElementCallback {
+ ctor public SharedElementCallback();
+ method public android.os.Parcelable onCaptureSharedElementSnapshot(android.view.View, android.graphics.Matrix, android.graphics.RectF);
+ method public android.view.View onCreateSnapshotView(android.content.Context, android.os.Parcelable);
+ method public void onMapSharedElements(java.util.List<java.lang.String>, java.util.Map<java.lang.String, android.view.View>);
+ method public void onRejectSharedElements(java.util.List<android.view.View>);
+ method public void onSharedElementEnd(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
+ }
+
+ public class TaskStackBuilder implements java.lang.Iterable {
+ method public android.support.v4.app.TaskStackBuilder addNextIntent(android.content.Intent);
+ method public android.support.v4.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
+ method public android.support.v4.app.TaskStackBuilder addParentStack(android.app.Activity);
+ method public android.support.v4.app.TaskStackBuilder addParentStack(java.lang.Class<?>);
+ method public android.support.v4.app.TaskStackBuilder addParentStack(android.content.ComponentName);
+ method public static android.support.v4.app.TaskStackBuilder create(android.content.Context);
+ method public android.content.Intent editIntentAt(int);
+ method public static deprecated android.support.v4.app.TaskStackBuilder from(android.content.Context);
+ method public deprecated android.content.Intent getIntent(int);
+ method public int getIntentCount();
+ method public android.content.Intent[] getIntents();
+ method public android.app.PendingIntent getPendingIntent(int, int);
+ method public android.app.PendingIntent getPendingIntent(int, int, android.os.Bundle);
+ method public deprecated java.util.Iterator<android.content.Intent> iterator();
+ method public void startActivities();
+ method public void startActivities(android.os.Bundle);
+ }
+
+ public static abstract interface TaskStackBuilder.SupportParentable {
+ method public abstract android.content.Intent getSupportParentActivityIntent();
+ }
+
+}
+
+package android.support.v4.content {
+
+ public abstract class AsyncTaskLoader extends android.support.v4.content.Loader {
+ ctor public AsyncTaskLoader(android.content.Context);
+ method public void cancelLoadInBackground();
+ method public boolean isLoadInBackgroundCanceled();
+ method public abstract D loadInBackground();
+ method public void onCanceled(D);
+ method protected D onLoadInBackground();
+ method public void setUpdateThrottle(long);
+ }
+
+ public class ContentResolverCompat {
+ method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.support.v4.os.CancellationSignal);
+ }
+
+ public class ContextCompat {
+ ctor public ContextCompat();
+ method public static int checkSelfPermission(android.content.Context, java.lang.String);
+ method public final java.io.File getCodeCacheDir(android.content.Context);
+ method public static final int getColor(android.content.Context, int);
+ method public static final android.content.res.ColorStateList getColorStateList(android.content.Context, int);
+ method public static final android.graphics.drawable.Drawable getDrawable(android.content.Context, int);
+ method public static java.io.File[] getExternalCacheDirs(android.content.Context);
+ method public static java.io.File[] getExternalFilesDirs(android.content.Context, java.lang.String);
+ method public final java.io.File getNoBackupFilesDir(android.content.Context);
+ method public static java.io.File[] getObbDirs(android.content.Context);
+ method public static boolean startActivities(android.content.Context, android.content.Intent[]);
+ method public static boolean startActivities(android.content.Context, android.content.Intent[], android.os.Bundle);
+ }
+
+ public class CursorLoader extends android.support.v4.content.AsyncTaskLoader {
+ ctor public CursorLoader(android.content.Context);
+ ctor public CursorLoader(android.content.Context, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+ method public void deliverResult(android.database.Cursor);
+ method public java.lang.String[] getProjection();
+ method public java.lang.String getSelection();
+ method public java.lang.String[] getSelectionArgs();
+ method public java.lang.String getSortOrder();
+ method public android.net.Uri getUri();
+ method public android.database.Cursor loadInBackground();
+ method public void onCanceled(android.database.Cursor);
+ method public void setProjection(java.lang.String[]);
+ method public void setSelection(java.lang.String);
+ method public void setSelectionArgs(java.lang.String[]);
+ method public void setSortOrder(java.lang.String);
+ method public void setUri(android.net.Uri);
+ }
+
+ public class FileProvider extends android.content.ContentProvider {
+ ctor public FileProvider();
+ method public int delete(android.net.Uri, java.lang.String, java.lang.String[]);
+ method public java.lang.String getType(android.net.Uri);
+ method public static android.net.Uri getUriForFile(android.content.Context, java.lang.String, java.io.File);
+ method public android.net.Uri insert(android.net.Uri, android.content.ContentValues);
+ method public boolean onCreate();
+ method public android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String);
+ method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
+ }
+
+ public class IntentCompat {
+ method public static android.content.Intent makeMainActivity(android.content.ComponentName);
+ method public static android.content.Intent makeMainSelectorActivity(java.lang.String, java.lang.String);
+ method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
+ field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
+ field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+ field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
+ field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
+ field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
+ field public static final int FLAG_ACTIVITY_CLEAR_TASK = 32768; // 0x8000
+ field public static final int FLAG_ACTIVITY_TASK_ON_HOME = 16384; // 0x4000
+ }
+
+ public class Loader {
+ ctor public Loader(android.content.Context);
+ method public void abandon();
+ method public boolean cancelLoad();
+ method public void commitContentChanged();
+ method public java.lang.String dataToString(D);
+ method public void deliverCancellation();
+ method public void deliverResult(D);
+ method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public void forceLoad();
+ method public android.content.Context getContext();
+ method public int getId();
+ method public boolean isAbandoned();
+ method public boolean isReset();
+ method public boolean isStarted();
+ method protected void onAbandon();
+ method protected boolean onCancelLoad();
+ method public void onContentChanged();
+ method protected void onForceLoad();
+ method protected void onReset();
+ method protected void onStartLoading();
+ method protected void onStopLoading();
+ method public void registerListener(int, android.support.v4.content.Loader.OnLoadCompleteListener<D>);
+ method public void registerOnLoadCanceledListener(android.support.v4.content.Loader.OnLoadCanceledListener<D>);
+ method public void reset();
+ method public void rollbackContentChanged();
+ method public final void startLoading();
+ method public void stopLoading();
+ method public boolean takeContentChanged();
+ method public void unregisterListener(android.support.v4.content.Loader.OnLoadCompleteListener<D>);
+ method public void unregisterOnLoadCanceledListener(android.support.v4.content.Loader.OnLoadCanceledListener<D>);
+ }
+
+ public final class Loader.ForceLoadContentObserver extends android.database.ContentObserver {
+ ctor public Loader.ForceLoadContentObserver();
+ }
+
+ public static abstract interface Loader.OnLoadCanceledListener {
+ method public abstract void onLoadCanceled(android.support.v4.content.Loader<D>);
+ }
+
+ public static abstract interface Loader.OnLoadCompleteListener {
+ method public abstract void onLoadComplete(android.support.v4.content.Loader<D>, D);
+ }
+
+ public class LocalBroadcastManager {
+ method public static android.support.v4.content.LocalBroadcastManager getInstance(android.content.Context);
+ method public void registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
+ method public boolean sendBroadcast(android.content.Intent);
+ method public void sendBroadcastSync(android.content.Intent);
+ method public void unregisterReceiver(android.content.BroadcastReceiver);
+ }
+
+ public class ParallelExecutorCompat {
+ ctor public ParallelExecutorCompat();
+ method public static java.util.concurrent.Executor getParallelExecutor();
+ }
+
+ public final class PermissionChecker {
+ method public static int checkCallingOrSelfPermission(android.content.Context, java.lang.String);
+ method public static int checkCallingPermission(android.content.Context, java.lang.String, java.lang.String);
+ method public static int checkPermission(android.content.Context, java.lang.String, int, int, java.lang.String);
+ method public static int checkSelfPermission(android.content.Context, java.lang.String);
+ field public static final int PERMISSION_DENIED = -1; // 0xffffffff
+ field public static final int PERMISSION_DENIED_APP_OP = -2; // 0xfffffffe
+ field public static final int PERMISSION_GRANTED = 0; // 0x0
+ }
+
+ public static abstract class PermissionChecker.PermissionResult implements java.lang.annotation.Annotation {
+ }
+
+ public class SharedPreferencesCompat {
+ ctor public SharedPreferencesCompat();
+ }
+
+ public static class SharedPreferencesCompat.EditorCompat {
+ method public void apply(android.content.SharedPreferences.Editor);
+ method public static android.support.v4.content.SharedPreferencesCompat.EditorCompat getInstance();
+ }
+
+ public abstract class WakefulBroadcastReceiver extends android.content.BroadcastReceiver {
+ ctor public WakefulBroadcastReceiver();
+ method public static boolean completeWakefulIntent(android.content.Intent);
+ method public static android.content.ComponentName startWakefulService(android.content.Context, android.content.Intent);
+ }
+
+}
+
+package android.support.v4.content.pm {
+
+ public class ActivityInfoCompat {
+ field public static final int CONFIG_UI_MODE = 512; // 0x200
+ }
+
+}
+
+package android.support.v4.content.res {
+
+ public class ResourcesCompat {
+ ctor public ResourcesCompat();
+ method public int getColor(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
+ method public android.content.res.ColorStateList getColorStateList(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable getDrawable(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
+ method public static android.graphics.drawable.Drawable getDrawableForDensity(android.content.res.Resources, int, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
+ }
+
+}
+
+package android.support.v4.database {
+
+ public class DatabaseUtilsCompat {
+ method public static java.lang.String[] appendSelectionArgs(java.lang.String[], java.lang.String[]);
+ method public static java.lang.String concatenateWhere(java.lang.String, java.lang.String);
+ }
+
+}
+
+package android.support.v4.graphics {
+
+ public class BitmapCompat {
+ ctor public BitmapCompat();
+ method public static int getAllocationByteCount(android.graphics.Bitmap);
+ method public static boolean hasMipMap(android.graphics.Bitmap);
+ method public static void setHasMipMap(android.graphics.Bitmap, boolean);
+ }
+
+ public class ColorUtils {
+ method public static int HSLToColor(float[]);
+ method public static void RGBToHSL(int, int, int, float[]);
+ method public static double calculateContrast(int, int);
+ method public static double calculateLuminance(int);
+ method public static int calculateMinimumAlpha(int, int, float);
+ method public static void colorToHSL(int, float[]);
+ method public static int compositeColors(int, int);
+ method public static int setAlphaComponent(int, int);
+ }
+
+}
+
+package android.support.v4.graphics.drawable {
+
+ public class DrawableCompat {
+ ctor public DrawableCompat();
+ method public static int getLayoutDirection(android.graphics.drawable.Drawable);
+ method public static boolean isAutoMirrored(android.graphics.drawable.Drawable);
+ method public static void jumpToCurrentState(android.graphics.drawable.Drawable);
+ method public static void setAutoMirrored(android.graphics.drawable.Drawable, boolean);
+ method public static void setHotspot(android.graphics.drawable.Drawable, float, float);
+ method public static void setHotspotBounds(android.graphics.drawable.Drawable, int, int, int, int);
+ method public static void setLayoutDirection(android.graphics.drawable.Drawable, int);
+ method public static void setTint(android.graphics.drawable.Drawable, int);
+ method public static void setTintList(android.graphics.drawable.Drawable, android.content.res.ColorStateList);
+ method public static void setTintMode(android.graphics.drawable.Drawable, android.graphics.PorterDuff.Mode);
+ method public static T unwrap(android.graphics.drawable.Drawable);
+ method public static android.graphics.drawable.Drawable wrap(android.graphics.drawable.Drawable);
+ }
+
+ public abstract class RoundedBitmapDrawable extends android.graphics.drawable.Drawable {
+ method public void draw(android.graphics.Canvas);
+ method public final android.graphics.Bitmap getBitmap();
+ method public float getCornerRadius();
+ method public int getGravity();
+ method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
+ method public boolean hasAntiAlias();
+ method public boolean hasMipMap();
+ method public boolean isCircular();
+ method public void setAlpha(int);
+ method public void setAntiAlias(boolean);
+ method public void setCircular(boolean);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setCornerRadius(float);
+ method public void setGravity(int);
+ method public void setMipMap(boolean);
+ method public void setTargetDensity(android.graphics.Canvas);
+ method public void setTargetDensity(android.util.DisplayMetrics);
+ method public void setTargetDensity(int);
+ }
+
+ public class RoundedBitmapDrawableFactory {
+ ctor public RoundedBitmapDrawableFactory();
+ method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, android.graphics.Bitmap);
+ method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.lang.String);
+ method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.io.InputStream);
+ }
+
+}
+
+package android.support.v4.hardware.display {
+
+ public abstract class DisplayManagerCompat {
+ method public abstract android.view.Display getDisplay(int);
+ method public abstract android.view.Display[] getDisplays();
+ method public abstract android.view.Display[] getDisplays(java.lang.String);
+ method public static android.support.v4.hardware.display.DisplayManagerCompat getInstance(android.content.Context);
+ field public static final java.lang.String DISPLAY_CATEGORY_PRESENTATION = "android.hardware.display.category.PRESENTATION";
+ }
+
+}
+
+package android.support.v4.hardware.fingerprint {
+
+ public class FingerprintManagerCompat {
+ method public void authenticate(android.support.v4.hardware.fingerprint.FingerprintManagerCompat.CryptoObject, int, android.support.v4.os.CancellationSignal, android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler);
+ method public static android.support.v4.hardware.fingerprint.FingerprintManagerCompat from(android.content.Context);
+ method public boolean hasEnrolledFingerprints();
+ method public boolean isHardwareDetected();
+ }
+
+ public static abstract class FingerprintManagerCompat.AuthenticationCallback {
+ ctor public FingerprintManagerCompat.AuthenticationCallback();
+ method public void onAuthenticationError(int, java.lang.CharSequence);
+ method public void onAuthenticationFailed();
+ method public void onAuthenticationHelp(int, java.lang.CharSequence);
+ method public void onAuthenticationSucceeded(android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationResult);
+ }
+
+ public static final class FingerprintManagerCompat.AuthenticationResult {
+ ctor public FingerprintManagerCompat.AuthenticationResult(android.support.v4.hardware.fingerprint.FingerprintManagerCompat.CryptoObject);
+ method public android.support.v4.hardware.fingerprint.FingerprintManagerCompat.CryptoObject getCryptoObject();
+ }
+
+ public static class FingerprintManagerCompat.CryptoObject {
+ ctor public FingerprintManagerCompat.CryptoObject(java.security.Signature);
+ ctor public FingerprintManagerCompat.CryptoObject(javax.crypto.Cipher);
+ ctor public FingerprintManagerCompat.CryptoObject(javax.crypto.Mac);
+ method public javax.crypto.Cipher getCipher();
+ method public javax.crypto.Mac getMac();
+ method public java.security.Signature getSignature();
+ }
+
+}
+
+package android.support.v4.media {
+
+ public final class MediaDescriptionCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.MediaDescriptionCompat fromMediaDescription(java.lang.Object);
+ method public java.lang.CharSequence getDescription();
+ method public android.os.Bundle getExtras();
+ method public android.graphics.Bitmap getIconBitmap();
+ method public android.net.Uri getIconUri();
+ method public java.lang.Object getMediaDescription();
+ method public java.lang.String getMediaId();
+ method public android.net.Uri getMediaUri();
+ method public java.lang.CharSequence getSubtitle();
+ method public java.lang.CharSequence getTitle();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaDescriptionCompat> CREATOR;
+ }
+
+ public static final class MediaDescriptionCompat.Builder {
+ ctor public MediaDescriptionCompat.Builder();
+ method public android.support.v4.media.MediaDescriptionCompat build();
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setDescription(java.lang.CharSequence);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setExtras(android.os.Bundle);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setIconBitmap(android.graphics.Bitmap);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setIconUri(android.net.Uri);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setMediaId(java.lang.String);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setMediaUri(android.net.Uri);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setSubtitle(java.lang.CharSequence);
+ method public android.support.v4.media.MediaDescriptionCompat.Builder setTitle(java.lang.CharSequence);
+ }
+
+ public final class MediaMetadataCompat implements android.os.Parcelable {
+ method public boolean containsKey(java.lang.String);
+ method public int describeContents();
+ method public static android.support.v4.media.MediaMetadataCompat fromMediaMetadata(java.lang.Object);
+ method public android.graphics.Bitmap getBitmap(java.lang.String);
+ method public android.os.Bundle getBundle();
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public long getLong(java.lang.String);
+ method public java.lang.Object getMediaMetadata();
+ method public android.support.v4.media.RatingCompat getRating(java.lang.String);
+ method public java.lang.String getString(java.lang.String);
+ method public java.lang.CharSequence getText(java.lang.String);
+ method public java.util.Set<java.lang.String> keySet();
+ method public int size();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.MediaMetadataCompat> CREATOR;
+ field public static final java.lang.String METADATA_KEY_ALBUM = "android.media.metadata.ALBUM";
+ field public static final java.lang.String METADATA_KEY_ALBUM_ART = "android.media.metadata.ALBUM_ART";
+ field public static final java.lang.String METADATA_KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final java.lang.String METADATA_KEY_ALBUM_ART_URI = "android.media.metadata.ALBUM_ART_URI";
+ field public static final java.lang.String METADATA_KEY_ART = "android.media.metadata.ART";
+ field public static final java.lang.String METADATA_KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final java.lang.String METADATA_KEY_ART_URI = "android.media.metadata.ART_URI";
+ field public static final java.lang.String METADATA_KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final java.lang.String METADATA_KEY_COMPILATION = "android.media.metadata.COMPILATION";
+ field public static final java.lang.String METADATA_KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final java.lang.String METADATA_KEY_DATE = "android.media.metadata.DATE";
+ field public static final java.lang.String METADATA_KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_DESCRIPTION = "android.media.metadata.DISPLAY_DESCRIPTION";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_ICON = "android.media.metadata.DISPLAY_ICON";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_ICON_URI = "android.media.metadata.DISPLAY_ICON_URI";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_SUBTITLE = "android.media.metadata.DISPLAY_SUBTITLE";
+ field public static final java.lang.String METADATA_KEY_DISPLAY_TITLE = "android.media.metadata.DISPLAY_TITLE";
+ field public static final java.lang.String METADATA_KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final java.lang.String METADATA_KEY_GENRE = "android.media.metadata.GENRE";
+ field public static final java.lang.String METADATA_KEY_MEDIA_ID = "android.media.metadata.MEDIA_ID";
+ field public static final java.lang.String METADATA_KEY_NUM_TRACKS = "android.media.metadata.NUM_TRACKS";
+ field public static final java.lang.String METADATA_KEY_RATING = "android.media.metadata.RATING";
+ field public static final java.lang.String METADATA_KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final java.lang.String METADATA_KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final java.lang.String METADATA_KEY_USER_RATING = "android.media.metadata.USER_RATING";
+ field public static final java.lang.String METADATA_KEY_WRITER = "android.media.metadata.WRITER";
+ field public static final java.lang.String METADATA_KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public static final class MediaMetadataCompat.Builder {
+ ctor public MediaMetadataCompat.Builder();
+ ctor public MediaMetadataCompat.Builder(android.support.v4.media.MediaMetadataCompat);
+ method public android.support.v4.media.MediaMetadataCompat build();
+ method public android.support.v4.media.MediaMetadataCompat.Builder putBitmap(java.lang.String, android.graphics.Bitmap);
+ method public android.support.v4.media.MediaMetadataCompat.Builder putLong(java.lang.String, long);
+ method public android.support.v4.media.MediaMetadataCompat.Builder putRating(java.lang.String, android.support.v4.media.RatingCompat);
+ method public android.support.v4.media.MediaMetadataCompat.Builder putString(java.lang.String, java.lang.String);
+ method public android.support.v4.media.MediaMetadataCompat.Builder putText(java.lang.String, java.lang.CharSequence);
+ }
+
+ public final class RatingCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.RatingCompat fromRating(java.lang.Object);
+ method public float getPercentRating();
+ method public java.lang.Object getRating();
+ method public int getRatingStyle();
+ method public float getStarRating();
+ method public boolean hasHeart();
+ method public boolean isRated();
+ method public boolean isThumbUp();
+ method public static android.support.v4.media.RatingCompat newHeartRating(boolean);
+ method public static android.support.v4.media.RatingCompat newPercentageRating(float);
+ method public static android.support.v4.media.RatingCompat newStarRating(int, float);
+ method public static android.support.v4.media.RatingCompat newThumbRating(boolean);
+ method public static android.support.v4.media.RatingCompat newUnratedRating(int);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.RatingCompat> CREATOR;
+ field public static final int RATING_3_STARS = 3; // 0x3
+ field public static final int RATING_4_STARS = 4; // 0x4
+ field public static final int RATING_5_STARS = 5; // 0x5
+ field public static final int RATING_HEART = 1; // 0x1
+ field public static final int RATING_NONE = 0; // 0x0
+ field public static final int RATING_PERCENTAGE = 6; // 0x6
+ field public static final int RATING_THUMB_UP_DOWN = 2; // 0x2
+ }
+
+ public abstract class TransportController {
+ ctor public TransportController();
+ method public abstract int getBufferPercentage();
+ method public abstract long getCurrentPosition();
+ method public abstract long getDuration();
+ method public abstract int getTransportControlFlags();
+ method public abstract boolean isPlaying();
+ method public abstract void pausePlaying();
+ method public abstract void registerStateListener(android.support.v4.media.TransportStateListener);
+ method public abstract void seekTo(long);
+ method public abstract void startPlaying();
+ method public abstract void stopPlaying();
+ method public abstract void unregisterStateListener(android.support.v4.media.TransportStateListener);
+ }
+
+ public class TransportMediator extends android.support.v4.media.TransportController {
+ ctor public TransportMediator(android.app.Activity, android.support.v4.media.TransportPerformer);
+ ctor public TransportMediator(android.view.View, android.support.v4.media.TransportPerformer);
+ method public void destroy();
+ method public boolean dispatchKeyEvent(android.view.KeyEvent);
+ method public int getBufferPercentage();
+ method public long getCurrentPosition();
+ method public long getDuration();
+ method public java.lang.Object getRemoteControlClient();
+ method public int getTransportControlFlags();
+ method public boolean isPlaying();
+ method public void pausePlaying();
+ method public void refreshState();
+ method public void registerStateListener(android.support.v4.media.TransportStateListener);
+ method public void seekTo(long);
+ method public void startPlaying();
+ method public void stopPlaying();
+ method public void unregisterStateListener(android.support.v4.media.TransportStateListener);
+ field public static final int FLAG_KEY_MEDIA_FAST_FORWARD = 64; // 0x40
+ field public static final int FLAG_KEY_MEDIA_NEXT = 128; // 0x80
+ field public static final int FLAG_KEY_MEDIA_PAUSE = 16; // 0x10
+ field public static final int FLAG_KEY_MEDIA_PLAY = 4; // 0x4
+ field public static final int FLAG_KEY_MEDIA_PLAY_PAUSE = 8; // 0x8
+ field public static final int FLAG_KEY_MEDIA_PREVIOUS = 1; // 0x1
+ field public static final int FLAG_KEY_MEDIA_REWIND = 2; // 0x2
+ field public static final int FLAG_KEY_MEDIA_STOP = 32; // 0x20
+ field public static final int KEYCODE_MEDIA_PAUSE = 127; // 0x7f
+ field public static final int KEYCODE_MEDIA_PLAY = 126; // 0x7e
+ field public static final int KEYCODE_MEDIA_RECORD = 130; // 0x82
+ }
+
+ public abstract class TransportPerformer {
+ ctor public TransportPerformer();
+ method public void onAudioFocusChange(int);
+ method public int onGetBufferPercentage();
+ method public abstract long onGetCurrentPosition();
+ method public abstract long onGetDuration();
+ method public int onGetTransportControlFlags();
+ method public abstract boolean onIsPlaying();
+ method public boolean onMediaButtonDown(int, android.view.KeyEvent);
+ method public boolean onMediaButtonUp(int, android.view.KeyEvent);
+ method public abstract void onPause();
+ method public abstract void onSeekTo(long);
+ method public abstract void onStart();
+ method public abstract void onStop();
+ }
+
+ public class TransportStateListener {
+ ctor public TransportStateListener();
+ method public void onPlayingChanged(android.support.v4.media.TransportController);
+ method public void onTransportControlsChanged(android.support.v4.media.TransportController);
+ }
+
+ public abstract class VolumeProviderCompat {
+ ctor public VolumeProviderCompat(int, int, int);
+ method public final int getCurrentVolume();
+ method public final int getMaxVolume();
+ method public final int getVolumeControl();
+ method public java.lang.Object getVolumeProvider();
+ method public void onAdjustVolume(int);
+ method public void onSetVolumeTo(int);
+ method public void setCallback(android.support.v4.media.VolumeProviderCompat.Callback);
+ method public final void setCurrentVolume(int);
+ field public static final int VOLUME_CONTROL_ABSOLUTE = 2; // 0x2
+ field public static final int VOLUME_CONTROL_FIXED = 0; // 0x0
+ field public static final int VOLUME_CONTROL_RELATIVE = 1; // 0x1
+ }
+
+ public static abstract class VolumeProviderCompat.Callback {
+ ctor public VolumeProviderCompat.Callback();
+ method public abstract void onVolumeChanged(android.support.v4.media.VolumeProviderCompat);
+ }
+
+}
+
+package android.support.v4.media.session {
+
+ public class MediaButtonReceiver extends android.content.BroadcastReceiver {
+ ctor public MediaButtonReceiver();
+ method public static android.view.KeyEvent handleIntent(android.support.v4.media.session.MediaSessionCompat, android.content.Intent);
+ method public void onReceive(android.content.Context, android.content.Intent);
+ }
+
+ public final class MediaControllerCompat {
+ ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat);
+ ctor public MediaControllerCompat(android.content.Context, android.support.v4.media.session.MediaSessionCompat.Token) throws android.os.RemoteException;
+ method public void adjustVolume(int, int);
+ method public boolean dispatchMediaButtonEvent(android.view.KeyEvent);
+ method public android.os.Bundle getExtras();
+ method public long getFlags();
+ method public java.lang.Object getMediaController();
+ method public android.support.v4.media.MediaMetadataCompat getMetadata();
+ method public java.lang.String getPackageName();
+ method public android.support.v4.media.session.MediaControllerCompat.PlaybackInfo getPlaybackInfo();
+ method public android.support.v4.media.session.PlaybackStateCompat getPlaybackState();
+ method public java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem> getQueue();
+ method public java.lang.CharSequence getQueueTitle();
+ method public int getRatingType();
+ method public android.app.PendingIntent getSessionActivity();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public android.support.v4.media.session.MediaControllerCompat.TransportControls getTransportControls();
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ method public void registerCallback(android.support.v4.media.session.MediaControllerCompat.Callback, android.os.Handler);
+ method public void sendCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void setVolumeTo(int, int);
+ method public void unregisterCallback(android.support.v4.media.session.MediaControllerCompat.Callback);
+ }
+
+ public static abstract class MediaControllerCompat.Callback implements android.os.IBinder.DeathRecipient {
+ ctor public MediaControllerCompat.Callback();
+ method public void binderDied();
+ method public void onAudioInfoChanged(android.support.v4.media.session.MediaControllerCompat.PlaybackInfo);
+ method public void onExtrasChanged(android.os.Bundle);
+ method public void onMetadataChanged(android.support.v4.media.MediaMetadataCompat);
+ method public void onPlaybackStateChanged(android.support.v4.media.session.PlaybackStateCompat);
+ method public void onQueueChanged(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>);
+ method public void onQueueTitleChanged(java.lang.CharSequence);
+ method public void onSessionDestroyed();
+ method public void onSessionEvent(java.lang.String, android.os.Bundle);
+ }
+
+ public static final class MediaControllerCompat.PlaybackInfo {
+ method public int getAudioStream();
+ method public int getCurrentVolume();
+ method public int getMaxVolume();
+ method public int getPlaybackType();
+ method public int getVolumeControl();
+ field public static final int PLAYBACK_TYPE_LOCAL = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_REMOTE = 2; // 0x2
+ }
+
+ public static abstract class MediaControllerCompat.TransportControls {
+ method public abstract void fastForward();
+ method public abstract void pause();
+ method public abstract void play();
+ method public abstract void playFromMediaId(java.lang.String, android.os.Bundle);
+ method public abstract void playFromSearch(java.lang.String, android.os.Bundle);
+ method public abstract void playFromUri(android.net.Uri, android.os.Bundle);
+ method public abstract void rewind();
+ method public abstract void seekTo(long);
+ method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction, android.os.Bundle);
+ method public abstract void sendCustomAction(java.lang.String, android.os.Bundle);
+ method public abstract void setRating(android.support.v4.media.RatingCompat);
+ method public abstract void skipToNext();
+ method public abstract void skipToPrevious();
+ method public abstract void skipToQueueItem(long);
+ method public abstract void stop();
+ }
+
+ public class MediaSessionCompat {
+ ctor public MediaSessionCompat(android.content.Context, java.lang.String);
+ ctor public MediaSessionCompat(android.content.Context, java.lang.String, android.content.ComponentName, android.app.PendingIntent);
+ method public void addOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
+ method public android.support.v4.media.session.MediaControllerCompat getController();
+ method public java.lang.Object getMediaSession();
+ method public java.lang.Object getRemoteControlClient();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
+ method public boolean isActive();
+ method public static android.support.v4.media.session.MediaSessionCompat obtain(android.content.Context, java.lang.Object);
+ method public void release();
+ method public void removeOnActiveChangeListener(android.support.v4.media.session.MediaSessionCompat.OnActiveChangeListener);
+ method public void sendSessionEvent(java.lang.String, android.os.Bundle);
+ method public void setActive(boolean);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback);
+ method public void setCallback(android.support.v4.media.session.MediaSessionCompat.Callback, android.os.Handler);
+ method public void setExtras(android.os.Bundle);
+ method public void setFlags(int);
+ method public void setMediaButtonReceiver(android.app.PendingIntent);
+ method public void setMetadata(android.support.v4.media.MediaMetadataCompat);
+ method public void setPlaybackState(android.support.v4.media.session.PlaybackStateCompat);
+ method public void setPlaybackToLocal(int);
+ method public void setPlaybackToRemote(android.support.v4.media.VolumeProviderCompat);
+ method public void setQueue(java.util.List<android.support.v4.media.session.MediaSessionCompat.QueueItem>);
+ method public void setQueueTitle(java.lang.CharSequence);
+ method public void setRatingType(int);
+ method public void setSessionActivity(android.app.PendingIntent);
+ field public static final int FLAG_HANDLES_MEDIA_BUTTONS = 1; // 0x1
+ field public static final int FLAG_HANDLES_TRANSPORT_CONTROLS = 2; // 0x2
+ }
+
+ public static abstract class MediaSessionCompat.Callback {
+ ctor public MediaSessionCompat.Callback();
+ method public void onCommand(java.lang.String, android.os.Bundle, android.os.ResultReceiver);
+ method public void onCustomAction(java.lang.String, android.os.Bundle);
+ method public void onFastForward();
+ method public boolean onMediaButtonEvent(android.content.Intent);
+ method public void onPause();
+ method public void onPlay();
+ method public void onPlayFromMediaId(java.lang.String, android.os.Bundle);
+ method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
+ method public void onPlayFromUri(android.net.Uri, android.os.Bundle);
+ method public void onRewind();
+ method public void onSeekTo(long);
+ method public void onSetRating(android.support.v4.media.RatingCompat);
+ method public void onSkipToNext();
+ method public void onSkipToPrevious();
+ method public void onSkipToQueueItem(long);
+ method public void onStop();
+ }
+
+ public static abstract interface MediaSessionCompat.OnActiveChangeListener {
+ method public abstract void onActiveChanged();
+ }
+
+ public static final class MediaSessionCompat.QueueItem implements android.os.Parcelable {
+ ctor public MediaSessionCompat.QueueItem(android.support.v4.media.MediaDescriptionCompat, long);
+ method public int describeContents();
+ method public android.support.v4.media.MediaDescriptionCompat getDescription();
+ method public long getQueueId();
+ method public java.lang.Object getQueueItem();
+ method public static android.support.v4.media.session.MediaSessionCompat.QueueItem obtain(java.lang.Object);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.QueueItem> CREATOR;
+ field public static final int UNKNOWN_ID = -1; // 0xffffffff
+ }
+
+ public static final class MediaSessionCompat.Token implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.MediaSessionCompat.Token fromToken(java.lang.Object);
+ method public java.lang.Object getToken();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.MediaSessionCompat.Token> CREATOR;
+ }
+
+ public class ParcelableVolumeInfo implements android.os.Parcelable {
+ ctor public ParcelableVolumeInfo(int, int, int, int, int);
+ ctor public ParcelableVolumeInfo(android.os.Parcel);
+ method public int describeContents();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.ParcelableVolumeInfo> CREATOR;
+ field public int audioStream;
+ field public int controlType;
+ field public int currentVolume;
+ field public int maxVolume;
+ field public int volumeType;
+ }
+
+ public final class PlaybackStateCompat implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat fromPlaybackState(java.lang.Object);
+ method public long getActions();
+ method public long getActiveQueueItemId();
+ method public long getBufferedPosition();
+ method public java.util.List<android.support.v4.media.session.PlaybackStateCompat.CustomAction> getCustomActions();
+ method public java.lang.CharSequence getErrorMessage();
+ method public android.os.Bundle getExtras();
+ method public long getLastPositionUpdateTime();
+ method public float getPlaybackSpeed();
+ method public java.lang.Object getPlaybackState();
+ method public long getPosition();
+ method public int getState();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final long ACTION_FAST_FORWARD = 64L; // 0x40L
+ field public static final long ACTION_PAUSE = 2L; // 0x2L
+ field public static final long ACTION_PLAY = 4L; // 0x4L
+ field public static final long ACTION_PLAY_FROM_MEDIA_ID = 1024L; // 0x400L
+ field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
+ field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
+ field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_REWIND = 8L; // 0x8L
+ field public static final long ACTION_SEEK_TO = 256L; // 0x100L
+ field public static final long ACTION_SET_RATING = 128L; // 0x80L
+ field public static final long ACTION_SKIP_TO_NEXT = 32L; // 0x20L
+ field public static final long ACTION_SKIP_TO_PREVIOUS = 16L; // 0x10L
+ field public static final long ACTION_SKIP_TO_QUEUE_ITEM = 4096L; // 0x1000L
+ field public static final long ACTION_STOP = 1L; // 0x1L
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat> CREATOR;
+ field public static final long PLAYBACK_POSITION_UNKNOWN = -1L; // 0xffffffffffffffffL
+ field public static final int STATE_BUFFERING = 6; // 0x6
+ field public static final int STATE_CONNECTING = 8; // 0x8
+ field public static final int STATE_ERROR = 7; // 0x7
+ field public static final int STATE_FAST_FORWARDING = 4; // 0x4
+ field public static final int STATE_NONE = 0; // 0x0
+ field public static final int STATE_PAUSED = 2; // 0x2
+ field public static final int STATE_PLAYING = 3; // 0x3
+ field public static final int STATE_REWINDING = 5; // 0x5
+ field public static final int STATE_SKIPPING_TO_NEXT = 10; // 0xa
+ field public static final int STATE_SKIPPING_TO_PREVIOUS = 9; // 0x9
+ field public static final int STATE_SKIPPING_TO_QUEUE_ITEM = 11; // 0xb
+ field public static final int STATE_STOPPED = 1; // 0x1
+ }
+
+ public static final class PlaybackStateCompat.Builder {
+ ctor public PlaybackStateCompat.Builder();
+ ctor public PlaybackStateCompat.Builder(android.support.v4.media.session.PlaybackStateCompat);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder addCustomAction(java.lang.String, java.lang.String, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder addCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction);
+ method public android.support.v4.media.session.PlaybackStateCompat build();
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setActions(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setActiveQueueItemId(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setBufferedPosition(long);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setErrorMessage(java.lang.CharSequence);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setExtras(android.os.Bundle);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setState(int, long, float);
+ method public android.support.v4.media.session.PlaybackStateCompat.Builder setState(int, long, float, long);
+ }
+
+ public static final class PlaybackStateCompat.CustomAction implements android.os.Parcelable {
+ method public int describeContents();
+ method public static android.support.v4.media.session.PlaybackStateCompat.CustomAction fromCustomAction(java.lang.Object);
+ method public java.lang.String getAction();
+ method public java.lang.Object getCustomAction();
+ method public android.os.Bundle getExtras();
+ method public int getIcon();
+ method public java.lang.CharSequence getName();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.media.session.PlaybackStateCompat.CustomAction> CREATOR;
+ }
+
+ public static final class PlaybackStateCompat.CustomAction.Builder {
+ ctor public PlaybackStateCompat.CustomAction.Builder(java.lang.String, java.lang.CharSequence, int);
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction build();
+ method public android.support.v4.media.session.PlaybackStateCompat.CustomAction.Builder setExtras(android.os.Bundle);
+ }
+
+}
+
+package android.support.v4.net {
+
+ public class ConnectivityManagerCompat {
+ ctor public ConnectivityManagerCompat();
+ method public static android.net.NetworkInfo getNetworkInfoFromBroadcast(android.net.ConnectivityManager, android.content.Intent);
+ method public static boolean isActiveNetworkMetered(android.net.ConnectivityManager);
+ }
+
+ public class TrafficStatsCompat {
+ ctor public TrafficStatsCompat();
+ method public static void clearThreadStatsTag();
+ method public static int getThreadStatsTag();
+ method public static void incrementOperationCount(int);
+ method public static void incrementOperationCount(int, int);
+ method public static void setThreadStatsTag(int);
+ method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
+ method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
+ }
+
+}
+
+package android.support.v4.os {
+
+ public class AsyncTaskCompat {
+ ctor public AsyncTaskCompat();
+ method public static android.os.AsyncTask<Params, Progress, Result> executeParallel(android.os.AsyncTask<Params, Progress, Result>, Params...);
+ }
+
+ public final class CancellationSignal {
+ ctor public CancellationSignal();
+ method public void cancel();
+ method public java.lang.Object getCancellationSignalObject();
+ method public boolean isCanceled();
+ method public void setOnCancelListener(android.support.v4.os.CancellationSignal.OnCancelListener);
+ method public void throwIfCanceled();
+ }
+
+ public static abstract interface CancellationSignal.OnCancelListener {
+ method public abstract void onCancel();
+ }
+
+ public class EnvironmentCompat {
+ ctor public EnvironmentCompat();
+ method public static java.lang.String getStorageState(java.io.File);
+ field public static final java.lang.String MEDIA_UNKNOWN = "unknown";
+ }
+
+ public class OperationCanceledException extends java.lang.RuntimeException {
+ ctor public OperationCanceledException();
+ ctor public OperationCanceledException(java.lang.String);
+ }
+
+ public class ParcelableCompat {
+ ctor public ParcelableCompat();
+ method public static android.os.Parcelable.Creator<T> newCreator(android.support.v4.os.ParcelableCompatCreatorCallbacks<T>);
+ }
+
+ public abstract interface ParcelableCompatCreatorCallbacks {
+ method public abstract T createFromParcel(android.os.Parcel, java.lang.ClassLoader);
+ method public abstract T[] newArray(int);
+ }
+
+ public class TraceCompat {
+ ctor public TraceCompat();
+ method public static void beginSection(java.lang.String);
+ method public static void endSection();
+ }
+
+}
+
+package android.support.v4.print {
+
+ public final class PrintHelper {
+ ctor public PrintHelper(android.content.Context);
+ method public int getColorMode();
+ method public int getOrientation();
+ method public int getScaleMode();
+ method public void printBitmap(java.lang.String, android.graphics.Bitmap);
+ method public void printBitmap(java.lang.String, android.graphics.Bitmap, android.support.v4.print.PrintHelper.OnPrintFinishCallback);
+ method public void printBitmap(java.lang.String, android.net.Uri) throws java.io.FileNotFoundException;
+ method public void printBitmap(java.lang.String, android.net.Uri, android.support.v4.print.PrintHelper.OnPrintFinishCallback) throws java.io.FileNotFoundException;
+ method public void setColorMode(int);
+ method public void setOrientation(int);
+ method public void setScaleMode(int);
+ method public static boolean systemSupportsPrint();
+ field public static final int COLOR_MODE_COLOR = 2; // 0x2
+ field public static final int COLOR_MODE_MONOCHROME = 1; // 0x1
+ field public static final int ORIENTATION_LANDSCAPE = 1; // 0x1
+ field public static final int ORIENTATION_PORTRAIT = 2; // 0x2
+ field public static final int SCALE_MODE_FILL = 2; // 0x2
+ field public static final int SCALE_MODE_FIT = 1; // 0x1
+ }
+
+ public static abstract interface PrintHelper.OnPrintFinishCallback {
+ method public abstract void onFinish();
+ }
+
+}
+
+package android.support.v4.provider {
+
+ public abstract class DocumentFile {
+ method public abstract boolean canRead();
+ method public abstract boolean canWrite();
+ method public abstract android.support.v4.provider.DocumentFile createDirectory(java.lang.String);
+ method public abstract android.support.v4.provider.DocumentFile createFile(java.lang.String, java.lang.String);
+ method public abstract boolean delete();
+ method public abstract boolean exists();
+ method public android.support.v4.provider.DocumentFile findFile(java.lang.String);
+ method public static android.support.v4.provider.DocumentFile fromFile(java.io.File);
+ method public static android.support.v4.provider.DocumentFile fromSingleUri(android.content.Context, android.net.Uri);
+ method public static android.support.v4.provider.DocumentFile fromTreeUri(android.content.Context, android.net.Uri);
+ method public abstract java.lang.String getName();
+ method public android.support.v4.provider.DocumentFile getParentFile();
+ method public abstract java.lang.String getType();
+ method public abstract android.net.Uri getUri();
+ method public abstract boolean isDirectory();
+ method public static boolean isDocumentUri(android.content.Context, android.net.Uri);
+ method public abstract boolean isFile();
+ method public abstract long lastModified();
+ method public abstract long length();
+ method public abstract android.support.v4.provider.DocumentFile[] listFiles();
+ method public abstract boolean renameTo(java.lang.String);
+ }
+
+}
+
+package android.support.v4.text {
+
+ public final class BidiFormatter {
+ method public static android.support.v4.text.BidiFormatter getInstance();
+ method public static android.support.v4.text.BidiFormatter getInstance(boolean);
+ method public static android.support.v4.text.BidiFormatter getInstance(java.util.Locale);
+ method public boolean getStereoReset();
+ method public boolean isRtl(java.lang.String);
+ method public boolean isRtlContext();
+ method public java.lang.String unicodeWrap(java.lang.String, android.support.v4.text.TextDirectionHeuristicCompat, boolean);
+ method public java.lang.String unicodeWrap(java.lang.String, android.support.v4.text.TextDirectionHeuristicCompat);
+ method public java.lang.String unicodeWrap(java.lang.String, boolean);
+ method public java.lang.String unicodeWrap(java.lang.String);
+ }
+
+ public static final class BidiFormatter.Builder {
+ ctor public BidiFormatter.Builder();
+ ctor public BidiFormatter.Builder(boolean);
+ ctor public BidiFormatter.Builder(java.util.Locale);
+ method public android.support.v4.text.BidiFormatter build();
+ method public android.support.v4.text.BidiFormatter.Builder setTextDirectionHeuristic(android.support.v4.text.TextDirectionHeuristicCompat);
+ method public android.support.v4.text.BidiFormatter.Builder stereoReset(boolean);
+ }
+
+ public class ICUCompat {
+ ctor public ICUCompat();
+ method public static java.lang.String maximizeAndGetScript(java.util.Locale);
+ }
+
+ public abstract interface TextDirectionHeuristicCompat {
+ method public abstract boolean isRtl(char[], int, int);
+ method public abstract boolean isRtl(java.lang.CharSequence, int, int);
+ }
+
+ public class TextDirectionHeuristicsCompat {
+ ctor public TextDirectionHeuristicsCompat();
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat ANYRTL_LTR;
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat FIRSTSTRONG_LTR;
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat FIRSTSTRONG_RTL;
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat LOCALE;
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat LTR;
+ field public static final android.support.v4.text.TextDirectionHeuristicCompat RTL;
+ }
+
+ public class TextUtilsCompat {
+ ctor public TextUtilsCompat();
+ method public static int getLayoutDirectionFromLocale(java.util.Locale);
+ method public static java.lang.String htmlEncode(java.lang.String);
+ field public static final java.util.Locale ROOT;
+ }
+
+}
+
+package android.support.v4.util {
+
+ public class ArrayMap extends android.support.v4.util.SimpleArrayMap implements java.util.Map {
+ ctor public ArrayMap();
+ ctor public ArrayMap(int);
+ ctor public ArrayMap(android.support.v4.util.SimpleArrayMap);
+ method public boolean containsAll(java.util.Collection<?>);
+ method public java.util.Set<java.util.Map.Entry<K, V>> entrySet();
+ method public java.util.Set<K> keySet();
+ method public void putAll(java.util.Map<? extends K, ? extends V>);
+ method public boolean removeAll(java.util.Collection<?>);
+ method public boolean retainAll(java.util.Collection<?>);
+ method public java.util.Collection<V> values();
+ }
+
+ public class AtomicFile {
+ ctor public AtomicFile(java.io.File);
+ method public void delete();
+ method public void failWrite(java.io.FileOutputStream);
+ method public void finishWrite(java.io.FileOutputStream);
+ method public java.io.File getBaseFile();
+ method public java.io.FileInputStream openRead() throws java.io.FileNotFoundException;
+ method public byte[] readFully() throws java.io.IOException;
+ method public java.io.FileOutputStream startWrite() throws java.io.IOException;
+ }
+
+ public final class CircularArray {
+ ctor public CircularArray();
+ ctor public CircularArray(int);
+ method public void addFirst(E);
+ method public void addLast(E);
+ method public void clear();
+ method public E get(int);
+ method public E getFirst();
+ method public E getLast();
+ method public boolean isEmpty();
+ method public E popFirst();
+ method public E popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public final class CircularIntArray {
+ ctor public CircularIntArray();
+ ctor public CircularIntArray(int);
+ method public void addFirst(int);
+ method public void addLast(int);
+ method public void clear();
+ method public int get(int);
+ method public int getFirst();
+ method public int getLast();
+ method public boolean isEmpty();
+ method public int popFirst();
+ method public int popLast();
+ method public void removeFromEnd(int);
+ method public void removeFromStart(int);
+ method public int size();
+ }
+
+ public class LongSparseArray {
+ ctor public LongSparseArray();
+ ctor public LongSparseArray(int);
+ method public void append(long, E);
+ method public void clear();
+ method public android.support.v4.util.LongSparseArray<E> clone();
+ method public void delete(long);
+ method public E get(long);
+ method public E get(long, E);
+ method public int indexOfKey(long);
+ method public int indexOfValue(E);
+ method public long keyAt(int);
+ method public void put(long, E);
+ method public void remove(long);
+ method public void removeAt(int);
+ method public void setValueAt(int, E);
+ method public int size();
+ method public E valueAt(int);
+ }
+
+ public class LruCache {
+ ctor public LruCache(int);
+ method protected V create(K);
+ method public final synchronized int createCount();
+ method protected void entryRemoved(boolean, K, V, V);
+ method public final void evictAll();
+ method public final synchronized int evictionCount();
+ method public final V get(K);
+ method public final synchronized int hitCount();
+ method public final synchronized int maxSize();
+ method public final synchronized int missCount();
+ method public final V put(K, V);
+ method public final synchronized int putCount();
+ method public final V remove(K);
+ method public void resize(int);
+ method public final synchronized int size();
+ method protected int sizeOf(K, V);
+ method public final synchronized java.util.Map<K, V> snapshot();
+ method public final synchronized java.lang.String toString();
+ method public void trimToSize(int);
+ }
+
+ public class Pair {
+ ctor public Pair(F, S);
+ method public static android.support.v4.util.Pair<A, B> create(A, B);
+ field public final F first;
+ field public final S second;
+ }
+
+ public final class Pools {
+ }
+
+ public static abstract interface Pools.Pool {
+ method public abstract T acquire();
+ method public abstract boolean release(T);
+ }
+
+ public static class Pools.SimplePool implements android.support.v4.util.Pools.Pool {
+ ctor public Pools.SimplePool(int);
+ method public T acquire();
+ method public boolean release(T);
+ }
+
+ public static class Pools.SynchronizedPool extends android.support.v4.util.Pools.SimplePool {
+ ctor public Pools.SynchronizedPool(int);
+ }
+
+ public class SimpleArrayMap {
+ ctor public SimpleArrayMap();
+ ctor public SimpleArrayMap(int);
+ ctor public SimpleArrayMap(android.support.v4.util.SimpleArrayMap);
+ method public void clear();
+ method public boolean containsKey(java.lang.Object);
+ method public boolean containsValue(java.lang.Object);
+ method public void ensureCapacity(int);
+ method public V get(java.lang.Object);
+ method public int indexOfKey(java.lang.Object);
+ method public boolean isEmpty();
+ method public K keyAt(int);
+ method public V put(K, V);
+ method public void putAll(android.support.v4.util.SimpleArrayMap<? extends K, ? extends V>);
+ method public V remove(java.lang.Object);
+ method public V removeAt(int);
+ method public V setValueAt(int, V);
+ method public int size();
+ method public V valueAt(int);
+ }
+
+ public class SparseArrayCompat {
+ ctor public SparseArrayCompat();
+ ctor public SparseArrayCompat(int);
+ method public void append(int, E);
+ method public void clear();
+ method public android.support.v4.util.SparseArrayCompat<E> clone();
+ method public void delete(int);
+ method public E get(int);
+ method public E get(int, E);
+ method public int indexOfKey(int);
+ method public int indexOfValue(E);
+ method public int keyAt(int);
+ method public void put(int, E);
+ method public void remove(int);
+ method public void removeAt(int);
+ method public void removeAtRange(int, int);
+ method public void setValueAt(int, E);
+ method public int size();
+ method public E valueAt(int);
+ }
+
+}
+
+package android.support.v4.view {
+
+ public class AccessibilityDelegateCompat {
+ ctor public AccessibilityDelegateCompat();
+ method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public android.support.v4.view.accessibility.AccessibilityNodeProviderCompat getAccessibilityNodeProvider(android.view.View);
+ method public void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(android.view.View, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean onRequestSendAccessibilityEvent(android.view.ViewGroup, android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public boolean performAccessibilityAction(android.view.View, int, android.os.Bundle);
+ method public void sendAccessibilityEvent(android.view.View, int);
+ method public void sendAccessibilityEventUnchecked(android.view.View, android.view.accessibility.AccessibilityEvent);
+ }
+
+ public abstract class ActionProvider {
+ ctor public ActionProvider(android.content.Context);
+ method public android.content.Context getContext();
+ method public boolean hasSubMenu();
+ method public boolean isVisible();
+ method public abstract android.view.View onCreateActionView();
+ method public android.view.View onCreateActionView(android.view.MenuItem);
+ method public boolean onPerformDefaultAction();
+ method public void onPrepareSubMenu(android.view.SubMenu);
+ method public boolean overridesItemVisibility();
+ method public void refreshVisibility();
+ method public void setVisibilityListener(android.support.v4.view.ActionProvider.VisibilityListener);
+ }
+
+ public static abstract interface ActionProvider.VisibilityListener {
+ method public abstract void onActionProviderVisibilityChanged(boolean);
+ }
+
+ public class GestureDetectorCompat {
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
+ ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler);
+ method public boolean isLongpressEnabled();
+ method public boolean onTouchEvent(android.view.MotionEvent);
+ method public void setIsLongpressEnabled(boolean);
+ method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener);
+ }
+
+ public class GravityCompat {
+ ctor public GravityCompat();
+ method public static void apply(int, int, int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static void apply(int, int, int, android.graphics.Rect, int, int, android.graphics.Rect, int);
+ method public static void applyDisplay(int, android.graphics.Rect, android.graphics.Rect, int);
+ method public static int getAbsoluteGravity(int, int);
+ field public static final int END = 8388613; // 0x800005
+ field public static final int RELATIVE_HORIZONTAL_GRAVITY_MASK = 8388615; // 0x800007
+ field public static final int RELATIVE_LAYOUT_DIRECTION = 8388608; // 0x800000
+ field public static final int START = 8388611; // 0x800003
+ }
+
+ public class InputDeviceCompat {
+ ctor public InputDeviceCompat();
+ field public static final int SOURCE_ANY = -256; // 0xffffff00
+ field public static final int SOURCE_CLASS_BUTTON = 1; // 0x1
+ field public static final int SOURCE_CLASS_JOYSTICK = 16; // 0x10
+ field public static final int SOURCE_CLASS_MASK = 255; // 0xff
+ field public static final int SOURCE_CLASS_NONE = 0; // 0x0
+ field public static final int SOURCE_CLASS_POINTER = 2; // 0x2
+ field public static final int SOURCE_CLASS_POSITION = 8; // 0x8
+ field public static final int SOURCE_CLASS_TRACKBALL = 4; // 0x4
+ field public static final int SOURCE_DPAD = 513; // 0x201
+ field public static final int SOURCE_GAMEPAD = 1025; // 0x401
+ field public static final int SOURCE_HDMI = 33554433; // 0x2000001
+ field public static final int SOURCE_JOYSTICK = 16777232; // 0x1000010
+ field public static final int SOURCE_KEYBOARD = 257; // 0x101
+ field public static final int SOURCE_MOUSE = 8194; // 0x2002
+ field public static final int SOURCE_STYLUS = 16386; // 0x4002
+ field public static final int SOURCE_TOUCHPAD = 1048584; // 0x100008
+ field public static final int SOURCE_TOUCHSCREEN = 4098; // 0x1002
+ field public static final int SOURCE_TOUCH_NAVIGATION = 2097152; // 0x200000
+ field public static final int SOURCE_TRACKBALL = 65540; // 0x10004
+ field public static final int SOURCE_UNKNOWN = 0; // 0x0
+ }
+
+ public class KeyEventCompat {
+ ctor public KeyEventCompat();
+ method public static boolean dispatch(android.view.KeyEvent, android.view.KeyEvent.Callback, java.lang.Object, java.lang.Object);
+ method public static java.lang.Object getKeyDispatcherState(android.view.View);
+ method public static boolean hasModifiers(android.view.KeyEvent, int);
+ method public static boolean hasNoModifiers(android.view.KeyEvent);
+ method public static boolean isTracking(android.view.KeyEvent);
+ method public static boolean metaStateHasModifiers(int, int);
+ method public static boolean metaStateHasNoModifiers(int);
+ method public static int normalizeMetaState(int);
+ method public static void startTracking(android.view.KeyEvent);
+ }
+
+ public class LayoutInflaterCompat {
+ method public static void setFactory(android.view.LayoutInflater, android.support.v4.view.LayoutInflaterFactory);
+ }
+
+ public abstract interface LayoutInflaterFactory {
+ method public abstract android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ }
+
+ public class MarginLayoutParamsCompat {
+ ctor public MarginLayoutParamsCompat();
+ method public static int getLayoutDirection(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginEnd(android.view.ViewGroup.MarginLayoutParams);
+ method public static int getMarginStart(android.view.ViewGroup.MarginLayoutParams);
+ method public static boolean isMarginRelative(android.view.ViewGroup.MarginLayoutParams);
+ method public static void resolveLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setLayoutDirection(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginEnd(android.view.ViewGroup.MarginLayoutParams, int);
+ method public static void setMarginStart(android.view.ViewGroup.MarginLayoutParams, int);
+ }
+
+ public class MenuCompat {
+ ctor public MenuCompat();
+ method public static deprecated void setShowAsAction(android.view.MenuItem, int);
+ }
+
+ public class MenuItemCompat {
+ ctor public MenuItemCompat();
+ method public static boolean collapseActionView(android.view.MenuItem);
+ method public static boolean expandActionView(android.view.MenuItem);
+ method public static android.support.v4.view.ActionProvider getActionProvider(android.view.MenuItem);
+ method public static android.view.View getActionView(android.view.MenuItem);
+ method public static boolean isActionViewExpanded(android.view.MenuItem);
+ method public static android.view.MenuItem setActionProvider(android.view.MenuItem, android.support.v4.view.ActionProvider);
+ method public static android.view.MenuItem setActionView(android.view.MenuItem, android.view.View);
+ method public static android.view.MenuItem setActionView(android.view.MenuItem, int);
+ method public static android.view.MenuItem setOnActionExpandListener(android.view.MenuItem, android.support.v4.view.MenuItemCompat.OnActionExpandListener);
+ method public static void setShowAsAction(android.view.MenuItem, int);
+ field public static final int SHOW_AS_ACTION_ALWAYS = 2; // 0x2
+ field public static final int SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW = 8; // 0x8
+ field public static final int SHOW_AS_ACTION_IF_ROOM = 1; // 0x1
+ field public static final int SHOW_AS_ACTION_NEVER = 0; // 0x0
+ field public static final int SHOW_AS_ACTION_WITH_TEXT = 4; // 0x4
+ }
+
+ public static abstract interface MenuItemCompat.OnActionExpandListener {
+ method public abstract boolean onMenuItemActionCollapse(android.view.MenuItem);
+ method public abstract boolean onMenuItemActionExpand(android.view.MenuItem);
+ }
+
+ public class MotionEventCompat {
+ ctor public MotionEventCompat();
+ method public static int findPointerIndex(android.view.MotionEvent, int);
+ method public static int getActionIndex(android.view.MotionEvent);
+ method public static int getActionMasked(android.view.MotionEvent);
+ method public static float getAxisValue(android.view.MotionEvent, int);
+ method public static float getAxisValue(android.view.MotionEvent, int, int);
+ method public static int getPointerCount(android.view.MotionEvent);
+ method public static int getPointerId(android.view.MotionEvent, int);
+ method public static int getSource(android.view.MotionEvent);
+ method public static float getX(android.view.MotionEvent, int);
+ method public static float getY(android.view.MotionEvent, int);
+ field public static final int ACTION_HOVER_ENTER = 9; // 0x9
+ field public static final int ACTION_HOVER_EXIT = 10; // 0xa
+ field public static final int ACTION_HOVER_MOVE = 7; // 0x7
+ field public static final int ACTION_MASK = 255; // 0xff
+ field public static final int ACTION_POINTER_DOWN = 5; // 0x5
+ field public static final int ACTION_POINTER_INDEX_MASK = 65280; // 0xff00
+ field public static final int ACTION_POINTER_INDEX_SHIFT = 8; // 0x8
+ field public static final int ACTION_POINTER_UP = 6; // 0x6
+ field public static final int ACTION_SCROLL = 8; // 0x8
+ field public static final int AXIS_BRAKE = 23; // 0x17
+ field public static final int AXIS_DISTANCE = 24; // 0x18
+ field public static final int AXIS_GAS = 22; // 0x16
+ field public static final int AXIS_GENERIC_1 = 32; // 0x20
+ field public static final int AXIS_GENERIC_10 = 41; // 0x29
+ field public static final int AXIS_GENERIC_11 = 42; // 0x2a
+ field public static final int AXIS_GENERIC_12 = 43; // 0x2b
+ field public static final int AXIS_GENERIC_13 = 44; // 0x2c
+ field public static final int AXIS_GENERIC_14 = 45; // 0x2d
+ field public static final int AXIS_GENERIC_15 = 46; // 0x2e
+ field public static final int AXIS_GENERIC_16 = 47; // 0x2f
+ field public static final int AXIS_GENERIC_2 = 33; // 0x21
+ field public static final int AXIS_GENERIC_3 = 34; // 0x22
+ field public static final int AXIS_GENERIC_4 = 35; // 0x23
+ field public static final int AXIS_GENERIC_5 = 36; // 0x24
+ field public static final int AXIS_GENERIC_6 = 37; // 0x25
+ field public static final int AXIS_GENERIC_7 = 38; // 0x26
+ field public static final int AXIS_GENERIC_8 = 39; // 0x27
+ field public static final int AXIS_GENERIC_9 = 40; // 0x28
+ field public static final int AXIS_HAT_X = 15; // 0xf
+ field public static final int AXIS_HAT_Y = 16; // 0x10
+ field public static final int AXIS_HSCROLL = 10; // 0xa
+ field public static final int AXIS_LTRIGGER = 17; // 0x11
+ field public static final int AXIS_ORIENTATION = 8; // 0x8
+ field public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RTRIGGER = 18; // 0x12
+ field public static final int AXIS_RUDDER = 20; // 0x14
+ field public static final int AXIS_RX = 12; // 0xc
+ field public static final int AXIS_RY = 13; // 0xd
+ field public static final int AXIS_RZ = 14; // 0xe
+ field public static final int AXIS_SIZE = 3; // 0x3
+ field public static final int AXIS_THROTTLE = 19; // 0x13
+ field public static final int AXIS_TILT = 25; // 0x19
+ field public static final int AXIS_TOOL_MAJOR = 6; // 0x6
+ field public static final int AXIS_TOOL_MINOR = 7; // 0x7
+ field public static final int AXIS_TOUCH_MAJOR = 4; // 0x4
+ field public static final int AXIS_TOUCH_MINOR = 5; // 0x5
+ field public static final int AXIS_VSCROLL = 9; // 0x9
+ field public static final int AXIS_WHEEL = 21; // 0x15
+ field public static final int AXIS_X = 0; // 0x0
+ field public static final int AXIS_Y = 1; // 0x1
+ field public static final int AXIS_Z = 11; // 0xb
+ }
+
+ public abstract interface NestedScrollingChild {
+ method public abstract boolean dispatchNestedFling(float, float, boolean);
+ method public abstract boolean dispatchNestedPreFling(float, float);
+ method public abstract boolean dispatchNestedPreScroll(int, int, int[], int[]);
+ method public abstract boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public abstract boolean hasNestedScrollingParent();
+ method public abstract boolean isNestedScrollingEnabled();
+ method public abstract void setNestedScrollingEnabled(boolean);
+ method public abstract boolean startNestedScroll(int);
+ method public abstract void stopNestedScroll();
+ }
+
+ public class NestedScrollingChildHelper {
+ ctor public NestedScrollingChildHelper(android.view.View);
+ method public boolean dispatchNestedFling(float, float, boolean);
+ method public boolean dispatchNestedPreFling(float, float);
+ method public boolean dispatchNestedPreScroll(int, int, int[], int[]);
+ method public boolean dispatchNestedScroll(int, int, int, int, int[]);
+ method public boolean hasNestedScrollingParent();
+ method public boolean isNestedScrollingEnabled();
+ method public void onDetachedFromWindow();
+ method public void onStopNestedScroll(android.view.View);
+ method public void setNestedScrollingEnabled(boolean);
+ method public boolean startNestedScroll(int);
+ method public void stopNestedScroll();
+ }
+
+ public abstract interface NestedScrollingParent {
+ method public abstract int getNestedScrollAxes();
+ method public abstract boolean onNestedFling(android.view.View, float, float, boolean);
+ method public abstract boolean onNestedPreFling(android.view.View, float, float);
+ method public abstract void onNestedPreScroll(android.view.View, int, int, int[]);
+ method public abstract void onNestedScroll(android.view.View, int, int, int, int);
+ method public abstract void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public abstract boolean onStartNestedScroll(android.view.View, android.view.View, int);
+ method public abstract void onStopNestedScroll(android.view.View);
+ }
+
+ public class NestedScrollingParentHelper {
+ ctor public NestedScrollingParentHelper(android.view.ViewGroup);
+ method public int getNestedScrollAxes();
+ method public void onNestedScrollAccepted(android.view.View, android.view.View, int);
+ method public void onStopNestedScroll(android.view.View);
+ }
+
+ public abstract interface OnApplyWindowInsetsListener {
+ method public abstract android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, android.support.v4.view.WindowInsetsCompat);
+ }
+
+ public abstract class PagerAdapter {
+ ctor public PagerAdapter();
+ method public void destroyItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void destroyItem(android.view.View, int, java.lang.Object);
+ method public void finishUpdate(android.view.ViewGroup);
+ method public deprecated void finishUpdate(android.view.View);
+ method public abstract int getCount();
+ method public int getItemPosition(java.lang.Object);
+ method public java.lang.CharSequence getPageTitle(int);
+ method public float getPageWidth(int);
+ method public java.lang.Object instantiateItem(android.view.ViewGroup, int);
+ method public deprecated java.lang.Object instantiateItem(android.view.View, int);
+ method public abstract boolean isViewFromObject(android.view.View, java.lang.Object);
+ method public void notifyDataSetChanged();
+ method public void registerDataSetObserver(android.database.DataSetObserver);
+ method public void restoreState(android.os.Parcelable, java.lang.ClassLoader);
+ method public android.os.Parcelable saveState();
+ method public void setPrimaryItem(android.view.ViewGroup, int, java.lang.Object);
+ method public deprecated void setPrimaryItem(android.view.View, int, java.lang.Object);
+ method public void startUpdate(android.view.ViewGroup);
+ method public deprecated void startUpdate(android.view.View);
+ method public void unregisterDataSetObserver(android.database.DataSetObserver);
+ field public static final int POSITION_NONE = -2; // 0xfffffffe
+ field public static final int POSITION_UNCHANGED = -1; // 0xffffffff
+ }
+
+ public class PagerTabStrip extends android.support.v4.view.PagerTitleStrip {
+ ctor public PagerTabStrip(android.content.Context);
+ ctor public PagerTabStrip(android.content.Context, android.util.AttributeSet);
+ method public boolean getDrawFullUnderline();
+ method public int getTabIndicatorColor();
+ method public void setDrawFullUnderline(boolean);
+ method public void setTabIndicatorColor(int);
+ method public void setTabIndicatorColorResource(int);
+ }
+
+ public class PagerTitleStrip extends android.view.ViewGroup {
+ ctor public PagerTitleStrip(android.content.Context);
+ ctor public PagerTitleStrip(android.content.Context, android.util.AttributeSet);
+ method public int getTextSpacing();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setGravity(int);
+ method public void setNonPrimaryAlpha(float);
+ method public void setTextColor(int);
+ method public void setTextSize(int, float);
+ method public void setTextSpacing(int);
+ }
+
+ public class ScaleGestureDetectorCompat {
+ method public static boolean isQuickScaleEnabled(java.lang.Object);
+ method public static void setQuickScaleEnabled(java.lang.Object, boolean);
+ }
+
+ public abstract interface ScrollingView {
+ method public abstract int computeHorizontalScrollExtent();
+ method public abstract int computeHorizontalScrollOffset();
+ method public abstract int computeHorizontalScrollRange();
+ method public abstract int computeVerticalScrollExtent();
+ method public abstract int computeVerticalScrollOffset();
+ method public abstract int computeVerticalScrollRange();
+ }
+
+ public abstract interface TintableBackgroundView {
+ method public abstract android.content.res.ColorStateList getSupportBackgroundTintList();
+ method public abstract android.graphics.PorterDuff.Mode getSupportBackgroundTintMode();
+ method public abstract void setSupportBackgroundTintList(android.content.res.ColorStateList);
+ method public abstract void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
+ }
+
+ public class VelocityTrackerCompat {
+ ctor public VelocityTrackerCompat();
+ method public static float getXVelocity(android.view.VelocityTracker, int);
+ method public static float getYVelocity(android.view.VelocityTracker, int);
+ }
+
+ public class ViewCompat {
+ ctor public ViewCompat();
+ method public static android.support.v4.view.ViewPropertyAnimatorCompat animate(android.view.View);
+ method public static boolean canScrollHorizontally(android.view.View, int);
+ method public static boolean canScrollVertically(android.view.View, int);
+ method public static int combineMeasuredStates(int, int);
+ method public static android.support.v4.view.WindowInsetsCompat dispatchApplyWindowInsets(android.view.View, android.support.v4.view.WindowInsetsCompat);
+ method public static void dispatchFinishTemporaryDetach(android.view.View);
+ method public static boolean dispatchNestedFling(android.view.View, float, float, boolean);
+ method public static boolean dispatchNestedPreFling(android.view.View, float, float);
+ method public static boolean dispatchNestedPreScroll(android.view.View, int, int, int[], int[]);
+ method public static boolean dispatchNestedScroll(android.view.View, int, int, int, int, int[]);
+ method public static void dispatchStartTemporaryDetach(android.view.View);
+ method public static int getAccessibilityLiveRegion(android.view.View);
+ method public static android.support.v4.view.accessibility.AccessibilityNodeProviderCompat getAccessibilityNodeProvider(android.view.View);
+ method public static float getAlpha(android.view.View);
+ method public static android.content.res.ColorStateList getBackgroundTintList(android.view.View);
+ method public static android.graphics.PorterDuff.Mode getBackgroundTintMode(android.view.View);
+ method public static android.graphics.Rect getClipBounds(android.view.View);
+ method public static float getElevation(android.view.View);
+ method public static boolean getFitsSystemWindows(android.view.View);
+ method public static int getImportantForAccessibility(android.view.View);
+ method public static int getLabelFor(android.view.View);
+ method public static int getLayerType(android.view.View);
+ method public static int getLayoutDirection(android.view.View);
+ method public static int getMeasuredHeightAndState(android.view.View);
+ method public static int getMeasuredState(android.view.View);
+ method public static int getMeasuredWidthAndState(android.view.View);
+ method public static int getMinimumHeight(android.view.View);
+ method public static int getMinimumWidth(android.view.View);
+ method public static int getOverScrollMode(android.view.View);
+ method public static int getPaddingEnd(android.view.View);
+ method public static int getPaddingStart(android.view.View);
+ method public static android.view.ViewParent getParentForAccessibility(android.view.View);
+ method public static float getPivotX(android.view.View);
+ method public static float getPivotY(android.view.View);
+ method public static float getRotation(android.view.View);
+ method public static float getRotationX(android.view.View);
+ method public static float getRotationY(android.view.View);
+ method public static float getScaleX(android.view.View);
+ method public static float getScaleY(android.view.View);
+ method public static int getScrollIndicators(android.view.View);
+ method public static java.lang.String getTransitionName(android.view.View);
+ method public static float getTranslationX(android.view.View);
+ method public static float getTranslationY(android.view.View);
+ method public static float getTranslationZ(android.view.View);
+ method public static int getWindowSystemUiVisibility(android.view.View);
+ method public static float getX(android.view.View);
+ method public static float getY(android.view.View);
+ method public static float getZ(android.view.View);
+ method public static boolean hasAccessibilityDelegate(android.view.View);
+ method public static boolean hasNestedScrollingParent(android.view.View);
+ method public static boolean hasOnClickListeners(android.view.View);
+ method public static boolean hasOverlappingRendering(android.view.View);
+ method public static boolean hasTransientState(android.view.View);
+ method public static boolean isAttachedToWindow(android.view.View);
+ method public static boolean isLaidOut(android.view.View);
+ method public static boolean isNestedScrollingEnabled(android.view.View);
+ method public static boolean isOpaque(android.view.View);
+ method public static boolean isPaddingRelative(android.view.View);
+ method public static void jumpDrawablesToCurrentState(android.view.View);
+ method public static void offsetLeftAndRight(android.view.View, int);
+ method public static void offsetTopAndBottom(android.view.View, int);
+ method public static android.support.v4.view.WindowInsetsCompat onApplyWindowInsets(android.view.View, android.support.v4.view.WindowInsetsCompat);
+ method public static void onInitializeAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public static void onInitializeAccessibilityNodeInfo(android.view.View, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public static void onPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public static boolean performAccessibilityAction(android.view.View, int, android.os.Bundle);
+ method public static void postInvalidateOnAnimation(android.view.View);
+ method public static void postInvalidateOnAnimation(android.view.View, int, int, int, int);
+ method public static void postOnAnimation(android.view.View, java.lang.Runnable);
+ method public static void postOnAnimationDelayed(android.view.View, java.lang.Runnable, long);
+ method public static void requestApplyInsets(android.view.View);
+ method public static int resolveSizeAndState(int, int, int);
+ method public static void setAccessibilityDelegate(android.view.View, android.support.v4.view.AccessibilityDelegateCompat);
+ method public static void setAccessibilityLiveRegion(android.view.View, int);
+ method public static void setActivated(android.view.View, boolean);
+ method public static void setAlpha(android.view.View, float);
+ method public static void setBackgroundTintList(android.view.View, android.content.res.ColorStateList);
+ method public static void setBackgroundTintMode(android.view.View, android.graphics.PorterDuff.Mode);
+ method public static void setChildrenDrawingOrderEnabled(android.view.ViewGroup, boolean);
+ method public static void setClipBounds(android.view.View, android.graphics.Rect);
+ method public static void setElevation(android.view.View, float);
+ method public static void setFitsSystemWindows(android.view.View, boolean);
+ method public static void setHasTransientState(android.view.View, boolean);
+ method public static void setImportantForAccessibility(android.view.View, int);
+ method public static void setLabelFor(android.view.View, int);
+ method public static void setLayerPaint(android.view.View, android.graphics.Paint);
+ method public static void setLayerType(android.view.View, int, android.graphics.Paint);
+ method public static void setLayoutDirection(android.view.View, int);
+ method public static void setNestedScrollingEnabled(android.view.View, boolean);
+ method public static void setOnApplyWindowInsetsListener(android.view.View, android.support.v4.view.OnApplyWindowInsetsListener);
+ method public static void setOverScrollMode(android.view.View, int);
+ method public static void setPaddingRelative(android.view.View, int, int, int, int);
+ method public static void setPivotX(android.view.View, float);
+ method public static void setPivotY(android.view.View, float);
+ method public static void setRotation(android.view.View, float);
+ method public static void setRotationX(android.view.View, float);
+ method public static void setRotationY(android.view.View, float);
+ method public static void setSaveFromParentEnabled(android.view.View, boolean);
+ method public static void setScaleX(android.view.View, float);
+ method public static void setScaleY(android.view.View, float);
+ method public static void setScrollIndicators(android.view.View, int);
+ method public static void setScrollIndicators(android.view.View, int, int);
+ method public static void setTransitionName(android.view.View, java.lang.String);
+ method public static void setTranslationX(android.view.View, float);
+ method public static void setTranslationY(android.view.View, float);
+ method public static void setTranslationZ(android.view.View, float);
+ method public static void setX(android.view.View, float);
+ method public static void setY(android.view.View, float);
+ method public static boolean startNestedScroll(android.view.View, int);
+ method public static void stopNestedScroll(android.view.View);
+ field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
+ field public static final int ACCESSIBILITY_LIVE_REGION_NONE = 0; // 0x0
+ field public static final int ACCESSIBILITY_LIVE_REGION_POLITE = 1; // 0x1
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_AUTO = 0; // 0x0
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO = 2; // 0x2
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS = 4; // 0x4
+ field public static final int IMPORTANT_FOR_ACCESSIBILITY_YES = 1; // 0x1
+ field public static final int LAYER_TYPE_HARDWARE = 2; // 0x2
+ field public static final int LAYER_TYPE_NONE = 0; // 0x0
+ field public static final int LAYER_TYPE_SOFTWARE = 1; // 0x1
+ field public static final int LAYOUT_DIRECTION_INHERIT = 2; // 0x2
+ field public static final int LAYOUT_DIRECTION_LOCALE = 3; // 0x3
+ field public static final int LAYOUT_DIRECTION_LTR = 0; // 0x0
+ field public static final int LAYOUT_DIRECTION_RTL = 1; // 0x1
+ field public static final int MEASURED_HEIGHT_STATE_SHIFT = 16; // 0x10
+ field public static final int MEASURED_SIZE_MASK = 16777215; // 0xffffff
+ field public static final int MEASURED_STATE_MASK = -16777216; // 0xff000000
+ field public static final int MEASURED_STATE_TOO_SMALL = 16777216; // 0x1000000
+ field public static final int OVER_SCROLL_ALWAYS = 0; // 0x0
+ field public static final int OVER_SCROLL_IF_CONTENT_SCROLLS = 1; // 0x1
+ field public static final int OVER_SCROLL_NEVER = 2; // 0x2
+ field public static final int SCROLL_AXIS_HORIZONTAL = 1; // 0x1
+ field public static final int SCROLL_AXIS_NONE = 0; // 0x0
+ field public static final int SCROLL_AXIS_VERTICAL = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_BOTTOM = 2; // 0x2
+ field public static final int SCROLL_INDICATOR_END = 32; // 0x20
+ field public static final int SCROLL_INDICATOR_LEFT = 4; // 0x4
+ field public static final int SCROLL_INDICATOR_RIGHT = 8; // 0x8
+ field public static final int SCROLL_INDICATOR_START = 16; // 0x10
+ field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
+ }
+
+ public class ViewConfigurationCompat {
+ ctor public ViewConfigurationCompat();
+ method public static int getScaledPagingTouchSlop(android.view.ViewConfiguration);
+ method public static boolean hasPermanentMenuKey(android.view.ViewConfiguration);
+ }
+
+ public class ViewGroupCompat {
+ method public static int getLayoutMode(android.view.ViewGroup);
+ method public static int getNestedScrollAxes(android.view.ViewGroup);
+ method public static boolean isTransitionGroup(android.view.ViewGroup);
+ method public static boolean onRequestSendAccessibilityEvent(android.view.ViewGroup, android.view.View, android.view.accessibility.AccessibilityEvent);
+ method public static void setLayoutMode(android.view.ViewGroup, int);
+ method public static void setMotionEventSplittingEnabled(android.view.ViewGroup, boolean);
+ method public static void setTransitionGroup(android.view.ViewGroup, boolean);
+ field public static final int LAYOUT_MODE_CLIP_BOUNDS = 0; // 0x0
+ field public static final int LAYOUT_MODE_OPTICAL_BOUNDS = 1; // 0x1
+ }
+
+ public class ViewPager extends android.view.ViewGroup {
+ ctor public ViewPager(android.content.Context);
+ ctor public ViewPager(android.content.Context, android.util.AttributeSet);
+ method public void addOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener);
+ method public boolean arrowScroll(int);
+ method public boolean beginFakeDrag();
+ method protected boolean canScroll(android.view.View, boolean, int, int, int);
+ method public void clearOnPageChangeListeners();
+ method public void endFakeDrag();
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fakeDragBy(float);
+ method public android.support.v4.view.PagerAdapter getAdapter();
+ method public int getCurrentItem();
+ method public int getOffscreenPageLimit();
+ method public int getPageMargin();
+ method public boolean isFakeDragging();
+ method protected void onLayout(boolean, int, int, int, int);
+ method protected void onPageScrolled(int, float, int);
+ method public void onRestoreInstanceState(android.os.Parcelable);
+ method public android.os.Parcelable onSaveInstanceState();
+ method public void removeOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener);
+ method public void setAdapter(android.support.v4.view.PagerAdapter);
+ method public void setCurrentItem(int);
+ method public void setCurrentItem(int, boolean);
+ method public void setOffscreenPageLimit(int);
+ method public deprecated void setOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener);
+ method public void setPageMargin(int);
+ method public void setPageMarginDrawable(android.graphics.drawable.Drawable);
+ method public void setPageMarginDrawable(int);
+ method public void setPageTransformer(boolean, android.support.v4.view.ViewPager.PageTransformer);
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ }
+
+ public static class ViewPager.LayoutParams extends android.view.ViewGroup.LayoutParams {
+ ctor public ViewPager.LayoutParams();
+ ctor public ViewPager.LayoutParams(android.content.Context, android.util.AttributeSet);
+ field public int gravity;
+ field public boolean isDecor;
+ }
+
+ public static abstract interface ViewPager.OnPageChangeListener {
+ method public abstract void onPageScrollStateChanged(int);
+ method public abstract void onPageScrolled(int, float, int);
+ method public abstract void onPageSelected(int);
+ }
+
+ public static abstract interface ViewPager.PageTransformer {
+ method public abstract void transformPage(android.view.View, float);
+ }
+
+ public static class ViewPager.SavedState extends android.view.View.BaseSavedState {
+ ctor public ViewPager.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.v4.view.ViewPager.SavedState> CREATOR;
+ }
+
+ public static class ViewPager.SimpleOnPageChangeListener implements android.support.v4.view.ViewPager.OnPageChangeListener {
+ ctor public ViewPager.SimpleOnPageChangeListener();
+ method public void onPageScrollStateChanged(int);
+ method public void onPageScrolled(int, float, int);
+ method public void onPageSelected(int);
+ }
+
+ public class ViewParentCompat {
+ method public static void notifySubtreeAccessibilityStateChanged(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onNestedFling(android.view.ViewParent, android.view.View, float, float, boolean);
+ method public static boolean onNestedPreFling(android.view.ViewParent, android.view.View, float, float);
+ method public static void onNestedPreScroll(android.view.ViewParent, android.view.View, int, int, int[]);
+ method public static void onNestedScroll(android.view.ViewParent, android.view.View, int, int, int, int);
+ method public static void onNestedScrollAccepted(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static boolean onStartNestedScroll(android.view.ViewParent, android.view.View, android.view.View, int);
+ method public static void onStopNestedScroll(android.view.ViewParent, android.view.View);
+ method public static boolean requestSendAccessibilityEvent(android.view.ViewParent, android.view.View, android.view.accessibility.AccessibilityEvent);
+ }
+
+ public class ViewPropertyAnimatorCompat {
+ method public android.support.v4.view.ViewPropertyAnimatorCompat alpha(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat alphaBy(float);
+ method public void cancel();
+ method public long getDuration();
+ method public android.view.animation.Interpolator getInterpolator();
+ method public long getStartDelay();
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotation(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotationBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotationX(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotationXBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotationY(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat rotationYBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat scaleX(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat scaleXBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat scaleY(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat scaleYBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat setDuration(long);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat setInterpolator(android.view.animation.Interpolator);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat setListener(android.support.v4.view.ViewPropertyAnimatorListener);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat setStartDelay(long);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat setUpdateListener(android.support.v4.view.ViewPropertyAnimatorUpdateListener);
+ method public void start();
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationX(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationXBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationY(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationYBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationZ(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat translationZBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat withEndAction(java.lang.Runnable);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat withLayer();
+ method public android.support.v4.view.ViewPropertyAnimatorCompat withStartAction(java.lang.Runnable);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat x(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat xBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat y(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat yBy(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat z(float);
+ method public android.support.v4.view.ViewPropertyAnimatorCompat zBy(float);
+ }
+
+ public abstract interface ViewPropertyAnimatorListener {
+ method public abstract void onAnimationCancel(android.view.View);
+ method public abstract void onAnimationEnd(android.view.View);
+ method public abstract void onAnimationStart(android.view.View);
+ }
+
+ public class ViewPropertyAnimatorListenerAdapter implements android.support.v4.view.ViewPropertyAnimatorListener {
+ ctor public ViewPropertyAnimatorListenerAdapter();
+ method public void onAnimationCancel(android.view.View);
+ method public void onAnimationEnd(android.view.View);
+ method public void onAnimationStart(android.view.View);
+ }
+
+ public abstract interface ViewPropertyAnimatorUpdateListener {
+ method public abstract void onAnimationUpdate(android.view.View);
+ }
+
+ public class WindowCompat {
+ ctor public WindowCompat();
+ field public static final int FEATURE_ACTION_BAR = 8; // 0x8
+ field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ }
+
+ public class WindowInsetsCompat {
+ method public android.support.v4.view.WindowInsetsCompat consumeStableInsets();
+ method public android.support.v4.view.WindowInsetsCompat consumeSystemWindowInsets();
+ method public int getStableInsetBottom();
+ method public int getStableInsetLeft();
+ method public int getStableInsetRight();
+ method public int getStableInsetTop();
+ method public int getSystemWindowInsetBottom();
+ method public int getSystemWindowInsetLeft();
+ method public int getSystemWindowInsetRight();
+ method public int getSystemWindowInsetTop();
+ method public boolean hasInsets();
+ method public boolean hasStableInsets();
+ method public boolean hasSystemWindowInsets();
+ method public boolean isConsumed();
+ method public boolean isRound();
+ method public android.support.v4.view.WindowInsetsCompat replaceSystemWindowInsets(int, int, int, int);
+ method public android.support.v4.view.WindowInsetsCompat replaceSystemWindowInsets(android.graphics.Rect);
+ }
+
+}
+
+package android.support.v4.view.accessibility {
+
+ public class AccessibilityEventCompat {
+ method public static void appendRecord(android.view.accessibility.AccessibilityEvent, android.support.v4.view.accessibility.AccessibilityRecordCompat);
+ method public static android.support.v4.view.accessibility.AccessibilityRecordCompat asRecord(android.view.accessibility.AccessibilityEvent);
+ method public static int getContentChangeTypes(android.view.accessibility.AccessibilityEvent);
+ method public static android.support.v4.view.accessibility.AccessibilityRecordCompat getRecord(android.view.accessibility.AccessibilityEvent, int);
+ method public static int getRecordCount(android.view.accessibility.AccessibilityEvent);
+ method public static void setContentChangeTypes(android.view.accessibility.AccessibilityEvent, int);
+ field public static final int CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION = 4; // 0x4
+ field public static final int CONTENT_CHANGE_TYPE_SUBTREE = 1; // 0x1
+ field public static final int CONTENT_CHANGE_TYPE_TEXT = 2; // 0x2
+ field public static final int CONTENT_CHANGE_TYPE_UNDEFINED = 0; // 0x0
+ field public static final int TYPES_ALL_MASK = -1; // 0xffffffff
+ field public static final int TYPE_ANNOUNCEMENT = 16384; // 0x4000
+ field public static final int TYPE_GESTURE_DETECTION_END = 524288; // 0x80000
+ field public static final int TYPE_GESTURE_DETECTION_START = 262144; // 0x40000
+ field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_END = 1024; // 0x400
+ field public static final int TYPE_TOUCH_EXPLORATION_GESTURE_START = 512; // 0x200
+ field public static final int TYPE_TOUCH_INTERACTION_END = 2097152; // 0x200000
+ field public static final int TYPE_TOUCH_INTERACTION_START = 1048576; // 0x100000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUSED = 32768; // 0x8000
+ field public static final int TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED = 65536; // 0x10000
+ field public static final int TYPE_VIEW_HOVER_ENTER = 128; // 0x80
+ field public static final int TYPE_VIEW_HOVER_EXIT = 256; // 0x100
+ field public static final int TYPE_VIEW_SCROLLED = 4096; // 0x1000
+ field public static final int TYPE_VIEW_TEXT_SELECTION_CHANGED = 8192; // 0x2000
+ field public static final int TYPE_VIEW_TEXT_TRAVERSED_AT_MOVEMENT_GRANULARITY = 131072; // 0x20000
+ field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
+ }
+
+ public class AccessibilityManagerCompat {
+ ctor public AccessibilityManagerCompat();
+ method public static boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager, android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat);
+ method public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(android.view.accessibility.AccessibilityManager, int);
+ method public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList(android.view.accessibility.AccessibilityManager);
+ method public static boolean isTouchExplorationEnabled(android.view.accessibility.AccessibilityManager);
+ method public static boolean removeAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager, android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat);
+ }
+
+ public static abstract class AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat {
+ ctor public AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat();
+ method public abstract void onAccessibilityStateChanged(boolean);
+ }
+
+ public class AccessibilityNodeInfoCompat {
+ ctor public AccessibilityNodeInfoCompat(java.lang.Object);
+ method public void addAction(int);
+ method public void addAction(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat);
+ method public void addChild(android.view.View);
+ method public void addChild(android.view.View, int);
+ method public boolean canOpenPopup();
+ method public java.util.List<android.support.v4.view.accessibility.AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(java.lang.String);
+ method public java.util.List<android.support.v4.view.accessibility.AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByViewId(java.lang.String);
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat findFocus(int);
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat focusSearch(int);
+ method public java.util.List<android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat> getActionList();
+ method public int getActions();
+ method public void getBoundsInParent(android.graphics.Rect);
+ method public void getBoundsInScreen(android.graphics.Rect);
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getChild(int);
+ method public int getChildCount();
+ method public java.lang.CharSequence getClassName();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat getCollectionInfo();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat getCollectionItemInfo();
+ method public java.lang.CharSequence getContentDescription();
+ method public java.lang.CharSequence getError();
+ method public android.os.Bundle getExtras();
+ method public java.lang.Object getInfo();
+ method public int getInputType();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getLabelFor();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getLabeledBy();
+ method public int getLiveRegion();
+ method public int getMaxTextLength();
+ method public int getMovementGranularities();
+ method public java.lang.CharSequence getPackageName();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getParent();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat getRangeInfo();
+ method public java.lang.CharSequence getText();
+ method public int getTextSelectionEnd();
+ method public int getTextSelectionStart();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getTraversalAfter();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getTraversalBefore();
+ method public java.lang.String getViewIdResourceName();
+ method public android.support.v4.view.accessibility.AccessibilityWindowInfoCompat getWindow();
+ method public int getWindowId();
+ method public boolean isAccessibilityFocused();
+ method public boolean isCheckable();
+ method public boolean isChecked();
+ method public boolean isClickable();
+ method public boolean isContentInvalid();
+ method public boolean isDismissable();
+ method public boolean isEditable();
+ method public boolean isEnabled();
+ method public boolean isFocusable();
+ method public boolean isFocused();
+ method public boolean isLongClickable();
+ method public boolean isMultiLine();
+ method public boolean isPassword();
+ method public boolean isScrollable();
+ method public boolean isSelected();
+ method public boolean isVisibleToUser();
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat obtain(android.view.View);
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat obtain(android.view.View, int);
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat obtain();
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat obtain(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public boolean performAction(int);
+ method public boolean performAction(int, android.os.Bundle);
+ method public void recycle();
+ method public boolean refresh();
+ method public boolean removeAction(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat);
+ method public boolean removeChild(android.view.View);
+ method public boolean removeChild(android.view.View, int);
+ method public void setAccessibilityFocused(boolean);
+ method public void setBoundsInParent(android.graphics.Rect);
+ method public void setBoundsInScreen(android.graphics.Rect);
+ method public void setCanOpenPopup(boolean);
+ method public void setCheckable(boolean);
+ method public void setChecked(boolean);
+ method public void setClassName(java.lang.CharSequence);
+ method public void setClickable(boolean);
+ method public void setCollectionInfo(java.lang.Object);
+ method public void setCollectionItemInfo(java.lang.Object);
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setContentInvalid(boolean);
+ method public void setDismissable(boolean);
+ method public void setEditable(boolean);
+ method public void setEnabled(boolean);
+ method public void setError(java.lang.CharSequence);
+ method public void setFocusable(boolean);
+ method public void setFocused(boolean);
+ method public void setInputType(int);
+ method public void setLabelFor(android.view.View);
+ method public void setLabelFor(android.view.View, int);
+ method public void setLabeledBy(android.view.View);
+ method public void setLabeledBy(android.view.View, int);
+ method public void setLiveRegion(int);
+ method public void setLongClickable(boolean);
+ method public void setMaxTextLength(int);
+ method public void setMovementGranularities(int);
+ method public void setMultiLine(boolean);
+ method public void setPackageName(java.lang.CharSequence);
+ method public void setParent(android.view.View);
+ method public void setParent(android.view.View, int);
+ method public void setPassword(boolean);
+ method public void setRangeInfo(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat);
+ method public void setScrollable(boolean);
+ method public void setSelected(boolean);
+ method public void setSource(android.view.View);
+ method public void setSource(android.view.View, int);
+ method public void setText(java.lang.CharSequence);
+ method public void setTextSelection(int, int);
+ method public void setTraversalAfter(android.view.View);
+ method public void setTraversalAfter(android.view.View, int);
+ method public void setTraversalBefore(android.view.View);
+ method public void setTraversalBefore(android.view.View, int);
+ method public void setViewIdResourceName(java.lang.String);
+ method public void setVisibleToUser(boolean);
+ field public static final int ACTION_ACCESSIBILITY_FOCUS = 64; // 0x40
+ field public static final java.lang.String ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN = "ACTION_ARGUMENT_EXTEND_SELECTION_BOOLEAN";
+ field public static final java.lang.String ACTION_ARGUMENT_HTML_ELEMENT_STRING = "ACTION_ARGUMENT_HTML_ELEMENT_STRING";
+ field public static final java.lang.String ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT = "ACTION_ARGUMENT_MOVEMENT_GRANULARITY_INT";
+ field public static final java.lang.String ACTION_ARGUMENT_SELECTION_END_INT = "ACTION_ARGUMENT_SELECTION_END_INT";
+ field public static final java.lang.String ACTION_ARGUMENT_SELECTION_START_INT = "ACTION_ARGUMENT_SELECTION_START_INT";
+ field public static final java.lang.String ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE = "ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE";
+ field public static final int ACTION_CLEAR_ACCESSIBILITY_FOCUS = 128; // 0x80
+ field public static final int ACTION_CLEAR_FOCUS = 2; // 0x2
+ field public static final int ACTION_CLEAR_SELECTION = 8; // 0x8
+ field public static final int ACTION_CLICK = 16; // 0x10
+ field public static final int ACTION_COLLAPSE = 524288; // 0x80000
+ field public static final int ACTION_COPY = 16384; // 0x4000
+ field public static final int ACTION_CUT = 65536; // 0x10000
+ field public static final int ACTION_DISMISS = 1048576; // 0x100000
+ field public static final int ACTION_EXPAND = 262144; // 0x40000
+ field public static final int ACTION_FOCUS = 1; // 0x1
+ field public static final int ACTION_LONG_CLICK = 32; // 0x20
+ field public static final int ACTION_NEXT_AT_MOVEMENT_GRANULARITY = 256; // 0x100
+ field public static final int ACTION_NEXT_HTML_ELEMENT = 1024; // 0x400
+ field public static final int ACTION_PASTE = 32768; // 0x8000
+ field public static final int ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY = 512; // 0x200
+ field public static final int ACTION_PREVIOUS_HTML_ELEMENT = 2048; // 0x800
+ field public static final int ACTION_SCROLL_BACKWARD = 8192; // 0x2000
+ field public static final int ACTION_SCROLL_FORWARD = 4096; // 0x1000
+ field public static final int ACTION_SELECT = 4; // 0x4
+ field public static final int ACTION_SET_SELECTION = 131072; // 0x20000
+ field public static final int ACTION_SET_TEXT = 2097152; // 0x200000
+ field public static final int FOCUS_ACCESSIBILITY = 2; // 0x2
+ field public static final int FOCUS_INPUT = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_CHARACTER = 1; // 0x1
+ field public static final int MOVEMENT_GRANULARITY_LINE = 4; // 0x4
+ field public static final int MOVEMENT_GRANULARITY_PAGE = 16; // 0x10
+ field public static final int MOVEMENT_GRANULARITY_PARAGRAPH = 8; // 0x8
+ field public static final int MOVEMENT_GRANULARITY_WORD = 2; // 0x2
+ }
+
+ public static class AccessibilityNodeInfoCompat.AccessibilityActionCompat {
+ ctor public AccessibilityNodeInfoCompat.AccessibilityActionCompat(int, java.lang.CharSequence);
+ method public int getId();
+ method public java.lang.CharSequence getLabel();
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_ACCESSIBILITY_FOCUS;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_CLEAR_ACCESSIBILITY_FOCUS;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_CLEAR_FOCUS;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_CLEAR_SELECTION;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_CLICK;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_COLLAPSE;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_COPY;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_CUT;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_DISMISS;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_EXPAND;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_FOCUS;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_LONG_CLICK;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_NEXT_AT_MOVEMENT_GRANULARITY;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_NEXT_HTML_ELEMENT;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PASTE;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PREVIOUS_AT_MOVEMENT_GRANULARITY;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_PREVIOUS_HTML_ELEMENT;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SCROLL_BACKWARD;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SCROLL_FORWARD;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SELECT;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SET_SELECTION;
+ field public static final android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat ACTION_SET_TEXT;
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionInfoCompat {
+ method public int getColumnCount();
+ method public int getRowCount();
+ method public boolean isHierarchical();
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat obtain(int, int, boolean, int);
+ field public static final int SELECTION_MODE_MULTIPLE = 2; // 0x2
+ field public static final int SELECTION_MODE_NONE = 0; // 0x0
+ field public static final int SELECTION_MODE_SINGLE = 1; // 0x1
+ }
+
+ public static class AccessibilityNodeInfoCompat.CollectionItemInfoCompat {
+ method public int getColumnIndex();
+ method public int getColumnSpan();
+ method public int getRowIndex();
+ method public int getRowSpan();
+ method public boolean isHeading();
+ method public boolean isSelected();
+ method public static android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat obtain(int, int, int, int, boolean, boolean);
+ }
+
+ public static class AccessibilityNodeInfoCompat.RangeInfoCompat {
+ method public float getCurrent();
+ method public float getMax();
+ method public float getMin();
+ method public int getType();
+ field public static final int RANGE_TYPE_FLOAT = 1; // 0x1
+ field public static final int RANGE_TYPE_INT = 0; // 0x0
+ field public static final int RANGE_TYPE_PERCENT = 2; // 0x2
+ }
+
+ public class AccessibilityNodeProviderCompat {
+ ctor public AccessibilityNodeProviderCompat();
+ ctor public AccessibilityNodeProviderCompat(java.lang.Object);
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int);
+ method public java.util.List<android.support.v4.view.accessibility.AccessibilityNodeInfoCompat> findAccessibilityNodeInfosByText(java.lang.String, int);
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat findFocus(int);
+ method public java.lang.Object getProvider();
+ method public boolean performAction(int, int, android.os.Bundle);
+ }
+
+ public class AccessibilityRecordCompat {
+ ctor public deprecated AccessibilityRecordCompat(java.lang.Object);
+ method public int getAddedCount();
+ method public java.lang.CharSequence getBeforeText();
+ method public java.lang.CharSequence getClassName();
+ method public java.lang.CharSequence getContentDescription();
+ method public int getCurrentItemIndex();
+ method public int getFromIndex();
+ method public deprecated java.lang.Object getImpl();
+ method public int getItemCount();
+ method public int getMaxScrollX();
+ method public int getMaxScrollY();
+ method public android.os.Parcelable getParcelableData();
+ method public int getRemovedCount();
+ method public int getScrollX();
+ method public int getScrollY();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getSource();
+ method public java.util.List<java.lang.CharSequence> getText();
+ method public int getToIndex();
+ method public int getWindowId();
+ method public boolean isChecked();
+ method public boolean isEnabled();
+ method public boolean isFullScreen();
+ method public boolean isPassword();
+ method public boolean isScrollable();
+ method public static android.support.v4.view.accessibility.AccessibilityRecordCompat obtain(android.support.v4.view.accessibility.AccessibilityRecordCompat);
+ method public static android.support.v4.view.accessibility.AccessibilityRecordCompat obtain();
+ method public void recycle();
+ method public void setAddedCount(int);
+ method public void setBeforeText(java.lang.CharSequence);
+ method public void setChecked(boolean);
+ method public void setClassName(java.lang.CharSequence);
+ method public void setContentDescription(java.lang.CharSequence);
+ method public void setCurrentItemIndex(int);
+ method public void setEnabled(boolean);
+ method public void setFromIndex(int);
+ method public void setFullScreen(boolean);
+ method public void setItemCount(int);
+ method public void setMaxScrollX(int);
+ method public void setMaxScrollY(int);
+ method public void setParcelableData(android.os.Parcelable);
+ method public void setPassword(boolean);
+ method public void setRemovedCount(int);
+ method public void setScrollX(int);
+ method public void setScrollY(int);
+ method public void setScrollable(boolean);
+ method public void setSource(android.view.View);
+ method public void setSource(android.view.View, int);
+ method public void setToIndex(int);
+ }
+
+ public class AccessibilityWindowInfoCompat {
+ method public void getBoundsInScreen(android.graphics.Rect);
+ method public android.support.v4.view.accessibility.AccessibilityWindowInfoCompat getChild(int);
+ method public int getChildCount();
+ method public int getId();
+ method public int getLayer();
+ method public android.support.v4.view.accessibility.AccessibilityWindowInfoCompat getParent();
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getRoot();
+ method public int getType();
+ method public boolean isAccessibilityFocused();
+ method public boolean isActive();
+ method public boolean isFocused();
+ method public static android.support.v4.view.accessibility.AccessibilityWindowInfoCompat obtain();
+ method public static android.support.v4.view.accessibility.AccessibilityWindowInfoCompat obtain(android.support.v4.view.accessibility.AccessibilityWindowInfoCompat);
+ method public void recycle();
+ field public static final int TYPE_ACCESSIBILITY_OVERLAY = 4; // 0x4
+ field public static final int TYPE_APPLICATION = 1; // 0x1
+ field public static final int TYPE_INPUT_METHOD = 2; // 0x2
+ field public static final int TYPE_SYSTEM = 3; // 0x3
+ }
+
+}
+
+package android.support.v4.view.animation {
+
+ public class FastOutLinearInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+ ctor public FastOutLinearInInterpolator();
+ }
+
+ public class FastOutSlowInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+ ctor public FastOutSlowInInterpolator();
+ }
+
+ public class LinearOutSlowInInterpolator extends android.support.v4.view.animation.LookupTableInterpolator {
+ ctor public LinearOutSlowInInterpolator();
+ }
+
+ abstract class LookupTableInterpolator implements android.view.animation.Interpolator {
+ ctor public LookupTableInterpolator(float[]);
+ method public float getInterpolation(float);
+ }
+
+ public class PathInterpolatorCompat {
+ method public static android.view.animation.Interpolator create(android.graphics.Path);
+ method public static android.view.animation.Interpolator create(float, float);
+ method public static android.view.animation.Interpolator create(float, float, float, float);
+ }
+
+}
+
+package android.support.v4.widget {
+
+ public abstract class AutoScrollHelper implements android.view.View.OnTouchListener {
+ ctor public AutoScrollHelper(android.view.View);
+ method public abstract boolean canTargetScrollHorizontally(int);
+ method public abstract boolean canTargetScrollVertically(int);
+ method public boolean isEnabled();
+ method public boolean isExclusive();
+ method public boolean onTouch(android.view.View, android.view.MotionEvent);
+ method public abstract void scrollTargetBy(int, int);
+ method public android.support.v4.widget.AutoScrollHelper setActivationDelay(int);
+ method public android.support.v4.widget.AutoScrollHelper setEdgeType(int);
+ method public android.support.v4.widget.AutoScrollHelper setEnabled(boolean);
+ method public android.support.v4.widget.AutoScrollHelper setExclusive(boolean);
+ method public android.support.v4.widget.AutoScrollHelper setMaximumEdges(float, float);
+ method public android.support.v4.widget.AutoScrollHelper setMaximumVelocity(float, float);
+ method public android.support.v4.widget.AutoScrollHelper setMinimumVelocity(float, float);
+ method public android.support.v4.widget.AutoScrollHelper setRampDownDuration(int);
+ method public android.support.v4.widget.AutoScrollHelper setRampUpDuration(int);
+ method public android.support.v4.widget.AutoScrollHelper setRelativeEdges(float, float);
+ method public android.support.v4.widget.AutoScrollHelper setRelativeVelocity(float, float);
+ field public static final int EDGE_TYPE_INSIDE = 0; // 0x0
+ field public static final int EDGE_TYPE_INSIDE_EXTEND = 1; // 0x1
+ field public static final int EDGE_TYPE_OUTSIDE = 2; // 0x2
+ field public static final float NO_MAX = 3.4028235E38f;
+ field public static final float NO_MIN = 0.0f;
+ field public static final float RELATIVE_UNSPECIFIED = 0.0f;
+ }
+
+ public final class CompoundButtonCompat {
+ method public static android.graphics.drawable.Drawable getButtonDrawable(android.widget.CompoundButton);
+ method public static android.content.res.ColorStateList getButtonTintList(android.widget.CompoundButton);
+ method public static android.graphics.PorterDuff.Mode getButtonTintMode(android.widget.CompoundButton);
+ method public static void setButtonTintList(android.widget.CompoundButton, android.content.res.ColorStateList);
+ method public static void setButtonTintMode(android.widget.CompoundButton, android.graphics.PorterDuff.Mode);
+ }
+
+ public class ContentLoadingProgressBar extends android.widget.ProgressBar {
+ ctor public ContentLoadingProgressBar(android.content.Context);
+ ctor public ContentLoadingProgressBar(android.content.Context, android.util.AttributeSet);
+ method public void hide();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void show();
+ }
+
+ public abstract class CursorAdapter extends android.widget.BaseAdapter {
+ ctor public deprecated CursorAdapter(android.content.Context, android.database.Cursor);
+ ctor public CursorAdapter(android.content.Context, android.database.Cursor, boolean);
+ ctor public CursorAdapter(android.content.Context, android.database.Cursor, int);
+ method public abstract void bindView(android.view.View, android.content.Context, android.database.Cursor);
+ method public void changeCursor(android.database.Cursor);
+ method public java.lang.CharSequence convertToString(android.database.Cursor);
+ method public int getCount();
+ method public android.database.Cursor getCursor();
+ method public android.widget.Filter getFilter();
+ method public android.widget.FilterQueryProvider getFilterQueryProvider();
+ method public java.lang.Object getItem(int);
+ method public long getItemId(int);
+ method public android.view.View getView(int, android.view.View, android.view.ViewGroup);
+ method protected deprecated void init(android.content.Context, android.database.Cursor, boolean);
+ method public android.view.View newDropDownView(android.content.Context, android.database.Cursor, android.view.ViewGroup);
+ method public abstract android.view.View newView(android.content.Context, android.database.Cursor, android.view.ViewGroup);
+ method protected void onContentChanged();
+ method public android.database.Cursor runQueryOnBackgroundThread(java.lang.CharSequence);
+ method public void setFilterQueryProvider(android.widget.FilterQueryProvider);
+ method public android.database.Cursor swapCursor(android.database.Cursor);
+ field public static final deprecated int FLAG_AUTO_REQUERY = 1; // 0x1
+ field public static final int FLAG_REGISTER_CONTENT_OBSERVER = 2; // 0x2
+ }
+
+ public class DrawerLayout extends android.view.ViewGroup {
+ ctor public DrawerLayout(android.content.Context);
+ ctor public DrawerLayout(android.content.Context, android.util.AttributeSet);
+ ctor public DrawerLayout(android.content.Context, android.util.AttributeSet, int);
+ method public void closeDrawer(android.view.View);
+ method public void closeDrawer(int);
+ method public void closeDrawers();
+ method public float getDrawerElevation();
+ method public int getDrawerLockMode(int);
+ method public int getDrawerLockMode(android.view.View);
+ method public java.lang.CharSequence getDrawerTitle(int);
+ method public android.graphics.drawable.Drawable getStatusBarBackgroundDrawable();
+ method public boolean isDrawerOpen(android.view.View);
+ method public boolean isDrawerOpen(int);
+ method public boolean isDrawerVisible(android.view.View);
+ method public boolean isDrawerVisible(int);
+ method public void onDraw(android.graphics.Canvas);
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void openDrawer(android.view.View);
+ method public void openDrawer(int);
+ method public void setDrawerElevation(float);
+ method public void setDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
+ method public void setDrawerLockMode(int);
+ method public void setDrawerLockMode(int, int);
+ method public void setDrawerLockMode(int, android.view.View);
+ method public void setDrawerShadow(android.graphics.drawable.Drawable, int);
+ method public void setDrawerShadow(int, int);
+ method public void setDrawerTitle(int, java.lang.CharSequence);
+ method public void setScrimColor(int);
+ method public void setStatusBarBackground(android.graphics.drawable.Drawable);
+ method public void setStatusBarBackground(int);
+ method public void setStatusBarBackgroundColor(int);
+ field public static final int LOCK_MODE_LOCKED_CLOSED = 1; // 0x1
+ field public static final int LOCK_MODE_LOCKED_OPEN = 2; // 0x2
+ field public static final int LOCK_MODE_UNLOCKED = 0; // 0x0
+ field public static final int STATE_DRAGGING = 1; // 0x1
+ field public static final int STATE_IDLE = 0; // 0x0
+ field public static final int STATE_SETTLING = 2; // 0x2
+ }
+
+ public static abstract interface DrawerLayout.DrawerListener {
+ method public abstract void onDrawerClosed(android.view.View);
+ method public abstract void onDrawerOpened(android.view.View);
+ method public abstract void onDrawerSlide(android.view.View, float);
+ method public abstract void onDrawerStateChanged(int);
+ }
+
+ public static class DrawerLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public DrawerLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public DrawerLayout.LayoutParams(int, int);
+ ctor public DrawerLayout.LayoutParams(int, int, int);
+ ctor public DrawerLayout.LayoutParams(android.support.v4.widget.DrawerLayout.LayoutParams);
+ ctor public DrawerLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public DrawerLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ field public int gravity;
+ }
+
+ protected static class DrawerLayout.SavedState extends android.view.View.BaseSavedState {
+ ctor public DrawerLayout.SavedState(android.os.Parcel);
+ ctor public DrawerLayout.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.v4.widget.DrawerLayout.SavedState> CREATOR;
+ }
+
+ public static abstract class DrawerLayout.SimpleDrawerListener implements android.support.v4.widget.DrawerLayout.DrawerListener {
+ ctor public DrawerLayout.SimpleDrawerListener();
+ method public void onDrawerClosed(android.view.View);
+ method public void onDrawerOpened(android.view.View);
+ method public void onDrawerSlide(android.view.View, float);
+ method public void onDrawerStateChanged(int);
+ }
+
+ public class EdgeEffectCompat {
+ ctor public EdgeEffectCompat(android.content.Context);
+ method public boolean draw(android.graphics.Canvas);
+ method public void finish();
+ method public boolean isFinished();
+ method public boolean onAbsorb(int);
+ method public deprecated boolean onPull(float);
+ method public boolean onPull(float, float);
+ method public boolean onRelease();
+ method public void setSize(int, int);
+ }
+
+ public abstract class ExploreByTouchHelper extends android.support.v4.view.AccessibilityDelegateCompat {
+ ctor public ExploreByTouchHelper(android.view.View);
+ method public boolean dispatchHoverEvent(android.view.MotionEvent);
+ method public int getFocusedVirtualView();
+ method protected abstract int getVirtualViewAt(float, float);
+ method protected abstract void getVisibleVirtualViews(java.util.List<java.lang.Integer>);
+ method public void invalidateRoot();
+ method public void invalidateVirtualView(int);
+ method protected abstract boolean onPerformActionForVirtualView(int, int, android.os.Bundle);
+ method protected abstract void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
+ method public void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method protected abstract void onPopulateNodeForVirtualView(int, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public boolean sendEventForVirtualView(int, int);
+ field public static final int HOST_ID = -1; // 0xffffffff
+ field public static final int INVALID_ID = -2147483648; // 0x80000000
+ }
+
+ public class ListPopupWindowCompat {
+ method public static android.view.View.OnTouchListener createDragToOpenListener(java.lang.Object, android.view.View);
+ }
+
+ public class ListViewAutoScrollHelper extends android.support.v4.widget.AutoScrollHelper {
+ ctor public ListViewAutoScrollHelper(android.widget.ListView);
+ method public boolean canTargetScrollHorizontally(int);
+ method public boolean canTargetScrollVertically(int);
+ method public void scrollTargetBy(int, int);
+ }
+
+ public class NestedScrollView extends android.widget.FrameLayout implements android.support.v4.view.NestedScrollingChild android.support.v4.view.NestedScrollingParent android.support.v4.view.ScrollingView {
+ ctor public NestedScrollView(android.content.Context);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet);
+ ctor public NestedScrollView(android.content.Context, android.util.AttributeSet, int);
+ method public boolean arrowScroll(int);
+ method protected int computeScrollDeltaToGetChildRectOnScreen(android.graphics.Rect);
+ method public boolean executeKeyEvent(android.view.KeyEvent);
+ method public void fling(int);
+ method public boolean fullScroll(int);
+ method public int getMaxScrollAmount();
+ method public boolean isFillViewport();
+ method public boolean isSmoothScrollingEnabled();
+ method public void onAttachedToWindow();
+ method public boolean pageScroll(int);
+ method public void setFillViewport(boolean);
+ method public void setOnScrollChangeListener(android.support.v4.widget.NestedScrollView.OnScrollChangeListener);
+ method public void setSmoothScrollingEnabled(boolean);
+ method public final void smoothScrollBy(int, int);
+ method public final void smoothScrollTo(int, int);
+ }
+
+ public static abstract interface NestedScrollView.OnScrollChangeListener {
+ method public abstract void onScrollChange(android.support.v4.widget.NestedScrollView, int, int, int, int);
+ }
+
+ public class PopupMenuCompat {
+ method public static android.view.View.OnTouchListener getDragToOpenListener(java.lang.Object);
+ }
+
+ public class PopupWindowCompat {
+ method public static boolean getOverlapAnchor(android.widget.PopupWindow);
+ method public static int getWindowLayoutType(android.widget.PopupWindow);
+ method public static void setOverlapAnchor(android.widget.PopupWindow, boolean);
+ method public static void setWindowLayoutType(android.widget.PopupWindow, int);
+ method public static void showAsDropDown(android.widget.PopupWindow, android.view.View, int, int, int);
+ }
+
+ public abstract class ResourceCursorAdapter extends android.support.v4.widget.CursorAdapter {
+ ctor public deprecated ResourceCursorAdapter(android.content.Context, int, android.database.Cursor);
+ ctor public ResourceCursorAdapter(android.content.Context, int, android.database.Cursor, boolean);
+ ctor public ResourceCursorAdapter(android.content.Context, int, android.database.Cursor, int);
+ method public android.view.View newView(android.content.Context, android.database.Cursor, android.view.ViewGroup);
+ method public void setDropDownViewResource(int);
+ method public void setViewResource(int);
+ }
+
+ public class ScrollerCompat {
+ method public void abortAnimation();
+ method public boolean computeScrollOffset();
+ method public static android.support.v4.widget.ScrollerCompat create(android.content.Context);
+ method public static android.support.v4.widget.ScrollerCompat create(android.content.Context, android.view.animation.Interpolator);
+ method public void fling(int, int, int, int, int, int, int, int);
+ method public void fling(int, int, int, int, int, int, int, int, int, int);
+ method public float getCurrVelocity();
+ method public int getCurrX();
+ method public int getCurrY();
+ method public int getFinalX();
+ method public int getFinalY();
+ method public boolean isFinished();
+ method public boolean isOverScrolled();
+ method public void notifyHorizontalEdgeReached(int, int, int);
+ method public void notifyVerticalEdgeReached(int, int, int);
+ method public boolean springBack(int, int, int, int, int, int);
+ method public void startScroll(int, int, int, int);
+ method public void startScroll(int, int, int, int, int);
+ }
+
+ public class SearchViewCompat {
+ method public static java.lang.CharSequence getQuery(android.view.View);
+ method public static boolean isIconified(android.view.View);
+ method public static boolean isQueryRefinementEnabled(android.view.View);
+ method public static boolean isSubmitButtonEnabled(android.view.View);
+ method public static android.view.View newSearchView(android.content.Context);
+ method public static void setIconified(android.view.View, boolean);
+ method public static void setImeOptions(android.view.View, int);
+ method public static void setInputType(android.view.View, int);
+ method public static void setMaxWidth(android.view.View, int);
+ method public static void setOnCloseListener(android.view.View, android.support.v4.widget.SearchViewCompat.OnCloseListenerCompat);
+ method public static void setOnQueryTextListener(android.view.View, android.support.v4.widget.SearchViewCompat.OnQueryTextListenerCompat);
+ method public static void setQuery(android.view.View, java.lang.CharSequence, boolean);
+ method public static void setQueryHint(android.view.View, java.lang.CharSequence);
+ method public static void setQueryRefinementEnabled(android.view.View, boolean);
+ method public static void setSearchableInfo(android.view.View, android.content.ComponentName);
+ method public static void setSubmitButtonEnabled(android.view.View, boolean);
+ }
+
+ public static abstract class SearchViewCompat.OnCloseListenerCompat {
+ ctor public SearchViewCompat.OnCloseListenerCompat();
+ method public boolean onClose();
+ }
+
+ public static abstract class SearchViewCompat.OnQueryTextListenerCompat {
+ ctor public SearchViewCompat.OnQueryTextListenerCompat();
+ method public boolean onQueryTextChange(java.lang.String);
+ method public boolean onQueryTextSubmit(java.lang.String);
+ }
+
+ public class SimpleCursorAdapter extends android.support.v4.widget.ResourceCursorAdapter {
+ ctor public deprecated SimpleCursorAdapter(android.content.Context, int, android.database.Cursor, java.lang.String[], int[]);
+ ctor public SimpleCursorAdapter(android.content.Context, int, android.database.Cursor, java.lang.String[], int[], int);
+ method public void bindView(android.view.View, android.content.Context, android.database.Cursor);
+ method public void changeCursorAndColumns(android.database.Cursor, java.lang.String[], int[]);
+ method public android.support.v4.widget.SimpleCursorAdapter.CursorToStringConverter getCursorToStringConverter();
+ method public int getStringConversionColumn();
+ method public android.support.v4.widget.SimpleCursorAdapter.ViewBinder getViewBinder();
+ method public void setCursorToStringConverter(android.support.v4.widget.SimpleCursorAdapter.CursorToStringConverter);
+ method public void setStringConversionColumn(int);
+ method public void setViewBinder(android.support.v4.widget.SimpleCursorAdapter.ViewBinder);
+ method public void setViewImage(android.widget.ImageView, java.lang.String);
+ method public void setViewText(android.widget.TextView, java.lang.String);
+ }
+
+ public static abstract interface SimpleCursorAdapter.CursorToStringConverter {
+ method public abstract java.lang.CharSequence convertToString(android.database.Cursor);
+ }
+
+ public static abstract interface SimpleCursorAdapter.ViewBinder {
+ method public abstract boolean setViewValue(android.view.View, android.database.Cursor, int);
+ }
+
+ public class SlidingPaneLayout extends android.view.ViewGroup {
+ ctor public SlidingPaneLayout(android.content.Context);
+ ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet);
+ ctor public SlidingPaneLayout(android.content.Context, android.util.AttributeSet, int);
+ method protected boolean canScroll(android.view.View, boolean, int, int, int);
+ method public deprecated boolean canSlide();
+ method public boolean closePane();
+ method public int getCoveredFadeColor();
+ method public int getParallaxDistance();
+ method public int getSliderFadeColor();
+ method public boolean isOpen();
+ method public boolean isSlideable();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public boolean openPane();
+ method public void setCoveredFadeColor(int);
+ method public void setPanelSlideListener(android.support.v4.widget.SlidingPaneLayout.PanelSlideListener);
+ method public void setParallaxDistance(int);
+ method public deprecated void setShadowDrawable(android.graphics.drawable.Drawable);
+ method public void setShadowDrawableLeft(android.graphics.drawable.Drawable);
+ method public void setShadowDrawableRight(android.graphics.drawable.Drawable);
+ method public deprecated void setShadowResource(int);
+ method public void setShadowResourceLeft(int);
+ method public void setShadowResourceRight(int);
+ method public void setSliderFadeColor(int);
+ method public deprecated void smoothSlideClosed();
+ method public deprecated void smoothSlideOpen();
+ }
+
+ public static class SlidingPaneLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public SlidingPaneLayout.LayoutParams();
+ ctor public SlidingPaneLayout.LayoutParams(int, int);
+ ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public SlidingPaneLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public SlidingPaneLayout.LayoutParams(android.support.v4.widget.SlidingPaneLayout.LayoutParams);
+ ctor public SlidingPaneLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ field public float weight;
+ }
+
+ public static abstract interface SlidingPaneLayout.PanelSlideListener {
+ method public abstract void onPanelClosed(android.view.View);
+ method public abstract void onPanelOpened(android.view.View);
+ method public abstract void onPanelSlide(android.view.View, float);
+ }
+
+ public static class SlidingPaneLayout.SimplePanelSlideListener implements android.support.v4.widget.SlidingPaneLayout.PanelSlideListener {
+ ctor public SlidingPaneLayout.SimplePanelSlideListener();
+ method public void onPanelClosed(android.view.View);
+ method public void onPanelOpened(android.view.View);
+ method public void onPanelSlide(android.view.View, float);
+ }
+
+ public class Space extends android.view.View {
+ ctor public Space(android.content.Context, android.util.AttributeSet, int);
+ ctor public Space(android.content.Context, android.util.AttributeSet);
+ ctor public Space(android.content.Context);
+ }
+
+ public class SwipeRefreshLayout extends android.view.ViewGroup implements android.support.v4.view.NestedScrollingChild android.support.v4.view.NestedScrollingParent {
+ ctor public SwipeRefreshLayout(android.content.Context);
+ ctor public SwipeRefreshLayout(android.content.Context, android.util.AttributeSet);
+ method public boolean canChildScrollUp();
+ method public int getProgressCircleDiameter();
+ method public boolean isRefreshing();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void onMeasure(int, int);
+ method public deprecated void setColorScheme(int...);
+ method public void setColorSchemeColors(int...);
+ method public void setColorSchemeResources(int...);
+ method public void setDistanceToTriggerSync(int);
+ method public void setOnRefreshListener(android.support.v4.widget.SwipeRefreshLayout.OnRefreshListener);
+ method public deprecated void setProgressBackgroundColor(int);
+ method public void setProgressBackgroundColorSchemeColor(int);
+ method public void setProgressBackgroundColorSchemeResource(int);
+ method public void setProgressViewEndTarget(boolean, int);
+ method public void setProgressViewOffset(boolean, int, int);
+ method public void setRefreshing(boolean);
+ method public void setSize(int);
+ field public static final int DEFAULT = 1; // 0x1
+ field public static final int LARGE = 0; // 0x0
+ field protected int mFrom;
+ field protected int mOriginalOffsetTop;
+ }
+
+ public static abstract interface SwipeRefreshLayout.OnRefreshListener {
+ method public abstract void onRefresh();
+ }
+
+ public class TextViewCompat {
+ method public static int getMaxLines(android.widget.TextView);
+ method public static int getMinLines(android.widget.TextView);
+ method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
+ method public static void setCompoundDrawablesRelativeWithIntrinsicBounds(android.widget.TextView, int, int, int, int);
+ }
+
+ public abstract interface TintableCompoundButton {
+ method public abstract android.content.res.ColorStateList getSupportButtonTintList();
+ method public abstract android.graphics.PorterDuff.Mode getSupportButtonTintMode();
+ method public abstract void setSupportButtonTintList(android.content.res.ColorStateList);
+ method public abstract void setSupportButtonTintMode(android.graphics.PorterDuff.Mode);
+ }
+
+ public class ViewDragHelper {
+ method public void abort();
+ method protected boolean canScroll(android.view.View, boolean, int, int, int, int);
+ method public void cancel();
+ method public void captureChildView(android.view.View, int);
+ method public boolean checkTouchSlop(int);
+ method public boolean checkTouchSlop(int, int);
+ method public boolean continueSettling(boolean);
+ method public static android.support.v4.widget.ViewDragHelper create(android.view.ViewGroup, android.support.v4.widget.ViewDragHelper.Callback);
+ method public static android.support.v4.widget.ViewDragHelper create(android.view.ViewGroup, float, android.support.v4.widget.ViewDragHelper.Callback);
+ method public android.view.View findTopChildUnder(int, int);
+ method public void flingCapturedView(int, int, int, int);
+ method public int getActivePointerId();
+ method public android.view.View getCapturedView();
+ method public int getEdgeSize();
+ method public float getMinVelocity();
+ method public int getTouchSlop();
+ method public int getViewDragState();
+ method public boolean isCapturedViewUnder(int, int);
+ method public boolean isEdgeTouched(int);
+ method public boolean isEdgeTouched(int, int);
+ method public boolean isPointerDown(int);
+ method public boolean isViewUnder(android.view.View, int, int);
+ method public void processTouchEvent(android.view.MotionEvent);
+ method public void setEdgeTrackingEnabled(int);
+ method public void setMinVelocity(float);
+ method public boolean settleCapturedViewAt(int, int);
+ method public boolean shouldInterceptTouchEvent(android.view.MotionEvent);
+ method public boolean smoothSlideViewTo(android.view.View, int, int);
+ field public static final int DIRECTION_ALL = 3; // 0x3
+ field public static final int DIRECTION_HORIZONTAL = 1; // 0x1
+ field public static final int DIRECTION_VERTICAL = 2; // 0x2
+ field public static final int EDGE_ALL = 15; // 0xf
+ field public static final int EDGE_BOTTOM = 8; // 0x8
+ field public static final int EDGE_LEFT = 1; // 0x1
+ field public static final int EDGE_RIGHT = 2; // 0x2
+ field public static final int EDGE_TOP = 4; // 0x4
+ field public static final int INVALID_POINTER = -1; // 0xffffffff
+ field public static final int STATE_DRAGGING = 1; // 0x1
+ field public static final int STATE_IDLE = 0; // 0x0
+ field public static final int STATE_SETTLING = 2; // 0x2
+ }
+
+ public static abstract class ViewDragHelper.Callback {
+ ctor public ViewDragHelper.Callback();
+ method public int clampViewPositionHorizontal(android.view.View, int, int);
+ method public int clampViewPositionVertical(android.view.View, int, int);
+ method public int getOrderedChildIndex(int);
+ method public int getViewHorizontalDragRange(android.view.View);
+ method public int getViewVerticalDragRange(android.view.View);
+ method public void onEdgeDragStarted(int, int);
+ method public boolean onEdgeLock(int);
+ method public void onEdgeTouched(int, int);
+ method public void onViewCaptured(android.view.View, int);
+ method public void onViewDragStateChanged(int);
+ method public void onViewPositionChanged(android.view.View, int, int, int, int);
+ method public void onViewReleased(android.view.View, float, float);
+ method public abstract boolean tryCaptureView(android.view.View, int);
+ }
+
+}
+
diff --git a/v4/api/current.txt b/v4/api/current.txt
index 9f50a24..bda93c5 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -1,6 +1,6 @@
package android.support.v4.accessibilityservice {
- public class AccessibilityServiceInfoCompat {
+ public final class AccessibilityServiceInfoCompat {
method public static java.lang.String capabilityToString(int);
method public static java.lang.String feedbackTypeToString(int);
method public static java.lang.String flagToString(int);
@@ -67,6 +67,7 @@
method public static boolean shouldShowRequestPermissionRationale(android.app.Activity, java.lang.String);
method public static void startActivity(android.app.Activity, android.content.Intent, android.os.Bundle);
method public static void startActivityForResult(android.app.Activity, android.content.Intent, int, android.os.Bundle);
+ method public static void startIntentSenderForResult(android.app.Activity, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public static void startPostponedEnterTransition(android.app.Activity);
}
@@ -89,8 +90,13 @@
method public void update(android.support.v4.app.ActivityOptionsCompat);
}
- public class AppOpsManagerCompat {
- ctor public AppOpsManagerCompat();
+ public class AppLaunchChecker {
+ ctor public AppLaunchChecker();
+ method public static boolean hasStartedFromLauncher(android.content.Context);
+ method public static void onActivityCreate(android.app.Activity);
+ }
+
+ public final class AppOpsManagerCompat {
method public static int noteOp(android.content.Context, java.lang.String, int, java.lang.String);
method public static int noteProxyOp(android.content.Context, java.lang.String, java.lang.String);
method public static java.lang.String permissionToOp(java.lang.String);
@@ -99,14 +105,7 @@
field public static final int MODE_IGNORED = 1; // 0x1
}
- abstract class BaseFragmentActivityDonut extends android.app.Activity {
- }
-
- abstract class BaseFragmentActivityHoneycomb extends android.support.v4.app.BaseFragmentActivityDonut {
- }
-
- public class BundleCompat {
- ctor public BundleCompat();
+ public final class BundleCompat {
method public static android.os.IBinder getBinder(android.os.Bundle, java.lang.String);
method public static void putBinder(android.os.Bundle, java.lang.String, android.os.IBinder);
}
@@ -178,6 +177,7 @@
method public void onActivityResult(int, int, android.content.Intent);
method public void onAttach(android.content.Context);
method public deprecated void onAttach(android.app.Activity);
+ method public void onAttachFragment(android.support.v4.app.Fragment);
method public void onConfigurationChanged(android.content.res.Configuration);
method public boolean onContextItemSelected(android.view.MenuItem);
method public void onCreate(android.os.Bundle);
@@ -193,9 +193,11 @@
method public void onInflate(android.content.Context, android.util.AttributeSet, android.os.Bundle);
method public deprecated void onInflate(android.app.Activity, android.util.AttributeSet, android.os.Bundle);
method public void onLowMemory();
+ method public void onMultiWindowModeChanged(boolean);
method public boolean onOptionsItemSelected(android.view.MenuItem);
method public void onOptionsMenuClosed(android.view.Menu);
method public void onPause();
+ method public void onPictureInPictureModeChanged(boolean);
method public void onPrepareOptionsMenu(android.view.Menu);
method public void onRequestPermissionsResult(int, java.lang.String[], int[]);
method public void onResume();
@@ -225,7 +227,10 @@
method public void setUserVisibleHint(boolean);
method public boolean shouldShowRequestPermissionRationale(java.lang.String);
method public void startActivity(android.content.Intent);
+ method public void startActivity(android.content.Intent, android.os.Bundle);
method public void startActivityForResult(android.content.Intent, int);
+ method public void startActivityForResult(android.content.Intent, int, android.os.Bundle);
+ method public void startIntentSenderForResult(android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void unregisterForContextMenu(android.view.View);
}
@@ -239,7 +244,7 @@
field public static final android.os.Parcelable.Creator<android.support.v4.app.Fragment.SavedState> CREATOR;
}
- public class FragmentActivity extends android.support.v4.app.BaseFragmentActivityHoneycomb implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
+ public class FragmentActivity extends android.app.Activity implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
ctor public FragmentActivity();
method public java.lang.Object getLastCustomNonConfigurationInstance();
method public android.support.v4.app.FragmentManager getSupportFragmentManager();
@@ -253,6 +258,8 @@
method public void setExitSharedElementCallback(android.support.v4.app.SharedElementCallback);
method public final void setSupportMediaController(android.support.v4.media.session.MediaControllerCompat);
method public void startActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int);
+ method public void startActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int, android.os.Bundle);
+ method public void startIntentSenderFromFragment(android.support.v4.app.Fragment, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void supportFinishAfterTransition();
method public void supportInvalidateOptionsMenu();
method public void supportPostponeEnterTransition();
@@ -277,9 +284,11 @@
method public void dispatchDestroy();
method public void dispatchDestroyView();
method public void dispatchLowMemory();
+ method public void dispatchMultiWindowModeChanged(boolean);
method public boolean dispatchOptionsItemSelected(android.view.MenuItem);
method public void dispatchOptionsMenuClosed(android.view.Menu);
method public void dispatchPause();
+ method public void dispatchPictureInPictureModeChanged(boolean);
method public boolean dispatchPrepareOptionsMenu(android.view.Menu);
method public void dispatchReallyStop();
method public void dispatchResume();
@@ -298,10 +307,12 @@
method public void noteStateNotSaved();
method public android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
method public void reportLoaderStart();
- method public void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+ method public deprecated void restoreAllState(android.os.Parcelable, java.util.List<android.support.v4.app.Fragment>);
+ method public void restoreAllState(android.os.Parcelable, android.support.v4.app.FragmentManagerNonConfig);
method public void restoreLoaderNonConfig(android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager>);
method public android.support.v4.util.SimpleArrayMap<java.lang.String, android.support.v4.app.LoaderManager> retainLoaderNonConfig();
- method public java.util.List<android.support.v4.app.Fragment> retainNonConfig();
+ method public android.support.v4.app.FragmentManagerNonConfig retainNestedNonConfig();
+ method public deprecated java.util.List<android.support.v4.app.Fragment> retainNonConfig();
method public android.os.Parcelable saveAllState();
}
@@ -318,6 +329,8 @@
method public boolean onShouldSaveFragmentState(android.support.v4.app.Fragment);
method public boolean onShouldShowRequestPermissionRationale(java.lang.String);
method public void onStartActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int);
+ method public void onStartActivityFromFragment(android.support.v4.app.Fragment, android.content.Intent, int, android.os.Bundle);
+ method public void onStartIntentSenderFromFragment(android.support.v4.app.Fragment, android.content.IntentSender, int, android.content.Intent, int, int, int, android.os.Bundle) throws android.content.IntentSender.SendIntentException;
method public void onSupportInvalidateOptionsMenu();
}
@@ -359,6 +372,9 @@
method public abstract void onBackStackChanged();
}
+ public class FragmentManagerNonConfig {
+ }
+
public abstract class FragmentPagerAdapter extends android.support.v4.view.PagerAdapter {
ctor public FragmentPagerAdapter(android.support.v4.app.FragmentManager);
method public abstract android.support.v4.app.Fragment getItem(int);
@@ -391,6 +407,8 @@
method public abstract android.support.v4.app.FragmentTransaction attach(android.support.v4.app.Fragment);
method public abstract int commit();
method public abstract int commitAllowingStateLoss();
+ method public abstract void commitNow();
+ method public abstract void commitNowAllowingStateLoss();
method public abstract android.support.v4.app.FragmentTransaction detach(android.support.v4.app.Fragment);
method public abstract android.support.v4.app.FragmentTransaction disallowAddToBackStack();
method public abstract android.support.v4.app.FragmentTransaction hide(android.support.v4.app.Fragment);
@@ -448,7 +466,7 @@
method public abstract void onLoaderReset(android.support.v4.content.Loader<D>);
}
- public class NavUtils {
+ public final class NavUtils {
method public static android.content.Intent getParentActivityIntent(android.app.Activity);
method public static android.content.Intent getParentActivityIntent(android.content.Context, java.lang.Class<?>) throws android.content.pm.PackageManager.NameNotFoundException;
method public static android.content.Intent getParentActivityIntent(android.content.Context, android.content.ComponentName) throws android.content.pm.PackageManager.NameNotFoundException;
@@ -479,6 +497,7 @@
field public static final java.lang.String CATEGORY_PROGRESS = "progress";
field public static final java.lang.String CATEGORY_PROMO = "promo";
field public static final java.lang.String CATEGORY_RECOMMENDATION = "recommendation";
+ field public static final java.lang.String CATEGORY_REMINDER = "reminder";
field public static final java.lang.String CATEGORY_SERVICE = "service";
field public static final java.lang.String CATEGORY_SOCIAL = "social";
field public static final java.lang.String CATEGORY_STATUS = "status";
@@ -492,15 +511,19 @@
field public static final java.lang.String EXTRA_BACKGROUND_IMAGE_URI = "android.backgroundImageUri";
field public static final java.lang.String EXTRA_BIG_TEXT = "android.bigText";
field public static final java.lang.String EXTRA_COMPACT_ACTIONS = "android.compactActions";
+ field public static final java.lang.String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
field public static final java.lang.String EXTRA_INFO_TEXT = "android.infoText";
field public static final java.lang.String EXTRA_LARGE_ICON = "android.largeIcon";
field public static final java.lang.String EXTRA_LARGE_ICON_BIG = "android.largeIcon.big";
field public static final java.lang.String EXTRA_MEDIA_SESSION = "android.mediaSession";
+ field public static final java.lang.String EXTRA_MESSAGES = "android.messages";
field public static final java.lang.String EXTRA_PEOPLE = "android.people";
field public static final java.lang.String EXTRA_PICTURE = "android.picture";
field public static final java.lang.String EXTRA_PROGRESS = "android.progress";
field public static final java.lang.String EXTRA_PROGRESS_INDETERMINATE = "android.progressIndeterminate";
field public static final java.lang.String EXTRA_PROGRESS_MAX = "android.progressMax";
+ field public static final java.lang.String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
+ field public static final java.lang.String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
field public static final java.lang.String EXTRA_SHOW_CHRONOMETER = "android.showChronometer";
field public static final java.lang.String EXTRA_SHOW_WHEN = "android.showWhen";
field public static final java.lang.String EXTRA_SMALL_ICON = "android.icon";
@@ -535,6 +558,7 @@
public static class NotificationCompat.Action {
ctor public NotificationCompat.Action(int, java.lang.CharSequence, android.app.PendingIntent);
method public android.app.PendingIntent getActionIntent();
+ method public boolean getAllowGeneratedReplies();
method public android.os.Bundle getExtras();
method public int getIcon();
method public android.support.v4.app.RemoteInput[] getRemoteInputs();
@@ -552,6 +576,7 @@
method public android.support.v4.app.NotificationCompat.Action build();
method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Extender);
method public android.os.Bundle getExtras();
+ method public android.support.v4.app.NotificationCompat.Action.Builder setAllowGeneratedReplies(boolean);
}
public static abstract interface NotificationCompat.Action.Extender {
@@ -565,11 +590,13 @@
method public android.support.v4.app.NotificationCompat.Action.Builder extend(android.support.v4.app.NotificationCompat.Action.Builder);
method public java.lang.CharSequence getCancelLabel();
method public java.lang.CharSequence getConfirmLabel();
+ method public boolean getHintLaunchesActivity();
method public java.lang.CharSequence getInProgressLabel();
method public boolean isAvailableOffline();
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setAvailableOffline(boolean);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setCancelLabel(java.lang.CharSequence);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setConfirmLabel(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Action.WearableExtender setHintLaunchesActivity(boolean);
method public android.support.v4.app.NotificationCompat.Action.WearableExtender setInProgressLabel(java.lang.CharSequence);
}
@@ -609,6 +636,9 @@
method public android.support.v4.app.NotificationCompat.Builder setContentIntent(android.app.PendingIntent);
method public android.support.v4.app.NotificationCompat.Builder setContentText(java.lang.CharSequence);
method public android.support.v4.app.NotificationCompat.Builder setContentTitle(java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.Builder setCustomBigContentView(android.widget.RemoteViews);
+ method public android.support.v4.app.NotificationCompat.Builder setCustomContentView(android.widget.RemoteViews);
+ method public android.support.v4.app.NotificationCompat.Builder setCustomHeadsUpContentView(android.widget.RemoteViews);
method public android.support.v4.app.NotificationCompat.Builder setDefaults(int);
method public android.support.v4.app.NotificationCompat.Builder setDeleteIntent(android.app.PendingIntent);
method public android.support.v4.app.NotificationCompat.Builder setExtras(android.os.Bundle);
@@ -624,6 +654,7 @@
method public android.support.v4.app.NotificationCompat.Builder setPriority(int);
method public android.support.v4.app.NotificationCompat.Builder setProgress(int, int, boolean);
method public android.support.v4.app.NotificationCompat.Builder setPublicVersion(android.app.Notification);
+ method public android.support.v4.app.NotificationCompat.Builder setRemoteInputHistory(java.lang.CharSequence[]);
method public android.support.v4.app.NotificationCompat.Builder setShowWhen(boolean);
method public android.support.v4.app.NotificationCompat.Builder setSmallIcon(int);
method public android.support.v4.app.NotificationCompat.Builder setSmallIcon(int, int);
@@ -684,6 +715,29 @@
method public android.support.v4.app.NotificationCompat.InboxStyle setSummaryText(java.lang.CharSequence);
}
+ public static class NotificationCompat.MessagingStyle extends android.support.v4.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MessagingStyle(java.lang.CharSequence);
+ method public void addCompatExtras(android.os.Bundle);
+ method public android.support.v4.app.NotificationCompat.MessagingStyle addMessage(java.lang.CharSequence, long, java.lang.CharSequence);
+ method public android.support.v4.app.NotificationCompat.MessagingStyle addMessage(android.support.v4.app.NotificationCompat.MessagingStyle.Message);
+ method public static android.support.v4.app.NotificationCompat.MessagingStyle extractMessagingStyleFromNotification(android.app.Notification);
+ method public java.lang.CharSequence getConversationTitle();
+ method public java.util.List<android.support.v4.app.NotificationCompat.MessagingStyle.Message> getMessages();
+ method public java.lang.CharSequence getUserDisplayName();
+ method public android.support.v4.app.NotificationCompat.MessagingStyle setConversationTitle(java.lang.CharSequence);
+ field public static final int MAXIMUM_RETAINED_MESSAGES = 25; // 0x19
+ }
+
+ public static final class NotificationCompat.MessagingStyle.Message {
+ ctor public NotificationCompat.MessagingStyle.Message(java.lang.CharSequence, long, java.lang.CharSequence);
+ method public java.lang.String getDataMimeType();
+ method public android.net.Uri getDataUri();
+ method public java.lang.CharSequence getSender();
+ method public java.lang.CharSequence getText();
+ method public long getTimestamp();
+ method public android.support.v4.app.NotificationCompat.MessagingStyle.Message setData(java.lang.String, android.net.Uri);
+ }
+
public static abstract class NotificationCompat.Style {
ctor public NotificationCompat.Style();
method public android.app.Notification build();
@@ -709,9 +763,12 @@
method public boolean getContentIntentAvailableOffline();
method public int getCustomContentHeight();
method public int getCustomSizePreset();
+ method public java.lang.String getDismissalId();
method public android.app.PendingIntent getDisplayIntent();
method public int getGravity();
+ method public boolean getHintAmbientBigPicture();
method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
method public boolean getHintHideIcon();
method public int getHintScreenTimeout();
method public boolean getHintShowBackgroundOnly();
@@ -724,9 +781,12 @@
method public android.support.v4.app.NotificationCompat.WearableExtender setContentIntentAvailableOffline(boolean);
method public android.support.v4.app.NotificationCompat.WearableExtender setCustomContentHeight(int);
method public android.support.v4.app.NotificationCompat.WearableExtender setCustomSizePreset(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setDismissalId(java.lang.String);
method public android.support.v4.app.NotificationCompat.WearableExtender setDisplayIntent(android.app.PendingIntent);
method public android.support.v4.app.NotificationCompat.WearableExtender setGravity(int);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintAmbientBigPicture(boolean);
method public android.support.v4.app.NotificationCompat.WearableExtender setHintAvoidBackgroundClipping(boolean);
+ method public android.support.v4.app.NotificationCompat.WearableExtender setHintContentIntentLaunchesActivity(boolean);
method public android.support.v4.app.NotificationCompat.WearableExtender setHintHideIcon(boolean);
method public android.support.v4.app.NotificationCompat.WearableExtender setHintScreenTimeout(int);
method public android.support.v4.app.NotificationCompat.WearableExtender setHintShowBackgroundOnly(boolean);
@@ -759,19 +819,28 @@
method public android.os.IBinder onBind(android.content.Intent);
}
- public class NotificationManagerCompat {
+ public final class NotificationManagerCompat {
+ method public boolean areNotificationsEnabled();
method public void cancel(int);
method public void cancel(java.lang.String, int);
method public void cancelAll();
method public static android.support.v4.app.NotificationManagerCompat from(android.content.Context);
method public static java.util.Set<java.lang.String> getEnabledListenerPackages(android.content.Context);
+ method public int getImportance();
method public void notify(int, android.app.Notification);
method public void notify(java.lang.String, int, android.app.Notification);
field public static final java.lang.String ACTION_BIND_SIDE_CHANNEL = "android.support.BIND_NOTIFICATION_SIDE_CHANNEL";
field public static final java.lang.String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ field public static final int IMPORTANCE_DEFAULT = 3; // 0x3
+ field public static final int IMPORTANCE_HIGH = 4; // 0x4
+ field public static final int IMPORTANCE_LOW = 2; // 0x2
+ field public static final int IMPORTANCE_MAX = 5; // 0x5
+ field public static final int IMPORTANCE_MIN = 1; // 0x1
+ field public static final int IMPORTANCE_NONE = 0; // 0x0
+ field public static final int IMPORTANCE_UNSPECIFIED = -1000; // 0xfffffc18
}
- public class RemoteInput extends android.support.v4.app.RemoteInputCompatBase.RemoteInput {
+ public final class RemoteInput extends android.support.v4.app.RemoteInputCompatBase.RemoteInput {
method public static void addResultsToIntent(android.support.v4.app.RemoteInput[], android.content.Intent, android.os.Bundle);
method public boolean getAllowFreeFormInput();
method public java.lang.CharSequence[] getChoices();
@@ -805,12 +874,11 @@
method protected abstract java.lang.String getResultKey();
}
- public class ServiceCompat {
+ public final class ServiceCompat {
field public static final int START_STICKY = 1; // 0x1
}
- public class ShareCompat {
- ctor public ShareCompat();
+ public final class ShareCompat {
method public static void configureMenuItem(android.view.MenuItem, android.support.v4.app.ShareCompat.IntentBuilder);
method public static void configureMenuItem(android.view.Menu, int, android.support.v4.app.ShareCompat.IntentBuilder);
method public static android.content.ComponentName getCallingActivity(android.app.Activity);
@@ -875,7 +943,7 @@
method public void onSharedElementStart(java.util.List<java.lang.String>, java.util.List<android.view.View>, java.util.List<android.view.View>);
}
- public class TaskStackBuilder implements java.lang.Iterable {
+ public final class TaskStackBuilder implements java.lang.Iterable {
method public android.support.v4.app.TaskStackBuilder addNextIntent(android.content.Intent);
method public android.support.v4.app.TaskStackBuilder addNextIntentWithParentStack(android.content.Intent);
method public android.support.v4.app.TaskStackBuilder addParentStack(android.app.Activity);
@@ -912,21 +980,24 @@
method public void setUpdateThrottle(long);
}
- public class ContentResolverCompat {
+ public final class ContentResolverCompat {
method public static android.database.Cursor query(android.content.ContentResolver, android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.support.v4.os.CancellationSignal);
}
public class ContextCompat {
ctor public ContextCompat();
method public static int checkSelfPermission(android.content.Context, java.lang.String);
+ method public static android.content.Context createDeviceProtectedStorageContext(android.content.Context);
method public static java.io.File getCodeCacheDir(android.content.Context);
method public static final int getColor(android.content.Context, int);
method public static final android.content.res.ColorStateList getColorStateList(android.content.Context, int);
+ method public static java.io.File getDataDir(android.content.Context);
method public static final android.graphics.drawable.Drawable getDrawable(android.content.Context, int);
method public static java.io.File[] getExternalCacheDirs(android.content.Context);
method public static java.io.File[] getExternalFilesDirs(android.content.Context, java.lang.String);
- method public static java.io.File getNoBackupFilesDir(android.content.Context);
+ method public static final java.io.File getNoBackupFilesDir(android.content.Context);
method public static java.io.File[] getObbDirs(android.content.Context);
+ method public static boolean isDeviceProtectedStorage(android.content.Context);
method public static boolean startActivities(android.content.Context, android.content.Intent[]);
method public static boolean startActivities(android.content.Context, android.content.Intent[], android.os.Bundle);
}
@@ -960,12 +1031,13 @@
method public int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]);
}
- public class IntentCompat {
+ public final class IntentCompat {
method public static android.content.Intent makeMainActivity(android.content.ComponentName);
method public static android.content.Intent makeMainSelectorActivity(java.lang.String, java.lang.String);
method public static android.content.Intent makeRestartActivityTask(android.content.ComponentName);
field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_AVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_AVAILABLE";
field public static final java.lang.String ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE = "android.intent.action.EXTERNAL_APPLICATIONS_UNAVAILABLE";
+ field public static final java.lang.String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
field public static final java.lang.String EXTRA_CHANGED_PACKAGE_LIST = "android.intent.extra.changed_package_list";
field public static final java.lang.String EXTRA_CHANGED_UID_LIST = "android.intent.extra.changed_uid_list";
field public static final java.lang.String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
@@ -1018,7 +1090,7 @@
method public abstract void onLoadComplete(android.support.v4.content.Loader<D>, D);
}
- public class LocalBroadcastManager {
+ public final class LocalBroadcastManager {
method public static android.support.v4.content.LocalBroadcastManager getInstance(android.content.Context);
method public void registerReceiver(android.content.BroadcastReceiver, android.content.IntentFilter);
method public boolean sendBroadcast(android.content.Intent);
@@ -1026,8 +1098,7 @@
method public void unregisterReceiver(android.content.BroadcastReceiver);
}
- public class ParallelExecutorCompat {
- ctor public ParallelExecutorCompat();
+ public final class ParallelExecutorCompat {
method public static java.util.concurrent.Executor getParallelExecutor();
}
@@ -1044,11 +1115,10 @@
public static abstract class PermissionChecker.PermissionResult implements java.lang.annotation.Annotation {
}
- public class SharedPreferencesCompat {
- ctor public SharedPreferencesCompat();
+ public final class SharedPreferencesCompat {
}
- public static class SharedPreferencesCompat.EditorCompat {
+ public static final class SharedPreferencesCompat.EditorCompat {
method public void apply(android.content.SharedPreferences.Editor);
method public static android.support.v4.content.SharedPreferencesCompat.EditorCompat getInstance();
}
@@ -1063,7 +1133,7 @@
package android.support.v4.content.pm {
- public class ActivityInfoCompat {
+ public final class ActivityInfoCompat {
field public static final int CONFIG_UI_MODE = 512; // 0x200
}
@@ -1071,8 +1141,14 @@
package android.support.v4.content.res {
- public class ResourcesCompat {
- ctor public ResourcesCompat();
+ public final class ConfigurationHelper {
+ method public static int getDensityDpi(android.content.res.Resources);
+ method public static int getScreenHeightDp(android.content.res.Resources);
+ method public static int getScreenWidthDp(android.content.res.Resources);
+ method public static int getSmallestScreenWidthDp(android.content.res.Resources);
+ }
+
+ public final class ResourcesCompat {
method public static int getColor(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public static android.content.res.ColorStateList getColorStateList(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
method public static android.graphics.drawable.Drawable getDrawable(android.content.res.Resources, int, android.content.res.Resources.Theme) throws android.content.res.Resources.NotFoundException;
@@ -1083,7 +1159,7 @@
package android.support.v4.database {
- public class DatabaseUtilsCompat {
+ public final class DatabaseUtilsCompat {
method public static java.lang.String[] appendSelectionArgs(java.lang.String[], java.lang.String[]);
method public static java.lang.String concatenateWhere(java.lang.String, java.lang.String);
}
@@ -1092,21 +1168,32 @@
package android.support.v4.graphics {
- public class BitmapCompat {
- ctor public BitmapCompat();
+ public final class BitmapCompat {
method public static int getAllocationByteCount(android.graphics.Bitmap);
method public static boolean hasMipMap(android.graphics.Bitmap);
method public static void setHasMipMap(android.graphics.Bitmap, boolean);
}
- public class ColorUtils {
+ public final class ColorUtils {
method public static int HSLToColor(float[]);
+ method public static int LABToColor(double, double, double);
+ method public static void LABToXYZ(double, double, double, double[]);
method public static void RGBToHSL(int, int, int, float[]);
+ method public static void RGBToLAB(int, int, int, double[]);
+ method public static void RGBToXYZ(int, int, int, double[]);
+ method public static int XYZToColor(double, double, double);
+ method public static void XYZToLAB(double, double, double, double[]);
+ method public static int blendARGB(int, int, float);
+ method public static void blendHSL(float[], float[], float, float[]);
+ method public static void blendLAB(double[], double[], double, double[]);
method public static double calculateContrast(int, int);
method public static double calculateLuminance(int);
method public static int calculateMinimumAlpha(int, int, float);
method public static void colorToHSL(int, float[]);
+ method public static void colorToLAB(int, double[]);
+ method public static void colorToXYZ(int, double[]);
method public static int compositeColors(int, int);
+ method public static double distanceEuclidean(double[], double[]);
method public static int setAlphaComponent(int, int);
}
@@ -1114,15 +1201,19 @@
package android.support.v4.graphics.drawable {
- public class DrawableCompat {
- ctor public DrawableCompat();
+ public final class DrawableCompat {
+ method public static void applyTheme(android.graphics.drawable.Drawable, android.content.res.Resources.Theme);
+ method public static boolean canApplyTheme(android.graphics.drawable.Drawable);
+ method public static int getAlpha(android.graphics.drawable.Drawable);
+ method public static android.graphics.ColorFilter getColorFilter(android.graphics.drawable.Drawable);
method public static int getLayoutDirection(android.graphics.drawable.Drawable);
+ method public static void inflate(android.graphics.drawable.Drawable, android.content.res.Resources, org.xmlpull.v1.XmlPullParser, android.util.AttributeSet, android.content.res.Resources.Theme) throws java.io.IOException, org.xmlpull.v1.XmlPullParserException;
method public static boolean isAutoMirrored(android.graphics.drawable.Drawable);
method public static void jumpToCurrentState(android.graphics.drawable.Drawable);
method public static void setAutoMirrored(android.graphics.drawable.Drawable, boolean);
method public static void setHotspot(android.graphics.drawable.Drawable, float, float);
method public static void setHotspotBounds(android.graphics.drawable.Drawable, int, int, int, int);
- method public static void setLayoutDirection(android.graphics.drawable.Drawable, int);
+ method public static boolean setLayoutDirection(android.graphics.drawable.Drawable, int);
method public static void setTint(android.graphics.drawable.Drawable, int);
method public static void setTintList(android.graphics.drawable.Drawable, android.content.res.ColorStateList);
method public static void setTintMode(android.graphics.drawable.Drawable, android.graphics.PorterDuff.Mode);
@@ -1152,8 +1243,7 @@
method public void setTargetDensity(int);
}
- public class RoundedBitmapDrawableFactory {
- ctor public RoundedBitmapDrawableFactory();
+ public final class RoundedBitmapDrawableFactory {
method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, android.graphics.Bitmap);
method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.lang.String);
method public static android.support.v4.graphics.drawable.RoundedBitmapDrawable create(android.content.res.Resources, java.io.InputStream);
@@ -1175,7 +1265,7 @@
package android.support.v4.hardware.fingerprint {
- public class FingerprintManagerCompat {
+ public final class FingerprintManagerCompat {
method public void authenticate(android.support.v4.hardware.fingerprint.FingerprintManagerCompat.CryptoObject, int, android.support.v4.os.CancellationSignal, android.support.v4.hardware.fingerprint.FingerprintManagerCompat.AuthenticationCallback, android.os.Handler);
method public static android.support.v4.hardware.fingerprint.FingerprintManagerCompat from(android.content.Context);
method public boolean hasEnrolledFingerprints();
@@ -1219,7 +1309,11 @@
method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
method public boolean isConnected();
method public void subscribe(java.lang.String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ method public void subscribe(java.lang.String, android.os.Bundle, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
method public void unsubscribe(java.lang.String);
+ method public void unsubscribe(java.lang.String, android.support.v4.media.MediaBrowserCompat.SubscriptionCallback);
+ field public static final java.lang.String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+ field public static final java.lang.String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
}
public static class MediaBrowserCompat.ConnectionCallback {
@@ -1252,17 +1346,22 @@
public static abstract class MediaBrowserCompat.SubscriptionCallback {
ctor public MediaBrowserCompat.SubscriptionCallback();
method public void onChildrenLoaded(java.lang.String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>);
+ method public void onChildrenLoaded(java.lang.String, java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>, android.os.Bundle);
method public void onError(java.lang.String);
+ method public void onError(java.lang.String, android.os.Bundle);
}
public abstract class MediaBrowserServiceCompat extends android.app.Service {
ctor public MediaBrowserServiceCompat();
method public void dump(java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
+ method public final android.os.Bundle getBrowserRootHints();
method public android.support.v4.media.session.MediaSessionCompat.Token getSessionToken();
method public void notifyChildrenChanged(java.lang.String);
+ method public void notifyChildrenChanged(java.lang.String, android.os.Bundle);
method public android.os.IBinder onBind(android.content.Intent);
method public abstract android.support.v4.media.MediaBrowserServiceCompat.BrowserRoot onGetRoot(java.lang.String, int, android.os.Bundle);
method public abstract void onLoadChildren(java.lang.String, android.support.v4.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>);
+ method public void onLoadChildren(java.lang.String, android.support.v4.media.MediaBrowserServiceCompat.Result<java.util.List<android.support.v4.media.MediaBrowserCompat.MediaItem>>, android.os.Bundle);
method public void onLoadItem(java.lang.String, android.support.v4.media.MediaBrowserServiceCompat.Result<android.support.v4.media.MediaBrowserCompat.MediaItem>);
method public void setSessionToken(android.support.v4.media.session.MediaSessionCompat.Token);
field public static final java.lang.String SERVICE_INTERFACE = "android.media.browse.MediaBrowserService";
@@ -1272,6 +1371,9 @@
ctor public MediaBrowserServiceCompat.BrowserRoot(java.lang.String, android.os.Bundle);
method public android.os.Bundle getExtras();
method public java.lang.String getRootId();
+ field public static final java.lang.String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+ field public static final java.lang.String EXTRA_RECENT = "android.service.media.extra.RECENT";
+ field public static final java.lang.String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
}
public static class MediaBrowserServiceCompat.Result {
@@ -1543,6 +1645,10 @@
method public abstract void playFromMediaId(java.lang.String, android.os.Bundle);
method public abstract void playFromSearch(java.lang.String, android.os.Bundle);
method public abstract void playFromUri(android.net.Uri, android.os.Bundle);
+ method public abstract void prepare();
+ method public abstract void prepareFromMediaId(java.lang.String, android.os.Bundle);
+ method public abstract void prepareFromSearch(java.lang.String, android.os.Bundle);
+ method public abstract void prepareFromUri(android.net.Uri, android.os.Bundle);
method public abstract void rewind();
method public abstract void seekTo(long);
method public abstract void sendCustomAction(android.support.v4.media.session.PlaybackStateCompat.CustomAction, android.os.Bundle);
@@ -1596,6 +1702,10 @@
method public void onPlayFromMediaId(java.lang.String, android.os.Bundle);
method public void onPlayFromSearch(java.lang.String, android.os.Bundle);
method public void onPlayFromUri(android.net.Uri, android.os.Bundle);
+ method public void onPrepare();
+ method public void onPrepareFromMediaId(java.lang.String, android.os.Bundle);
+ method public void onPrepareFromSearch(java.lang.String, android.os.Bundle);
+ method public void onPrepareFromUri(android.net.Uri, android.os.Bundle);
method public void onRewind();
method public void onSeekTo(long);
method public void onSetRating(android.support.v4.media.RatingCompat);
@@ -1664,6 +1774,10 @@
field public static final long ACTION_PLAY_FROM_SEARCH = 2048L; // 0x800L
field public static final long ACTION_PLAY_FROM_URI = 8192L; // 0x2000L
field public static final long ACTION_PLAY_PAUSE = 512L; // 0x200L
+ field public static final long ACTION_PREPARE = 16384L; // 0x4000L
+ field public static final long ACTION_PREPARE_FROM_MEDIA_ID = 32768L; // 0x8000L
+ field public static final long ACTION_PREPARE_FROM_SEARCH = 65536L; // 0x10000L
+ field public static final long ACTION_PREPARE_FROM_URI = 131072L; // 0x20000L
field public static final long ACTION_REWIND = 8L; // 0x8L
field public static final long ACTION_SEEK_TO = 256L; // 0x100L
field public static final long ACTION_SET_RATING = 128L; // 0x80L
@@ -1724,20 +1838,20 @@
package android.support.v4.net {
- public class ConnectivityManagerCompat {
- ctor public ConnectivityManagerCompat();
+ public final class ConnectivityManagerCompat {
method public static android.net.NetworkInfo getNetworkInfoFromBroadcast(android.net.ConnectivityManager, android.content.Intent);
method public static boolean isActiveNetworkMetered(android.net.ConnectivityManager);
}
- public class TrafficStatsCompat {
- ctor public TrafficStatsCompat();
+ public final class TrafficStatsCompat {
method public static void clearThreadStatsTag();
method public static int getThreadStatsTag();
method public static void incrementOperationCount(int);
method public static void incrementOperationCount(int, int);
method public static void setThreadStatsTag(int);
+ method public static void tagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
method public static void tagSocket(java.net.Socket) throws java.net.SocketException;
+ method public static void untagDatagramSocket(java.net.DatagramSocket) throws java.net.SocketException;
method public static void untagSocket(java.net.Socket) throws java.net.SocketException;
}
@@ -1745,11 +1859,14 @@
package android.support.v4.os {
- public class AsyncTaskCompat {
- ctor public AsyncTaskCompat();
+ public final class AsyncTaskCompat {
method public static android.os.AsyncTask<Params, Progress, Result> executeParallel(android.os.AsyncTask<Params, Progress, Result>, Params...);
}
+ public class BuildCompat {
+ method public static boolean isAtLeastN();
+ }
+
public final class CancellationSignal {
ctor public CancellationSignal();
method public void cancel();
@@ -1763,8 +1880,7 @@
method public abstract void onCancel();
}
- public class EnvironmentCompat {
- ctor public EnvironmentCompat();
+ public final class EnvironmentCompat {
method public static java.lang.String getStorageState(java.io.File);
field public static final java.lang.String MEDIA_UNKNOWN = "unknown";
}
@@ -1774,8 +1890,7 @@
ctor public OperationCanceledException(java.lang.String);
}
- public class ParcelableCompat {
- ctor public ParcelableCompat();
+ public final class ParcelableCompat {
method public static android.os.Parcelable.Creator<T> newCreator(android.support.v4.os.ParcelableCompatCreatorCallbacks<T>);
}
@@ -1784,12 +1899,15 @@
method public abstract T[] newArray(int);
}
- public class TraceCompat {
- ctor public TraceCompat();
+ public final class TraceCompat {
method public static void beginSection(java.lang.String);
method public static void endSection();
}
+ public class UserManagerCompat {
+ method public static boolean isUserUnlocked(android.content.Context);
+ }
+
}
package android.support.v4.print {
@@ -1873,8 +1991,7 @@
method public android.support.v4.text.BidiFormatter.Builder stereoReset(boolean);
}
- public class ICUCompat {
- ctor public ICUCompat();
+ public final class ICUCompat {
method public static java.lang.String maximizeAndGetScript(java.util.Locale);
}
@@ -1883,8 +2000,7 @@
method public abstract boolean isRtl(java.lang.CharSequence, int, int);
}
- public class TextDirectionHeuristicsCompat {
- ctor public TextDirectionHeuristicsCompat();
+ public final class TextDirectionHeuristicsCompat {
field public static final android.support.v4.text.TextDirectionHeuristicCompat ANYRTL_LTR;
field public static final android.support.v4.text.TextDirectionHeuristicCompat FIRSTSTRONG_LTR;
field public static final android.support.v4.text.TextDirectionHeuristicCompat FIRSTSTRONG_RTL;
@@ -1893,8 +2009,7 @@
field public static final android.support.v4.text.TextDirectionHeuristicCompat RTL;
}
- public class TextUtilsCompat {
- ctor public TextUtilsCompat();
+ public final class TextUtilsCompat {
method public static int getLayoutDirectionFromLocale(java.util.Locale);
method public static java.lang.String htmlEncode(java.lang.String);
field public static final java.util.Locale ROOT;
@@ -2075,6 +2190,17 @@
package android.support.v4.view {
+ public abstract class AbsSavedState implements android.os.Parcelable {
+ ctor protected AbsSavedState(android.os.Parcelable);
+ ctor protected AbsSavedState(android.os.Parcel);
+ ctor protected AbsSavedState(android.os.Parcel, java.lang.ClassLoader);
+ method public int describeContents();
+ method public final android.os.Parcelable getSuperState();
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.support.v4.view.AbsSavedState> CREATOR;
+ field public static final android.support.v4.view.AbsSavedState EMPTY_STATE;
+ }
+
public class AccessibilityDelegateCompat {
ctor public AccessibilityDelegateCompat();
method public boolean dispatchPopulateAccessibilityEvent(android.view.View, android.view.accessibility.AccessibilityEvent);
@@ -2106,7 +2232,16 @@
method public abstract void onActionProviderVisibilityChanged(boolean);
}
- public class GestureDetectorCompat {
+ public final class AsyncLayoutInflater {
+ ctor public AsyncLayoutInflater(android.content.Context);
+ method public void inflate(int, android.view.ViewGroup, android.support.v4.view.AsyncLayoutInflater.OnInflateFinishedListener);
+ }
+
+ public static abstract interface AsyncLayoutInflater.OnInflateFinishedListener {
+ method public abstract void onInflateFinished(android.view.View, int, android.view.ViewGroup);
+ }
+
+ public final class GestureDetectorCompat {
ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener);
ctor public GestureDetectorCompat(android.content.Context, android.view.GestureDetector.OnGestureListener, android.os.Handler);
method public boolean isLongpressEnabled();
@@ -2115,8 +2250,7 @@
method public void setOnDoubleTapListener(android.view.GestureDetector.OnDoubleTapListener);
}
- public class GravityCompat {
- ctor public GravityCompat();
+ public final class GravityCompat {
method public static void apply(int, int, int, android.graphics.Rect, android.graphics.Rect, int);
method public static void apply(int, int, int, android.graphics.Rect, int, int, android.graphics.Rect, int);
method public static void applyDisplay(int, android.graphics.Rect, android.graphics.Rect, int);
@@ -2127,8 +2261,7 @@
field public static final int START = 8388611; // 0x800003
}
- public class InputDeviceCompat {
- ctor public InputDeviceCompat();
+ public final class InputDeviceCompat {
field public static final int SOURCE_ANY = -256; // 0xffffff00
field public static final int SOURCE_CLASS_BUTTON = 1; // 0x1
field public static final int SOURCE_CLASS_JOYSTICK = 16; // 0x10
@@ -2151,12 +2284,12 @@
field public static final int SOURCE_UNKNOWN = 0; // 0x0
}
- public class KeyEventCompat {
- ctor public KeyEventCompat();
+ public final class KeyEventCompat {
method public static boolean dispatch(android.view.KeyEvent, android.view.KeyEvent.Callback, java.lang.Object, java.lang.Object);
method public static java.lang.Object getKeyDispatcherState(android.view.View);
method public static boolean hasModifiers(android.view.KeyEvent, int);
method public static boolean hasNoModifiers(android.view.KeyEvent);
+ method public static boolean isCtrlPressed(android.view.KeyEvent);
method public static boolean isTracking(android.view.KeyEvent);
method public static boolean metaStateHasModifiers(int, int);
method public static boolean metaStateHasNoModifiers(int);
@@ -2164,7 +2297,8 @@
method public static void startTracking(android.view.KeyEvent);
}
- public class LayoutInflaterCompat {
+ public final class LayoutInflaterCompat {
+ method public static android.support.v4.view.LayoutInflaterFactory getFactory(android.view.LayoutInflater);
method public static void setFactory(android.view.LayoutInflater, android.support.v4.view.LayoutInflaterFactory);
}
@@ -2172,8 +2306,7 @@
method public abstract android.view.View onCreateView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
}
- public class MarginLayoutParamsCompat {
- ctor public MarginLayoutParamsCompat();
+ public final class MarginLayoutParamsCompat {
method public static int getLayoutDirection(android.view.ViewGroup.MarginLayoutParams);
method public static int getMarginEnd(android.view.ViewGroup.MarginLayoutParams);
method public static int getMarginStart(android.view.ViewGroup.MarginLayoutParams);
@@ -2184,13 +2317,11 @@
method public static void setMarginStart(android.view.ViewGroup.MarginLayoutParams, int);
}
- public class MenuCompat {
- ctor public MenuCompat();
+ public final class MenuCompat {
method public static deprecated void setShowAsAction(android.view.MenuItem, int);
}
- public class MenuItemCompat {
- ctor public MenuItemCompat();
+ public final class MenuItemCompat {
method public static boolean collapseActionView(android.view.MenuItem);
method public static boolean expandActionView(android.view.MenuItem);
method public static android.support.v4.view.ActionProvider getActionProvider(android.view.MenuItem);
@@ -2213,18 +2344,19 @@
method public abstract boolean onMenuItemActionExpand(android.view.MenuItem);
}
- public class MotionEventCompat {
- ctor public MotionEventCompat();
+ public final class MotionEventCompat {
method public static int findPointerIndex(android.view.MotionEvent, int);
method public static int getActionIndex(android.view.MotionEvent);
method public static int getActionMasked(android.view.MotionEvent);
method public static float getAxisValue(android.view.MotionEvent, int);
method public static float getAxisValue(android.view.MotionEvent, int, int);
+ method public static int getButtonState(android.view.MotionEvent);
method public static int getPointerCount(android.view.MotionEvent);
method public static int getPointerId(android.view.MotionEvent, int);
method public static int getSource(android.view.MotionEvent);
method public static float getX(android.view.MotionEvent, int);
method public static float getY(android.view.MotionEvent, int);
+ method public static boolean isFromSource(android.view.MotionEvent, int);
field public static final int ACTION_HOVER_ENTER = 9; // 0x9
field public static final int ACTION_HOVER_EXIT = 10; // 0xa
field public static final int ACTION_HOVER_MOVE = 7; // 0x7
@@ -2259,6 +2391,8 @@
field public static final int AXIS_LTRIGGER = 17; // 0x11
field public static final int AXIS_ORIENTATION = 8; // 0x8
field public static final int AXIS_PRESSURE = 2; // 0x2
+ field public static final int AXIS_RELATIVE_X = 27; // 0x1b
+ field public static final int AXIS_RELATIVE_Y = 28; // 0x1c
field public static final int AXIS_RTRIGGER = 18; // 0x12
field public static final int AXIS_RUDDER = 20; // 0x14
field public static final int AXIS_RX = 12; // 0xc
@@ -2276,6 +2410,7 @@
field public static final int AXIS_X = 0; // 0x0
field public static final int AXIS_Y = 1; // 0x1
field public static final int AXIS_Z = 11; // 0xb
+ field public static final int BUTTON_PRIMARY = 1; // 0x1
}
public abstract interface NestedScrollingChild {
@@ -2375,7 +2510,36 @@
method public void setTextSpacing(int);
}
- public class ScaleGestureDetectorCompat {
+ public final class PointerIconCompat {
+ method public static android.support.v4.view.PointerIconCompat create(android.graphics.Bitmap, float, float);
+ method public static android.support.v4.view.PointerIconCompat getSystemIcon(android.content.Context, int);
+ method public static android.support.v4.view.PointerIconCompat load(android.content.res.Resources, int);
+ field public static final int TYPE_ALIAS = 1010; // 0x3f2
+ field public static final int TYPE_ALL_SCROLL = 1013; // 0x3f5
+ field public static final int TYPE_ARROW = 1000; // 0x3e8
+ field public static final int TYPE_CELL = 1006; // 0x3ee
+ field public static final int TYPE_CONTEXT_MENU = 1001; // 0x3e9
+ field public static final int TYPE_COPY = 1011; // 0x3f3
+ field public static final int TYPE_CROSSHAIR = 1007; // 0x3ef
+ field public static final int TYPE_DEFAULT = 1000; // 0x3e8
+ field public static final int TYPE_GRAB = 1020; // 0x3fc
+ field public static final int TYPE_GRABBING = 1021; // 0x3fd
+ field public static final int TYPE_HAND = 1002; // 0x3ea
+ field public static final int TYPE_HELP = 1003; // 0x3eb
+ field public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014; // 0x3f6
+ field public static final int TYPE_NO_DROP = 1012; // 0x3f4
+ field public static final int TYPE_NULL = 0; // 0x0
+ field public static final int TYPE_TEXT = 1008; // 0x3f0
+ field public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017; // 0x3f9
+ field public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016; // 0x3f8
+ field public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015; // 0x3f7
+ field public static final int TYPE_VERTICAL_TEXT = 1009; // 0x3f1
+ field public static final int TYPE_WAIT = 1004; // 0x3ec
+ field public static final int TYPE_ZOOM_IN = 1018; // 0x3fa
+ field public static final int TYPE_ZOOM_OUT = 1019; // 0x3fb
+ }
+
+ public final class ScaleGestureDetectorCompat {
method public static boolean isQuickScaleEnabled(java.lang.Object);
method public static void setQuickScaleEnabled(java.lang.Object, boolean);
}
@@ -2396,14 +2560,13 @@
method public abstract void setSupportBackgroundTintMode(android.graphics.PorterDuff.Mode);
}
- public class VelocityTrackerCompat {
- ctor public VelocityTrackerCompat();
+ public final class VelocityTrackerCompat {
method public static float getXVelocity(android.view.VelocityTracker, int);
method public static float getYVelocity(android.view.VelocityTracker, int);
}
public class ViewCompat {
- ctor public ViewCompat();
+ ctor protected ViewCompat();
method public static android.support.v4.view.ViewPropertyAnimatorCompat animate(android.view.View);
method public static boolean canScrollHorizontally(android.view.View, int);
method public static boolean canScrollVertically(android.view.View, int);
@@ -2427,6 +2590,7 @@
method public static int getLabelFor(android.view.View);
method public static int getLayerType(android.view.View);
method public static int getLayoutDirection(android.view.View);
+ method public static android.graphics.Matrix getMatrix(android.view.View);
method public static int getMeasuredHeightAndState(android.view.View);
method public static int getMeasuredState(android.view.View);
method public static int getMeasuredWidthAndState(android.view.View);
@@ -2458,7 +2622,9 @@
method public static boolean hasOverlappingRendering(android.view.View);
method public static boolean hasTransientState(android.view.View);
method public static boolean isAttachedToWindow(android.view.View);
+ method public static boolean isInLayout(android.view.View);
method public static boolean isLaidOut(android.view.View);
+ method public static boolean isLayoutDirectionResolved(android.view.View);
method public static boolean isNestedScrollingEnabled(android.view.View);
method public static boolean isOpaque(android.view.View);
method public static boolean isPaddingRelative(android.view.View);
@@ -2498,6 +2664,7 @@
method public static void setPaddingRelative(android.view.View, int, int, int, int);
method public static void setPivotX(android.view.View, float);
method public static void setPivotY(android.view.View, float);
+ method public static void setPointerIcon(android.view.View, android.support.v4.view.PointerIconCompat);
method public static void setRotation(android.view.View, float);
method public static void setRotationX(android.view.View, float);
method public static void setRotationY(android.view.View, float);
@@ -2512,6 +2679,7 @@
method public static void setTranslationZ(android.view.View, float);
method public static void setX(android.view.View, float);
method public static void setY(android.view.View, float);
+ method public static void setZ(android.view.View, float);
method public static boolean startNestedScroll(android.view.View, int);
method public static void stopNestedScroll(android.view.View);
field public static final int ACCESSIBILITY_LIVE_REGION_ASSERTIVE = 2; // 0x2
@@ -2546,13 +2714,12 @@
field public static final int SCROLL_INDICATOR_TOP = 1; // 0x1
}
- public class ViewConfigurationCompat {
- ctor public ViewConfigurationCompat();
+ public final class ViewConfigurationCompat {
method public static int getScaledPagingTouchSlop(android.view.ViewConfiguration);
method public static boolean hasPermanentMenuKey(android.view.ViewConfiguration);
}
- public class ViewGroupCompat {
+ public final class ViewGroupCompat {
method public static int getLayoutMode(android.view.ViewGroup);
method public static int getNestedScrollAxes(android.view.ViewGroup);
method public static boolean isTransitionGroup(android.view.ViewGroup);
@@ -2567,6 +2734,7 @@
public class ViewPager extends android.view.ViewGroup {
ctor public ViewPager(android.content.Context);
ctor public ViewPager(android.content.Context, android.util.AttributeSet);
+ method public void addOnAdapterChangeListener(android.support.v4.view.ViewPager.OnAdapterChangeListener);
method public void addOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener);
method public boolean arrowScroll(int);
method public boolean beginFakeDrag();
@@ -2584,6 +2752,7 @@
method protected void onPageScrolled(int, float, int);
method public void onRestoreInstanceState(android.os.Parcelable);
method public android.os.Parcelable onSaveInstanceState();
+ method public void removeOnAdapterChangeListener(android.support.v4.view.ViewPager.OnAdapterChangeListener);
method public void removeOnPageChangeListener(android.support.v4.view.ViewPager.OnPageChangeListener);
method public void setAdapter(android.support.v4.view.PagerAdapter);
method public void setCurrentItem(int);
@@ -2599,6 +2768,9 @@
field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
}
+ public static abstract class ViewPager.DecorView implements java.lang.annotation.Annotation {
+ }
+
public static class ViewPager.LayoutParams extends android.view.ViewGroup.LayoutParams {
ctor public ViewPager.LayoutParams();
ctor public ViewPager.LayoutParams(android.content.Context, android.util.AttributeSet);
@@ -2606,6 +2778,10 @@
field public boolean isDecor;
}
+ public static abstract interface ViewPager.OnAdapterChangeListener {
+ method public abstract void onAdapterChanged(android.support.v4.view.ViewPager, android.support.v4.view.PagerAdapter, android.support.v4.view.PagerAdapter);
+ }
+
public static abstract interface ViewPager.OnPageChangeListener {
method public abstract void onPageScrollStateChanged(int);
method public abstract void onPageScrolled(int, float, int);
@@ -2616,7 +2792,7 @@
method public abstract void transformPage(android.view.View, float);
}
- public static class ViewPager.SavedState extends android.view.View.BaseSavedState {
+ public static class ViewPager.SavedState extends android.support.v4.view.AbsSavedState {
ctor public ViewPager.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.v4.view.ViewPager.SavedState> CREATOR;
}
@@ -2628,7 +2804,7 @@
method public void onPageSelected(int);
}
- public class ViewParentCompat {
+ public final class ViewParentCompat {
method public static void notifySubtreeAccessibilityStateChanged(android.view.ViewParent, android.view.View, android.view.View, int);
method public static boolean onNestedFling(android.view.ViewParent, android.view.View, float, float, boolean);
method public static boolean onNestedPreFling(android.view.ViewParent, android.view.View, float, float);
@@ -2640,7 +2816,7 @@
method public static boolean requestSendAccessibilityEvent(android.view.ViewParent, android.view.View, android.view.accessibility.AccessibilityEvent);
}
- public class ViewPropertyAnimatorCompat {
+ public final class ViewPropertyAnimatorCompat {
method public android.support.v4.view.ViewPropertyAnimatorCompat alpha(float);
method public android.support.v4.view.ViewPropertyAnimatorCompat alphaBy(float);
method public void cancel();
@@ -2697,8 +2873,7 @@
method public abstract void onAnimationUpdate(android.view.View);
}
- public class WindowCompat {
- ctor public WindowCompat();
+ public final class WindowCompat {
field public static final int FEATURE_ACTION_BAR = 8; // 0x8
field public static final int FEATURE_ACTION_BAR_OVERLAY = 9; // 0x9
field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
@@ -2728,7 +2903,7 @@
package android.support.v4.view.accessibility {
- public class AccessibilityEventCompat {
+ public final class AccessibilityEventCompat {
method public static void appendRecord(android.view.accessibility.AccessibilityEvent, android.support.v4.view.accessibility.AccessibilityRecordCompat);
method public static android.support.v4.view.accessibility.AccessibilityRecordCompat asRecord(android.view.accessibility.AccessibilityEvent);
method public static int getContentChangeTypes(android.view.accessibility.AccessibilityEvent);
@@ -2757,8 +2932,7 @@
field public static final int TYPE_WINDOW_CONTENT_CHANGED = 2048; // 0x800
}
- public class AccessibilityManagerCompat {
- ctor public AccessibilityManagerCompat();
+ public final class AccessibilityManagerCompat {
method public static boolean addAccessibilityStateChangeListener(android.view.accessibility.AccessibilityManager, android.support.v4.view.accessibility.AccessibilityManagerCompat.AccessibilityStateChangeListenerCompat);
method public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getEnabledAccessibilityServiceList(android.view.accessibility.AccessibilityManager, int);
method public static java.util.List<android.accessibilityservice.AccessibilityServiceInfo> getInstalledAccessibilityServiceList(android.view.accessibility.AccessibilityManager);
@@ -2792,6 +2966,7 @@
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionInfoCompat getCollectionInfo();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat getCollectionItemInfo();
method public java.lang.CharSequence getContentDescription();
+ method public int getDrawingOrder();
method public java.lang.CharSequence getError();
method public android.os.Bundle getExtras();
method public java.lang.Object getInfo();
@@ -2804,6 +2979,7 @@
method public java.lang.CharSequence getPackageName();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getParent();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat getRangeInfo();
+ method public java.lang.CharSequence getRoleDescription();
method public java.lang.CharSequence getText();
method public int getTextSelectionEnd();
method public int getTextSelectionStart();
@@ -2822,6 +2998,7 @@
method public boolean isEnabled();
method public boolean isFocusable();
method public boolean isFocused();
+ method public boolean isImportantForAccessibility();
method public boolean isLongClickable();
method public boolean isMultiLine();
method public boolean isPassword();
@@ -2852,11 +3029,13 @@
method public void setContentDescription(java.lang.CharSequence);
method public void setContentInvalid(boolean);
method public void setDismissable(boolean);
+ method public void setDrawingOrder(int);
method public void setEditable(boolean);
method public void setEnabled(boolean);
method public void setError(java.lang.CharSequence);
method public void setFocusable(boolean);
method public void setFocused(boolean);
+ method public void setImportantForAccessibility(boolean);
method public void setInputType(int);
method public void setLabelFor(android.view.View);
method public void setLabelFor(android.view.View, int);
@@ -2872,6 +3051,7 @@
method public void setParent(android.view.View, int);
method public void setPassword(boolean);
method public void setRangeInfo(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.RangeInfoCompat);
+ method public void setRoleDescription(java.lang.CharSequence);
method public void setScrollable(boolean);
method public void setSelected(boolean);
method public void setSource(android.view.View);
@@ -3041,6 +3221,7 @@
}
public class AccessibilityWindowInfoCompat {
+ method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getAnchor();
method public void getBoundsInScreen(android.graphics.Rect);
method public android.support.v4.view.accessibility.AccessibilityWindowInfoCompat getChild(int);
method public int getChildCount();
@@ -3048,6 +3229,7 @@
method public int getLayer();
method public android.support.v4.view.accessibility.AccessibilityWindowInfoCompat getParent();
method public android.support.v4.view.accessibility.AccessibilityNodeInfoCompat getRoot();
+ method public java.lang.CharSequence getTitle();
method public int getType();
method public boolean isAccessibilityFocused();
method public boolean isActive();
@@ -3082,7 +3264,7 @@
method public float getInterpolation(float);
}
- public class PathInterpolatorCompat {
+ public final class PathInterpolatorCompat {
method public static android.view.animation.Interpolator create(android.graphics.Path);
method public static android.view.animation.Interpolator create(float, float);
method public static android.view.animation.Interpolator create(float, float, float, float);
@@ -3165,8 +3347,11 @@
ctor public DrawerLayout(android.content.Context);
ctor public DrawerLayout(android.content.Context, android.util.AttributeSet);
ctor public DrawerLayout(android.content.Context, android.util.AttributeSet, int);
+ method public void addDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
method public void closeDrawer(android.view.View);
+ method public void closeDrawer(android.view.View, boolean);
method public void closeDrawer(int);
+ method public void closeDrawer(int, boolean);
method public void closeDrawers();
method public float getDrawerElevation();
method public int getDrawerLockMode(int);
@@ -3180,9 +3365,12 @@
method public void onDraw(android.graphics.Canvas);
method protected void onLayout(boolean, int, int, int, int);
method public void openDrawer(android.view.View);
+ method public void openDrawer(android.view.View, boolean);
method public void openDrawer(int);
+ method public void openDrawer(int, boolean);
+ method public void removeDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
method public void setDrawerElevation(float);
- method public void setDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
+ method public deprecated void setDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
method public void setDrawerLockMode(int);
method public void setDrawerLockMode(int, int);
method public void setDrawerLockMode(int, android.view.View);
@@ -3219,8 +3407,8 @@
field public int gravity;
}
- protected static class DrawerLayout.SavedState extends android.view.View.BaseSavedState {
- ctor public DrawerLayout.SavedState(android.os.Parcel);
+ protected static class DrawerLayout.SavedState extends android.support.v4.view.AbsSavedState {
+ ctor public DrawerLayout.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public DrawerLayout.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.v4.widget.DrawerLayout.SavedState> CREATOR;
}
@@ -3233,7 +3421,7 @@
method public void onDrawerStateChanged(int);
}
- public class EdgeEffectCompat {
+ public final class EdgeEffectCompat {
ctor public EdgeEffectCompat(android.content.Context);
method public boolean draw(android.graphics.Canvas);
method public void finish();
@@ -3247,22 +3435,31 @@
public abstract class ExploreByTouchHelper extends android.support.v4.view.AccessibilityDelegateCompat {
ctor public ExploreByTouchHelper(android.view.View);
- method public boolean dispatchHoverEvent(android.view.MotionEvent);
- method public int getFocusedVirtualView();
+ method public final boolean clearKeyboardFocusForVirtualView(int);
+ method public final boolean dispatchHoverEvent(android.view.MotionEvent);
+ method public final boolean dispatchKeyEvent(android.view.KeyEvent);
+ method public final int getAccessibilityFocusedVirtualViewId();
+ method public deprecated int getFocusedVirtualView();
+ method public final int getKeyboardFocusedVirtualViewId();
method protected abstract int getVirtualViewAt(float, float);
method protected abstract void getVisibleVirtualViews(java.util.List<java.lang.Integer>);
- method public void invalidateRoot();
- method public void invalidateVirtualView(int);
+ method public final void invalidateRoot();
+ method public final void invalidateVirtualView(int);
+ method public final void invalidateVirtualView(int, int);
+ method public final void onFocusChanged(boolean, int, android.graphics.Rect);
method protected abstract boolean onPerformActionForVirtualView(int, int, android.os.Bundle);
- method protected abstract void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
- method public void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method protected void onPopulateEventForHost(android.view.accessibility.AccessibilityEvent);
+ method protected void onPopulateEventForVirtualView(int, android.view.accessibility.AccessibilityEvent);
+ method protected void onPopulateNodeForHost(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
method protected abstract void onPopulateNodeForVirtualView(int, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
- method public boolean sendEventForVirtualView(int, int);
+ method protected void onVirtualViewKeyboardFocusChanged(int, boolean);
+ method public final boolean requestKeyboardFocusForVirtualView(int);
+ method public final boolean sendEventForVirtualView(int, int);
field public static final int HOST_ID = -1; // 0xffffffff
field public static final int INVALID_ID = -2147483648; // 0x80000000
}
- public class ListPopupWindowCompat {
+ public final class ListPopupWindowCompat {
method public static android.view.View.OnTouchListener createDragToOpenListener(java.lang.Object, android.view.View);
}
@@ -3273,6 +3470,10 @@
method public void scrollTargetBy(int, int);
}
+ public final class ListViewCompat {
+ method public static void scrollListBy(android.widget.ListView, int);
+ }
+
public class NestedScrollView extends android.widget.FrameLayout implements android.support.v4.view.NestedScrollingChild android.support.v4.view.NestedScrollingParent android.support.v4.view.ScrollingView {
ctor public NestedScrollView(android.content.Context);
ctor public NestedScrollView(android.content.Context, android.util.AttributeSet);
@@ -3298,11 +3499,11 @@
method public abstract void onScrollChange(android.support.v4.widget.NestedScrollView, int, int, int, int);
}
- public class PopupMenuCompat {
+ public final class PopupMenuCompat {
method public static android.view.View.OnTouchListener getDragToOpenListener(java.lang.Object);
}
- public class PopupWindowCompat {
+ public final class PopupWindowCompat {
method public static boolean getOverlapAnchor(android.widget.PopupWindow);
method public static int getWindowLayoutType(android.widget.PopupWindow);
method public static void setOverlapAnchor(android.widget.PopupWindow, boolean);
@@ -3312,14 +3513,14 @@
public abstract class ResourceCursorAdapter extends android.support.v4.widget.CursorAdapter {
ctor public deprecated ResourceCursorAdapter(android.content.Context, int, android.database.Cursor);
- ctor public ResourceCursorAdapter(android.content.Context, int, android.database.Cursor, boolean);
+ ctor public deprecated ResourceCursorAdapter(android.content.Context, int, android.database.Cursor, boolean);
ctor public ResourceCursorAdapter(android.content.Context, int, android.database.Cursor, int);
method public android.view.View newView(android.content.Context, android.database.Cursor, android.view.ViewGroup);
method public void setDropDownViewResource(int);
method public void setViewResource(int);
}
- public class ScrollerCompat {
+ public final class ScrollerCompat {
method public void abortAnimation();
method public boolean computeScrollOffset();
method public static android.support.v4.widget.ScrollerCompat create(android.content.Context);
@@ -3340,7 +3541,7 @@
method public void startScroll(int, int, int, int, int);
}
- public class SearchViewCompat {
+ public final class SearchViewCompat {
method public static java.lang.CharSequence getQuery(android.view.View);
method public static boolean isIconified(android.view.View);
method public static boolean isQueryRefinementEnabled(android.view.View);
@@ -3480,7 +3681,7 @@
method public abstract void onRefresh();
}
- public class TextViewCompat {
+ public final class TextViewCompat {
method public static int getMaxLines(android.widget.TextView);
method public static int getMinLines(android.widget.TextView);
method public static void setCompoundDrawablesRelative(android.widget.TextView, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable, android.graphics.drawable.Drawable);
diff --git a/v4/api/removed.txt b/v4/api/removed.txt
index e69de29..98f8c44 100644
--- a/v4/api/removed.txt
+++ b/v4/api/removed.txt
@@ -0,0 +1,18 @@
+package android.support.v4.content {
+
+ public class ContextCompat {
+ method public static deprecated android.content.Context createDeviceEncryptedStorageContext(android.content.Context);
+ method public static deprecated boolean isDeviceEncryptedStorage(android.content.Context);
+ }
+
+}
+
+package android.support.v4.os {
+
+ public class UserManagerCompat {
+ method public static deprecated boolean isUserRunningAndLocked(android.content.Context);
+ method public static deprecated boolean isUserRunningAndUnlocked(android.content.Context);
+ }
+
+}
+
diff --git a/v4/api20/android/support/v4/app/NotificationCompatApi20.java b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
index fcd7576..036fb24 100644
--- a/v4/api20/android/support/v4/app/NotificationCompatApi20.java
+++ b/v4/api20/android/support/v4/app/NotificationCompatApi20.java
@@ -32,6 +32,8 @@
NotificationBuilderWithActions {
private Notification.Builder b;
private Bundle mExtras;
+ private RemoteViews mContentView;
+ private RemoteViews mBigContentView;
public Builder(Context context, Notification n,
CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
@@ -40,7 +42,7 @@
int progressMax, int progress, boolean progressIndeterminate, boolean showWhen,
boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
ArrayList<String> people, Bundle extras, String groupKey, boolean groupSummary,
- String sortKey) {
+ String sortKey, RemoteViews contentView, RemoteViews bigContentView) {
b = new Notification.Builder(context)
.setWhen(n.when)
.setShowWhen(showWhen)
@@ -79,6 +81,8 @@
mExtras.putStringArray(Notification.EXTRA_PEOPLE,
people.toArray(new String[people.size()]));
}
+ mContentView = contentView;
+ mBigContentView = bigContentView;
}
@Override
@@ -94,7 +98,14 @@
@Override
public Notification build() {
b.setExtras(mExtras);
- return b.build();
+ Notification notification = b.build();
+ if (mContentView != null) {
+ notification.contentView = mContentView;
+ }
+ if (mBigContentView != null) {
+ notification.bigContentView = mBigContentView;
+ }
+ return notification;
}
}
@@ -107,9 +118,15 @@
actionBuilder.addRemoteInput(remoteInput);
}
}
+ Bundle actionExtras;
if (action.getExtras() != null) {
- actionBuilder.addExtras(action.getExtras());
+ actionExtras = new Bundle(action.getExtras());
+ } else {
+ actionExtras = new Bundle();
}
+ actionExtras.putBoolean(NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES,
+ action.getAllowGeneratedReplies());
+ actionBuilder.addExtras(action.getExtras());
b.addAction(actionBuilder.build());
}
@@ -124,8 +141,10 @@
RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory) {
RemoteInputCompatBase.RemoteInput[] remoteInputs = RemoteInputCompatApi20.toCompat(
action.getRemoteInputs(), remoteInputFactory);
+ boolean allowGeneratedReplies = action.getExtras().getBoolean(
+ NotificationCompatJellybean.EXTRA_ALLOW_GENERATED_REPLIES);
return actionFactory.build(action.icon, action.title, action.actionIntent,
- action.getExtras(), remoteInputs);
+ action.getExtras(), remoteInputs, allowGeneratedReplies);
}
private static Notification.Action getActionFromActionCompat(
diff --git a/v4/api20/android/support/v4/print/PrintHelperApi20.java b/v4/api20/android/support/v4/print/PrintHelperApi20.java
new file mode 100644
index 0000000..ce62106
--- /dev/null
+++ b/v4/api20/android/support/v4/print/PrintHelperApi20.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.print;
+
+import android.content.Context;
+
+/**
+ * Api20 specific PrintManager API implementation.
+ */
+class PrintHelperApi20 extends PrintHelperKitkat {
+ PrintHelperApi20(Context context) {
+ super(context);
+
+ /**
+ * There is a bug in the PrintActivity that causes it to ignore the orientation
+ */
+ mPrintActivityRespectsOrientation = false;
+ }
+}
\ No newline at end of file
diff --git a/v4/api21/android/content/pm/ParceledListSlice.java b/v4/api21/android/content/pm/ParceledListSlice.java
deleted file mode 100644
index b5183c0..0000000
--- a/v4/api21/android/content/pm/ParceledListSlice.java
+++ /dev/null
@@ -1,70 +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.content.pm;
-
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import java.util.List;
-
-/**
- * A dummy implementation for overriding a hidden framework class, ParceledListSlice.
- * When there are duplicated signatures between app and framework code, the framework code will be
- * run.
- * @hide
- */
-public class ParceledListSlice<T extends Parcelable> implements Parcelable {
- public ParceledListSlice(List<T> list) {
- }
-
- @SuppressWarnings("unchecked")
- private ParceledListSlice(Parcel p, ClassLoader loader) {
- }
-
- private static void verifySameType(final Class<?> expected, final Class<?> actual) {
- }
-
- public List<T> getList() {
- return null;
- }
-
- @Override
- public int describeContents() {
- return 0;
- }
-
- @Override
- public void writeToParcel(Parcel dest, int flags) {
- }
-
- @SuppressWarnings("unchecked")
- public static final Parcelable.ClassLoaderCreator<ParceledListSlice> CREATOR =
- new Parcelable.ClassLoaderCreator<ParceledListSlice>() {
- public ParceledListSlice createFromParcel(Parcel in) {
- return null;
- }
-
- @Override
- public ParceledListSlice createFromParcel(Parcel in, ClassLoader loader) {
- return null;
- }
-
- public ParceledListSlice[] newArray(int size) {
- return null;
- }
- };
-}
diff --git a/v4/api21/android/service/media/IMediaBrowserService.java b/v4/api21/android/service/media/IMediaBrowserService.java
deleted file mode 100644
index 086dc9cc..0000000
--- a/v4/api21/android/service/media/IMediaBrowserService.java
+++ /dev/null
@@ -1,65 +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.service.media;
-
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-
-/**
- * A dummy implementation for overriding a hidden framework class, IMediaBrowserService.
- * When there are duplicated signatures between app and framework code, the framework code will be
- * run.
- * TODO: Consider using aidl instead of this.
- * @hide
- */
-public interface IMediaBrowserService extends IInterface {
-
- public static abstract class Stub extends Binder
- implements IMediaBrowserService {
- public Stub() {
- }
-
- public static IMediaBrowserService asInterface(IBinder obj) {
- return null;
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws android.os.RemoteException {
- return false;
- }
- }
-
- public void connect(String pkg, Bundle rootHints, IMediaBrowserServiceCallbacks callbacks)
- throws android.os.RemoteException;
- public void disconnect(IMediaBrowserServiceCallbacks callbacks) throws RemoteException;
- public void addSubscription(String uri, IMediaBrowserServiceCallbacks callbacks)
- throws RemoteException;
- public void removeSubscription(String uri, IMediaBrowserServiceCallbacks callbacks)
- throws RemoteException;
- public void getMediaItem(String uri, ResultReceiver cb) throws android.os.RemoteException;
-}
diff --git a/v4/api21/android/service/media/IMediaBrowserServiceCallbacks.java b/v4/api21/android/service/media/IMediaBrowserServiceCallbacks.java
deleted file mode 100644
index fe988fe..0000000
--- a/v4/api21/android/service/media/IMediaBrowserServiceCallbacks.java
+++ /dev/null
@@ -1,91 +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.service.media;
-
-import android.content.pm.ParceledListSlice;
-import android.media.session.MediaSession;
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.IInterface;
-import android.os.Parcel;
-import android.os.RemoteException;
-
-/**
- * A dummy implementation for overriding a hidden framework class, IMediaBrowserServiceCallbacks.
- * When there are duplicated signatures between app and framework code, the framework code will be
- * run.
- * TODO: Consider using aidl instead of this.
- * @hide
- */
-public interface IMediaBrowserServiceCallbacks extends IInterface {
- public static abstract class Stub extends Binder implements IMediaBrowserServiceCallbacks
- {
- public Stub() {
- }
-
- public static IMediaBrowserServiceCallbacks asInterface(IBinder obj) {
- return null;
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- return false;
- }
-
- private static class Proxy implements IMediaBrowserServiceCallbacks
- {
- Proxy(IBinder remote) {
- }
-
- @Override
- public IBinder asBinder() {
- return null;
- }
-
- public String getInterfaceDescriptor() {
- return null;
- }
-
- @Override
- public void onConnect(String root, MediaSession.Token session, Bundle extras)
- throws RemoteException {
- }
-
- @Override
- public void onConnectFailed() throws RemoteException {
- }
-
- @Override
- public void onLoadChildren(String mediaId, ParceledListSlice list)
- throws RemoteException {
- }
- }
- }
-
- public void onConnect(String root, MediaSession.Token session, Bundle extras)
- throws RemoteException;
- public void onConnectFailed() throws RemoteException;
- public void onLoadChildren(String mediaId, ParceledListSlice list) throws RemoteException;
-}
-
diff --git a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
index ddcff2e..37c4b97 100644
--- a/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
+++ b/v4/api21/android/support/v4/app/FragmentTransitionCompat21.java
@@ -92,6 +92,36 @@
return transitionSet;
}
+ private static void excludeViews(Transition transition, Transition fromTransition,
+ ArrayList<View> views, boolean exclude) {
+ if (transition != null) {
+ final int viewCount = fromTransition == null ? 0 : views.size();
+ for (int i = 0; i < viewCount; i++) {
+ transition.excludeTarget(views.get(i), exclude);
+ }
+ }
+ }
+
+ /**
+ * Exclude (or remove the exclude) of shared element views from the enter and exit transitions.
+ *
+ * @param enterTransitionObj The enter transition
+ * @param exitTransitionObj The exit transition
+ * @param sharedElementTransitionObj The shared element transition
+ * @param views The shared element target views.
+ * @param exclude <code>true</code> to exclude or <code>false</code> to remove the excluded
+ * views.
+ */
+ public static void excludeSharedElementViews(Object enterTransitionObj,
+ Object exitTransitionObj, Object sharedElementTransitionObj, ArrayList<View> views,
+ boolean exclude) {
+ Transition enterTransition = (Transition) enterTransitionObj;
+ Transition exitTransition = (Transition) exitTransitionObj;
+ Transition sharedElementTransition = (Transition) sharedElementTransitionObj;
+ excludeViews(enterTransition, sharedElementTransition, views, exclude);
+ excludeViews(exitTransition, sharedElementTransition, views, exclude);
+ }
+
/**
* Prepares the enter transition by adding a non-existent view to the transition's target list
* and setting it epicenter callback. By adding a non-existent view to the target list,
@@ -104,35 +134,42 @@
* capturing the final state of the Transition.</p>
*/
public static void addTransitionTargets(Object enterTransitionObject,
- Object sharedElementTransitionObject, final View container,
+ Object sharedElementTransitionObject, Object exitTransitionObject, final View container,
final ViewRetriever inFragment, final View nonExistentView,
EpicenterView epicenterView, final Map<String, String> nameOverrides,
- final ArrayList<View> enteringViews, final Map<String, View> namedViews,
- final Map<String, View> renamedViews, final ArrayList<View> sharedElementTargets) {
+ final ArrayList<View> enteringViews, final ArrayList<View> exitingViews,
+ final Map<String, View> namedViews, final Map<String, View> renamedViews,
+ final ArrayList<View> sharedElementTargets) {
+ final Transition enterTransition = (Transition) enterTransitionObject;
+ final Transition exitTransition = (Transition) exitTransitionObject;
+ final Transition sharedElementTransition = (Transition) sharedElementTransitionObject;
+ excludeViews(enterTransition, exitTransition, exitingViews, true);
if (enterTransitionObject != null || sharedElementTransitionObject != null) {
- final Transition enterTransition = (Transition) enterTransitionObject;
if (enterTransition != null) {
enterTransition.addTarget(nonExistentView);
}
if (sharedElementTransitionObject != null) {
- setSharedElementTargets(sharedElementTransitionObject, nonExistentView,
+ setSharedElementTargets(sharedElementTransition, nonExistentView,
namedViews, sharedElementTargets);
+ excludeViews(enterTransition, sharedElementTransition, sharedElementTargets, true);
+ excludeViews(exitTransition, sharedElementTransition, sharedElementTargets, true);
}
- if (inFragment != null) {
- container.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- public boolean onPreDraw() {
- container.getViewTreeObserver().removeOnPreDrawListener(this);
- if (enterTransition != null) {
- enterTransition.removeTarget(nonExistentView);
- }
+ container.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ public boolean onPreDraw() {
+ container.getViewTreeObserver().removeOnPreDrawListener(this);
+ if (enterTransition != null) {
+ enterTransition.removeTarget(nonExistentView);
+ }
+ if (inFragment != null) {
View fragmentView = inFragment.getView();
if (fragmentView != null) {
if (!nameOverrides.isEmpty()) {
findNamedViews(renamedViews, fragmentView);
renamedViews.keySet().retainAll(nameOverrides.values());
- for (Map.Entry<String, String> entry : nameOverrides.entrySet()) {
+ for (Map.Entry<String, String> entry : nameOverrides
+ .entrySet()) {
String to = entry.getValue();
View view = renamedViews.get(to);
if (view != null) {
@@ -148,10 +185,12 @@
addTargets(enterTransition, enteringViews);
}
}
- return true;
}
- });
- }
+ excludeViews(exitTransition, enterTransition, enteringViews, true);
+
+ return true;
+ }
+ });
setSharedElementEpicenter(enterTransition, epicenterView);
}
}
@@ -355,9 +394,15 @@
sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
if (enterTransition != null) {
removeTargets(enterTransition, enteringViews);
+ excludeViews(enterTransition, exitTransition, exitingViews, false);
+ excludeViews(enterTransition, sharedElementTransition, sharedElementTargets,
+ false);
}
if (exitTransition != null) {
removeTargets(exitTransition, exitingViews);
+ excludeViews(exitTransition, enterTransition, enteringViews, false);
+ excludeViews(exitTransition, sharedElementTransition, sharedElementTargets,
+ false);
}
if (sharedElementTransition != null) {
removeTargets(sharedElementTransition, sharedElementTargets);
diff --git a/v4/api21/android/support/v4/app/NotificationCompatApi21.java b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
index dbce1db..f060486 100644
--- a/v4/api21/android/support/v4/app/NotificationCompatApi21.java
+++ b/v4/api21/android/support/v4/app/NotificationCompatApi21.java
@@ -55,6 +55,10 @@
public static class Builder implements NotificationBuilderWithBuilderAccessor,
NotificationBuilderWithActions {
private Notification.Builder b;
+ private Bundle mExtras;
+ private RemoteViews mContentView;
+ private RemoteViews mBigContentView;
+ private RemoteViews mHeadsUpContentView;
public Builder(Context context, Notification n,
CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
@@ -64,7 +68,8 @@
boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
String category, ArrayList<String> people, Bundle extras, int color,
int visibility, Notification publicVersion, String groupKey, boolean groupSummary,
- String sortKey) {
+ String sortKey, RemoteViews contentView, RemoteViews bigContentView,
+ RemoteViews headsUpContentView) {
b = new Notification.Builder(context)
.setWhen(n.when)
.setShowWhen(showWhen)
@@ -92,7 +97,6 @@
.setPriority(priority)
.setProgress(progressMax, progress, progressIndeterminate)
.setLocalOnly(localOnly)
- .setExtras(extras)
.setGroup(groupKey)
.setGroupSummary(groupSummary)
.setSortKey(sortKey)
@@ -100,9 +104,16 @@
.setColor(color)
.setVisibility(visibility)
.setPublicVersion(publicVersion);
+ mExtras = new Bundle();
+ if (extras != null) {
+ mExtras.putAll(extras);
+ }
for (String person: people) {
b.addPerson(person);
}
+ mContentView = contentView;
+ mBigContentView = bigContentView;
+ mHeadsUpContentView = headsUpContentView;
}
@Override
@@ -117,7 +128,18 @@
@Override
public Notification build() {
- return b.build();
+ b.setExtras(mExtras);
+ Notification notification = b.build();
+ if (mContentView != null) {
+ notification.contentView = mContentView;
+ }
+ if (mBigContentView != null) {
+ notification.bigContentView = mBigContentView;
+ }
+ if (mHeadsUpContentView != null) {
+ notification.headsUpContentView = mHeadsUpContentView;
+ }
+ return notification;
}
}
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
index 2f3b45a..2c50df9 100644
--- a/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableCompatLollipop.java
@@ -17,10 +17,15 @@
package android.support.v4.graphics.drawable;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableContainer;
-import android.graphics.drawable.GradientDrawable;
+import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* Implementation of drawable compatibility that can call L APIs.
@@ -49,16 +54,27 @@
}
public static Drawable wrapForTinting(final Drawable drawable) {
- if (!(drawable instanceof DrawableWrapperLollipop)) {
- return new DrawableWrapperLollipop(drawable, shouldForceCompatTinting(drawable));
+ if (!(drawable instanceof TintAwareDrawable)) {
+ return new DrawableWrapperLollipop(drawable);
}
return drawable;
}
- private static boolean shouldForceCompatTinting(Drawable drawable) {
- // GradientDrawable on Lollipop does not support tinting, so we'll use our compatible
- // functionality instead. We also do the same for DrawableContainers since they may
- // contain GradientDrawable instances.
- return drawable instanceof GradientDrawable || drawable instanceof DrawableContainer;
+ public static void applyTheme(Drawable drawable, Resources.Theme t) {
+ drawable.applyTheme(t);
+ }
+
+ public static boolean canApplyTheme(Drawable drawable) {
+ return drawable.canApplyTheme();
+ }
+
+ public static ColorFilter getColorFilter(Drawable drawable) {
+ return drawable.getColorFilter();
+ }
+
+ public static void inflate(Drawable drawable, Resources res, XmlPullParser parser,
+ AttributeSet attrs, Resources.Theme t)
+ throws IOException, XmlPullParserException {
+ drawable.inflate(res, parser, attrs, t);
}
}
diff --git a/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
index 9533afd..ea69677 100644
--- a/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
+++ b/v4/api21/android/support/v4/graphics/drawable/DrawableWrapperLollipop.java
@@ -22,18 +22,21 @@
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
class DrawableWrapperLollipop extends DrawableWrapperKitKat {
- private final boolean mUseCompatTinting;
-
DrawableWrapperLollipop(Drawable drawable) {
- this(drawable, false);
+ super(drawable);
}
- DrawableWrapperLollipop(Drawable drawable, boolean useCompatTinting) {
- super(drawable);
- mUseCompatTinting = useCompatTinting;
+ DrawableWrapperLollipop(DrawableWrapperState state, Resources resources) {
+ super(state, resources);
}
@Override
@@ -52,24 +55,14 @@
}
@Override
- public void applyTheme(Resources.Theme t) {
- mDrawable.applyTheme(t);
- }
-
- @Override
- public boolean canApplyTheme() {
- return mDrawable.canApplyTheme();
- }
-
- @Override
public Rect getDirtyBounds() {
return mDrawable.getDirtyBounds();
}
@Override
public void setTintList(ColorStateList tint) {
- if (mUseCompatTinting) {
- setCompatTintList(tint);
+ if (isCompatTintEnabled()) {
+ super.setTintList(tint);
} else {
mDrawable.setTintList(tint);
}
@@ -77,8 +70,8 @@
@Override
public void setTint(int tintColor) {
- if (mUseCompatTinting) {
- setCompatTint(tintColor);
+ if (isCompatTintEnabled()) {
+ super.setTint(tintColor);
} else {
mDrawable.setTint(tintColor);
}
@@ -86,8 +79,8 @@
@Override
public void setTintMode(PorterDuff.Mode tintMode) {
- if (mUseCompatTinting) {
- setCompatTintMode(tintMode);
+ if (isCompatTintEnabled()) {
+ super.setTintMode(tintMode);
} else {
mDrawable.setTintMode(tintMode);
}
@@ -106,6 +99,29 @@
@Override
protected boolean isCompatTintEnabled() {
- return mUseCompatTinting;
+ if (Build.VERSION.SDK_INT == 21) {
+ final Drawable drawable = mDrawable;
+ return drawable instanceof GradientDrawable || drawable instanceof DrawableContainer
+ || drawable instanceof InsetDrawable;
+ }
+ return false;
+ }
+
+ @NonNull
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ return new DrawableWrapperStateLollipop(mState, null);
+ }
+
+ private static class DrawableWrapperStateLollipop extends DrawableWrapperState {
+ DrawableWrapperStateLollipop(@Nullable DrawableWrapperState orig,
+ @Nullable Resources res) {
+ super(orig, res);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new DrawableWrapperLollipop(this, res);
+ }
}
}
diff --git a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
index c6ccb14..3dc4e7c 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -129,25 +129,26 @@
@Override
public void onChildrenLoaded(@NonNull String parentId,
List<MediaBrowser.MediaItem> children) {
- List<Parcel> parcelList = null;
- if (children != null && children.size() == 1
- && children.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID)) {
- children = null;
- }
- if (children != null) {
- parcelList = new ArrayList<>();
- for (MediaBrowser.MediaItem item : children) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
- }
- }
- mSubscriptionCallback.onChildrenLoaded(parentId, parcelList);
+ mSubscriptionCallback.onChildrenLoaded(parentId, itemListToParcelList(children));
}
@Override
public void onError(@NonNull String parentId) {
mSubscriptionCallback.onError(parentId);
}
+
+ static List<Parcel> itemListToParcelList(List<MediaBrowser.MediaItem> itemList) {
+ if (itemList == null || (itemList.size() == 1
+ && itemList.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID))) {
+ return null;
+ }
+ List<Parcel> parcelList = new ArrayList<>();
+ for (MediaBrowser.MediaItem item : itemList) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ return parcelList;
+ }
}
}
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index a1506d3..59e44e2 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -16,19 +16,13 @@
package android.support.v4.media;
+import android.content.Context;
import android.content.Intent;
-import android.content.pm.ParceledListSlice;
-import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
import android.media.session.MediaSession;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.service.media.IMediaBrowserService;
-import android.service.media.IMediaBrowserServiceCallbacks;
import android.service.media.MediaBrowserService;
import java.util.ArrayList;
@@ -36,130 +30,95 @@
class MediaBrowserServiceCompatApi21 {
- public static Object createService() {
- return new MediaBrowserServiceAdaptorApi21();
+ public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
+ return new MediaBrowserServiceAdaptor(context, serviceProxy);
}
- public static void onCreate(Object serviceObj, ServiceImplApi21 serviceImpl) {
- ((MediaBrowserServiceAdaptorApi21) serviceObj).onCreate(serviceImpl);
+ public static void onCreate(Object serviceObj) {
+ ((MediaBrowserService) serviceObj).onCreate();
}
public static IBinder onBind(Object serviceObj, Intent intent) {
- return ((MediaBrowserServiceAdaptorApi21) serviceObj).onBind(intent);
+ return ((MediaBrowserService) serviceObj).onBind(intent);
}
- public interface ServiceImplApi21 {
- void connect(final String pkg, final Bundle rootHints, final ServiceCallbacks callbacks);
- void disconnect(final ServiceCallbacks callbacks);
- void addSubscription(final String id, final ServiceCallbacks callbacks);
- void removeSubscription(final String id, final ServiceCallbacks callbacks);
+ public static void setSessionToken(Object serviceObj, Object token) {
+ ((MediaBrowserService) serviceObj).setSessionToken((MediaSession.Token) token);
}
- public interface ServiceCallbacks {
- IBinder asBinder();
- void onConnect(String root, Object session, Bundle extras) throws RemoteException;
- void onConnectFailed() throws RemoteException;
- void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException;
+ public static void notifyChildrenChanged(Object serviceObj, String parentId) {
+ ((MediaBrowserService) serviceObj).notifyChildrenChanged(parentId);
}
- public static class ServiceCallbacksApi21 implements ServiceCallbacks {
- private static final ParceledListSlice sNullParceledListSlice;
- static {
- MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
- MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
- MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
- List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
- nullMediaItemList.add(nullMediaItem);
- sNullParceledListSlice = new ParceledListSlice(nullMediaItemList);
+ public interface ServiceCompatProxy {
+ BrowserRoot onGetRoot(String clientPackageName, int clientUid, Bundle rootHints);
+ void onLoadChildren(String parentId, ResultWrapper<List<Parcel>> result);
+ }
+
+ static class ResultWrapper<T> {
+ MediaBrowserService.Result mResultObj;
+
+ ResultWrapper(MediaBrowserService.Result result) {
+ mResultObj = result;
}
- private final IMediaBrowserServiceCallbacks mCallbacks;
-
- ServiceCallbacksApi21(IMediaBrowserServiceCallbacks callbacks) {
- mCallbacks = callbacks;
- }
-
- public IBinder asBinder() {
- return mCallbacks.asBinder();
- }
-
- public void onConnect(String root, Object session, Bundle extras) throws RemoteException {
- mCallbacks.onConnect(root, (MediaSession.Token) session, extras);
- }
-
- public void onConnectFailed() throws RemoteException {
- mCallbacks.onConnectFailed();
- }
-
- public void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException {
- List<MediaBrowser.MediaItem> itemList = null;
- if (list != null) {
- itemList = new ArrayList<>();
- for (Parcel parcel : list) {
- parcel.setDataPosition(0);
- itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- }
+ public void sendResult(T result) {
+ if (result instanceof List) {
+ mResultObj.sendResult(parcelListToItemList((List<Parcel>)result));
+ } else if (result instanceof Parcel) {
+ mResultObj.sendResult(
+ MediaBrowser.MediaItem.CREATOR.createFromParcel((Parcel) result));
}
- ParceledListSlice<MediaBrowser.MediaItem> pls;
- if (Build.VERSION.SDK_INT > 23) {
- pls = itemList == null ? null : new ParceledListSlice(itemList);
- } else {
- pls = itemList == null ? sNullParceledListSlice : new ParceledListSlice(itemList);
+ }
+
+ public void detach() {
+ mResultObj.detach();
+ }
+
+ List<MediaBrowser.MediaItem> parcelListToItemList(List<Parcel> parcelList) {
+ if (parcelList == null) {
+ return null;
}
- mCallbacks.onLoadChildren(mediaId, pls);
+ List<MediaBrowser.MediaItem> items = new ArrayList<>();
+ for (Parcel parcel : parcelList) {
+ parcel.setDataPosition(0);
+ items.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ return items;
}
}
- static class MediaBrowserServiceAdaptorApi21 {
- ServiceBinderProxyApi21 mBinder;
+ static class BrowserRoot {
+ final String mRootId;
+ final Bundle mExtras;
- public void onCreate(ServiceImplApi21 serviceImpl) {
- mBinder = new ServiceBinderProxyApi21(serviceImpl);
+ BrowserRoot(String rootId, Bundle extras) {
+ mRootId = rootId;
+ mExtras = extras;
+ }
+ }
+
+ static class MediaBrowserServiceAdaptor extends MediaBrowserService {
+ final ServiceCompatProxy mServiceProxy;
+
+ MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
+ attachBaseContext(context);
+ mServiceProxy = serviceWrapper;
}
- public IBinder onBind(Intent intent) {
- if (MediaBrowserService.SERVICE_INTERFACE.equals(intent.getAction())) {
- return mBinder;
- }
- return null;
+ @Override
+ public MediaBrowserService.BrowserRoot onGetRoot(String clientPackageName, int clientUid,
+ Bundle rootHints) {
+ MediaBrowserServiceCompatApi21.BrowserRoot browserRoot = mServiceProxy.onGetRoot(
+ clientPackageName, clientUid, rootHints);
+ return browserRoot == null ? null : new MediaBrowserService.BrowserRoot(
+ browserRoot.mRootId, browserRoot.mExtras);
}
- static class ServiceBinderProxyApi21 extends IMediaBrowserService.Stub {
- final ServiceImplApi21 mServiceImpl;
-
- ServiceBinderProxyApi21(ServiceImplApi21 serviceImpl) {
- mServiceImpl = serviceImpl;
- }
-
- @Override
- public void connect(final String pkg, final Bundle rootHints,
- final IMediaBrowserServiceCallbacks callbacks) {
- mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
- }
-
- @Override
- public void disconnect(final IMediaBrowserServiceCallbacks callbacks) {
- mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
- }
-
-
- @Override
- public void addSubscription(final String id,
- final IMediaBrowserServiceCallbacks callbacks) {
- mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
- }
-
- @Override
- public void removeSubscription(final String id,
- final IMediaBrowserServiceCallbacks callbacks) {
- mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
- }
-
- @Override
- public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
- // No operation since this method is added in API 23.
- }
+ @Override
+ public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result) {
+ mServiceProxy.onLoadChildren(parentId, new ResultWrapper<List<Parcel>>(result));
}
}
}
diff --git a/v4/api21/android/support/v4/media/ParceledListSliceAdapterApi21.java b/v4/api21/android/support/v4/media/ParceledListSliceAdapterApi21.java
new file mode 100644
index 0000000..9292b93
--- /dev/null
+++ b/v4/api21/android/support/v4/media/ParceledListSliceAdapterApi21.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+
+import java.lang.reflect.Constructor;
+import java.lang.reflect.InvocationTargetException;
+import java.util.List;
+
+/**
+ * An adapter class for accessing the hidden framework classes, ParceledListSlice using reflection.
+ */
+class ParceledListSliceAdapterApi21 {
+ private static Constructor sConstructor;
+ static {
+ try {
+ Class theClass = Class.forName("android.content.pm.ParceledListSlice");
+ sConstructor = theClass.getConstructor(new Class[] { List.class });
+ } catch (ClassNotFoundException | NoSuchMethodException e) {
+ e.printStackTrace();
+ }
+ }
+
+ static Object newInstance(List<MediaBrowser.MediaItem> itemList) {
+ Object result = null;
+ try {
+ result = sConstructor.newInstance(itemList);
+ } catch (InstantiationException | IllegalAccessException | InvocationTargetException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+}
diff --git a/v4/api21/android/support/v4/media/RatingCompatApi21.java b/v4/api21/android/support/v4/media/RatingCompatApi21.java
deleted file mode 100644
index 6c8b9b2b..0000000
--- a/v4/api21/android/support/v4/media/RatingCompatApi21.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.media.Rating;
-
-class RatingCompatApi21 {
- public static Object newUnratedRating(int ratingStyle) {
- return Rating.newUnratedRating(ratingStyle);
- }
-
- public static Object newHeartRating(boolean hasHeart) {
- return Rating.newHeartRating(hasHeart);
- }
-
- public static Object newThumbRating(boolean thumbIsUp) {
- return Rating.newThumbRating(thumbIsUp);
- }
-
- public static Object newStarRating(int starRatingStyle, float starRating) {
- return Rating.newStarRating(starRatingStyle, starRating);
- }
-
- public static Object newPercentageRating(float percent) {
- return Rating.newPercentageRating(percent);
- }
-
- public static boolean isRated(Object ratingObj) {
- return ((Rating)ratingObj).isRated();
- }
-
- public static int getRatingStyle(Object ratingObj) {
- return ((Rating)ratingObj).getRatingStyle();
- }
-
- public static boolean hasHeart(Object ratingObj) {
- return ((Rating)ratingObj).hasHeart();
- }
-
- public static boolean isThumbUp(Object ratingObj) {
- return ((Rating)ratingObj).isThumbUp();
- }
-
- public static float getStarRating(Object ratingObj) {
- return ((Rating)ratingObj).getStarRating();
- }
-
- public static float getPercentRating(Object ratingObj) {
- return ((Rating)ratingObj).getPercentRating();
- }
-}
diff --git a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
index b3e7fd1..1b3f8fd 100644
--- a/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
+++ b/v4/api21/android/support/v4/media/session/MediaSessionCompatApi21.java
@@ -132,7 +132,7 @@
((MediaSession) sessionObj).setExtras(extras);
}
- static interface Callback {
+ interface Callback extends MediaSessionCompatApi19.Callback {
public void onCommand(String command, Bundle extras, ResultReceiver cb);
public boolean onMediaButtonEvent(Intent mediaButtonIntent);
public void onPlay();
@@ -145,8 +145,6 @@
public void onFastForward();
public void onRewind();
public void onStop();
- public void onSeekTo(long pos);
- public void onSetRating(Object ratingObj);
public void onCustomAction(String action, Bundle extras);
}
diff --git a/v4/api21/android/support/v4/view/ViewCompatLollipop.java b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
index 09b84b3..218c90a 100644
--- a/v4/api21/android/support/v4/view/ViewCompatLollipop.java
+++ b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
@@ -18,11 +18,17 @@
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
import android.view.View;
+import android.view.ViewParent;
import android.view.WindowInsets;
class ViewCompatLollipop {
+ private static ThreadLocal<Rect> sThreadLocalRect;
+
public static void setTransitionName(View view, String transitionName) {
view.setTransitionName(transitionName);
}
@@ -53,17 +59,21 @@
public static void setOnApplyWindowInsetsListener(View view,
final OnApplyWindowInsetsListener listener) {
- view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
- @Override
- public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
- // Wrap the framework insets in our wrapper
- WindowInsetsCompatApi21 insets = new WindowInsetsCompatApi21(windowInsets);
- // Give the listener a chance to use the wrapped insets
- insets = (WindowInsetsCompatApi21) listener.onApplyWindowInsets(view, insets);
- // Return the unwrapped insets
- return insets.unwrap();
- }
- });
+ if (listener == null) {
+ view.setOnApplyWindowInsetsListener(null);
+ } else {
+ view.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+ // Wrap the framework insets in our wrapper
+ WindowInsetsCompatApi21 insets = new WindowInsetsCompatApi21(windowInsets);
+ // Give the listener a chance to use the wrapped insets
+ insets = (WindowInsetsCompatApi21) listener.onApplyWindowInsets(view, insets);
+ // Return the unwrapped insets
+ return insets.unwrap();
+ }
+ });
+ }
}
public static boolean isImportantForAccessibility(View view) {
@@ -76,6 +86,20 @@
static void setBackgroundTintList(View view, ColorStateList tintList) {
view.setBackgroundTintList(tintList);
+
+ if (Build.VERSION.SDK_INT == 21) {
+ // Work around a bug in L that did not update the state of the background
+ // after applying the tint
+ Drawable background = view.getBackground();
+ boolean hasTint = (view.getBackgroundTintList() != null)
+ && (view.getBackgroundTintMode() != null);
+ if ((background != null) && hasTint) {
+ if (background.isStateful()) {
+ background.setState(view.getDrawableState());
+ }
+ view.setBackground(background);
+ }
+ }
}
static PorterDuff.Mode getBackgroundTintMode(View view) {
@@ -84,6 +108,20 @@
static void setBackgroundTintMode(View view, PorterDuff.Mode mode) {
view.setBackgroundTintMode(mode);
+
+ if (Build.VERSION.SDK_INT == 21) {
+ // Work around a bug in L that did not update the state of the background
+ // after applying the tint
+ Drawable background = view.getBackground();
+ boolean hasTint = (view.getBackgroundTintList() != null)
+ && (view.getBackgroundTintMode() != null);
+ if ((background != null) && hasTint) {
+ if (background.isStateful()) {
+ background.setState(view.getDrawableState());
+ }
+ view.setBackground(background);
+ }
+ }
}
public static WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
@@ -159,4 +197,71 @@
public static float getZ(View view) {
return view.getZ();
}
+
+ public static void setZ(View view, float z) {
+ view.setZ(z);
+ }
+
+ static void offsetTopAndBottom(final View view, final int offset) {
+ final Rect parentRect = getEmptyTempRect();
+ boolean needInvalidateWorkaround = false;
+
+ final ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ final View p = (View) parent;
+ parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+ // If the view currently does not currently intersect the parent (and is therefore
+ // not displayed) we may need need to invalidate
+ needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom());
+ }
+
+ // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
+ ViewCompatHC.offsetTopAndBottom(view, offset);
+
+ // The view has now been offset, so let's intersect the Rect and invalidate where
+ // the View is now displayed
+ if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom())) {
+ ((View) parent).invalidate(parentRect);
+ }
+ }
+
+ static void offsetLeftAndRight(final View view, final int offset) {
+ final Rect parentRect = getEmptyTempRect();
+ boolean needInvalidateWorkaround = false;
+
+ final ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ final View p = (View) parent;
+ parentRect.set(p.getLeft(), p.getTop(), p.getRight(), p.getBottom());
+ // If the view currently does not currently intersect the parent (and is therefore
+ // not displayed) we may need need to invalidate
+ needInvalidateWorkaround = !parentRect.intersects(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom());
+ }
+
+ // Now offset, invoking the API 11+ implementation (which contains it's own workarounds)
+ ViewCompatHC.offsetLeftAndRight(view, offset);
+
+ // The view has now been offset, so let's intersect the Rect and invalidate where
+ // the View is now displayed
+ if (needInvalidateWorkaround && parentRect.intersect(view.getLeft(), view.getTop(),
+ view.getRight(), view.getBottom())) {
+ ((View) parent).invalidate(parentRect);
+ }
+ }
+
+ private static Rect getEmptyTempRect() {
+ if (sThreadLocalRect == null) {
+ sThreadLocalRect = new ThreadLocal<>();
+ }
+ Rect rect = sThreadLocalRect.get();
+ if (rect == null) {
+ rect = new Rect();
+ sThreadLocalRect.set(rect);
+ }
+ rect.setEmpty();
+ return rect;
+ }
}
diff --git a/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java b/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
deleted file mode 100644
index 50f2657..0000000
--- a/v4/api22/android/support/v4/graphics/drawable/DrawableCompatApi22.java
+++ /dev/null
@@ -1,31 +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.v4.graphics.drawable;
-
-import android.graphics.drawable.Drawable;
-
-/**
- * Implementation of drawable compatibility that can call Lollipop-MR1 APIs.
- */
-class DrawableCompatApi22 {
-
- public static Drawable wrapForTinting(Drawable drawable) {
- // We need to wrap to force an invalidation on any state change
- return new DrawableWrapperLollipop(drawable);
- }
-
-}
diff --git a/v4/api23/android/support/v4/app/NotificationCompatApi23.java b/v4/api23/android/support/v4/app/NotificationCompatApi23.java
new file mode 100644
index 0000000..61173d1
--- /dev/null
+++ b/v4/api23/android/support/v4/app/NotificationCompatApi23.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+
+class NotificationCompatApi23 {
+
+ public static final String CATEGORY_REMINDER = Notification.CATEGORY_REMINDER;
+}
diff --git a/v4/api23/android/support/v4/content/ResourcesCompatApi23.java b/v4/api23/android/support/v4/content/res/ResourcesCompatApi23.java
similarity index 100%
rename from v4/api23/android/support/v4/content/ResourcesCompatApi23.java
rename to v4/api23/android/support/v4/content/res/ResourcesCompatApi23.java
diff --git a/v4/api23/android/support/v4/graphics/drawable/DrawableCompatApi23.java b/v4/api23/android/support/v4/graphics/drawable/DrawableCompatApi23.java
index 975d501..2a41b60 100644
--- a/v4/api23/android/support/v4/graphics/drawable/DrawableCompatApi23.java
+++ b/v4/api23/android/support/v4/graphics/drawable/DrawableCompatApi23.java
@@ -22,8 +22,8 @@
* Implementation of drawable compatibility that can call M APIs.
*/
class DrawableCompatApi23 {
- public static void setLayoutDirection(Drawable drawable, int layoutDirection) {
- drawable.setLayoutDirection(layoutDirection);
+ public static boolean setLayoutDirection(Drawable drawable, int layoutDirection) {
+ return drawable.setLayoutDirection(layoutDirection);
}
public static int getLayoutDirection(Drawable drawable) {
diff --git a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
index fcaea40..4eab9c2 100644
--- a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
+++ b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
@@ -1,5 +1,5 @@
/*
- * Copyright (C) 2015 The Android Open Source Project
+ * Copyright (C) 2016 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -16,71 +16,31 @@
package android.support.v4.media;
+import android.content.Context;
import android.media.browse.MediaBrowser;
-import android.os.Bundle;
import android.os.Parcel;
-import android.os.ResultReceiver;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
+import android.support.v4.media.MediaBrowserServiceCompatApi21.ResultWrapper;
-class MediaBrowserServiceCompatApi23 extends MediaBrowserServiceCompatApi21 {
- private static final String TAG = "MediaBrowserServiceCompatApi21";
+class MediaBrowserServiceCompatApi23 {
- public static Object createService() {
- return new MediaBrowserServiceAdaptorApi23();
+ public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
+ return new MediaBrowserServiceAdaptor(context, serviceProxy);
}
- public static void onCreate(Object serviceObj, ServiceImplApi23 serviceImpl) {
- ((MediaBrowserServiceAdaptorApi23) serviceObj).onCreate(serviceImpl);
+ public interface ServiceCompatProxy extends MediaBrowserServiceCompatApi21.ServiceCompatProxy {
+ void onLoadItem(String itemId, ResultWrapper<Parcel> result);
}
- public interface ServiceImplApi23 extends ServiceImplApi21 {
- void getMediaItem(final String mediaId, final ItemCallback cb);
- }
-
- public interface ItemCallback {
- void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel);
- }
-
- static class MediaBrowserServiceAdaptorApi23 extends MediaBrowserServiceAdaptorApi21 {
-
- public void onCreate(ServiceImplApi23 serviceImpl) {
- mBinder = new ServiceBinderProxyApi23(serviceImpl);
+ static class MediaBrowserServiceAdaptor extends
+ MediaBrowserServiceCompatApi21.MediaBrowserServiceAdaptor {
+ MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
+ super(context, serviceWrapper);
}
- private static class ServiceBinderProxyApi23 extends ServiceBinderProxyApi21 {
- ServiceImplApi23 mServiceImpl;
-
- ServiceBinderProxyApi23(ServiceImplApi23 serviceImpl) {
- super(serviceImpl);
- mServiceImpl = serviceImpl;
- }
-
- @Override
- public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
- final String KEY_MEDIA_ITEM;
- try {
- KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
- "KEY_MEDIA_ITEM").get(null);
- } catch (IllegalAccessException | NoSuchFieldException e) {
- Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
- return;
- }
-
- mServiceImpl.getMediaItem(mediaId, new ItemCallback() {
- @Override
- public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
- if (itemParcel != null) {
- itemParcel.setDataPosition(0);
- MediaBrowser.MediaItem item =
- MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
- resultData.putParcelable(KEY_MEDIA_ITEM, item);
- itemParcel.recycle();
- }
- receiver.send(resultCode, resultData);
- }
- });
- }
+ @Override
+ public void onLoadItem(String itemId, Result<MediaBrowser.MediaItem> result) {
+ ((ServiceCompatProxy) mServiceProxy).onLoadItem(itemId,
+ new ResultWrapper<Parcel>(result));
}
}
}
diff --git a/v4/api23/android/support/v4/media/MediaDescriptionCompatApi23.java b/v4/api23/android/support/v4/media/MediaDescriptionCompatApi23.java
index d531a15..2c1bda3 100644
--- a/v4/api23/android/support/v4/media/MediaDescriptionCompatApi23.java
+++ b/v4/api23/android/support/v4/media/MediaDescriptionCompatApi23.java
@@ -15,11 +15,8 @@
*/
package android.support.v4.media;
-import android.graphics.Bitmap;
import android.media.MediaDescription;
import android.net.Uri;
-import android.os.Bundle;
-import android.os.Parcel;
class MediaDescriptionCompatApi23 extends MediaDescriptionCompatApi21 {
public static Uri getMediaUri(Object descriptionObj) {
diff --git a/v4/api23/android/support/v4/media/MediaSessionCompatApi23.java b/v4/api23/android/support/v4/media/MediaSessionCompatApi23.java
deleted file mode 100644
index 1775653..0000000
--- a/v4/api23/android/support/v4/media/MediaSessionCompatApi23.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright (C) 2014 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.session;
-
-import android.net.Uri;
-import android.os.Bundle;
-
-class MediaSessionCompatApi23 {
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static interface Callback extends MediaSessionCompatApi21.Callback {
- public void onPlayFromUri(Uri uri, Bundle extras);
- }
-
- static class CallbackProxy<T extends Callback> extends MediaSessionCompatApi21.CallbackProxy<T> {
- public CallbackProxy(T callback) {
- super(callback);
- }
-
- @Override
- public void onPlayFromUri(Uri uri, Bundle extras) {
- mCallback.onPlayFromUri(uri, extras);
- }
- }
-}
diff --git a/v4/api23/android/support/v4/media/MediaControllerCompatApi23.java b/v4/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
similarity index 100%
rename from v4/api23/android/support/v4/media/MediaControllerCompatApi23.java
rename to v4/api23/android/support/v4/media/session/MediaControllerCompatApi23.java
diff --git a/v4/api23/android/support/v4/media/session/MediaSessionCompatApi23.java b/v4/api23/android/support/v4/media/session/MediaSessionCompatApi23.java
new file mode 100644
index 0000000..cc8bba3
--- /dev/null
+++ b/v4/api23/android/support/v4/media/session/MediaSessionCompatApi23.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.net.Uri;
+import android.os.Bundle;
+
+class MediaSessionCompatApi23 {
+
+ public static Object createCallback(Callback callback) {
+ return new CallbackProxy<Callback>(callback);
+ }
+
+ public interface Callback extends MediaSessionCompatApi21.Callback {
+ public void onPlayFromUri(Uri uri, Bundle extras);
+ }
+
+ static class CallbackProxy<T extends Callback> extends MediaSessionCompatApi21.CallbackProxy<T> {
+ public CallbackProxy(T callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onPlayFromUri(Uri uri, Bundle extras) {
+ mCallback.onPlayFromUri(uri, extras);
+ }
+ }
+}
diff --git a/v4/api23/android/support/v4/print/PrintHelperApi23.java b/v4/api23/android/support/v4/print/PrintHelperApi23.java
new file mode 100644
index 0000000..ba646e3
--- /dev/null
+++ b/v4/api23/android/support/v4/print/PrintHelperApi23.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.print;
+
+import android.content.Context;
+import android.print.PrintAttributes;
+
+/**
+ * Api23 specific PrintManager API implementation.
+ */
+class PrintHelperApi23 extends PrintHelperApi20 {
+ @Override
+ protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
+ PrintAttributes.Builder b = super.copyAttributes(other);
+
+ if (other.getDuplexMode() != 0) {
+ b.setDuplexMode(other.getDuplexMode());
+ }
+
+ return b;
+ }
+
+ PrintHelperApi23(Context context) {
+ super(context);
+
+ mIsMinMarginsHandlingCorrect = false;
+ }
+}
diff --git a/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java b/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java
index 16d3ae8..38fa479 100644
--- a/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java
+++ b/v4/api23/android/support/v4/view/ViewCompatMarshmallow.java
@@ -30,4 +30,12 @@
public static int getScrollIndicators(View view) {
return view.getScrollIndicators();
}
+
+ static void offsetTopAndBottom(View view, int offset) {
+ view.offsetTopAndBottom(offset);
+ }
+
+ static void offsetLeftAndRight(View view, int offset) {
+ view.offsetLeftAndRight(offset);
+ }
}
diff --git a/v4/api23/android/support/v4/widget/TextViewCompatApi23.java b/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
index 8370bfc..ad21409 100644
--- a/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
+++ b/v4/api23/android/support/v4/widget/TextViewCompatApi23.java
@@ -18,10 +18,11 @@
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
+import android.support.annotation.StyleRes;
import android.widget.TextView;
class TextViewCompatApi23 {
- public static void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+ public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
textView.setTextAppearance(resId);
}
}
diff --git a/v4/api24/android/support/v4/app/NotificationCompatApi24.java b/v4/api24/android/support/v4/app/NotificationCompatApi24.java
new file mode 100644
index 0000000..e533b21
--- /dev/null
+++ b/v4/api24/android/support/v4/app/NotificationCompatApi24.java
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Notification;
+import android.app.PendingIntent;
+import android.app.RemoteInput;
+import android.content.Context;
+import android.graphics.Bitmap;
+import android.net.Uri;
+import android.os.Bundle;
+import android.widget.RemoteViews;
+
+import java.util.ArrayList;
+import java.util.List;
+
+class NotificationCompatApi24 {
+
+ public static final String CATEGORY_CALL = Notification.CATEGORY_CALL;
+ public static final String CATEGORY_MESSAGE = Notification.CATEGORY_MESSAGE;
+ public static final String CATEGORY_EMAIL = Notification.CATEGORY_EMAIL;
+ public static final String CATEGORY_EVENT = Notification.CATEGORY_EVENT;
+ public static final String CATEGORY_PROMO = Notification.CATEGORY_PROMO;
+ public static final String CATEGORY_ALARM = Notification.CATEGORY_ALARM;
+ public static final String CATEGORY_PROGRESS = Notification.CATEGORY_PROGRESS;
+ public static final String CATEGORY_SOCIAL = Notification.CATEGORY_SOCIAL;
+ public static final String CATEGORY_ERROR = Notification.CATEGORY_ERROR;
+ public static final String CATEGORY_TRANSPORT = Notification.CATEGORY_TRANSPORT;
+ public static final String CATEGORY_SYSTEM = Notification.CATEGORY_SYSTEM;
+ public static final String CATEGORY_SERVICE = Notification.CATEGORY_SERVICE;
+ public static final String CATEGORY_RECOMMENDATION = Notification.CATEGORY_RECOMMENDATION;
+ public static final String CATEGORY_STATUS = Notification.CATEGORY_STATUS;
+
+ public static class Builder implements NotificationBuilderWithBuilderAccessor,
+ NotificationBuilderWithActions {
+ private Notification.Builder b;
+
+ public Builder(Context context, Notification n,
+ CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
+ RemoteViews tickerView, int number,
+ PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
+ int progressMax, int progress, boolean progressIndeterminate, boolean showWhen,
+ boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
+ String category, ArrayList<String> people, Bundle extras, int color,
+ int visibility, Notification publicVersion, String groupKey, boolean groupSummary,
+ String sortKey, CharSequence[] remoteInputHistory, RemoteViews contentView,
+ RemoteViews bigContentView, RemoteViews headsUpContentView) {
+ b = new Notification.Builder(context)
+ .setWhen(n.when)
+ .setShowWhen(showWhen)
+ .setSmallIcon(n.icon, n.iconLevel)
+ .setContent(n.contentView)
+ .setTicker(n.tickerText, tickerView)
+ .setSound(n.sound, n.audioStreamType)
+ .setVibrate(n.vibrate)
+ .setLights(n.ledARGB, n.ledOnMS, n.ledOffMS)
+ .setOngoing((n.flags & Notification.FLAG_ONGOING_EVENT) != 0)
+ .setOnlyAlertOnce((n.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0)
+ .setAutoCancel((n.flags & Notification.FLAG_AUTO_CANCEL) != 0)
+ .setDefaults(n.defaults)
+ .setContentTitle(contentTitle)
+ .setContentText(contentText)
+ .setSubText(subText)
+ .setContentInfo(contentInfo)
+ .setContentIntent(contentIntent)
+ .setDeleteIntent(n.deleteIntent)
+ .setFullScreenIntent(fullScreenIntent,
+ (n.flags & Notification.FLAG_HIGH_PRIORITY) != 0)
+ .setLargeIcon(largeIcon)
+ .setNumber(number)
+ .setUsesChronometer(useChronometer)
+ .setPriority(priority)
+ .setProgress(progressMax, progress, progressIndeterminate)
+ .setLocalOnly(localOnly)
+ .setExtras(extras)
+ .setGroup(groupKey)
+ .setGroupSummary(groupSummary)
+ .setSortKey(sortKey)
+ .setCategory(category)
+ .setColor(color)
+ .setVisibility(visibility)
+ .setPublicVersion(publicVersion)
+ .setRemoteInputHistory(remoteInputHistory);
+ if (contentView != null) {
+ b.setCustomContentView(contentView);
+ }
+ if (bigContentView != null) {
+ b.setCustomBigContentView(bigContentView);
+ }
+ if (headsUpContentView != null) {
+ b.setCustomHeadsUpContentView(headsUpContentView);
+ }
+ for (String person: people) {
+ b.addPerson(person);
+ }
+ }
+
+ @Override
+ public void addAction(NotificationCompatBase.Action action) {
+ Notification.Action.Builder actionBuilder = new Notification.Action.Builder(
+ action.getIcon(), action.getTitle(), action.getActionIntent());
+ if (action.getRemoteInputs() != null) {
+ for (RemoteInput remoteInput : RemoteInputCompatApi20.fromCompat(
+ action.getRemoteInputs())) {
+ actionBuilder.addRemoteInput(remoteInput);
+ }
+ }
+ if (action.getExtras() != null) {
+ actionBuilder.addExtras(action.getExtras());
+ }
+ actionBuilder.setAllowGeneratedReplies(action.getAllowGeneratedReplies());
+ b.addAction(actionBuilder.build());
+ }
+
+ @Override
+ public Notification.Builder getBuilder() {
+ return b;
+ }
+
+ @Override
+ public Notification build() {
+ return b.build();
+ }
+ }
+
+ public static void addMessagingStyle(NotificationBuilderWithBuilderAccessor b,
+ CharSequence userDisplayName, CharSequence conversationTitle, List<CharSequence> texts,
+ List<Long> timestamps, List<CharSequence> senders, List<String> dataMimeTypes,
+ List<Uri> dataUris) {
+ Notification.MessagingStyle style = new Notification.MessagingStyle(userDisplayName)
+ .setConversationTitle(conversationTitle);
+ for (int i = 0; i < texts.size(); i++) {
+ Notification.MessagingStyle.Message message = new Notification.MessagingStyle.Message(
+ texts.get(i), timestamps.get(i), senders.get(i));
+ if (dataMimeTypes.get(i) != null) {
+ message.setData(dataMimeTypes.get(i), dataUris.get(i));
+ }
+ style.addMessage(message);
+ }
+ style.setBuilder(b.getBuilder());
+ }
+}
diff --git a/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java b/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java
new file mode 100644
index 0000000..f65d299
--- /dev/null
+++ b/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.app.NotificationManager;
+import android.content.Context;
+
+class NotificationManagerCompatApi24 {
+ public static boolean areNotificationsEnabled(NotificationManager notificationManager) {
+ return notificationManager.areNotificationsEnabled();
+ }
+
+ public static int getImportance(NotificationManager notificationManager) {
+ return notificationManager.getImportance();
+ }
+}
diff --git a/v4/api24/android/support/v4/content/ContextCompatApi24.java b/v4/api24/android/support/v4/content/ContextCompatApi24.java
new file mode 100644
index 0000000..9763240
--- /dev/null
+++ b/v4/api24/android/support/v4/content/ContextCompatApi24.java
@@ -0,0 +1,36 @@
+/*
+ * 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.v4.content;
+
+import android.content.Context;
+
+import java.io.File;
+
+/** {@hide} */
+public class ContextCompatApi24 {
+ public static File getDataDir(Context context) {
+ return context.getDataDir();
+ }
+
+ public static Context createDeviceProtectedStorageContext(Context context) {
+ return context.createDeviceProtectedStorageContext();
+ }
+
+ public static boolean isDeviceProtectedStorage(Context context) {
+ return context.isDeviceProtectedStorage();
+ }
+}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
new file mode 100644
index 0000000..e0c03a8
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.support.annotation.NonNull;
+
+import java.util.List;
+
+class MediaBrowserCompatApi24 {
+ public static Object createSubscriptionCallback(SubscriptionCallback callback) {
+ return new SubscriptionCallbackProxy<>(callback);
+ }
+
+ public static void subscribe(Object browserObj, String parentId, Bundle options,
+ Object subscriptionCallbackObj) {
+ ((MediaBrowser) browserObj).subscribe(parentId, options,
+ (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
+ }
+
+ public static void unsubscribe(Object browserObj, String parentId,
+ Object subscriptionCallbackObj) {
+ ((MediaBrowser) browserObj).unsubscribe(parentId,
+ (MediaBrowser.SubscriptionCallback) subscriptionCallbackObj);
+ }
+
+ interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
+ void onChildrenLoaded(@NonNull String parentId, List<Parcel> children,
+ @NonNull Bundle options);
+ void onError(@NonNull String parentId, @NonNull Bundle options);
+ }
+
+ static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
+ extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
+ public SubscriptionCallbackProxy(T callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onChildrenLoaded(@NonNull String parentId,
+ List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
+ mSubscriptionCallback.onChildrenLoaded(
+ parentId, itemListToParcelList(children), options);
+ }
+
+ @Override
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ mSubscriptionCallback.onError(parentId, options);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
new file mode 100644
index 0000000..a1a4f48
--- /dev/null
+++ b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.content.Context;
+import android.media.browse.MediaBrowser;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+
+class MediaBrowserServiceCompatApi24 {
+ private static final String TAG = "MBSCompatApi24";
+
+ private static Field sResultFlags;
+ static {
+ try {
+ sResultFlags = MediaBrowserService.Result.class.getDeclaredField("mFlags");
+ sResultFlags.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ Log.w(TAG, e);
+ }
+ }
+
+ public static Object createService(Context context, ServiceCompatProxy serviceProxy) {
+ return new MediaBrowserServiceAdaptor(context, serviceProxy);
+ }
+
+ public static void notifyChildrenChanged(Object serviceObj, String parentId, Bundle options) {
+ ((MediaBrowserService) serviceObj).notifyChildrenChanged(parentId, options);
+ }
+
+ public static Bundle getBrowserRootHints(Object serviceObj) {
+ return ((MediaBrowserService) serviceObj).getBrowserRootHints();
+ }
+
+ public interface ServiceCompatProxy extends MediaBrowserServiceCompatApi23.ServiceCompatProxy {
+ void onLoadChildren(String parentId, ResultWrapper result, Bundle options);
+ }
+
+ static class ResultWrapper {
+ MediaBrowserService.Result mResultObj;
+
+ ResultWrapper(MediaBrowserService.Result result) {
+ mResultObj = result;
+ }
+
+ public void sendResult(List<Parcel> result, int flags) {
+ try {
+ sResultFlags.setInt(mResultObj, flags);
+ } catch (IllegalAccessException e) {
+ Log.w(TAG, e);
+ }
+ mResultObj.sendResult(parcelListToItemList(result));
+ }
+
+ public void detach() {
+ mResultObj.detach();
+ }
+
+ List<MediaBrowser.MediaItem> parcelListToItemList(List<Parcel> parcelList) {
+ if (parcelList == null) {
+ return null;
+ }
+ List<MediaBrowser.MediaItem> items = new ArrayList<>();
+ for (Parcel parcel : parcelList) {
+ parcel.setDataPosition(0);
+ items.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ return items;
+ }
+ }
+
+ static class MediaBrowserServiceAdaptor extends
+ MediaBrowserServiceCompatApi23.MediaBrowserServiceAdaptor {
+ MediaBrowserServiceAdaptor(Context context, ServiceCompatProxy serviceWrapper) {
+ super(context, serviceWrapper);
+ }
+
+ @Override
+ public void onLoadChildren(String parentId, Result<List<MediaBrowser.MediaItem>> result,
+ Bundle options) {
+ ((ServiceCompatProxy) mServiceProxy).onLoadChildren(
+ parentId, new ResultWrapper(result), options);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java b/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
new file mode 100644
index 0000000..04bf843
--- /dev/null
+++ b/v4/api24/android/support/v4/media/session/MediaControllerCompatApi24.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.media.session.MediaController;
+import android.net.Uri;
+import android.os.Bundle;
+
+class MediaControllerCompatApi24 {
+
+ public static class TransportControls extends MediaControllerCompatApi23.TransportControls {
+ public static void prepare(Object controlsObj) {
+ ((MediaController.TransportControls) controlsObj).prepare();
+ }
+
+ public static void prepareFromMediaId(Object controlsObj, String mediaId, Bundle extras) {
+ ((MediaController.TransportControls) controlsObj).prepareFromMediaId(mediaId, extras);
+ }
+
+ public static void prepareFromSearch(Object controlsObj, String query, Bundle extras) {
+ ((MediaController.TransportControls) controlsObj).prepareFromSearch(query, extras);
+ }
+
+ public static void prepareFromUri(Object controlsObj, Uri uri, Bundle extras) {
+ ((MediaController.TransportControls) controlsObj).prepareFromUri(uri, extras);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java b/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
new file mode 100644
index 0000000..e03a3ee6
--- /dev/null
+++ b/v4/api24/android/support/v4/media/session/MediaSessionCompatApi24.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media.session;
+
+import android.media.session.MediaSession;
+import android.net.Uri;
+import android.os.Bundle;
+import android.util.Log;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class MediaSessionCompatApi24 {
+ private static final String TAG = "MediaSessionCompatApi24";
+
+ public static Object createCallback(Callback callback) {
+ return new CallbackProxy<Callback>(callback);
+ }
+
+ public static String getCallingPackage(Object sessionObj) {
+ MediaSession session = (MediaSession) sessionObj;
+ try {
+ Method getCallingPackageMethod = session.getClass().getMethod("getCallingPackage");
+ return (String) getCallingPackageMethod.invoke(session);
+ } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) {
+ Log.e(TAG, "Cannot execute MediaSession.getCallingPackage()", e);
+ }
+ return null;
+ }
+
+ public interface Callback extends MediaSessionCompatApi23.Callback {
+ public void onPrepare();
+ public void onPrepareFromMediaId(String mediaId, Bundle extras);
+ public void onPrepareFromSearch(String query, Bundle extras);
+ public void onPrepareFromUri(Uri uri, Bundle extras);
+ }
+
+ static class CallbackProxy<T extends Callback>
+ extends MediaSessionCompatApi23.CallbackProxy<T> {
+ public CallbackProxy(T callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onPrepare() {
+ mCallback.onPrepare();
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ mCallback.onPrepareFromMediaId(mediaId, extras);
+ }
+
+ @Override
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ mCallback.onPrepareFromSearch(query, extras);
+ }
+
+ @Override
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ mCallback.onPrepareFromUri(uri, extras);
+ }
+ }
+}
diff --git a/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
new file mode 100644
index 0000000..f525deae
--- /dev/null
+++ b/v4/api24/android/support/v4/net/TrafficStatsCompatApi24.java
@@ -0,0 +1,33 @@
+/*
+ * 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.v4.net;
+
+import android.net.TrafficStats;
+
+import java.net.DatagramSocket;
+import java.net.SocketException;
+
+/** {@hide} */
+public class TrafficStatsCompatApi24 {
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.tagDatagramSocket(socket);
+ }
+
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStats.untagDatagramSocket(socket);
+ }
+}
diff --git a/v4/api24/android/support/v4/os/UserManagerCompatApi24.java b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
new file mode 100644
index 0000000..7067e01
--- /dev/null
+++ b/v4/api24/android/support/v4/os/UserManagerCompatApi24.java
@@ -0,0 +1,27 @@
+/*
+ * 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.v4.os;
+
+import android.content.Context;
+import android.os.UserManager;
+
+/** {@hide} */
+public class UserManagerCompatApi24 {
+ public static boolean isUserUnlocked(Context context) {
+ return context.getSystemService(UserManager.class).isUserUnlocked();
+ }
+}
diff --git a/v4/api24/android/support/v4/print/PrintHelperApi24.java b/v4/api24/android/support/v4/print/PrintHelperApi24.java
new file mode 100644
index 0000000..3815941
--- /dev/null
+++ b/v4/api24/android/support/v4/print/PrintHelperApi24.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.print;
+
+import android.content.Context;
+
+/**
+ * Api24 specific PrintManager API implementation.
+ */
+class PrintHelperApi24 extends PrintHelperApi23 {
+ PrintHelperApi24(Context context) {
+ super(context);
+
+ mIsMinMarginsHandlingCorrect = true;
+ mPrintActivityRespectsOrientation = true;
+ }
+}
\ No newline at end of file
diff --git a/v4/api24/android/support/v4/view/PointerIconCompatApi24.java b/v4/api24/android/support/v4/view/PointerIconCompatApi24.java
new file mode 100644
index 0000000..d8c7ff0
--- /dev/null
+++ b/v4/api24/android/support/v4/view/PointerIconCompatApi24.java
@@ -0,0 +1,36 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.view.PointerIcon;
+
+class PointerIconCompatApi24 {
+ public static Object getSystemIcon(Context context, int style) {
+ return PointerIcon.getSystemIcon(context, style);
+ }
+
+ public static Object create(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return PointerIcon.create(bitmap, hotSpotX, hotSpotY);
+ }
+
+ public static Object load(Resources resources, int resourceId) {
+ return PointerIcon.load(resources, resourceId);
+ }
+}
diff --git a/v4/api24/android/support/v4/view/ViewCompatApi24.java b/v4/api24/android/support/v4/view/ViewCompatApi24.java
new file mode 100644
index 0000000..517f3cc
--- /dev/null
+++ b/v4/api24/android/support/v4/view/ViewCompatApi24.java
@@ -0,0 +1,26 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.PointerIcon;
+import android.view.View;
+
+class ViewCompatApi24 {
+ public static void setPointerIcon(View view, Object pointerIcon) {
+ view.setPointerIcon((PointerIcon)pointerIcon);
+ }
+}
diff --git a/v4/api24/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi24.java b/v4/api24/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi24.java
new file mode 100644
index 0000000..dedf88d
--- /dev/null
+++ b/v4/api24/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatApi24.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.accessibility;
+
+import android.view.accessibility.AccessibilityNodeInfo;
+
+/**
+ * Api24-specific AccessibilityNodeInfo API implementation.
+ */
+class AccessibilityNodeInfoCompatApi24 {
+ public static int getDrawingOrder(Object info) {
+ return ((AccessibilityNodeInfo) info).getDrawingOrder();
+ }
+
+ public static void setDrawingOrder(Object info, int drawingOrderInParent) {
+ ((AccessibilityNodeInfo) info).setDrawingOrder(drawingOrderInParent);
+ }
+
+ public static boolean isImportantForAccessibility(Object info) {
+ return ((AccessibilityNodeInfo) info).isImportantForAccessibility();
+ }
+
+ public static void setImportantForAccessibility(Object info,
+ boolean importantForAccessibility) {
+ ((AccessibilityNodeInfo) info).setImportantForAccessibility(importantForAccessibility);
+ }
+}
diff --git a/v4/api24/android/support/v4/view/accessibility/AccessibilityWindowInfoCompatApi24.java b/v4/api24/android/support/v4/view/accessibility/AccessibilityWindowInfoCompatApi24.java
new file mode 100644
index 0000000..34ba542
--- /dev/null
+++ b/v4/api24/android/support/v4/view/accessibility/AccessibilityWindowInfoCompatApi24.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view.accessibility;
+
+import android.view.accessibility.AccessibilityWindowInfo;
+
+/**
+ * Api24-specific AccessibilityWindowInfo API implementation.
+ */
+class AccessibilityWindowInfoCompatApi24 {
+ public static CharSequence getTitle(Object info) {
+ return ((AccessibilityWindowInfo) info).getTitle();
+ }
+
+ public static Object getAnchor(Object info) {
+ return ((AccessibilityWindowInfo) info).getAnchor();
+ }
+}
diff --git a/v4/build.gradle b/v4/build.gradle
index ba65cab..4da2100 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -1,75 +1,25 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'support-v4'
-// create a jar task for the code internal implementation
-tasks.create(name: "internalJar", type: Jar) {
- baseName "internal_impl"
-}
-// --------------------------
-// TO ADD NEW PLATFORM SPECIFIC CODE, UPDATE THIS:
-// create and configure the sourcesets/dependencies for platform-specific code.
-// values are: sourceset name, source folder name, api level, previous sourceset.
-
-ext.allSS = []
-
-def baseSS = createApiSourceset('donut', 'donut', '4', null)
-def eclairSS = createApiSourceset('eclair', 'eclair', '5', baseSS)
-def eclairMr1SS = createApiSourceset('eclairmr1', 'eclair-mr1', '7', eclairSS)
-def froyoSS = createApiSourceset('froyo', 'froyo', '8', eclairMr1SS)
-def gingerbreadSS = createApiSourceset('gingerbread', 'gingerbread', '9', froyoSS)
-def honeycombSS = createApiSourceset('honeycomb', 'honeycomb', '11', gingerbreadSS)
-def honeycombMr1SS = createApiSourceset('honeycombmr1', 'honeycomb_mr1', '12', honeycombSS)
-def honeycombMr2SS = createApiSourceset('honeycombmr2', 'honeycomb_mr2', '13', honeycombMr1SS)
-def icsSS = createApiSourceset('ics', 'ics', '14', honeycombMr2SS)
-def icsMr1SS = createApiSourceset('icsmr1', 'ics-mr1', '15', icsSS)
-def jbSS = createApiSourceset('jellybean', 'jellybean', '16', icsMr1SS)
-def jbMr1SS = createApiSourceset('jellybeanmr1', 'jellybean-mr1', '17', jbSS)
-def jbMr2SS = createApiSourceset('jellybeanmr2', 'jellybean-mr2', '18', jbMr1SS)
-def kitkatSS = createApiSourceset('kitkat', 'kitkat', '19', jbMr2SS)
-def api20SS = createApiSourceset('api20', 'api20', '20', kitkatSS)
-def api21SS = createApiSourceset('api21', 'api21', '21', api20SS)
-def api22SS = createApiSourceset('api22', 'api22', '22', api21SS)
-def api23SS = createApiSourceset('api23', 'api23', 'current', api22SS)
-
-
-def createApiSourceset(String name, String folder, String apiLevel, SourceSet previousSource) {
- def sourceSet = sourceSets.create(name)
- sourceSet.java.srcDirs = [folder]
-
- def configName = sourceSet.getCompileConfigurationName()
-
- project.getDependencies().add(configName, getAndroidPrebuilt(apiLevel))
- if (previousSource != null) {
- setupDependencies(configName, previousSource)
- }
- ext.allSS.add(sourceSet)
-
- internalJar.from sourceSet.output
-
- return sourceSet
-}
-
-def setupDependencies(String configName, SourceSet previousSourceSet) {
- project.getDependencies().add(configName, previousSourceSet.output)
- project.getDependencies().add(configName, previousSourceSet.compileClasspath)
-}
-
+createApiSourceSets(project, gradle.ext.studioCompat.modules.v4.apiTargets)
dependencies {
- compile project(':support-annotations')
- donutCompile project(':support-annotations')
-
- // add the internal implementation as a dependency.
- // this is not enough to make the regular compileJava task
- // depend on the generation of this jar. This is done below
- // when manipulating the libraryVariants.
- compile files(internalJar.archivePath)
-
- 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'
+ }
+ androidTestCompile 'org.mockito:mockito-core:1.9.5'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'junit:junit:4.12'
}
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
+setApiModuleDependencies(project, dependencies, gradle.ext.studioCompat.modules.v4.dependencies)
+
android {
compileSdkVersion 4
@@ -97,6 +47,10 @@
abortOnError false
}
+ buildTypes.all {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
+
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_7
targetCompatibility JavaVersion.VERSION_1_7
@@ -104,12 +58,11 @@
testOptions {
unitTests.returnDefaultValues = true
+ compileSdkVersion project.ext.currentSdk
}
}
android.libraryVariants.all { variant ->
- variant.javaCompile.dependsOn internalJar
-
def name = variant.buildType.name
if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
@@ -149,17 +102,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 {
@@ -192,4 +134,4 @@
}
}
}
-}
+}
\ No newline at end of file
diff --git a/v4/donut/android/support/v4/app/BaseFragmentActivityDonut.java b/v4/donut/android/support/v4/app/BaseFragmentActivityDonut.java
index e94d47d..4179632 100644
--- a/v4/donut/android/support/v4/app/BaseFragmentActivityDonut.java
+++ b/v4/donut/android/support/v4/app/BaseFragmentActivityDonut.java
@@ -25,6 +25,8 @@
/**
* Base class for {@code FragmentActivity} to be able to use Donut APIs.
+ *
+ * @hide
*/
abstract class BaseFragmentActivityDonut extends Activity {
@@ -51,4 +53,12 @@
abstract View dispatchFragmentsOnCreateView(View parent, String name,
Context context, AttributeSet attrs);
+ /**
+ * Called when the back button has been pressed.and not handled by the support fragment manager.
+ */
+ void onBackPressedNotHandled() {
+ // on v4, just call finish manually
+ finish();
+ }
+
}
diff --git a/v4/donut/android/support/v4/app/NotificationCompatBase.java b/v4/donut/android/support/v4/app/NotificationCompatBase.java
index 777a57f..b7104f2 100644
--- a/v4/donut/android/support/v4/app/NotificationCompatBase.java
+++ b/v4/donut/android/support/v4/app/NotificationCompatBase.java
@@ -16,7 +16,9 @@
package android.support.v4.app;
+import android.app.Notification;
import android.app.PendingIntent;
+import android.content.Context;
import android.os.Bundle;
/**
@@ -30,10 +32,12 @@
public abstract PendingIntent getActionIntent();
public abstract Bundle getExtras();
public abstract RemoteInputCompatBase.RemoteInput[] getRemoteInputs();
+ public abstract boolean getAllowGeneratedReplies();
public interface Factory {
Action build(int icon, CharSequence title, PendingIntent actionIntent,
- Bundle extras, RemoteInputCompatBase.RemoteInput[] remoteInputs);
+ Bundle extras, RemoteInputCompatBase.RemoteInput[] remoteInputs,
+ boolean allowGeneratedReplies);
public Action[] newArray(int length);
}
}
@@ -54,4 +58,10 @@
String[] participants, long latestTimestamp);
}
}
+
+ public static Notification add(Notification notification, Context context,
+ CharSequence contentTitle, CharSequence contentText, PendingIntent contentIntent) {
+ notification.setLatestEventInfo(context, contentTitle, contentText, contentIntent);
+ return notification;
+ }
}
diff --git a/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java b/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java
new file mode 100644
index 0000000..3bb9397
--- /dev/null
+++ b/v4/donut/android/support/v4/content/res/ConfigurationHelperDonut.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+import android.util.DisplayMetrics;
+
+class ConfigurationHelperDonut {
+
+ static int getScreenHeightDp(@NonNull Resources resources) {
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ return (int) (metrics.heightPixels / metrics.density);
+ }
+
+ static int getScreenWidthDp(@NonNull Resources resources) {
+ final DisplayMetrics metrics = resources.getDisplayMetrics();
+ return (int) (metrics.widthPixels / metrics.density);
+ }
+
+ static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+ // Not perfect, but close enough
+ return Math.min(getScreenWidthDp(resources), getScreenHeightDp(resources));
+ }
+
+ static int getDensityDpi(@NonNull Resources resources) {
+ return resources.getDisplayMetrics().densityDpi;
+ }
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java b/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java
index fe0163d..53de6ff 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableCompatBase.java
@@ -17,8 +17,14 @@
package android.support.v4.graphics.drawable;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* Base implementation of drawable compatibility.
@@ -26,28 +32,33 @@
class DrawableCompatBase {
public static void setTint(Drawable drawable, int tint) {
- if (drawable instanceof DrawableWrapper) {
- ((DrawableWrapper) drawable).setCompatTint(tint);
+ if (drawable instanceof TintAwareDrawable) {
+ ((TintAwareDrawable) drawable).setTint(tint);
}
}
public static void setTintList(Drawable drawable, ColorStateList tint) {
- if (drawable instanceof DrawableWrapper) {
- ((DrawableWrapper) drawable).setCompatTintList(tint);
+ if (drawable instanceof TintAwareDrawable) {
+ ((TintAwareDrawable) drawable).setTintList(tint);
}
}
public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
- if (drawable instanceof DrawableWrapper) {
- ((DrawableWrapper) drawable).setCompatTintMode(tintMode);
+ if (drawable instanceof TintAwareDrawable) {
+ ((TintAwareDrawable) drawable).setTintMode(tintMode);
}
}
public static Drawable wrapForTinting(Drawable drawable) {
- if (!(drawable instanceof DrawableWrapperDonut)) {
+ if (!(drawable instanceof TintAwareDrawable)) {
return new DrawableWrapperDonut(drawable);
}
return drawable;
}
+ public static void inflate(Drawable drawable, Resources res, XmlPullParser parser,
+ AttributeSet attrs, Resources.Theme t)
+ throws IOException, XmlPullParserException {
+ drawable.inflate(res, parser, attrs);
+ }
}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java
index edbe5ad..2cfe4ea 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapper.java
@@ -16,26 +16,15 @@
package android.support.v4.graphics.drawable;
-import android.content.res.ColorStateList;
-import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
/**
- * Interface which allows a {@link android.graphics.drawable.Drawable} to receive tinting calls from
- * {@code DrawableCompat}.
+ * Interface which allows a {@link android.graphics.drawable.Drawable} to get/set wrapped
+ * drawables from {@code DrawableCompat}.
*
* @hide
*/
public interface DrawableWrapper {
-
- void setCompatTint(int tint);
-
- void setCompatTintList(ColorStateList tint);
-
- void setCompatTintMode(PorterDuff.Mode tintMode);
-
Drawable getWrappedDrawable();
-
void setWrappedDrawable(Drawable drawable);
-
}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
index 300e2e8..916debf 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
@@ -17,12 +17,15 @@
package android.support.v4.graphics.drawable;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
/**
* Drawable which delegates all calls to it's wrapped {@link android.graphics.drawable.Drawable}.
@@ -30,21 +33,54 @@
* Also allows backward compatible tinting via a color or {@link ColorStateList}.
* This functionality is accessed via static methods in {@code DrawableCompat}.
*/
-class DrawableWrapperDonut extends Drawable implements Drawable.Callback, DrawableWrapper {
+class DrawableWrapperDonut extends Drawable
+ implements Drawable.Callback, DrawableWrapper, TintAwareDrawable {
- static final PorterDuff.Mode DEFAULT_MODE = PorterDuff.Mode.SRC_IN;
-
- private ColorStateList mTintList;
- private PorterDuff.Mode mTintMode = DEFAULT_MODE;
+ static final PorterDuff.Mode DEFAULT_TINT_MODE = PorterDuff.Mode.SRC_IN;
private int mCurrentColor;
private PorterDuff.Mode mCurrentMode;
private boolean mColorFilterSet;
+ DrawableWrapperState mState;
+ private boolean mMutated;
+
Drawable mDrawable;
- DrawableWrapperDonut(Drawable drawable) {
- setWrappedDrawable(drawable);
+ DrawableWrapperDonut(@NonNull DrawableWrapperState state, @Nullable Resources res) {
+ mState = state;
+ updateLocalState(res);
+ }
+
+ /**
+ * Creates a new wrapper around the specified drawable.
+ *
+ * @param dr the drawable to wrap
+ */
+ DrawableWrapperDonut(@Nullable Drawable dr) {
+ mState = mutateConstantState();
+ // Now set the drawable...
+ setWrappedDrawable(dr);
+ }
+
+ /**
+ * Initializes local dynamic properties from state. This should be called
+ * after significant state changes, e.g. from the One True Constructor and
+ * after inflating or applying a theme.
+ */
+ private void updateLocalState(@Nullable Resources res) {
+ if (mState != null && mState.mDrawableState != null) {
+ final Drawable dr = newDrawableFromState(mState.mDrawableState, res);
+ setWrappedDrawable(dr);
+ }
+ }
+
+ /**
+ * Allows us to call ConstantState.newDrawable(*) is a API safe way
+ */
+ protected Drawable newDrawableFromState(@NonNull Drawable.ConstantState state,
+ @Nullable Resources res) {
+ return state.newDrawable();
}
@Override
@@ -54,7 +90,9 @@
@Override
protected void onBoundsChange(Rect bounds) {
- mDrawable.setBounds(bounds);
+ if (mDrawable != null) {
+ mDrawable.setBounds(bounds);
+ }
}
@Override
@@ -64,7 +102,9 @@
@Override
public int getChangingConfigurations() {
- return mDrawable.getChangingConfigurations();
+ return super.getChangingConfigurations()
+ | (mState != null ? mState.getChangingConfigurations() : 0)
+ | mDrawable.getChangingConfigurations();
}
@Override
@@ -89,7 +129,9 @@
@Override
public boolean isStateful() {
- final ColorStateList tintList = isCompatTintEnabled() ? mTintList : null;
+ final ColorStateList tintList = (isCompatTintEnabled() && mState != null)
+ ? mState.mTint
+ : null;
return (tintList != null && tintList.isStateful()) || mDrawable.isStateful();
}
@@ -151,18 +193,44 @@
}
@Override
- public Drawable mutate() {
- Drawable wrapped = mDrawable;
- Drawable mutated = wrapped.mutate();
- if (mutated != wrapped) {
- // If mutate() returned a new instance, update our reference
- setWrappedDrawable(mutated);
+ @Nullable
+ public ConstantState getConstantState() {
+ if (mState != null && mState.canConstantState()) {
+ mState.mChangingConfigurations = getChangingConfigurations();
+ return mState;
}
- // We return ourselves, since only the wrapped drawable needs to mutate
+ return null;
+ }
+
+ @Override
+ public Drawable mutate() {
+ if (!mMutated && super.mutate() == this) {
+ mState = mutateConstantState();
+ if (mDrawable != null) {
+ mDrawable.mutate();
+ }
+ if (mState != null) {
+ mState.mDrawableState = mDrawable != null ? mDrawable.getConstantState() : null;
+ }
+ mMutated = true;
+ }
return this;
}
/**
+ * Mutates the constant state and returns the new state.
+ * <p>
+ * This method should never call the super implementation; it should always
+ * mutate and return its own constant state.
+ *
+ * @return the new state
+ */
+ @NonNull
+ DrawableWrapperState mutateConstantState() {
+ return new DrawableWrapperStateDonut(mState, null);
+ }
+
+ /**
* {@inheritDoc}
*/
public void invalidateDrawable(Drawable who) {
@@ -189,24 +257,20 @@
}
@Override
- public void setCompatTint(int tint) {
- setCompatTintList(ColorStateList.valueOf(tint));
+ public void setTint(int tint) {
+ setTintList(ColorStateList.valueOf(tint));
}
@Override
- public void setCompatTintList(ColorStateList tint) {
- if (mTintList != tint) {
- mTintList = tint;
- updateTint(getState());
- }
+ public void setTintList(ColorStateList tint) {
+ mState.mTint = tint;
+ updateTint(getState());
}
@Override
- public void setCompatTintMode(PorterDuff.Mode tintMode) {
- if (mTintMode != tintMode) {
- mTintMode = tintMode;
- updateTint(getState());
- }
+ public void setTintMode(PorterDuff.Mode tintMode) {
+ mState.mTintMode = tintMode;
+ updateTint(getState());
}
private boolean updateTint(int[] state) {
@@ -215,12 +279,15 @@
return false;
}
- if (mTintList != null && mTintMode != null) {
- final int color = mTintList.getColorForState(state, mTintList.getDefaultColor());
- if (!mColorFilterSet || color != mCurrentColor || mTintMode != mCurrentMode) {
- setColorFilter(color, mTintMode);
+ final ColorStateList tintList = mState.mTint;
+ final PorterDuff.Mode tintMode = mState.mTintMode;
+
+ if (tintList != null && tintMode != null) {
+ final int color = tintList.getColorForState(state, tintList.getDefaultColor());
+ if (!mColorFilterSet || color != mCurrentColor || tintMode != mCurrentMode) {
+ setColorFilter(color, tintMode);
mCurrentColor = color;
- mCurrentMode = mTintMode;
+ mCurrentMode = tintMode;
mColorFilterSet = true;
return true;
}
@@ -234,32 +301,32 @@
/**
* Returns the wrapped {@link Drawable}
*/
- public Drawable getWrappedDrawable() {
+ public final Drawable getWrappedDrawable() {
return mDrawable;
}
/**
* Sets the current wrapped {@link Drawable}
*/
- public void setWrappedDrawable(Drawable drawable) {
+ public final void setWrappedDrawable(Drawable dr) {
if (mDrawable != null) {
mDrawable.setCallback(null);
}
- mDrawable = null;
- if (drawable != null) {
- // Copy over the bounds from the drawable
- setBounds(drawable.getBounds());
- // Set ourselves as the callback for invalidations
- drawable.setCallback(this);
- } else {
- // Clear our bounds
- setBounds(0, 0, 0, 0);
+ mDrawable = dr;
+
+ if (dr != null) {
+ dr.setCallback(this);
+ // Only call setters for data that's stored in the base Drawable.
+ dr.setVisible(isVisible(), true);
+ dr.setState(getState());
+ dr.setLevel(getLevel());
+ dr.setBounds(getBounds());
+ if (mState != null) {
+ mState.mDrawableState = dr.getConstantState();
+ }
}
- mDrawable = drawable;
-
- // Invalidate ourselves
invalidateSelf();
}
@@ -267,4 +334,50 @@
// It's enabled by default on Donut
return true;
}
+
+ protected static abstract class DrawableWrapperState extends Drawable.ConstantState {
+ int mChangingConfigurations;
+ Drawable.ConstantState mDrawableState;
+
+ ColorStateList mTint = null;
+ PorterDuff.Mode mTintMode = DEFAULT_TINT_MODE;
+
+ DrawableWrapperState(@Nullable DrawableWrapperState orig, @Nullable Resources res) {
+ if (orig != null) {
+ mChangingConfigurations = orig.mChangingConfigurations;
+ mDrawableState = orig.mDrawableState;
+ mTint = orig.mTint;
+ mTintMode = orig.mTintMode;
+ }
+ }
+
+ @Override
+ public Drawable newDrawable() {
+ return newDrawable(null);
+ }
+
+ public abstract Drawable newDrawable(@Nullable Resources res);
+
+ @Override
+ public int getChangingConfigurations() {
+ return mChangingConfigurations
+ | (mDrawableState != null ? mDrawableState.getChangingConfigurations() : 0);
+ }
+
+ boolean canConstantState() {
+ return mDrawableState != null;
+ }
+ }
+
+ private static class DrawableWrapperStateDonut extends DrawableWrapperState {
+ DrawableWrapperStateDonut(
+ @Nullable DrawableWrapperState orig, @Nullable Resources res) {
+ super(orig, res);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new DrawableWrapperDonut(this, res);
+ }
+ }
}
diff --git a/v4/donut/android/support/v4/graphics/drawable/TintAwareDrawable.java b/v4/donut/android/support/v4/graphics/drawable/TintAwareDrawable.java
new file mode 100644
index 0000000..e59d9f2
--- /dev/null
+++ b/v4/donut/android/support/v4/graphics/drawable/TintAwareDrawable.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+
+/**
+ * Interface which allows a {@link android.graphics.drawable.Drawable} to receive tinting calls
+ * from {@code DrawableCompat}.
+ *
+ * @hide
+ */
+public interface TintAwareDrawable {
+ void setTint(@ColorInt int tint);
+ void setTintList(ColorStateList tint);
+ void setTintMode(PorterDuff.Mode tintMode);
+}
diff --git a/v4/donut/android/support/v4/os/BuildCompat.java b/v4/donut/android/support/v4/os/BuildCompat.java
new file mode 100644
index 0000000..4b2ba3d
--- /dev/null
+++ b/v4/donut/android/support/v4/os/BuildCompat.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.os;
+
+import android.os.Build.VERSION;
+import android.text.TextUtils;
+
+/**
+ * BuildCompat contains additional platform version checking methods for
+ * testing compatibility with new features.
+ */
+public class BuildCompat {
+ private BuildCompat() {
+ }
+
+ /**
+ * Check if the device is running on the Android N release or newer.
+ *
+ * @return {@code true} if N APIs are available for use
+ */
+ public static boolean isAtLeastN() {
+ return VERSION.SDK_INT >= 24;
+ }
+}
diff --git a/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java b/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java
index ea19333..c85235c 100644
--- a/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java
+++ b/v4/donut/android/support/v4/view/LayoutInflaterCompatBase.java
@@ -45,4 +45,12 @@
inflater.setFactory(factory != null ? new FactoryWrapper(factory) : null);
}
+ static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
+ LayoutInflater.Factory factory = inflater.getFactory();
+ if (factory instanceof FactoryWrapper) {
+ return ((FactoryWrapper) factory).mDelegateFactory;
+ }
+ return null;
+ }
+
}
diff --git a/v4/donut/android/support/v4/view/ViewCompatBase.java b/v4/donut/android/support/v4/view/ViewCompatBase.java
index 33706592..f4e113e 100644
--- a/v4/donut/android/support/v4/view/ViewCompatBase.java
+++ b/v4/donut/android/support/v4/view/ViewCompatBase.java
@@ -19,6 +19,7 @@
import android.content.res.ColorStateList;
import android.graphics.PorterDuff;
import android.view.View;
+import android.view.ViewParent;
import java.lang.reflect.Field;
@@ -108,4 +109,44 @@
static boolean isAttachedToWindow(View view) {
return view.getWindowToken() != null;
}
+
+ static void offsetTopAndBottom(View view, int offset) {
+ final int currentTop = view.getTop();
+ view.offsetTopAndBottom(offset);
+
+ if (offset != 0) {
+ // We need to manually invalidate pre-honeycomb
+ final ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ final int absOffset = Math.abs(offset);
+ ((View) parent).invalidate(
+ view.getLeft(),
+ currentTop - absOffset,
+ view.getRight(),
+ currentTop + view.getHeight() + absOffset);
+ } else {
+ view.invalidate();
+ }
+ }
+ }
+
+ static void offsetLeftAndRight(View view, int offset) {
+ final int currentLeft = view.getLeft();
+ view.offsetLeftAndRight(offset);
+
+ if (offset != 0) {
+ // We need to manually invalidate pre-honeycomb
+ final ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ final int absOffset = Math.abs(offset);
+ ((View) parent).invalidate(
+ currentLeft - absOffset,
+ view.getTop(),
+ currentLeft + view.getWidth() + absOffset,
+ view.getBottom());
+ } else {
+ view.invalidate();
+ }
+ }
+ }
}
diff --git a/v4/donut/android/support/v4/widget/ListViewCompatDonut.java b/v4/donut/android/support/v4/widget/ListViewCompatDonut.java
new file mode 100644
index 0000000..4e9aede
--- /dev/null
+++ b/v4/donut/android/support/v4/widget/ListViewCompatDonut.java
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.view.View;
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+class ListViewCompatDonut {
+ static void scrollListBy(final ListView listView, int y) {
+ final int firstPosition = listView.getFirstVisiblePosition();
+ if (firstPosition == ListView.INVALID_POSITION) {
+ return;
+ }
+
+ final View firstView = listView.getChildAt(0);
+ if (firstView == null) {
+ return;
+ }
+
+ final int newTop = firstView.getTop() - y;
+ listView.setSelectionFromTop(firstPosition, newTop);
+ }
+}
diff --git a/v4/eclair/android/support/v4/app/ActivityCompatEclair.java b/v4/eclair/android/support/v4/app/ActivityCompatEclair.java
new file mode 100644
index 0000000..95a77f8
--- /dev/null
+++ b/v4/eclair/android/support/v4/app/ActivityCompatEclair.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+
+import android.app.Activity;
+import android.content.Intent;
+import android.content.IntentSender;
+
+class ActivityCompatEclair {
+
+ public static void startIntentSenderForResult(Activity activity, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ activity.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags);
+ }
+}
diff --git a/v4/eclair/android/support/v4/app/BaseFragmentActivityEclair.java b/v4/eclair/android/support/v4/app/BaseFragmentActivityEclair.java
new file mode 100644
index 0000000..a77f057
--- /dev/null
+++ b/v4/eclair/android/support/v4/app/BaseFragmentActivityEclair.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.content.Intent;
+import android.content.IntentSender;
+import android.support.annotation.Nullable;
+
+/**
+ * Base class for {@code FragmentActivity} to be able to use v5 APIs.
+ *
+ * @hide
+ */
+abstract class BaseFragmentActivityEclair extends BaseFragmentActivityDonut {
+
+ // We need to keep track of whether startIntentSenderForResult originated from a Fragment, so we
+ // can conditionally check whether the requestCode collides with our reserved ID space for the
+ // request index (see above). Unfortunately we can't just call
+ // super.startIntentSenderForResult(...) to bypass the check when the call didn't come from a
+ // fragment, since we need to use the ActivityCompat version for backward compatibility.
+ boolean mStartedIntentSenderFromFragment;
+
+ @Override
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags)
+ throws IntentSender.SendIntentException {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedIntentSenderFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+ extraFlags);
+ }
+
+ @Override
+ void onBackPressedNotHandled() {
+ // On v5+, delegate to the framework impl of onBackPressed()
+ super.onBackPressed();
+ }
+
+ /**
+ * Checks whether the given request code is a valid code by masking it with 0xffff0000. Throws
+ * an {@link IllegalArgumentException} if the code is not valid.
+ */
+ static void checkForValidRequestCode(int requestCode) {
+ if ((requestCode & 0xffff0000) != 0) {
+ throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
+ }
+ }
+}
diff --git a/v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.java b/v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.java
new file mode 100644
index 0000000..a33efe5
--- /dev/null
+++ b/v4/eclair/android/support/v4/graphics/drawable/DrawableCompatEclair.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.graphics.drawable.Drawable;
+
+/**
+ * Implementation of drawable compatibility that can call Eclair APIs.
+ */
+class DrawableCompatEclair {
+ public static Drawable wrapForTinting(Drawable drawable) {
+ if (!(drawable instanceof TintAwareDrawable)) {
+ return new DrawableWrapperEclair(drawable);
+ }
+ return drawable;
+ }
+}
diff --git a/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java b/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java
new file mode 100644
index 0000000..11a65ee
--- /dev/null
+++ b/v4/eclair/android/support/v4/graphics/drawable/DrawableWrapperEclair.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics.drawable;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.Canvas;
+import android.graphics.ColorFilter;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.Region;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+
+class DrawableWrapperEclair extends DrawableWrapperDonut {
+
+ DrawableWrapperEclair(Drawable drawable) {
+ super(drawable);
+ }
+
+ DrawableWrapperEclair(DrawableWrapperState state, Resources resources) {
+ super(state, resources);
+ }
+
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ return new DrawableWrapperStateEclair(mState, null);
+ }
+
+ @Override
+ protected Drawable newDrawableFromState(Drawable.ConstantState state, Resources res) {
+ return state.newDrawable(res);
+ }
+
+ private static class DrawableWrapperStateEclair extends DrawableWrapperState {
+ DrawableWrapperStateEclair(@Nullable DrawableWrapperState orig,
+ @Nullable Resources res) {
+ super(orig, res);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new DrawableWrapperEclair(this, res);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v4/honeycomb/android/support/v4/app/BaseFragmentActivityHoneycomb.java b/v4/honeycomb/android/support/v4/app/BaseFragmentActivityHoneycomb.java
index 5d9688d..10e6a79 100644
--- a/v4/honeycomb/android/support/v4/app/BaseFragmentActivityHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/app/BaseFragmentActivityHoneycomb.java
@@ -23,8 +23,10 @@
/**
* Base class for {@code FragmentActivity} to be able to use v11 APIs.
+ *
+ * @hide
*/
-abstract class BaseFragmentActivityHoneycomb extends BaseFragmentActivityDonut {
+abstract class BaseFragmentActivityHoneycomb extends BaseFragmentActivityEclair {
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
diff --git a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
index def23ed..08066a46 100644
--- a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableCompatHoneycomb.java
@@ -30,7 +30,7 @@
}
public static Drawable wrapForTinting(Drawable drawable) {
- if (!(drawable instanceof DrawableWrapperHoneycomb)) {
+ if (!(drawable instanceof TintAwareDrawable)) {
return new DrawableWrapperHoneycomb(drawable);
}
return drawable;
diff --git a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
index f9fd7d8..d37ba97 100644
--- a/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/graphics/drawable/DrawableWrapperHoneycomb.java
@@ -16,7 +16,10 @@
package android.support.v4.graphics.drawable;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
class DrawableWrapperHoneycomb extends DrawableWrapperDonut {
@@ -24,8 +27,30 @@
super(drawable);
}
+ DrawableWrapperHoneycomb(DrawableWrapperState state, Resources resources) {
+ super(state, resources);
+ }
+
@Override
public void jumpToCurrentState() {
mDrawable.jumpToCurrentState();
}
+
+ @NonNull
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ return new DrawableWrapperStateHoneycomb(mState, null);
+ }
+
+ private static class DrawableWrapperStateHoneycomb extends DrawableWrapperState {
+ DrawableWrapperStateHoneycomb(@Nullable DrawableWrapperState orig,
+ @Nullable Resources res) {
+ super(orig, res);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new DrawableWrapperHoneycomb(this, res);
+ }
+ }
}
diff --git a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
index f1bd117..6cd185b 100644
--- a/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
+++ b/v4/honeycomb/android/support/v4/view/KeyEventCompatHoneycomb.java
@@ -33,4 +33,8 @@
public static boolean metaStateHasNoModifiers(int metaState) {
return KeyEvent.metaStateHasNoModifiers(metaState);
}
+
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return event.isCtrlPressed();
+ }
}
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index 8b21259..7d17bde 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -17,8 +17,10 @@
package android.support.v4.view;
import android.animation.ValueAnimator;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.view.View;
+import android.view.ViewParent;
class ViewCompatHC {
static long getFrameTime() {
@@ -97,6 +99,10 @@
view.setTranslationY(value);
}
+ public static Matrix getMatrix(View view) {
+ return view.getMatrix();
+ }
+
public static void setAlpha(View view, float value) {
view.setAlpha(value);
}
@@ -160,4 +166,30 @@
public static int combineMeasuredStates(int curState, int newState) {
return View.combineMeasuredStates(curState, newState);
}
+
+ static void offsetTopAndBottom(View view, int offset) {
+ view.offsetTopAndBottom(offset);
+ tickleInvalidationFlag(view);
+
+ ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ tickleInvalidationFlag((View) parent);
+ }
+ }
+
+ static void offsetLeftAndRight(View view, int offset) {
+ view.offsetLeftAndRight(offset);
+ tickleInvalidationFlag(view);
+
+ ViewParent parent = view.getParent();
+ if (parent instanceof View) {
+ tickleInvalidationFlag((View) parent);
+ }
+ }
+
+ private static void tickleInvalidationFlag(View view) {
+ final float y = view.getTranslationY();
+ view.setTranslationY(y + 1);
+ view.setTranslationY(y);
+ }
}
diff --git a/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.java b/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.java
new file mode 100644
index 0000000..62eba95
--- /dev/null
+++ b/v4/honeycomb_mr2/android/support/v4/content/res/ConfigurationHelperHoneycombMr2.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+
+class ConfigurationHelperHoneycombMr2 {
+
+ static int getScreenHeightDp(@NonNull Resources resources) {
+ return resources.getConfiguration().screenHeightDp;
+ }
+
+ static int getScreenWidthDp(@NonNull Resources resources) {
+ return resources.getConfiguration().screenWidthDp;
+ }
+
+ static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+ return resources.getConfiguration().smallestScreenWidthDp;
+ }
+}
diff --git a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
index 3378e1e..a0e0e7d1 100644
--- a/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
+++ b/v4/ics/android/support/v4/media/session/MediaSessionCompatApi14.java
@@ -223,28 +223,4 @@
metadata.getString(METADATA_KEY_WRITER));
}
}
-
- static interface Callback {
- public void onCommand(String command, Bundle extras, ResultReceiver cb);
-
- public boolean onMediaButtonEvent(Intent mediaButtonIntent);
-
- public void onPlay();
-
- public void onPause();
-
- public void onSkipToNext();
-
- public void onSkipToPrevious();
-
- public void onFastForward();
-
- public void onRewind();
-
- public void onStop();
-
- public void onSeekTo(long pos);
-
- public void onSetRating(Object ratingObj);
- }
}
diff --git a/v4/ics/android/support/v4/net/DatagramSocketWrapper.java b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
new file mode 100644
index 0000000..d7646d9
--- /dev/null
+++ b/v4/ics/android/support/v4/net/DatagramSocketWrapper.java
@@ -0,0 +1,116 @@
+/*
+ * 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.v4.net;
+
+import java.io.FileDescriptor;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.DatagramSocket;
+import java.net.InetAddress;
+import java.net.Socket;
+import java.net.SocketAddress;
+import java.net.SocketException;
+import java.net.SocketImpl;
+
+/** {@hide} */
+class DatagramSocketWrapper extends Socket {
+ public DatagramSocketWrapper(DatagramSocket socket, FileDescriptor fd) throws SocketException {
+ super(new DatagramSocketImplWrapper(socket, fd));
+ }
+
+ /**
+ * Empty implementation which wires in the given {@link FileDescriptor}.
+ */
+ private static class DatagramSocketImplWrapper extends SocketImpl {
+ public DatagramSocketImplWrapper(DatagramSocket socket, FileDescriptor fd) {
+ super();
+ this.localport = socket.getLocalPort();
+ this.fd = fd;
+ }
+
+ @Override
+ protected void accept(SocketImpl newSocket) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected int available() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void bind(InetAddress address, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void close() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(String host, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(InetAddress address, int port) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void create(boolean isStreaming) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected InputStream getInputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected OutputStream getOutputStream() throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void listen(int backlog) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void connect(SocketAddress remoteAddr, int timeout) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ protected void sendUrgentData(int value) throws IOException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public Object getOption(int optID) throws SocketException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public void setOption(int optID, Object val) throws SocketException {
+ throw new UnsupportedOperationException();
+ }
+ }
+}
diff --git a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
index 0f63fce..e3f3fab 100644
--- a/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
+++ b/v4/ics/android/support/v4/net/TrafficStatsCompatIcs.java
@@ -17,7 +17,9 @@
package android.support.v4.net;
import android.net.TrafficStats;
+import android.os.ParcelFileDescriptor;
+import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
@@ -52,4 +54,24 @@
public static void untagSocket(Socket socket) throws SocketException {
TrafficStats.untagSocket(socket);
}
+
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket);
+ TrafficStats.tagSocket(new DatagramSocketWrapper(socket, pfd.getFileDescriptor()));
+ // The developer is still using the FD, so we need to detach it to
+ // prevent the PFD finalizer from closing it in their face. We had to
+ // wait until after the tagging call above, since detaching clears out
+ // the getFileDescriptor() result which tagging depends on.
+ pfd.detachFd();
+ }
+
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ final ParcelFileDescriptor pfd = ParcelFileDescriptor.fromDatagramSocket(socket);
+ TrafficStats.untagSocket(new DatagramSocketWrapper(socket, pfd.getFileDescriptor()));
+ // The developer is still using the FD, so we need to detach it to
+ // prevent the PFD finalizer from closing it in their face. We had to
+ // wait until after the tagging call above, since detaching clears out
+ // the getFileDescriptor() result which tagging depends on.
+ pfd.detachFd();
+ }
}
diff --git a/v4/ics/android/support/v4/view/MotionEventCompatICS.java b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
new file mode 100644
index 0000000..e7979de
--- /dev/null
+++ b/v4/ics/android/support/v4/view/MotionEventCompatICS.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.view.MotionEvent;
+
+class MotionEventCompatICS {
+ public static int getButtonState(MotionEvent event) {
+ return event.getButtonState();
+ }
+}
diff --git a/v4/java/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java b/v4/java/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
index abd208b..3de3631 100644
--- a/v4/java/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
+++ b/v4/java/android/support/v4/accessibilityservice/AccessibilityServiceInfoCompat.java
@@ -26,7 +26,7 @@
* Helper for accessing features in {@link android.accessibilityservice.AccessibilityService}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class AccessibilityServiceInfoCompat {
+public final class AccessibilityServiceInfoCompat {
static interface AccessibilityServiceInfoVersionImpl {
public String getId(AccessibilityServiceInfo info);
@@ -277,9 +277,7 @@
/*
* Hide constructor
*/
- private AccessibilityServiceInfoCompat() {
-
- }
+ private AccessibilityServiceInfoCompat() {}
/**
* The accessibility service id.
diff --git a/v4/java/android/support/v4/app/ActivityCompat.java b/v4/java/android/support/v4/app/ActivityCompat.java
index 0926d49..9d6e940 100644
--- a/v4/java/android/support/v4/app/ActivityCompat.java
+++ b/v4/java/android/support/v4/app/ActivityCompat.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.pm.PackageManager;
import android.graphics.Matrix;
import android.graphics.RectF;
@@ -166,6 +167,44 @@
}
/**
+ * Start new IntentSender with options, if able, for which you would like a
+ * result when it finished.
+ *
+ * <p>In Android 4.1+ additional options were introduced to allow for more
+ * control on activity launch animations. Applications can use this method
+ * along with {@link ActivityOptionsCompat} to use these animations when
+ * available. When run on versions of the platform where this feature does
+ * not exist the activity will be launched normally.</p>
+ *
+ * @param activity Origin activity to launch from.
+ * @param intent The IntentSender to launch.
+ * @param requestCode If >= 0, this code will be returned in
+ * onActivityResult() when the activity exits.
+ * @param fillInIntent If non-null, this will be provided as the
+ * intent parameter to {@link IntentSender#sendIntent}.
+ * @param flagsMask Intent flags in the original IntentSender that you
+ * would like to change.
+ * @param flagsValues Desired values for any bits set in <var>flagsMask</var>
+ * @param extraFlags Always set to 0.
+ * @param options Additional options for how the Activity should be started.
+ * May be null if there are no options. See
+ * {@link ActivityOptionsCompat} for how to build the Bundle
+ * supplied here; there are no supported definitions for
+ * building it manually.
+ */
+ public static void startIntentSenderForResult(Activity activity, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, @Nullable Bundle options) throws IntentSender.SendIntentException {
+ if (Build.VERSION.SDK_INT >= 16) {
+ ActivityCompatJB.startIntentSenderForResult(activity, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ } else if (Build.VERSION.SDK_INT >= 5) {
+ ActivityCompatEclair.startIntentSenderForResult(activity, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags);
+ }
+ }
+
+ /**
* Finish this activity, and tries to finish all activities immediately below it
* in the current task that have the same affinity.
*
@@ -300,6 +339,12 @@
* When checking whether you have a permission you should use {@link
* #checkSelfPermission(android.content.Context, String)}.
* </p>
+ * <p>
+ * Calling this API for permissions already granted to your app would show UI
+ * to the user to decided whether the app can still hold these permissions. This
+ * can be useful if the way your app uses the data guarded by the permissions
+ * changes significantly.
+ * </p>
*
* @param activity The target activity.
* @param permissions The requested permissions.
diff --git a/v4/java/android/support/v4/app/ActivityManagerCompat.java b/v4/java/android/support/v4/app/ActivityManagerCompat.java
index ff5c180..53d8703 100644
--- a/v4/java/android/support/v4/app/ActivityManagerCompat.java
+++ b/v4/java/android/support/v4/app/ActivityManagerCompat.java
@@ -25,8 +25,8 @@
* introduced after API level 4 in a backwards compatible fashion.
*/
public final class ActivityManagerCompat {
- private ActivityManagerCompat() {
- }
+
+ private ActivityManagerCompat() {}
/**
* Returns true if this is a low-RAM device. Exactly whether a device is low-RAM
diff --git a/v4/java/android/support/v4/app/AppLaunchChecker.java b/v4/java/android/support/v4/app/AppLaunchChecker.java
new file mode 100644
index 0000000..86219d4
--- /dev/null
+++ b/v4/java/android/support/v4/app/AppLaunchChecker.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.ContextWrapper;
+import android.content.Intent;
+import android.content.SharedPreferences;
+import android.os.Bundle;
+import android.support.v4.content.IntentCompat;
+import android.support.v4.content.SharedPreferencesCompat;
+
+/**
+ * This class provides APIs for determining how an app has been launched.
+ * This can be useful if you want to confirm that a user has launched your
+ * app through its front door activity from their launcher/home screen, rather
+ * than just if the app has been opened in the past in order to view a link,
+ * open a document or perform some other service for other apps on the device.
+ */
+public class AppLaunchChecker {
+ private static final String SHARED_PREFS_NAME = "android.support.AppLaunchChecker";
+ private static final String KEY_STARTED_FROM_LAUNCHER = "startedFromLauncher";
+
+ /**
+ * Checks if this app has been launched by the user from their launcher or home screen
+ * since it was installed.
+ *
+ * <p>To track this state properly you must call {@link #onActivityCreate(Activity)}
+ * in your launcher activity's {@link Activity#onCreate(Bundle)} method.</p>
+ *
+ * @param context Context to check
+ * @return true if this app has been started by the user from the launcher at least once
+ */
+ public static boolean hasStartedFromLauncher(Context context) {
+ return context.getSharedPreferences(SHARED_PREFS_NAME, 0)
+ .getBoolean(KEY_STARTED_FROM_LAUNCHER, false);
+ }
+
+ /**
+ * Records the parameters of an activity's launch for later use by the other
+ * methods available on this class.
+ *
+ * <p>Your app should call this method in your launcher activity's
+ * {@link Activity#onCreate(Bundle)} method to track launch state.
+ * If the app targets API 23 (Android 6.0 Marshmallow) or later, this state will be
+ * eligible for full data backup and may be restored to the user's device automatically.</p> *
+ *
+ * @param activity the Activity currently running onCreate
+ */
+ public static void onActivityCreate(Activity activity) {
+ final SharedPreferences sp = activity.getSharedPreferences(SHARED_PREFS_NAME, 0);
+ if (sp.getBoolean(KEY_STARTED_FROM_LAUNCHER, false)) {
+ return;
+ }
+
+ final Intent launchIntent = activity.getIntent();
+ if (launchIntent == null) {
+ return;
+ }
+
+ if (Intent.ACTION_MAIN.equals(launchIntent.getAction())
+ && (launchIntent.hasCategory(Intent.CATEGORY_LAUNCHER)
+ || launchIntent.hasCategory(IntentCompat.CATEGORY_LEANBACK_LAUNCHER))) {
+ SharedPreferencesCompat.EditorCompat.getInstance().apply(
+ sp.edit().putBoolean(KEY_STARTED_FROM_LAUNCHER, true));
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/app/AppOpsManagerCompat.java b/v4/java/android/support/v4/app/AppOpsManagerCompat.java
index 6ec0fe4..c0058a9 100644
--- a/v4/java/android/support/v4/app/AppOpsManagerCompat.java
+++ b/v4/java/android/support/v4/app/AppOpsManagerCompat.java
@@ -24,7 +24,7 @@
* Helper for accessing features in android.app.AppOpsManager
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class AppOpsManagerCompat {
+public final class AppOpsManagerCompat {
/**
* Result from {@link #noteOp}: the given caller is allowed to
@@ -87,6 +87,8 @@
}
}
+ private AppOpsManagerCompat() {}
+
/**
* Gets the app op name associated with a given permission.
*
diff --git a/v4/java/android/support/v4/app/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index 24cf866..ee0d758 100644
--- a/v4/java/android/support/v4/app/BackStackRecord.java
+++ b/v4/java/android/support/v4/app/BackStackRecord.java
@@ -134,6 +134,10 @@
op.removed.add(r);
}
}
+ bse.mEnterAnim = op.enterAnim;
+ bse.mExitAnim = op.exitAnim;
+ bse.mPopEnterAnim = op.popEnterAnim;
+ bse.mPopExitAnim = op.popExitAnim;
bse.addOp(op);
num++;
}
@@ -423,6 +427,10 @@
}
if (containerViewId != 0) {
+ if (containerViewId == View.NO_ID) {
+ throw new IllegalArgumentException("Can't add fragment "
+ + fragment + " with tag " + tag + " to container view with no id");
+ }
if (fragment.mFragmentId != 0 && fragment.mFragmentId != containerViewId) {
throw new IllegalStateException("Can't change container ID of fragment "
+ fragment + ": was " + fragment.mFragmentId
@@ -616,6 +624,18 @@
public int commitAllowingStateLoss() {
return commitInternal(true);
}
+
+ @Override
+ public void commitNow() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, false);
+ }
+
+ @Override
+ public void commitNowAllowingStateLoss() {
+ disallowAddToBackStack();
+ mManager.execSingleAction(this, true);
+ }
int commitInternal(boolean allowStateLoss) {
if (mCommitted) throw new IllegalStateException("commit already called");
@@ -649,7 +669,7 @@
TransitionState state = null;
SparseArray<Fragment> firstOutFragments = null;
SparseArray<Fragment> lastInFragments = null;
- if (SUPPORTS_TRANSITIONS) {
+ if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
firstOutFragments = new SparseArray<Fragment>();
lastInFragments = new SparseArray<Fragment>();
@@ -674,7 +694,7 @@
Fragment f = op.fragment;
int containerId = f.mContainerId;
if (mManager.mAdded != null) {
- for (int i=0; i<mManager.mAdded.size(); i++) {
+ for (int i = mManager.mAdded.size() - 1; i >= 0; i--) {
Fragment old = mManager.mAdded.get(i);
if (FragmentManagerImpl.DEBUG) Log.v(TAG,
"OP_REPLACE: adding=" + f + " old=" + old);
@@ -742,21 +762,37 @@
}
}
- private static void setFirstOut(SparseArray<Fragment> fragments, Fragment fragment) {
+ private static void setFirstOut(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
- if (containerId != 0 && !fragment.isHidden() && fragment.isAdded() &&
- fragment.getView() != null && fragments.get(containerId) == null) {
- fragments.put(containerId, fragment);
+ if (containerId != 0 && !fragment.isHidden()) {
+ if (fragment.isAdded() && fragment.getView() != null
+ && firstOutFragments.get(containerId) == null) {
+ firstOutFragments.put(containerId, fragment);
+ }
+ if (lastInFragments.get(containerId) == fragment) {
+ lastInFragments.remove(containerId);
+ }
}
}
}
- private void setLastIn(SparseArray<Fragment> fragments, Fragment fragment) {
+ private void setLastIn(SparseArray<Fragment> firstOutFragments,
+ SparseArray<Fragment> lastInFragments, Fragment fragment) {
if (fragment != null) {
int containerId = fragment.mContainerId;
if (containerId != 0) {
- fragments.put(containerId, fragment);
+ if (!fragment.isAdded()) {
+ lastInFragments.put(containerId, fragment);
+ }
+ if (firstOutFragments.get(containerId) == fragment) {
+ firstOutFragments.remove(containerId);
+ }
+ }
+ if (fragment.mState < Fragment.CREATED && mManager.mCurState >= Fragment.CREATED) {
+ mManager.makeActive(fragment);
+ mManager.moveToState(fragment, Fragment.CREATED, 0, 0, false);
}
}
}
@@ -779,7 +815,7 @@
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE: {
Fragment f = op.fragment;
@@ -789,29 +825,30 @@
if (f == null || old.mContainerId == f.mContainerId) {
if (old == f) {
f = null;
+ lastInFragments.remove(old.mContainerId);
} else {
- setFirstOut(firstOutFragments, old);
+ setFirstOut(firstOutFragments, lastInFragments, old);
}
}
}
}
- setLastIn(lastInFragments, f);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
case OP_REMOVE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
}
@@ -833,38 +870,38 @@
if (!mManager.mContainer.onHasView()) {
return; // nothing to see, so no transitions
}
- Op op = mHead;
+ Op op = mTail;
while (op != null) {
switch (op.cmd) {
case OP_ADD:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REPLACE:
if (op.removed != null) {
for (int i = op.removed.size() - 1; i >= 0; i--) {
- setLastIn(lastInFragments, op.removed.get(i));
+ setLastIn(firstOutFragments, lastInFragments, op.removed.get(i));
}
}
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_REMOVE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_HIDE:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_SHOW:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_DETACH:
- setLastIn(lastInFragments, op.fragment);
+ setLastIn(firstOutFragments, lastInFragments, op.fragment);
break;
case OP_ATTACH:
- setFirstOut(firstOutFragments, op.fragment);
+ setFirstOut(firstOutFragments, lastInFragments, op.fragment);
break;
}
- op = op.next;
+ op = op.prev;
}
}
@@ -877,7 +914,7 @@
dump(" ", null, pw, null);
}
- if (SUPPORTS_TRANSITIONS) {
+ if (SUPPORTS_TRANSITIONS && mManager.mCurState >= Fragment.CREATED) {
if (state == null) {
if (firstOutFragments.size() != 0 || lastInFragments.size() != 0) {
state = beginTransition(firstOutFragments, lastInFragments, true);
@@ -1158,7 +1195,8 @@
callback.onSharedElementStart(names, views, null);
}
prepareSharedElementTransition(state, sceneRoot, sharedElementTransition,
- inFragment, outFragment, isBack, sharedElementTargets);
+ inFragment, outFragment, isBack, sharedElementTargets, enterTransition,
+ exitTransition);
}
}
if (enterTransition == null && sharedElementTransition == null &&
@@ -1205,9 +1243,9 @@
if (transition != null) {
FragmentTransitionCompat21.addTransitionTargets(enterTransition,
- sharedElementTransition, sceneRoot, viewRetriever, state.nonExistentView,
- state.enteringEpicenterView, state.nameOverrides, enteringViews,
- namedViews, renamedViews, sharedElementTargets);
+ sharedElementTransition, exitTransition, sceneRoot, viewRetriever,
+ state.nonExistentView, state.enteringEpicenterView, state.nameOverrides,
+ enteringViews, exitingViews, namedViews, renamedViews, sharedElementTargets);
excludeHiddenFragmentsAfterEnter(sceneRoot, state, containerId, transition);
// We want to exclude hidden views later, so we need a non-null list in the
@@ -1229,16 +1267,22 @@
private void prepareSharedElementTransition(final TransitionState state, final View sceneRoot,
final Object sharedElementTransition, final Fragment inFragment,
final Fragment outFragment, final boolean isBack,
- final ArrayList<View> sharedElementTargets) {
- sceneRoot.getViewTreeObserver().addOnPreDrawListener(
- new ViewTreeObserver.OnPreDrawListener() {
- @Override
- public boolean onPreDraw() {
- sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
+ final ArrayList<View> sharedElementTargets, final Object enterTransition,
+ final Object exitTransition) {
+ if (sharedElementTransition != null) {
+ sceneRoot.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ sceneRoot.getViewTreeObserver().removeOnPreDrawListener(this);
- if (sharedElementTransition != null) {
+ // Remove the exclude for the shared elements from the exiting fragment.
FragmentTransitionCompat21.removeTargets(sharedElementTransition,
sharedElementTargets);
+ // keep the nonExistentView as excluded so the list doesn't get emptied
+ sharedElementTargets.remove(state.nonExistentView);
+ FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+ exitTransition, sharedElementTransition, sharedElementTargets, false);
sharedElementTargets.clear();
ArrayMap<String, View> namedViews = mapSharedElementsIn(
@@ -1250,11 +1294,14 @@
callSharedElementEnd(state, inFragment, outFragment, isBack,
namedViews);
- }
- return true;
- }
- });
+ // Exclude the shared elements from the entering fragment.
+ FragmentTransitionCompat21.excludeSharedElementViews(enterTransition,
+ exitTransition, sharedElementTransition, sharedElementTargets, true);
+ return true;
+ }
+ });
+ }
}
private void callSharedElementEnd(TransitionState state, Fragment inFragment,
diff --git a/v4/java/android/support/v4/app/BundleCompat.java b/v4/java/android/support/v4/app/BundleCompat.java
index b17ddcc..9c54e0c 100644
--- a/v4/java/android/support/v4/app/BundleCompat.java
+++ b/v4/java/android/support/v4/app/BundleCompat.java
@@ -24,7 +24,10 @@
* Helper for accessing features in {@link Bundle}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class BundleCompat {
+public final class BundleCompat {
+
+ private BundleCompat() {}
+
/**
* A convenience method to handle getting an {@link IBinder} inside a {@link Bundle} for all
* Android versions.
diff --git a/v4/java/android/support/v4/app/DialogFragment.java b/v4/java/android/support/v4/app/DialogFragment.java
index fa7ad7b..4eadf5e 100644
--- a/v4/java/android/support/v4/app/DialogFragment.java
+++ b/v4/java/android/support/v4/app/DialogFragment.java
@@ -262,8 +262,8 @@
}
@Override
- public void onAttach(Activity activity) {
- super.onAttach(activity);
+ public void onAttach(Context context) {
+ super.onAttach(context);
if (!mShownByMe) {
// If not explicitly shown through our API, take this as an
// indication that the dialog is no longer dismissed.
@@ -295,7 +295,6 @@
mShowsDialog = savedInstanceState.getBoolean(SAVED_SHOWS_DIALOG, mShowsDialog);
mBackStackId = savedInstanceState.getInt(SAVED_BACK_STACK_ID, -1);
}
-
}
/** @hide */
@@ -382,11 +381,15 @@
View view = getView();
if (view != null) {
if (view.getParent() != null) {
- throw new IllegalStateException("DialogFragment can not be attached to a container view");
+ throw new IllegalStateException(
+ "DialogFragment can not be attached to a container view");
}
mDialog.setContentView(view);
}
- mDialog.setOwnerActivity(getActivity());
+ final Activity activity = getActivity();
+ if (activity != null) {
+ mDialog.setOwnerActivity(activity);
+ }
mDialog.setCancelable(mCancelable);
mDialog.setOnCancelListener(this);
mDialog.setOnDismissListener(this);
@@ -401,6 +404,7 @@
@Override
public void onStart() {
super.onStart();
+
if (mDialog != null) {
mViewDestroyed = false;
mDialog.show();
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 48fc495b..1ee0236 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -20,11 +20,13 @@
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
@@ -59,11 +61,12 @@
final boolean mRetainInstance;
final boolean mDetached;
final Bundle mArguments;
+ final boolean mHidden;
Bundle mSavedFragmentState;
Fragment mInstance;
-
+
public FragmentState(Fragment frag) {
mClassName = frag.getClass().getName();
mIndex = frag.mIndex;
@@ -74,6 +77,7 @@
mRetainInstance = frag.mRetainInstance;
mDetached = frag.mDetached;
mArguments = frag.mArguments;
+ mHidden = frag.mHidden;
}
public FragmentState(Parcel in) {
@@ -86,38 +90,39 @@
mRetainInstance = in.readInt() != 0;
mDetached = in.readInt() != 0;
mArguments = in.readBundle();
+ mHidden = in.readInt() != 0;
mSavedFragmentState = in.readBundle();
}
- public Fragment instantiate(FragmentHostCallback host, Fragment parent) {
- if (mInstance != null) {
- return mInstance;
+ public Fragment instantiate(FragmentHostCallback host, Fragment parent,
+ FragmentManagerNonConfig childNonConfig) {
+ if (mInstance == null) {
+ final Context context = host.getContext();
+ if (mArguments != null) {
+ mArguments.setClassLoader(context.getClassLoader());
+ }
+
+ mInstance = Fragment.instantiate(context, mClassName, mArguments);
+
+ if (mSavedFragmentState != null) {
+ mSavedFragmentState.setClassLoader(context.getClassLoader());
+ mInstance.mSavedFragmentState = mSavedFragmentState;
+ }
+ mInstance.setIndex(mIndex, parent);
+ mInstance.mFromLayout = mFromLayout;
+ mInstance.mRestored = true;
+ mInstance.mFragmentId = mFragmentId;
+ mInstance.mContainerId = mContainerId;
+ mInstance.mTag = mTag;
+ mInstance.mRetainInstance = mRetainInstance;
+ mInstance.mDetached = mDetached;
+ mInstance.mHidden = mHidden;
+ mInstance.mFragmentManager = host.mFragmentManager;
+
+ if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
+ "Instantiated fragment " + mInstance);
}
-
- final Context context = host.getContext();
- if (mArguments != null) {
- mArguments.setClassLoader(context.getClassLoader());
- }
-
- mInstance = Fragment.instantiate(context, mClassName, mArguments);
-
- if (mSavedFragmentState != null) {
- mSavedFragmentState.setClassLoader(context.getClassLoader());
- mInstance.mSavedFragmentState = mSavedFragmentState;
- }
- mInstance.setIndex(mIndex, parent);
- mInstance.mFromLayout = mFromLayout;
- mInstance.mRestored = true;
- mInstance.mFragmentId = mFragmentId;
- mInstance.mContainerId = mContainerId;
- mInstance.mTag = mTag;
- mInstance.mRetainInstance = mRetainInstance;
- mInstance.mDetached = mDetached;
- mInstance.mFragmentManager = host.mFragmentManager;
-
- if (FragmentManagerImpl.DEBUG) Log.v(FragmentManagerImpl.TAG,
- "Instantiated fragment " + mInstance);
-
+ mInstance.mChildNonConfig = childNonConfig;
return mInstance;
}
@@ -135,6 +140,7 @@
dest.writeInt(mRetainInstance ? 1 : 0);
dest.writeInt(mDetached ? 1 : 0);
dest.writeBundle(mArguments);
+ dest.writeInt(mHidden? 1 : 0);
dest.writeBundle(mSavedFragmentState);
}
@@ -216,9 +222,6 @@
// If set this fragment is being removed from its activity.
boolean mRemoving;
-
- // True if the fragment is in the resumed state.
- boolean mResumed;
// Set to true if this fragment was instantiated from a layout file.
boolean mFromLayout;
@@ -243,6 +246,10 @@
// Private fragment manager for child fragments inside of this one.
FragmentManagerImpl mChildFragmentManager;
+ // For use when restoring fragment state and descendant fragments are retained.
+ // This state is set by FragmentState.instantiate and cleared in onCreate.
+ FragmentManagerNonConfig mChildNonConfig;
+
// If this Fragment is contained in another Fragment, this is that container.
Fragment mParentFragment;
@@ -757,7 +764,7 @@
* for the duration of {@link #onResume()} and {@link #onPause()} as well.
*/
final public boolean isResumed() {
- return mResumed;
+ return mState >= RESUMED;
}
/**
@@ -816,10 +823,6 @@
* </ul>
*/
public void setRetainInstance(boolean retain) {
- if (retain && mParentFragment != null) {
- throw new IllegalStateException(
- "Can't retain fragements that are nested in other fragments");
- }
mRetainInstance = retain;
}
@@ -875,11 +878,12 @@
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
- if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) {
+ if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
+ && mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
- mDeferStart = !isVisibleToUser;
+ mDeferStart = mState < STARTED && !isVisibleToUser;
}
/**
@@ -910,10 +914,18 @@
* containing Activity.
*/
public void startActivity(Intent intent) {
+ startActivity(intent, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivity(Intent, Bundle)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivity(Intent intent, @Nullable Bundle options) {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1);
+ mHost.onStartActivityFromFragment(this /*fragment*/, intent, -1, options);
}
/**
@@ -921,10 +933,32 @@
* containing Activity.
*/
public void startActivityForResult(Intent intent, int requestCode) {
+ startActivityForResult(intent, requestCode, null);
+ }
+
+ /**
+ * Call {@link Activity#startActivityForResult(Intent, int, Bundle)} from the fragment's
+ * containing Activity.
+ */
+ public void startActivityForResult(Intent intent, int requestCode, @Nullable Bundle options) {
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode);
+ mHost.onStartActivityFromFragment(this /*fragment*/, intent, requestCode, options);
+ }
+
+ /**
+ * Call {@link Activity#startIntentSenderForResult(IntentSender, int, Intent, int, int, int,
+ * Bundle)} from the fragment's containing Activity.
+ */
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ if (mHost == null) {
+ throw new IllegalStateException("Fragment " + this + " not attached to Activity");
+ }
+ mHost.onStartIntentSenderFromFragment(this, intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
}
/**
@@ -981,6 +1015,12 @@
* android.content.Context#checkSelfPermission(String)}.
* </p>
* <p>
+ * Calling this API for permissions already granted to your app would show UI
+ * to the user to decided whether the app can still hold these permissions. This
+ * can be useful if the way your app uses the data guarded by the permissions
+ * changes significantly.
+ * </p>
+ * <p>
* A sample permissions request looks like this:
* </p>
* <code><pre><p>
@@ -1015,7 +1055,7 @@
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mHost.onRequestPermissionsFromFragment(this, permissions,requestCode);
+ mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}
/**
@@ -1121,6 +1161,7 @@
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
+ @CallSuper
public void onInflate(Context context, AttributeSet attrs, Bundle savedInstanceState) {
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
@@ -1133,17 +1174,32 @@
/**
* Called when a fragment is being created as part of a view layout
* inflation, typically from setting the content view of an activity.
- * <p>Deprecated. See {@link #onInflate(Context, AttributeSet, Bundle)}.
+ *
+ * @deprecated See {@link #onInflate(Context, AttributeSet, Bundle)}.
*/
@Deprecated
+ @CallSuper
public void onInflate(Activity activity, AttributeSet attrs, Bundle savedInstanceState) {
mCalled = true;
}
/**
+ * Called when a fragment is attached as a child of this fragment.
+ *
+ * <p>This is called after the attached fragment's <code>onAttach</code> and before
+ * the attached fragment's <code>onCreate</code> if the fragment has not yet had a previous
+ * call to <code>onCreate</code>.</p>
+ *
+ * @param childFragment child fragment being attached
+ */
+ public void onAttachFragment(Fragment childFragment) {
+ }
+
+ /**
* Called when a fragment is first attached to its context.
* {@link #onCreate(Bundle)} will be called after this.
*/
+ @CallSuper
public void onAttach(Context context) {
mCalled = true;
final Activity hostActivity = mHost == null ? null : mHost.getActivity();
@@ -1156,9 +1212,11 @@
/**
* Called when a fragment is first attached to its activity.
* {@link #onCreate(Bundle)} will be called after this.
- * <p>Deprecated. See {@link #onAttach(Context)}.
+ *
+ * @deprecated See {@link #onAttach(Context)}.
*/
@Deprecated
+ @CallSuper
public void onAttach(Activity activity) {
mCalled = true;
}
@@ -1180,12 +1238,48 @@
* on things like the activity's content view hierarchy being initialized
* at this point. If you want to do work once the activity itself is
* created, see {@link #onActivityCreated(Bundle)}.
+ *
+ * <p>Any restored child fragments will be created before the base
+ * <code>Fragment.onCreate</code> method returns.</p>
*
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
+ @CallSuper
public void onCreate(@Nullable Bundle savedInstanceState) {
mCalled = true;
+ restoreChildFragmentState(savedInstanceState);
+ if (mChildFragmentManager != null
+ && !mChildFragmentManager.isStateAtLeast(Fragment.CREATED)) {
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
+
+ /**
+ * Restore the state of the child FragmentManager. Called by either
+ * {@link #onCreate(Bundle)} for non-retained instance fragments or by
+ * {@link FragmentManagerImpl#moveToState(Fragment, int, int, int, boolean)}
+ * for retained instance fragments.
+ *
+ * <p><strong>Postcondition:</strong> if there were child fragments to restore,
+ * the child FragmentManager will be instantiated and brought to the {@link #CREATED} state.
+ * </p>
+ *
+ * @param savedInstanceState the savedInstanceState potentially containing fragment info
+ */
+ void restoreChildFragmentState(@Nullable Bundle savedInstanceState) {
+ if (savedInstanceState != null) {
+ Parcelable p = savedInstanceState.getParcelable(
+ FragmentActivity.FRAGMENTS_TAG);
+ if (p != null) {
+ if (mChildFragmentManager == null) {
+ instantiateChildFragmentManager();
+ }
+ mChildFragmentManager.restoreAllState(p, mChildNonConfig);
+ mChildNonConfig = null;
+ mChildFragmentManager.dispatchCreate();
+ }
+ }
}
/**
@@ -1250,6 +1344,7 @@
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
+ @CallSuper
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
mCalled = true;
}
@@ -1265,6 +1360,7 @@
* @param savedInstanceState If the fragment is being re-created from
* a previous saved state, this is the state.
*/
+ @CallSuper
public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
mCalled = true;
}
@@ -1274,6 +1370,7 @@
* tied to {@link Activity#onStart() Activity.onStart} of the containing
* Activity's lifecycle.
*/
+ @CallSuper
public void onStart() {
mCalled = true;
@@ -1295,6 +1392,7 @@
* tied to {@link Activity#onResume() Activity.onResume} of the containing
* Activity's lifecycle.
*/
+ @CallSuper
public void onResume() {
mCalled = true;
}
@@ -1320,16 +1418,37 @@
*/
public void onSaveInstanceState(Bundle outState) {
}
-
+
+ /**
+ * Called when the Fragment's activity changes from fullscreen mode to multi-window mode and
+ * visa-versa. This is generally tied to {@link Activity#onMultiWindowModeChanged} of the
+ * containing Activity.
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ */
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ }
+
+ /**
+ * Called by the system when the activity changes to and from picture-in-picture mode. This is
+ * generally tied to {@link Activity#onPictureInPictureModeChanged} of the containing Activity.
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ */
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ }
+
+ @CallSuper
public void onConfigurationChanged(Configuration newConfig) {
mCalled = true;
}
-
+
/**
* Called when the Fragment is no longer resumed. This is generally
* tied to {@link Activity#onPause() Activity.onPause} of the containing
* Activity's lifecycle.
*/
+ @CallSuper
public void onPause() {
mCalled = true;
}
@@ -1339,10 +1458,12 @@
* tied to {@link Activity#onStop() Activity.onStop} of the containing
* Activity's lifecycle.
*/
+ @CallSuper
public void onStop() {
mCalled = true;
}
-
+
+ @CallSuper
public void onLowMemory() {
mCalled = true;
}
@@ -1356,6 +1477,7 @@
* non-null view. Internally it is called after the view's state has
* been saved but before it has been removed from its parent.
*/
+ @CallSuper
public void onDestroyView() {
mCalled = true;
}
@@ -1364,6 +1486,7 @@
* Called when the fragment is no longer in use. This is called
* after {@link #onStop()} and before {@link #onDetach()}.
*/
+ @CallSuper
public void onDestroy() {
mCalled = true;
//Log.v("foo", "onDestroy: mCheckedForLoaderManager=" + mCheckedForLoaderManager
@@ -1388,7 +1511,6 @@
mWho = null;
mAdded = false;
mRemoving = false;
- mResumed = false;
mFromLayout = false;
mInLayout = false;
mRestored = false;
@@ -1411,12 +1533,13 @@
* Called when the fragment is no longer attached to its activity. This
* is called after {@link #onDestroy()}.
*/
+ @CallSuper
public void onDetach() {
mCalled = true;
}
/**
- * Initialize the contents of the Activity's standard options menu. You
+ * Initialize the contents of the Fragment host's standard options menu. You
* should place your menu items in to <var>menu</var>. For this method
* to be called, you must have first called {@link #setHasOptionsMenu}. See
* {@link Activity#onCreateOptionsMenu(Menu) Activity.onCreateOptionsMenu}
@@ -1432,7 +1555,7 @@
}
/**
- * Prepare the Screen's standard options menu to be displayed. This is
+ * Prepare the Fragment host's standard options menu to be displayed. This is
* called right before the menu is shown, every time it is shown. You can
* use this method to efficiently enable/disable items or otherwise
* dynamically modify the contents. See
@@ -1837,7 +1960,6 @@
writer.print(" mBackStackNesting="); writer.println(mBackStackNesting);
writer.print(prefix); writer.print("mAdded="); writer.print(mAdded);
writer.print(" mRemoving="); writer.print(mRemoving);
- writer.print(" mResumed="); writer.print(mResumed);
writer.print(" mFromLayout="); writer.print(mFromLayout);
writer.print(" mInLayout="); writer.println(mInLayout);
writer.print(prefix); writer.print("mHidden="); writer.print(mHidden);
@@ -1935,23 +2057,13 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
+ mState = CREATED;
mCalled = false;
onCreate(savedInstanceState);
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onCreate()");
}
- if (savedInstanceState != null) {
- Parcelable p = savedInstanceState.getParcelable(
- FragmentActivity.FRAGMENTS_TAG);
- if (p != null) {
- if (mChildFragmentManager == null) {
- instantiateChildFragmentManager();
- }
- mChildFragmentManager.restoreAllState(p, null);
- mChildFragmentManager.dispatchCreate();
- }
- }
}
View performCreateView(LayoutInflater inflater, ViewGroup container,
@@ -1966,6 +2078,7 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.noteStateNotSaved();
}
+ mState = ACTIVITY_CREATED;
mCalled = false;
onActivityCreated(savedInstanceState);
if (!mCalled) {
@@ -1982,6 +2095,7 @@
mChildFragmentManager.noteStateNotSaved();
mChildFragmentManager.execPendingActions();
}
+ mState = STARTED;
mCalled = false;
onStart();
if (!mCalled) {
@@ -2001,6 +2115,7 @@
mChildFragmentManager.noteStateNotSaved();
mChildFragmentManager.execPendingActions();
}
+ mState = RESUMED;
mCalled = false;
onResume();
if (!mCalled) {
@@ -2013,6 +2128,20 @@
}
}
+ void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ onMultiWindowModeChanged(isInMultiWindowMode);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+ }
+
+ void performPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ onPictureInPictureModeChanged(isInPictureInPictureMode);
+ if (mChildFragmentManager != null) {
+ mChildFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+
void performConfigurationChanged(Configuration newConfig) {
onConfigurationChanged(newConfig);
if (mChildFragmentManager != null) {
@@ -2119,6 +2248,7 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchPause();
}
+ mState = STARTED;
mCalled = false;
onPause();
if (!mCalled) {
@@ -2131,6 +2261,7 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchStop();
}
+ mState = STOPPED;
mCalled = false;
onStop();
if (!mCalled) {
@@ -2143,6 +2274,7 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchReallyStop();
}
+ mState = ACTIVITY_CREATED;
if (mLoadersStarted) {
mLoadersStarted = false;
if (!mCheckedForLoaderManager) {
@@ -2163,6 +2295,7 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchDestroyView();
}
+ mState = CREATED;
mCalled = false;
onDestroyView();
if (!mCalled) {
@@ -2178,11 +2311,35 @@
if (mChildFragmentManager != null) {
mChildFragmentManager.dispatchDestroy();
}
+ mState = INITIALIZING;
mCalled = false;
onDestroy();
if (!mCalled) {
throw new SuperNotCalledException("Fragment " + this
+ " did not call through to super.onDestroy()");
}
+ mChildFragmentManager = null;
}
+
+ void performDetach() {
+ mCalled = false;
+ onDetach();
+ if (!mCalled) {
+ throw new SuperNotCalledException("Fragment " + this
+ + " did not call through to super.onDetach()");
+ }
+
+ // Destroy the child FragmentManager if we still have it here.
+ // We won't unless we're retaining our instance and if we do,
+ // our child FragmentManager instance state will have already been saved.
+ if (mChildFragmentManager != null) {
+ if (!mRetaining) {
+ throw new IllegalStateException("Child FragmentManager of " + this + " was not "
+ + " destroyed and this fragment is not retaining instance");
+ }
+ mChildFragmentManager.dispatchDestroy();
+ mChildFragmentManager = null;
+ }
+ }
+
}
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 59881a2..82d6530 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -19,16 +19,19 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
+import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.util.SimpleArrayMap;
+import android.support.v4.util.SparseArrayCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -41,8 +44,6 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.List;
/**
* Base class for activities that want to use the support-based
@@ -54,11 +55,6 @@
* and {@link #getSupportLoaderManager()} methods respectively to access
* those features.
*
- * <p class="note"><strong>Note:</strong> If you want to implement an activity that includes
- * an <a href="{@docRoot}guide/topics/ui/actionbar.html">action bar</a>, you should instead use
- * the {@link android.support.v7.app.ActionBarActivity} class, which is a subclass of this one,
- * so allows you to use {@link android.support.v4.app.Fragment} APIs on API level 7 and higher.</p>
- *
* <p>Known limitations:</p>
* <ul>
* <li> <p>When using the <code><fragment></code> tag, this implementation can not
@@ -76,12 +72,16 @@
* state, this may be a snapshot slightly before what the user last saw.</p>
* </ul>
*/
-public class FragmentActivity extends BaseFragmentActivityHoneycomb implements
+public class FragmentActivity extends BaseFragmentActivityJB implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
private static final String TAG = "FragmentActivity";
static final String FRAGMENTS_TAG = "android:support:fragments";
+ static final String NEXT_CANDIDATE_REQUEST_INDEX_TAG = "android:support:next_request_index";
+ static final String ALLOCATED_REQUEST_INDICIES_TAG = "android:support:request_indicies";
+ static final String REQUEST_FRAGMENT_WHO_TAG = "android:support:request_fragment_who";
+ static final int MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS = 0xffff - 1;
// This is the SDK API version of Honeycomb (3.0).
private static final int HONEYCOMB = 11;
@@ -119,9 +119,20 @@
boolean mOptionsMenuInvalidated;
boolean mRequestedPermissionsFromFragment;
+ // A hint for the next candidate request index. Request indicies are ints between 0 and 2^16-1
+ // which are encoded into the upper 16 bits of the requestCode for
+ // Fragment.startActivityForResult(...) calls. This allows us to dispatch onActivityResult(...)
+ // to the appropriate Fragment. Request indicies are allocated by allocateRequestIndex(...).
+ int mNextCandidateRequestIndex;
+ // A map from request index to Fragment "who" (i.e. a Fragment's unique identifier). Used to
+ // keep track of the originating Fragment for Fragment.startActivityForResult(...) calls, so we
+ // can dispatch the onActivityResult(...) to the appropriate Fragment. Will only contain entries
+ // for startActivityForResult calls where a result has not yet been delivered.
+ SparseArrayCompat<String> mPendingFragmentActivityResults;
+
static final class NonConfigurationInstances {
Object custom;
- List<Fragment> fragments;
+ FragmentManagerNonConfig fragments;
SimpleArrayMap<String, LoaderManager> loaders;
}
@@ -137,23 +148,21 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
- int index = requestCode>>16;
- if (index != 0) {
- index--;
- final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
- if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
- Log.w(TAG, "Activity result fragment index out of range: 0x"
- + Integer.toHexString(requestCode));
+ int requestIndex = requestCode>>16;
+ if (requestIndex != 0) {
+ requestIndex--;
+
+ String who = mPendingFragmentActivityResults.get(requestIndex);
+ mPendingFragmentActivityResults.remove(requestIndex);
+ if (who == null) {
+ Log.w(TAG, "Activity result delivered for unknown Fragment.");
return;
}
- final List<Fragment> activeFragments =
- mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
- Fragment frag = activeFragments.get(index);
- if (frag == null) {
- Log.w(TAG, "Activity result no fragment exists for index: 0x"
- + Integer.toHexString(requestCode));
+ Fragment targetFragment = mFragments.findFragmentByWho(who);
+ if (targetFragment == null) {
+ Log.w(TAG, "Activity result no fragment exists for who: " + who);
} else {
- frag.onActivityResult(requestCode&0xffff, resultCode, data);
+ targetFragment.onActivityResult(requestCode & 0xffff, resultCode, data);
}
return;
}
@@ -167,7 +176,7 @@
*/
public void onBackPressed() {
if (!mFragments.getSupportFragmentManager().popBackStackImmediate()) {
- supportFinishAfterTransition();
+ onBackPressedNotHandled();
}
}
@@ -259,6 +268,34 @@
}
/**
+ * {@inheritDoc}
+ *
+ * <p><strong>Note:</strong> If you override this method you must call
+ * <code>super.onMultiWindowModeChanged</code> to correctly dispatch the event
+ * to support fragments attached to this activity.</p>
+ *
+ * @param isInMultiWindowMode True if the activity is in multi-window mode.
+ */
+ @CallSuper
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * {@inheritDoc}
+ *
+ * <p><strong>Note:</strong> If you override this method you must call
+ * <code>super.onPictureInPictureModeChanged</code> to correctly dispatch the event
+ * to support fragments attached to this activity.</p>
+ *
+ * @param isInPictureInPictureMode True if the activity is in picture-in-picture mode.
+ */
+ @CallSuper
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
* Dispatch configuration change to all fragments.
*/
@Override
@@ -285,7 +322,30 @@
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
+
+ // Check if there are any pending onActivityResult calls to descendent Fragments.
+ if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
+ mNextCandidateRequestIndex =
+ savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
+ int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
+ String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
+ if (requestCodes == null || fragmentWhos == null ||
+ requestCodes.length != fragmentWhos.length) {
+ Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
+ } else {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
+ for (int i = 0; i < requestCodes.length; i++) {
+ mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
+ }
+ }
+ }
}
+
+ if (mPendingFragmentActivityResults == null) {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>();
+ mNextCandidateRequestIndex = 0;
+ }
+
mFragments.dispatchCreate();
}
@@ -500,7 +560,7 @@
Object custom = onRetainCustomNonConfigurationInstance();
- List<Fragment> fragments = mFragments.retainNonConfig();
+ FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
SimpleArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
if (fragments == null && loaders == null && custom == null) {
@@ -524,6 +584,18 @@
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
+ if (mPendingFragmentActivityResults.size() > 0) {
+ outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
+
+ int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
+ String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
+ for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
+ requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
+ fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
+ }
+ outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
+ outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
+ }
}
/**
@@ -733,6 +805,13 @@
mRetaining = retaining;
mHandler.removeMessages(MSG_REALLY_STOPPED);
onReallyStop();
+ } else if (retaining) {
+ // We're already really stopped, but we've been asked to retain.
+ // Our fragments are taken care of but we need to mark the loaders for retention.
+ // In order to do this correctly we need to restart the loaders first before
+ // handing them off to the next activity.
+ mFragments.doLoaderStart();
+ mFragments.doLoaderStop(true);
}
}
@@ -755,6 +834,10 @@
/**
* Called when a fragment is attached to the activity.
+ *
+ * <p>This is called after the attached fragment's <code>onAttach</code> and before
+ * the attached fragment's <code>onCreate</code> if the fragment has not yet had a previous
+ * call to <code>onCreate</code>.</p>
*/
@SuppressWarnings("unused")
public void onAttachFragment(Fragment fragment) {
@@ -778,25 +861,28 @@
*/
@Override
public void startActivityForResult(Intent intent, int requestCode) {
- if (requestCode != -1 && (requestCode&0xffff0000) != 0) {
- throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedActivityFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
}
super.startActivityForResult(intent, requestCode);
}
@Override
public final void validateRequestPermissionsRequestCode(int requestCode) {
- // We use 8 bits of the request code to encode the fragment id when
+ // We use 16 bits of the request code to encode the fragment id when
// requesting permissions from a fragment. Hence, requestPermissions()
// should validate the code against that but we cannot override it as
// we can not then call super and also the ActivityCompat would call
// back to this override. To handle this we use dependency inversion
// where we are the validator of request codes when requesting
// permissions in ActivityCompat.
- if (mRequestedPermissionsFromFragment) {
- mRequestedPermissionsFromFragment = false;
- } else if ((requestCode & 0xffffff00) != 0) {
- throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
+ if (!mRequestedPermissionsFromFragment
+ && requestCode != -1) {
+ checkForValidRequestCode(requestCode);
}
}
@@ -819,23 +905,21 @@
*/
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
- int index = (requestCode>>8)&0xff;
+ int index = (requestCode >> 16) & 0xffff;
if (index != 0) {
index--;
- final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
- if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
- Log.w(TAG, "Activity result fragment index out of range: 0x"
- + Integer.toHexString(requestCode));
+
+ String who = mPendingFragmentActivityResults.get(index);
+ mPendingFragmentActivityResults.remove(index);
+ if (who == null) {
+ Log.w(TAG, "Activity result delivered for unknown Fragment.");
return;
}
- final List<Fragment> activeFragments =
- mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
- Fragment frag = activeFragments.get(index);
+ Fragment frag = mFragments.findFragmentByWho(who);
if (frag == null) {
- Log.w(TAG, "Activity result no fragment exists for index: 0x"
- + Integer.toHexString(requestCode));
+ Log.w(TAG, "Activity result no fragment exists for who: " + who);
} else {
- frag.onRequestPermissionsResult(requestCode&0xff, permissions, grantResults);
+ frag.onRequestPermissionsResult(requestCode & 0xffff, permissions, grantResults);
}
}
}
@@ -845,14 +929,71 @@
*/
public void startActivityFromFragment(Fragment fragment, Intent intent,
int requestCode) {
- if (requestCode == -1) {
- super.startActivityForResult(intent, -1);
- return;
+ startActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
+ * Called by Fragment.startActivityForResult() to implement its behavior.
+ */
+ public void startActivityFromFragment(Fragment fragment, Intent intent,
+ int requestCode, @Nullable Bundle options) {
+ mStartedActivityFromFragment = true;
+ try {
+ if (requestCode == -1) {
+ ActivityCompat.startActivityForResult(this, intent, -1, options);
+ return;
+ }
+ checkForValidRequestCode(requestCode);
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.startActivityForResult(
+ this, intent, ((requestIndex + 1) << 16) + (requestCode & 0xffff), options);
+ } finally {
+ mStartedActivityFromFragment = false;
}
- if ((requestCode&0xffff0000) != 0) {
- throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
+ }
+
+ /**
+ * Called by Fragment.startIntentSenderForResult() to implement its behavior.
+ */
+ public void startIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+ mStartedIntentSenderFromFragment = true;
+ try {
+ if (requestCode == -1) {
+ ActivityCompat.startIntentSenderForResult(this, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ return;
+ }
+ checkForValidRequestCode(requestCode);
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.startIntentSenderForResult(this, intent,
+ ((requestIndex + 1) << 16) + (requestCode & 0xffff), fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ } finally {
+ mStartedIntentSenderFromFragment = false;
}
- super.startActivityForResult(intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff));
+ }
+
+ // Allocates the next available startActivityForResult request index.
+ private int allocateRequestIndex(Fragment fragment) {
+ // Sanity check that we havn't exhaused the request index space.
+ if (mPendingFragmentActivityResults.size() >= MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS) {
+ throw new IllegalStateException("Too many pending Fragment activity results.");
+ }
+
+ // Find an unallocated request index in the mPendingFragmentActivityResults map.
+ while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+ }
+
+ int requestIndex = mNextCandidateRequestIndex;
+ mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+
+ return requestIndex;
}
/**
@@ -864,12 +1005,15 @@
ActivityCompat.requestPermissions(this, permissions, requestCode);
return;
}
- if ((requestCode&0xffffff00) != 0) {
- throw new IllegalArgumentException("Can only use lower 8 bits for requestCode");
+ checkForValidRequestCode(requestCode);
+ try {
+ mRequestedPermissionsFromFragment = true;
+ int requestIndex = allocateRequestIndex(fragment);
+ ActivityCompat.requestPermissions(this, permissions,
+ ((requestIndex + 1) << 16) + (requestCode & 0xffff));
+ } finally {
+ mRequestedPermissionsFromFragment = false;
}
- mRequestedPermissionsFromFragment = true;
- ActivityCompat.requestPermissions(this, permissions,
- ((fragment.mIndex + 1) << 8) + (requestCode & 0xff));
}
class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
@@ -908,6 +1052,20 @@
}
@Override
+ public void onStartActivityFromFragment(
+ Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
+ FragmentActivity.this.startActivityFromFragment(fragment, intent, requestCode, options);
+ }
+
+ @Override
+ public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+ FragmentActivity.this.startIntentSenderFromFragment(fragment, intent, requestCode,
+ fillInIntent, flagsMask, flagsValues, extraFlags, options);
+ }
+
+ @Override
public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
@NonNull String[] permissions, int requestCode) {
FragmentActivity.this.requestPermissionsFromFragment(fragment, permissions,
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 5d647b0..5e2f955 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
import android.support.v4.util.SimpleArrayMap;
import android.util.AttributeSet;
import android.view.Menu;
@@ -66,6 +67,14 @@
}
/**
+ * Returns a fragment with the given identifier.
+ */
+ @Nullable
+ Fragment findFragmentByWho(String who) {
+ return mHost.mFragmentManager.findFragmentByWho(who);
+ }
+
+ /**
* Returns the number of active fragments.
*/
public int getActiveFragmentsCount() {
@@ -130,16 +139,43 @@
* instances retained across configuration changes.
*
* @see #retainNonConfig()
+ *
+ * @deprecated use {@link #restoreAllState(Parcelable, FragmentManagerNonConfig)}
*/
+ @Deprecated
public void restoreAllState(Parcelable state, List<Fragment> nonConfigList) {
- mHost.mFragmentManager.restoreAllState(state, nonConfigList);
+ mHost.mFragmentManager.restoreAllState(state,
+ new FragmentManagerNonConfig(nonConfigList, null));
+ }
+
+ /**
+ * Restores the saved state for all Fragments. The given FragmentManagerNonConfig are Fragment
+ * instances retained across configuration changes, including nested fragments
+ *
+ * @see #retainNestedNonConfig()
+ */
+ public void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
+ mHost.mFragmentManager.restoreAllState(state, nonConfig);
}
/**
* Returns a list of Fragments that have opted to retain their instance across
* configuration changes.
+ *
+ * @deprecated use {@link #retainNestedNonConfig()} to also track retained
+ * nested child fragments
*/
+ @Deprecated
public List<Fragment> retainNonConfig() {
+ FragmentManagerNonConfig nonconf = mHost.mFragmentManager.retainNonConfig();
+ return nonconf != null ? nonconf.getFragments() : null;
+ }
+
+ /**
+ * Returns a nested tree of Fragments that have opted to retain their instance across
+ * configuration changes.
+ */
+ public FragmentManagerNonConfig retainNestedNonConfig() {
return mHost.mFragmentManager.retainNonConfig();
}
@@ -236,6 +272,28 @@
}
/**
+ * Lets all Fragments managed by the controller's FragmentManager know the multi-window mode of
+ * the activity changed.
+ * <p>Call when the multi-window mode of the activity changed.
+ *
+ * @see Fragment#onMultiWindowModeChanged
+ */
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mHost.mFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * Lets all Fragments managed by the controller's FragmentManager know the picture-in-picture
+ * mode of the activity changed.
+ * <p>Call when the picture-in-picture mode of the activity changed.
+ *
+ * @see Fragment#onPictureInPictureModeChanged
+ */
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mHost.mFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
* Lets all Fragments managed by the controller's FragmentManager
* know a configuration change occurred.
* <p>Call when there is a configuration change.
diff --git a/v4/java/android/support/v4/app/FragmentHostCallback.java b/v4/java/android/support/v4/app/FragmentHostCallback.java
index 5d6145a..6a91a3a 100644
--- a/v4/java/android/support/v4/app/FragmentHostCallback.java
+++ b/v4/java/android/support/v4/app/FragmentHostCallback.java
@@ -19,6 +19,8 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -115,6 +117,15 @@
* See {@link FragmentActivity#startActivityForResult(Intent, int)}.
*/
public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
+ onStartActivityFromFragment(fragment, intent, requestCode, null);
+ }
+
+ /**
+ * Starts a new {@link Activity} from the given fragment.
+ * See {@link FragmentActivity#startActivityForResult(Intent, int, Bundle)}.
+ */
+ public void onStartActivityFromFragment(
+ Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
if (requestCode != -1) {
throw new IllegalStateException(
"Starting activity with a requestCode requires a FragmentActivity host");
@@ -123,6 +134,21 @@
}
/**
+ * Starts a new {@link IntentSender} from the given fragment.
+ * See {@link Activity#startIntentSender(IntentSender, Intent, int, int, int, Bundle)}.
+ */
+ public void onStartIntentSenderFromFragment(Fragment fragment, IntentSender intent,
+ int requestCode, @Nullable Intent fillInIntent, int flagsMask, int flagsValues,
+ int extraFlags, Bundle options) throws IntentSender.SendIntentException {
+ if (requestCode != -1) {
+ throw new IllegalStateException(
+ "Starting intent sender with a requestCode requires a FragmentActivity host");
+ }
+ ActivityCompat.startIntentSenderForResult(mActivity, intent, requestCode, fillInIntent,
+ flagsMask, flagsValues, extraFlags, options);
+ }
+
+ /**
* Requests permissions from the given fragment.
* See {@link FragmentActivity#requestPermissions(String[], int)}
*/
@@ -292,15 +318,22 @@
SimpleArrayMap<String, LoaderManager> retainLoaderNonConfig() {
boolean retainLoaders = false;
if (mAllLoaderManagers != null) {
- // prune out any loader managers that were already stopped and so
- // have nothing useful to retain.
+ // Restart any loader managers that were already stopped so that they
+ // will be ready to retain
final int N = mAllLoaderManagers.size();
LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];
for (int i=N-1; i>=0; i--) {
loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i);
}
+ final boolean doRetainLoaders = getRetainLoaders();
for (int i=0; i<N; i++) {
LoaderManagerImpl lm = loaders[i];
+ if (!lm.mRetaining && doRetainLoaders) {
+ if (!lm.mStarted) {
+ lm.doStart();
+ }
+ lm.doRetain();
+ }
if (lm.mRetaining) {
retainLoaders = true;
} else {
diff --git a/v4/java/android/support/v4/app/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index caf1cfe..1040bd2 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -18,6 +18,7 @@
import android.content.Context;
import android.content.res.Configuration;
+import android.content.res.Resources.NotFoundException;
import android.content.res.TypedArray;
import android.os.Build;
import android.os.Bundle;
@@ -28,6 +29,7 @@
import android.support.annotation.CallSuper;
import android.support.annotation.IdRes;
import android.support.annotation.StringRes;
+import android.support.v4.os.BuildCompat;
import android.support.v4.util.DebugUtils;
import android.support.v4.util.LogWriter;
import android.support.v4.view.LayoutInflaterFactory;
@@ -147,7 +149,10 @@
*/
public abstract FragmentTransaction beginTransaction();
- /** @hide -- remove once prebuilts are in. */
+ /**
+ * @hide -- remove once prebuilts are in.
+ * @deprecated
+ */
@Deprecated
public FragmentTransaction openTransaction() {
return beginTransaction();
@@ -162,6 +167,12 @@
* all callbacks and other related behavior will be done from within this
* call, so be careful about where this is called from.
*
+ * <p>If you are committing a single transaction that does not modify the
+ * fragment back stack, strongly consider using
+ * {@link FragmentTransaction#commitNow()} instead. This can help avoid
+ * unwanted side effects when other code in your app has pending committed
+ * transactions that expect different timing.</p>
+ *
* @return Returns true if there were any pending transactions to be
* executed.
*/
@@ -413,11 +424,11 @@
static final String VIEW_STATE_TAG = "android:view_state";
static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint";
-
static class AnimateOnHWLayerIfNeededListener implements AnimationListener {
- private AnimationListener mOrignalListener = null;
- private boolean mShouldRunOnHWLayer = false;
- private View mView = null;
+ private AnimationListener mOrignalListener;
+ private boolean mShouldRunOnHWLayer;
+ private View mView;
+
public AnimateOnHWLayerIfNeededListener(final View v, Animation anim) {
if (v == null || anim == null) {
return;
@@ -432,22 +443,12 @@
}
mOrignalListener = listener;
mView = v;
+ mShouldRunOnHWLayer = true;
}
@Override
@CallSuper
public void onAnimationStart(Animation animation) {
- if (mView != null) {
- mShouldRunOnHWLayer = shouldRunOnHWLayer(mView, animation);
- if (mShouldRunOnHWLayer) {
- mView.post(new Runnable() {
- @Override
- public void run() {
- ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_HARDWARE, null);
- }
- });
- }
- }
if (mOrignalListener != null) {
mOrignalListener.onAnimationStart(animation);
}
@@ -457,12 +458,26 @@
@CallSuper
public void onAnimationEnd(Animation animation) {
if (mView != null && mShouldRunOnHWLayer) {
- mView.post(new Runnable() {
- @Override
- public void run() {
- ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null);
- }
- });
+ // If we're attached to a window, assume we're in the normal performTraversals
+ // drawing path for Animations running. It's not safe to change the layer type
+ // during drawing, so post it to the View to run later. If we're not attached
+ // or we're running on N and above, post it to the view. If we're not on N and
+ // not attached, do it right now since existing platform versions don't run the
+ // hwui renderer for detached views off the UI thread making changing layer type
+ // safe, but posting may not be.
+ // Prior to N posting to a detached view from a non-Looper thread could cause
+ // leaks, since the thread-local run queue on a non-Looper thread would never
+ // be flushed.
+ if (ViewCompat.isAttachedToWindow(mView) || BuildCompat.isAtLeastN()) {
+ mView.post(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null);
+ }
+ });
+ } else {
+ ViewCompat.setLayerType(mView, ViewCompat.LAYER_TYPE_NONE, null);
+ }
}
if (mOrignalListener != null) {
mOrignalListener.onAnimationEnd(animation);
@@ -954,11 +969,16 @@
// If there's already a listener set on the animation, we need wrap the new listener
// around the existing listener, so that they will both get animation listener
// callbacks.
+ ViewCompat.setLayerType(v, ViewCompat.LAYER_TYPE_HARDWARE, null);
anim.setAnimationListener(new AnimateOnHWLayerIfNeededListener(v, anim,
originalListener));
}
}
+ boolean isStateAtLeast(int state) {
+ return mCurState >= state;
+ }
+
void moveToState(Fragment f, int newState, int transit, int transitionStyle,
boolean keepActive) {
// Fragments that are not currently added will sit in the onCreate() state.
@@ -1023,10 +1043,15 @@
}
if (f.mParentFragment == null) {
mHost.onAttachFragment(f);
+ } else {
+ f.mParentFragment.onAttachFragment(f);
}
if (!f.mRetaining) {
f.performCreate(f.mSavedFragmentState);
+ } else {
+ f.restoreChildFragmentState(f.mSavedFragmentState);
+ f.mState = Fragment.CREATED;
}
f.mRetaining = false;
if (f.mFromLayout) {
@@ -1054,12 +1079,24 @@
if (!f.mFromLayout) {
ViewGroup container = null;
if (f.mContainerId != 0) {
- container = (ViewGroup)mContainer.onFindViewById(f.mContainerId);
+ if (f.mContainerId == View.NO_ID) {
+ throwException(new IllegalArgumentException(
+ "Cannot create fragment "
+ + f
+ + " for a container view with no id"));
+ }
+ container = (ViewGroup) mContainer.onFindViewById(f.mContainerId);
if (container == null && !f.mRestored) {
+ String resName;
+ try {
+ resName = f.getResources().getResourceName(f.mContainerId);
+ } catch (NotFoundException e) {
+ resName = "unknown";
+ }
throwException(new IllegalArgumentException(
"No view found for id 0x"
+ Integer.toHexString(f.mContainerId) + " ("
- + f.getResources().getResourceName(f.mContainerId)
+ + resName
+ ") for fragment " + f));
}
}
@@ -1096,6 +1133,9 @@
f.mSavedFragmentState = null;
}
case Fragment.ACTIVITY_CREATED:
+ if (newState > Fragment.ACTIVITY_CREATED) {
+ f.mState = Fragment.STOPPED;
+ }
case Fragment.STOPPED:
if (newState > Fragment.STOPPED) {
if (DEBUG) Log.v(TAG, "moveto STARTED: " + f);
@@ -1104,7 +1144,6 @@
case Fragment.STARTED:
if (newState > Fragment.STARTED) {
if (DEBUG) Log.v(TAG, "moveto RESUMED: " + f);
- f.mResumed = true;
f.performResume();
f.mSavedFragmentState = null;
f.mSavedViewState = null;
@@ -1116,7 +1155,6 @@
if (newState < Fragment.RESUMED) {
if (DEBUG) Log.v(TAG, "movefrom RESUMED: " + f);
f.performPause();
- f.mResumed = false;
}
case Fragment.STARTED:
if (newState < Fragment.STARTED) {
@@ -1147,7 +1185,6 @@
}
if (anim != null) {
final Fragment fragment = f;
- final ViewGroup container = f.mContainer;
f.mAnimatingAway = f.mView;
f.mStateAfterAnimating = newState;
final View viewToAnimate = f.mView;
@@ -1157,7 +1194,6 @@
public void onAnimationEnd(Animation animation) {
super.onAnimationEnd(animation);
if (fragment.mAnimatingAway != null) {
- container.removeView(fragment.mAnimatingAway);
fragment.mAnimatingAway = null;
moveToState(fragment, fragment.mStateAfterAnimating,
0, 0, false);
@@ -1165,9 +1201,8 @@
}
});
f.mView.startAnimation(anim);
- } else {
- f.mContainer.removeView(f.mView);
}
+ f.mContainer.removeView(f.mView);
}
f.mContainer = null;
f.mView = null;
@@ -1199,14 +1234,11 @@
if (DEBUG) Log.v(TAG, "movefrom CREATED: " + f);
if (!f.mRetaining) {
f.performDestroy();
+ } else {
+ f.mState = Fragment.INITIALIZING;
}
- f.mCalled = false;
- f.onDetach();
- if (!f.mCalled) {
- throw new SuperNotCalledException("Fragment " + f
- + " did not call through to super.onDetach()");
- }
+ f.performDetach();
if (!keepActive) {
if (!f.mRetaining) {
makeInactive(f);
@@ -1214,15 +1246,18 @@
f.mHost = null;
f.mParentFragment = null;
f.mFragmentManager = null;
- f.mChildFragmentManager = null;
}
}
}
}
}
}
-
- f.mState = newState;
+
+ if (f.mState != newState) {
+ Log.w(TAG, "moveToState: Fragment state for " + f + " not updated inline; "
+ + "expected state " + newState + " found " + f.mState);
+ f.mState = newState;
+ }
}
void moveToState(Fragment f) {
@@ -1580,16 +1615,36 @@
}
}
+ public void execSingleAction(Runnable action, boolean allowStateLoss) {
+ if (mExecutingActions) {
+ throw new IllegalStateException("FragmentManager is already executing transactions");
+ }
+
+ if (Looper.myLooper() != mHost.getHandler().getLooper()) {
+ throw new IllegalStateException("Must be called from main thread of fragment host");
+ }
+
+ if (!allowStateLoss) {
+ checkStateLoss();
+ }
+
+ mExecutingActions = true;
+ action.run();
+ mExecutingActions = false;
+
+ doPendingDeferredStart();
+ }
+
/**
* Only call from main thread!
*/
public boolean execPendingActions() {
if (mExecutingActions) {
- throw new IllegalStateException("Recursive entry to executePendingTransactions");
+ throw new IllegalStateException("FragmentManager is already executing transactions");
}
if (Looper.myLooper() != mHost.getHandler().getLooper()) {
- throw new IllegalStateException("Must be called from main thread of process");
+ throw new IllegalStateException("Must be called from main thread of fragment host");
}
boolean didSomething = false;
@@ -1620,9 +1675,15 @@
didSomething = true;
}
+ doPendingDeferredStart();
+
+ return didSomething;
+ }
+
+ void doPendingDeferredStart() {
if (mHavePendingDeferredStart) {
boolean loadersRunning = false;
- for (int i=0; i<mActive.size(); i++) {
+ for (int i = 0; i < mActive.size(); i++) {
Fragment f = mActive.get(i);
if (f != null && f.mLoaderManager != null) {
loadersRunning |= f.mLoaderManager.hasRunningLoaders();
@@ -1633,7 +1694,6 @@
startPendingDeferredFragments();
}
}
- return didSomething;
}
void reportBackStackChanged() {
@@ -1665,7 +1725,9 @@
final BackStackRecord bss = mBackStack.remove(last);
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
- bss.calculateBackFragments(firstOutFragments, lastInFragments);
+ if (mCurState >= Fragment.CREATED) {
+ bss.calculateBackFragments(firstOutFragments, lastInFragments);
+ }
bss.popFromBackStack(true, null, firstOutFragments, lastInFragments);
reportBackStackChanged();
} else {
@@ -1712,8 +1774,10 @@
final int LAST = states.size()-1;
SparseArray<Fragment> firstOutFragments = new SparseArray<Fragment>();
SparseArray<Fragment> lastInFragments = new SparseArray<Fragment>();
- for (int i=0; i<=LAST; i++) {
- states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
+ if (mCurState >= Fragment.CREATED) {
+ for (int i = 0; i <= LAST; i++) {
+ states.get(i).calculateBackFragments(firstOutFragments, lastInFragments);
+ }
}
BackStackRecord.TransitionState state = null;
for (int i=0; i<=LAST; i++) {
@@ -1726,23 +1790,46 @@
return true;
}
- ArrayList<Fragment> retainNonConfig() {
+ FragmentManagerNonConfig retainNonConfig() {
ArrayList<Fragment> fragments = null;
+ ArrayList<FragmentManagerNonConfig> childFragments = null;
if (mActive != null) {
for (int i=0; i<mActive.size(); i++) {
Fragment f = mActive.get(i);
- if (f != null && f.mRetainInstance) {
- if (fragments == null) {
- fragments = new ArrayList<Fragment>();
+ if (f != null) {
+ if (f.mRetainInstance) {
+ if (fragments == null) {
+ fragments = new ArrayList<Fragment>();
+ }
+ fragments.add(f);
+ f.mRetaining = true;
+ f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
+ if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
}
- fragments.add(f);
- f.mRetaining = true;
- f.mTargetIndex = f.mTarget != null ? f.mTarget.mIndex : -1;
- if (DEBUG) Log.v(TAG, "retainNonConfig: keeping retained " + f);
+ boolean addedChild = false;
+ if (f.mChildFragmentManager != null) {
+ FragmentManagerNonConfig child = f.mChildFragmentManager.retainNonConfig();
+ if (child != null) {
+ if (childFragments == null) {
+ childFragments = new ArrayList<FragmentManagerNonConfig>();
+ for (int j = 0; j < i; j++) {
+ childFragments.add(null);
+ }
+ }
+ childFragments.add(child);
+ addedChild = true;
+ }
+ }
+ if (childFragments != null && !addedChild) {
+ childFragments.add(null);
+ }
}
}
}
- return fragments;
+ if (fragments == null && childFragments == null) {
+ return null;
+ }
+ return new FragmentManagerNonConfig(fragments, childFragments);
}
void saveFragmentViewState(Fragment f) {
@@ -1910,18 +1997,23 @@
return fms;
}
- void restoreAllState(Parcelable state, List<Fragment> nonConfig) {
+ void restoreAllState(Parcelable state, FragmentManagerNonConfig nonConfig) {
// If there is no saved state at all, then there can not be
// any nonConfig fragments either, so that is that.
if (state == null) return;
FragmentManagerState fms = (FragmentManagerState)state;
if (fms.mActive == null) return;
-
+
+ List<FragmentManagerNonConfig> childNonConfigs = null;
+
// First re-attach any non-config instances we are retaining back
// to their saved state, so we don't try to instantiate them again.
if (nonConfig != null) {
- for (int i=0; i<nonConfig.size(); i++) {
- Fragment f = nonConfig.get(i);
+ List<Fragment> nonConfigFragments = nonConfig.getFragments();
+ childNonConfigs = nonConfig.getChildNonConfigs();
+ final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+ for (int i = 0; i < count; i++) {
+ Fragment f = nonConfigFragments.get(i);
if (DEBUG) Log.v(TAG, "restoreAllState: re-attaching retained " + f);
FragmentState fs = fms.mActive[f.mIndex];
fs.mInstance = f;
@@ -1941,14 +2033,18 @@
// Build the full list of active fragments, instantiating them from
// their saved state.
- mActive = new ArrayList<Fragment>(fms.mActive.length);
+ mActive = new ArrayList<>(fms.mActive.length);
if (mAvailIndices != null) {
mAvailIndices.clear();
}
for (int i=0; i<fms.mActive.length; i++) {
FragmentState fs = fms.mActive[i];
if (fs != null) {
- Fragment f = fs.instantiate(mHost, mParent);
+ FragmentManagerNonConfig childNonConfig = null;
+ if (childNonConfigs != null && i < childNonConfigs.size()) {
+ childNonConfig = childNonConfigs.get(i);
+ }
+ Fragment f = fs.instantiate(mHost, mParent, childNonConfig);
if (DEBUG) Log.v(TAG, "restoreAllState: active #" + i + ": " + f);
mActive.add(f);
// Now that the fragment is instantiated (or came from being
@@ -1967,8 +2063,10 @@
// Update the target of all retained fragments.
if (nonConfig != null) {
- for (int i=0; i<nonConfig.size(); i++) {
- Fragment f = nonConfig.get(i);
+ List<Fragment> nonConfigFragments = nonConfig.getFragments();
+ final int count = nonConfigFragments != null ? nonConfigFragments.size() : 0;
+ for (int i = 0; i < count; i++) {
+ Fragment f = nonConfigFragments.get(i);
if (f.mTargetIndex >= 0) {
if (f.mTargetIndex < mActive.size()) {
f.mTarget = mActive.get(f.mTargetIndex);
@@ -2084,7 +2182,31 @@
mContainer = null;
mParent = null;
}
-
+
+ public void dispatchMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ if (mAdded == null) {
+ return;
+ }
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final android.support.v4.app.Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performMultiWindowModeChanged(isInMultiWindowMode);
+ }
+ }
+ }
+
+ public void dispatchPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ if (mAdded == null) {
+ return;
+ }
+ for (int i = mAdded.size() - 1; i >= 0; --i) {
+ final android.support.v4.app.Fragment f = mAdded.get(i);
+ if (f != null) {
+ f.performPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ }
+ }
+
public void dispatchConfigurationChanged(Configuration newConfig) {
if (mAdded != null) {
for (int i=0; i<mAdded.size(); i++) {
diff --git a/v4/java/android/support/v4/app/FragmentManagerNonConfig.java b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
new file mode 100644
index 0000000..832426a
--- /dev/null
+++ b/v4/java/android/support/v4/app/FragmentManagerNonConfig.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.os.Parcelable;
+
+import java.util.List;
+
+/**
+ * FragmentManagerNonConfig stores the retained instance fragments across
+ * activity recreation events.
+ *
+ * <p>Apps should treat objects of this type as opaque, returned by
+ * and passed to the state save and restore process for fragments in
+ * {@link FragmentController#retainNonConfig()} and
+ * {@link FragmentController#restoreAllState(Parcelable, FragmentManagerNonConfig)}.</p>
+ */
+public class FragmentManagerNonConfig {
+ private final List<Fragment> mFragments;
+ private final List<FragmentManagerNonConfig> mChildNonConfigs;
+
+ FragmentManagerNonConfig(List<Fragment> fragments,
+ List<FragmentManagerNonConfig> childNonConfigs) {
+ mFragments = fragments;
+ mChildNonConfigs = childNonConfigs;
+ }
+
+ /**
+ * @return the retained instance fragments returned by a FragmentManager
+ */
+ List<Fragment> getFragments() {
+ return mFragments;
+ }
+
+ /**
+ * @return the FragmentManagerNonConfigs from any applicable fragment's child FragmentManager
+ */
+ List<FragmentManagerNonConfig> getChildNonConfigs() {
+ return mChildNonConfigs;
+ }
+}
diff --git a/v4/java/android/support/v4/app/FragmentPagerAdapter.java b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
index 01ca411..d0d1006 100644
--- a/v4/java/android/support/v4/app/FragmentPagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentPagerAdapter.java
@@ -77,6 +77,10 @@
@Override
public void startUpdate(ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
}
@Override
@@ -136,9 +140,8 @@
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
- mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
- mFragmentManager.executePendingTransactions();
}
}
diff --git a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
index 0c3c6c5..0154750 100644
--- a/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
+++ b/v4/java/android/support/v4/app/FragmentStatePagerAdapter.java
@@ -16,8 +16,6 @@
package android.support.v4.app;
-import java.util.ArrayList;
-
import android.os.Bundle;
import android.os.Parcelable;
import android.support.v4.view.PagerAdapter;
@@ -25,6 +23,8 @@
import android.view.View;
import android.view.ViewGroup;
+import java.util.ArrayList;
+
/**
* Implementation of {@link android.support.v4.view.PagerAdapter} that
* uses a {@link Fragment} to manage each page. This class also handles
@@ -83,6 +83,10 @@
@Override
public void startUpdate(ViewGroup container) {
+ if (container.getId() == View.NO_ID) {
+ throw new IllegalStateException("ViewPager with adapter " + this
+ + " requires a view id");
+ }
}
@Override
@@ -123,7 +127,7 @@
@Override
public void destroyItem(ViewGroup container, int position, Object object) {
- Fragment fragment = (Fragment)object;
+ Fragment fragment = (Fragment) object;
if (mCurTransaction == null) {
mCurTransaction = mFragmentManager.beginTransaction();
@@ -133,7 +137,8 @@
while (mSavedState.size() <= position) {
mSavedState.add(null);
}
- mSavedState.set(position, mFragmentManager.saveFragmentInstanceState(fragment));
+ mSavedState.set(position, fragment.isAdded()
+ ? mFragmentManager.saveFragmentInstanceState(fragment) : null);
mFragments.set(position, null);
mCurTransaction.remove(fragment);
@@ -158,9 +163,8 @@
@Override
public void finishUpdate(ViewGroup container) {
if (mCurTransaction != null) {
- mCurTransaction.commitAllowingStateLoss();
+ mCurTransaction.commitNowAllowingStateLoss();
mCurTransaction = null;
- mFragmentManager.executePendingTransactions();
}
}
diff --git a/v4/java/android/support/v4/app/FragmentTabHost.java b/v4/java/android/support/v4/app/FragmentTabHost.java
index 7ffabdd..7cb1266 100644
--- a/v4/java/android/support/v4/app/FragmentTabHost.java
+++ b/v4/java/android/support/v4/app/FragmentTabHost.java
@@ -300,7 +300,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
- SavedState ss = (SavedState)state;
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
setCurrentTabByTag(ss.curTab);
}
diff --git a/v4/java/android/support/v4/app/FragmentTransaction.java b/v4/java/android/support/v4/app/FragmentTransaction.java
index 48f2088..4dbc791 100644
--- a/v4/java/android/support/v4/app/FragmentTransaction.java
+++ b/v4/java/android/support/v4/app/FragmentTransaction.java
@@ -162,7 +162,7 @@
public static final int TRANSIT_EXIT_MASK = 0x2000;
/** @hide */
- @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE})
+ @IntDef({TRANSIT_NONE, TRANSIT_FRAGMENT_OPEN, TRANSIT_FRAGMENT_CLOSE, TRANSIT_FRAGMENT_FADE})
@Retention(RetentionPolicy.SOURCE)
private @interface Transit {}
@@ -212,7 +212,7 @@
/**
* Select a standard transition animation for this transaction. May be
* one of {@link #TRANSIT_NONE}, {@link #TRANSIT_FRAGMENT_OPEN},
- * or {@link #TRANSIT_FRAGMENT_CLOSE}
+ * {@link #TRANSIT_FRAGMENT_CLOSE}, or {@link #TRANSIT_FRAGMENT_FADE}.
*/
public abstract FragmentTransaction setTransition(@Transit int transit);
@@ -303,4 +303,45 @@
* to change unexpectedly on the user.
*/
public abstract int commitAllowingStateLoss();
+
+ /**
+ * Commits this transaction synchronously. Any added fragments will be
+ * initialized and brought completely to the lifecycle state of their host
+ * and any removed fragments will be torn down accordingly before this
+ * call returns. Committing a transaction in this way allows fragments
+ * to be added as dedicated, encapsulated components that monitor the
+ * lifecycle state of their host while providing firmer ordering guarantees
+ * around when those fragments are fully initialized and ready. Fragments
+ * that manage views will have those views created and attached.
+ *
+ * <p>Calling <code>commitNow</code> is preferable to calling
+ * {@link #commit()} followed by {@link FragmentManager#executePendingTransactions()}
+ * as the latter will have the side effect of attempting to commit <em>all</em>
+ * currently pending transactions whether that is the desired behavior
+ * or not.</p>
+ *
+ * <p>Transactions committed in this way may not be added to the
+ * FragmentManager's back stack, as doing so would break other expected
+ * ordering guarantees for other asynchronously committed transactions.
+ * This method will throw {@link IllegalStateException} if the transaction
+ * previously requested to be added to the back stack with
+ * {@link #addToBackStack(String)}.</p>
+ *
+ * <p class="note">A transaction can only be committed with this method
+ * prior to its containing activity saving its state. If the commit is
+ * attempted after that point, an exception will be thrown. This is
+ * because the state after the commit can be lost if the activity needs to
+ * be restored from its state. See {@link #commitAllowingStateLoss()} for
+ * situations where it may be okay to lose the commit.</p>
+ */
+ public abstract void commitNow();
+
+ /**
+ * Like {@link #commitNow} but allows the commit to be executed after an
+ * activity's state is saved. This is dangerous because the commit can
+ * be lost if the activity needs to later be restored from its state, so
+ * this should only be used for cases where it is okay for the UI state
+ * to change unexpectedly on the user.
+ */
+ public abstract void commitNowAllowingStateLoss();
}
diff --git a/v4/java/android/support/v4/app/ListFragment.java b/v4/java/android/support/v4/app/ListFragment.java
index 1ddea1c..9923e8a 100644
--- a/v4/java/android/support/v4/app/ListFragment.java
+++ b/v4/java/android/support/v4/app/ListFragment.java
@@ -88,7 +88,7 @@
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
- final Context context = getActivity();
+ final Context context = getContext();
FrameLayout root = new FrameLayout(context);
@@ -113,13 +113,13 @@
FrameLayout lframe = new FrameLayout(context);
lframe.setId(INTERNAL_LIST_CONTAINER_ID);
- TextView tv = new TextView(getActivity());
+ TextView tv = new TextView(context);
tv.setId(INTERNAL_EMPTY_ID);
tv.setGravity(Gravity.CENTER);
lframe.addView(tv, new FrameLayout.LayoutParams(
ViewGroup.LayoutParams.FILL_PARENT, ViewGroup.LayoutParams.FILL_PARENT));
- ListView lv = new ListView(getActivity());
+ ListView lv = new ListView(context);
lv.setId(android.R.id.list);
lv.setDrawSelectorOnTop(false);
lframe.addView(lv, new FrameLayout.LayoutParams(
@@ -216,7 +216,7 @@
}
/**
- * Get the activity's list view widget.
+ * Get the fragment's list view widget.
*/
public ListView getListView() {
ensureList();
@@ -288,9 +288,9 @@
if (shown) {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_out));
+ getContext(), android.R.anim.fade_out));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_in));
+ getContext(), android.R.anim.fade_in));
} else {
mProgressContainer.clearAnimation();
mListContainer.clearAnimation();
@@ -300,9 +300,9 @@
} else {
if (animate) {
mProgressContainer.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_in));
+ getContext(), android.R.anim.fade_in));
mListContainer.startAnimation(AnimationUtils.loadAnimation(
- getActivity(), android.R.anim.fade_out));
+ getContext(), android.R.anim.fade_out));
} else {
mProgressContainer.clearAnimation();
mListContainer.clearAnimation();
@@ -313,7 +313,7 @@
}
/**
- * Get the ListAdapter associated with this activity's ListView.
+ * Get the ListAdapter associated with this fragment's ListView.
*/
public ListAdapter getListAdapter() {
return mAdapter;
diff --git a/v4/java/android/support/v4/app/LoaderManager.java b/v4/java/android/support/v4/app/LoaderManager.java
index fad56e0..789e0e9 100644
--- a/v4/java/android/support/v4/app/LoaderManager.java
+++ b/v4/java/android/support/v4/app/LoaderManager.java
@@ -311,7 +311,7 @@
if (mStarted) {
if (mReportNextStart) {
mReportNextStart = false;
- if (mHaveData) {
+ if (mHaveData && !mRetaining) {
callOnLoadFinished(mLoader, mData);
}
}
@@ -332,13 +332,16 @@
}
}
- void cancel() {
+ boolean cancel() {
if (DEBUG) Log.v(TAG, " Canceling: " + this);
if (mStarted && mLoader != null && mListenerRegistered) {
- if (!mLoader.cancelLoad()) {
+ final boolean cancelLoadResult = mLoader.cancelLoad();
+ if (!cancelLoadResult) {
onLoadCanceled(mLoader);
}
+ return cancelLoadResult;
}
+ return false;
}
void destroy() {
@@ -660,20 +663,21 @@
mInactiveLoaders.put(id, info);
} else {
// We already have an inactive loader for this ID that we are
- // waiting for! What to do, what to do...
- if (!info.mStarted) {
- // The current Loader has not been started... we thus
- // have no reason to keep it around, so bam, slam,
- // thank-you-ma'am.
+ // waiting for! Try to cancel; if this returns true then the task is still
+ // running and we have more work to do.
+ if (!info.cancel()) {
+ // The current Loader has not been started or was successfully canceled,
+ // we thus have no reason to keep it around. Remove it and a new
+ // LoaderInfo will be created below.
if (DEBUG) Log.v(TAG, " Current loader is stopped; replacing");
mLoaders.put(id, null);
info.destroy();
} else {
// Now we have three active loaders... we'll queue
// up this request to be processed once one of the other loaders
- // finishes or is canceled.
- if (DEBUG) Log.v(TAG, " Current loader is running; attempting to cancel");
- info.cancel();
+ // finishes.
+ if (DEBUG) Log.v(TAG,
+ " Current loader is running; configuring pending loader");
if (info.mPendingLoader != null) {
if (DEBUG) Log.v(TAG, " Removing pending loader: " + info.mPendingLoader);
info.mPendingLoader.destroy();
diff --git a/v4/java/android/support/v4/app/NavUtils.java b/v4/java/android/support/v4/app/NavUtils.java
index 682aaaf..f9661a7 100644
--- a/v4/java/android/support/v4/app/NavUtils.java
+++ b/v4/java/android/support/v4/app/NavUtils.java
@@ -35,7 +35,7 @@
* from the developer guide and <a href="{@docRoot}design/patterns/navigation.html">Navigation</a>
* from the design guide.
*/
-public class NavUtils {
+public final class NavUtils {
private static final String TAG = "NavUtils";
public static final String PARENT_ACTIVITY = "android.support.PARENT_ACTIVITY";
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index e23c11c..d7b778c 100644
--- a/v4/java/android/support/v4/app/NotificationCompat.java
+++ b/v4/java/android/support/v4/app/NotificationCompat.java
@@ -16,6 +16,7 @@
package android.support.v4.app;
+import android.app.Activity;
import android.app.Notification;
import android.app.PendingIntent;
import android.content.Context;
@@ -27,6 +28,7 @@
import android.os.Bundle;
import android.os.Parcelable;
import android.support.annotation.ColorInt;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.GravityCompat;
import android.view.Gravity;
import android.widget.RemoteViews;
@@ -139,6 +141,7 @@
*
* @deprecated Use {@link NotificationCompat.Builder#setPriority(int)} with a positive value.
*/
+ @Deprecated
public static final int FLAG_HIGH_PRIORITY = 0x00000080;
/**
@@ -217,6 +220,22 @@
public static final String EXTRA_SUB_TEXT = "android.subText";
/**
+ * Notification extras key: this is the remote input history, as supplied to
+ * {@link Builder#setRemoteInputHistory(CharSequence[])}.
+ *
+ * Apps can fill this through {@link Builder#setRemoteInputHistory(CharSequence[])}
+ * with the most recent inputs that have been sent through a {@link RemoteInput} of this
+ * Notification and are expected to clear it once the it is no longer relevant (e.g. for chat
+ * notifications once the other party has responded).
+ *
+ * The extra with this key is of type CharSequence[] and contains the most recent entry at
+ * the 0 index, the second most recent at the 1 index, etc.
+ *
+ * @see Builder#setRemoteInputHistory(CharSequence[])
+ */
+ public static final String EXTRA_REMOTE_INPUT_HISTORY = "android.remoteInputHistory";
+
+ /**
* Notification extras key: this is a small piece of additional text as supplied to
* {@link Builder#setContentInfo(CharSequence)}.
*/
@@ -338,6 +357,26 @@
public static final String EXTRA_COMPACT_ACTIONS = "android.compactActions";
/**
+ * Notification key: the username to be displayed for all messages sent by the user
+ * including
+ * direct replies
+ * {@link MessagingStyle} notification.
+ */
+ public static final String EXTRA_SELF_DISPLAY_NAME = "android.selfDisplayName";
+
+ /**
+ * Notification key: a {@link String} to be displayed as the title to a conversation
+ * represented by a {@link MessagingStyle}
+ */
+ public static final String EXTRA_CONVERSATION_TITLE = "android.conversationTitle";
+
+ /**
+ * Notification key: an array of {@link Bundle} objects representing
+ * {@link MessagingStyle.Message} objects for a {@link MessagingStyle} notification.
+ */
+ public static final String EXTRA_MESSAGES = "android.messages";
+
+ /**
* Value of {@link Notification#color} equal to 0 (also known as
* {@link android.graphics.Color#TRANSPARENT Color.TRANSPARENT}),
* telling the system not to decorate this notification with any special color but instead use
@@ -429,6 +468,11 @@
public static final String CATEGORY_SERVICE = NotificationCompatApi21.CATEGORY_SERVICE;
/**
+ * Notification category: user-scheduled reminder.
+ */
+ public static final String CATEGORY_REMINDER = NotificationCompatApi23.CATEGORY_REMINDER;
+
+ /**
* Notification category: a specific, timely recommendation for a single thing.
* For example, a news app might want to recommend a news story it believes the user will
* want to read next.
@@ -476,12 +520,15 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
Notification result = b.mNotification;
- result.setLatestEventInfo(b.mContext, b.mContentTitle,
- b.mContentText, b.mContentIntent);
+ result = NotificationCompatBase.add(result, b.mContext,
+ b.mContentTitle, b.mContentText, b.mContentIntent);
// translate high priority requests into legacy flag
if (b.mPriority > PRIORITY_DEFAULT) {
result.flags |= FLAG_HIGH_PRIORITY;
}
+ if (b.mContentView != null) {
+ result.contentView = b.mContentView;
+ }
return result;
}
@@ -553,14 +600,15 @@
@Override
public Notification build(Builder b, BuilderExtender extender) {
Notification result = b.mNotification;
- result.setLatestEventInfo(b.mContext, b.mContentTitle,
- b.mContentText, b.mContentIntent);
result = NotificationCompatGingerbread.add(result, b.mContext,
b.mContentTitle, b.mContentText, b.mContentIntent, b.mFullScreenIntent);
// translate high priority requests into legacy flag
if (b.mPriority > PRIORITY_DEFAULT) {
result.flags |= FLAG_HIGH_PRIORITY;
}
+ if (b.mContentView != null) {
+ result.contentView = b.mContentView;
+ }
return result;
}
}
@@ -568,9 +616,13 @@
static class NotificationCompatImplHoneycomb extends NotificationCompatImplBase {
@Override
public Notification build(Builder b, BuilderExtender extender) {
- return NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
+ Notification notification = NotificationCompatHoneycomb.add(b.mContext, b.mNotification,
b.mContentTitle, b.mContentText, b.mContentInfo, b.mTickerView,
b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon);
+ if (b.mContentView != null) {
+ notification.contentView = b.mContentView;
+ }
+ return notification;
}
}
@@ -579,10 +631,14 @@
public Notification build(Builder b, BuilderExtender extender) {
NotificationCompatIceCreamSandwich.Builder builder =
new NotificationCompatIceCreamSandwich.Builder(
- b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
- b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
- b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
- return extender.build(b, builder);
+ b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ b.mProgressMax, b.mProgress, b.mProgressIndeterminate);
+ Notification notification = extender.build(b, builder);
+ if (b.mContentView != null) {
+ notification.contentView = b.mContentView;
+ }
+ return notification;
}
}
@@ -594,10 +650,14 @@
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mExtras,
- b.mGroupKey, b.mGroupSummary, b.mSortKey);
+ b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView);
addActionsToBuilder(builder, b.mActions);
addStyleToBuilderJellybean(builder, b.mStyle);
- return extender.build(b, builder);
+ Notification notification = extender.build(b, builder);
+ if (b.mStyle != null) {
+ b.mStyle.addCompatExtras(getExtras(notification));
+ }
+ return notification;
}
@Override
@@ -658,7 +718,8 @@
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly,
- b.mPeople, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey);
+ b.mPeople, b.mExtras, b.mGroupKey, b.mGroupSummary, b.mSortKey,
+ b.mContentView, b.mBigContentView);
addActionsToBuilder(builder, b.mActions);
addStyleToBuilderJellybean(builder, b.mStyle);
return extender.build(b, builder);
@@ -709,10 +770,14 @@
b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mPeople, b.mExtras,
- b.mGroupKey, b.mGroupSummary, b.mSortKey);
+ b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView);
addActionsToBuilder(builder, b.mActions);
addStyleToBuilderJellybean(builder, b.mStyle);
- return extender.build(b, builder);
+ Notification notification = extender.build(b, builder);
+ if (b.mStyle != null) {
+ b.mStyle.addCompatExtras(getExtras(notification));
+ }
+ return notification;
}
@Override
@@ -764,10 +829,15 @@
b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mCategory,
b.mPeople, b.mExtras, b.mColor, b.mVisibility, b.mPublicVersion,
- b.mGroupKey, b.mGroupSummary, b.mSortKey);
+ b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mContentView, b.mBigContentView,
+ b.mHeadsUpContentView);
addActionsToBuilder(builder, b.mActions);
addStyleToBuilderJellybean(builder, b.mStyle);
- return extender.build(b, builder);
+ Notification notification = extender.build(b, builder);
+ if (b.mStyle != null) {
+ b.mStyle.addCompatExtras(getExtras(notification));
+ }
+ return notification;
}
@Override
@@ -789,6 +859,28 @@
}
}
+ static class NotificationCompatImplApi24 extends NotificationCompatImplApi21 {
+ @Override
+ public Notification build(Builder b,
+ BuilderExtender extender) {
+ NotificationCompatApi24.Builder builder = new NotificationCompatApi24.Builder(
+ b.mContext, b.mNotification, b.mContentTitle, b.mContentText, b.mContentInfo,
+ b.mTickerView, b.mNumber, b.mContentIntent, b.mFullScreenIntent, b.mLargeIcon,
+ b.mProgressMax, b.mProgress, b.mProgressIndeterminate, b.mShowWhen,
+ b.mUseChronometer, b.mPriority, b.mSubText, b.mLocalOnly, b.mCategory,
+ b.mPeople, b.mExtras, b.mColor, b.mVisibility, b.mPublicVersion,
+ b.mGroupKey, b.mGroupSummary, b.mSortKey, b.mRemoteInputHistory, b.mContentView,
+ b.mBigContentView, b.mHeadsUpContentView);
+ addActionsToBuilder(builder, b.mActions);
+ addStyleToBuilderApi24(builder, b.mStyle);
+ Notification notification = extender.build(b, builder);
+ if (b.mStyle != null) {
+ b.mStyle.addCompatExtras(getExtras(notification));
+ }
+ return notification;
+ }
+ }
+
private static void addActionsToBuilder(NotificationBuilderWithActions builder,
ArrayList<Action> actions) {
for (Action action : actions) {
@@ -822,12 +914,43 @@
bigPictureStyle.mPicture,
bigPictureStyle.mBigLargeIcon,
bigPictureStyle.mBigLargeIconSet);
+ } else if (style instanceof MessagingStyle) {
+ // TODO implement BigText fallback
+ }
+ }
+ }
+
+ private static void addStyleToBuilderApi24(NotificationBuilderWithBuilderAccessor builder,
+ Style style) {
+ if (style != null) {
+ if (style instanceof MessagingStyle) {
+ MessagingStyle messagingStyle = (MessagingStyle) style;
+ List<CharSequence> texts = new ArrayList<>();
+ List<Long> timestamps = new ArrayList<>();
+ List<CharSequence> senders = new ArrayList<>();
+ List<String> dataMimeTypes = new ArrayList<>();
+ List<Uri> dataUris = new ArrayList<>();
+
+ for (MessagingStyle.Message message : messagingStyle.mMessages) {
+ texts.add(message.getText());
+ timestamps.add(message.getTimestamp());
+ senders.add(message.getSender());
+ dataMimeTypes.add(message.getDataMimeType());
+ dataUris.add(message.getDataUri());
+ }
+ NotificationCompatApi24.addMessagingStyle(builder, messagingStyle.mUserDisplayName,
+ messagingStyle.mConversationTitle, texts, timestamps, senders,
+ dataMimeTypes, dataUris);
+ } else {
+ addStyleToBuilderJellybean(builder, style);
}
}
}
static {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new NotificationCompatImplApi24();
+ } else if (Build.VERSION.SDK_INT >= 21) {
IMPL = new NotificationCompatImplApi21();
} else if (Build.VERSION.SDK_INT >= 20) {
IMPL = new NotificationCompatImplApi20();
@@ -902,6 +1025,8 @@
public Style mStyle;
/** @hide */
public CharSequence mSubText;
+ /** @hide */
+ public CharSequence[] mRemoteInputHistory;
int mProgressMax;
int mProgress;
boolean mProgressIndeterminate;
@@ -916,6 +1041,9 @@
int mColor = COLOR_DEFAULT;
int mVisibility = VISIBILITY_PRIVATE;
Notification mPublicVersion;
+ RemoteViews mContentView;
+ RemoteViews mBigContentView;
+ RemoteViews mHeadsUpContentView;
/** @hide */
public Notification mNotification = new Notification();
@@ -1035,6 +1163,25 @@
}
/**
+ * Set the remote input history.
+ *
+ * This should be set to the most recent inputs that have been sent
+ * through a {@link RemoteInput} of this Notification and cleared once the it is no
+ * longer relevant (e.g. for chat notifications once the other party has responded).
+ *
+ * The most recent input must be stored at the 0 index, the second most recent at the
+ * 1 index, etc. Note that the system will limit both how far back the inputs will be shown
+ * and how much of each individual input is shown.
+ *
+ * <p>Note: The reply text will only be shown on notifications that have least one action
+ * with a {@code RemoteInput}.</p>
+ */
+ public Builder setRemoteInputHistory(CharSequence[] text) {
+ mRemoteInputHistory = text;
+ return this;
+ }
+
+ /**
* Set the large number at the right-hand side of the notification. This is
* equivalent to setContentInfo, although it might show the number in a different
* font size for readability.
@@ -1537,6 +1684,43 @@
}
/**
+ * Supply custom RemoteViews to use instead of the platform template.
+ *
+ * This will override the layout that would otherwise be constructed by this Builder
+ * object.
+ */
+ public Builder setCustomContentView(RemoteViews contentView) {
+ mContentView = contentView;
+ return this;
+ }
+
+ /**
+ * Supply custom RemoteViews to use instead of the platform template in the expanded form.
+ *
+ * This will override the expanded layout that would otherwise be constructed by this
+ * Builder object.
+ *
+ * No-op on versions prior to {@link android.os.Build.VERSION_CODES#JELLY_BEAN}.
+ */
+ public Builder setCustomBigContentView(RemoteViews contentView) {
+ mBigContentView = contentView;
+ return this;
+ }
+
+ /**
+ * Supply custom RemoteViews to use instead of the platform template in the heads up dialog.
+ *
+ * This will override the heads-up layout that would otherwise be constructed by this
+ * Builder object.
+ *
+ * No-op on versions prior to {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
+ */
+ public Builder setCustomHeadsUpContentView(RemoteViews contentView) {
+ mHeadsUpContentView = contentView;
+ return this;
+ }
+
+ /**
* Apply an extender to this notification builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@@ -1606,6 +1790,20 @@
}
return notification;
}
+
+ /**
+ * @hide
+ */
+ // TODO: implement for all styles
+ public void addCompatExtras(Bundle extras) {
+ }
+
+ /**
+ * @hide
+ */
+ // TODO: implement for all styles
+ protected void restoreFromCompatExtras(Bundle extras) {
+ }
}
/**
@@ -1736,6 +1934,337 @@
}
/**
+ * Helper class for generating large-format notifications that include multiple back-and-forth
+ * messages of varying types between any number of people.
+ *
+ * <br>
+ * If the platform does not provide large-format notifications, this method has no effect. The
+ * user will always see the normal notification view.
+ * <br>
+ * This class is a "rebuilder": It attaches to a Builder object and modifies its behavior, like
+ * so:
+ * <pre class="prettyprint">
+ *
+ * Notification noti = new Notification.Builder()
+ * .setContentTitle("2 new messages wtih " + sender.toString())
+ * .setContentText(subject)
+ * .setSmallIcon(R.drawable.new_message)
+ * .setLargeIcon(aBitmap)
+ * .setStyle(new Notification.MessagingStyle(resources.getString(R.string.reply_name))
+ * .addMessage(messages[0].getText(), messages[0].getTime(), messages[0].getSender())
+ * .addMessage(messages[1].getText(), messages[1].getTime(), messages[1].getSender()))
+ * .build();
+ * </pre>
+ */
+ public static class MessagingStyle extends Style {
+
+ /**
+ * The maximum number of messages that will be retained in the Notification itself (the
+ * number displayed is up to the platform).
+ */
+ public static final int MAXIMUM_RETAINED_MESSAGES = 25;
+
+ CharSequence mUserDisplayName;
+ CharSequence mConversationTitle;
+ List<Message> mMessages = new ArrayList<>();
+
+ MessagingStyle() {
+ }
+
+ /**
+ * @param userDisplayName the name to be displayed for any replies sent by the user before the
+ * posting app reposts the notification with those messages after they've been actually
+ * sent and in previous messages sent by the user added in
+ * {@link #addMessage(Message)}
+ */
+ public MessagingStyle(CharSequence userDisplayName) {
+ mUserDisplayName = userDisplayName;
+ }
+
+ /**
+ * Returns the name to be displayed for any replies sent by the user
+ */
+ public CharSequence getUserDisplayName() {
+ return mUserDisplayName;
+ }
+
+ /**
+ * Sets the title to be displayed on this conversation. This should only be used for
+ * group messaging and left unset for one-on-one conversations.
+ * @param conversationTitle
+ * @return this object for method chaining.
+ */
+ public MessagingStyle setConversationTitle(CharSequence conversationTitle) {
+ mConversationTitle = conversationTitle;
+ return this;
+ }
+
+ /**
+ * Return the title to be displayed on this conversation. Can be <code>null</code> and
+ * should be for one-on-one conversations
+ */
+ public CharSequence getConversationTitle() {
+ return mConversationTitle;
+ }
+
+ /**
+ * Adds a message for display by this notification. Convenience call for a simple
+ * {@link Message} in {@link #addMessage(Message)}
+ * @param text A {@link CharSequence} to be displayed as the message content
+ * @param timestamp Time at which the message arrived
+ * @param sender A {@link CharSequence} to be used for displaying the name of the
+ * sender. Should be <code>null</code> for messages by the current user, in which case
+ * the platform will insert {@link #getUserDisplayName()}.
+ * Should be unique amongst all individuals in the conversation, and should be
+ * consistent during re-posts of the notification.
+ *
+ * @see Message#Message(CharSequence, long, CharSequence)
+ *
+ * @return this object for method chaining
+ */
+ public MessagingStyle addMessage(CharSequence text, long timestamp, CharSequence sender) {
+ mMessages.add(new Message(text, timestamp, sender));
+ if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
+ mMessages.remove(0);
+ }
+ return this;
+ }
+
+ /**
+ * Adds a {@link Message} for display in this notification.
+ * @param message The {@link Message} to be displayed
+ * @return this object for method chaining
+ */
+ public MessagingStyle addMessage(Message message) {
+ mMessages.add(message);
+ if (mMessages.size() > MAXIMUM_RETAINED_MESSAGES) {
+ mMessages.remove(0);
+ }
+ return this;
+ }
+
+ /**
+ * Gets the list of {@code Message} objects that represent the notification
+ */
+ public List<Message> getMessages() {
+ return mMessages;
+ }
+
+ /**
+ * Retrieves a {@link MessagingStyle} from a {@link Notification}, enabling an application
+ * that has set a {@link MessagingStyle} using {@link NotificationCompat} or
+ * {@link android.app.Notification.Builder} to send messaging information to another
+ * application using {@link NotificationCompat}, regardless of the API level of the system.
+ * Returns {@code null} if there is no {@link MessagingStyle} set.
+ */
+ public static MessagingStyle extractMessagingStyleFromNotification(Notification notif) {
+ MessagingStyle style;
+ Bundle extras = IMPL.getExtras(notif);
+ if (!extras.containsKey(EXTRA_SELF_DISPLAY_NAME)) {
+ style = null;
+ } else {
+ try {
+ style = new MessagingStyle();
+ style.restoreFromCompatExtras(extras);
+ } catch (ClassCastException e) {
+ style = null;
+ }
+ }
+ return style;
+ }
+
+ @Override
+ public void addCompatExtras(Bundle extras) {
+ super.addCompatExtras(extras);
+ if (mUserDisplayName != null) {
+ extras.putCharSequence(EXTRA_SELF_DISPLAY_NAME, mUserDisplayName);
+ }
+ if (mConversationTitle != null) {
+ extras.putCharSequence(EXTRA_CONVERSATION_TITLE, mConversationTitle);
+ }
+ if (!mMessages.isEmpty()) { extras.putParcelableArray(EXTRA_MESSAGES,
+ Message.getBundleArrayForMessages(mMessages));
+ }
+ }
+
+ /**
+ * @hide
+ */
+ @Override
+ protected void restoreFromCompatExtras(Bundle extras) {
+ mMessages.clear();
+ mUserDisplayName = extras.getString(EXTRA_SELF_DISPLAY_NAME);
+ mConversationTitle = extras.getString(EXTRA_CONVERSATION_TITLE);
+ Parcelable[] parcelables = extras.getParcelableArray(EXTRA_MESSAGES);
+ if (parcelables != null) {
+ mMessages = Message.getMessagesFromBundleArray(parcelables);
+ }
+ }
+
+ public static final class Message {
+
+ static final String KEY_TEXT = "text";
+ static final String KEY_TIMESTAMP = "time";
+ static final String KEY_SENDER = "sender";
+ static final String KEY_DATA_MIME_TYPE = "type";
+ static final String KEY_DATA_URI= "uri";
+
+ private final CharSequence mText;
+ private final long mTimestamp;
+ private final CharSequence mSender;
+
+ private String mDataMimeType;
+ private Uri mDataUri;
+
+ /**
+ * Constructor
+ * @param text A {@link CharSequence} to be displayed as the message content
+ * @param timestamp Time at which the message arrived
+ * @param sender A {@link CharSequence} to be used for displaying the name of the
+ * sender. Should be <code>null</code> for messages by the current user, in which case
+ * the platform will insert {@link MessagingStyle#getUserDisplayName()}.
+ * Should be unique amongst all individuals in the conversation, and should be
+ * consistent during re-posts of the notification.
+ */
+ public Message(CharSequence text, long timestamp, CharSequence sender){
+ mText = text;
+ mTimestamp = timestamp;
+ mSender = sender;
+ }
+
+ /**
+ * Sets a binary blob of data and an associated MIME type for a message. In the case
+ * where the platform doesn't support the MIME type, the original text provided in the
+ * constructor will be used.
+ * @param dataMimeType The MIME type of the content. See
+ * <a href="{@docRoot}notifications/messaging.html"> for the list of supported MIME
+ * types on Android and Android Wear.
+ * @param dataUri The uri containing the content whose type is given by the MIME type.
+ * <p class="note">
+ * <ol>
+ * <li>Notification Listeners including the System UI need permission to access the
+ * data the Uri points to. The recommended ways to do this are:</li>
+ * <li>Store the data in your own ContentProvider, making sure that other apps have
+ * the correct permission to access your provider. The preferred mechanism for
+ * providing access is to use per-URI permissions which are temporary and only
+ * grant access to the receiving application. An easy way to create a
+ * ContentProvider like this is to use the FileProvider helper class.</li>
+ * <li>Use the system MediaStore. The MediaStore is primarily aimed at video, audio
+ * and image MIME types, however beginning with Android 3.0 (API level 11) it can
+ * also store non-media types (see MediaStore.Files for more info). Files can be
+ * inserted into the MediaStore using scanFile() after which a content:// style
+ * Uri suitable for sharing is passed to the provided onScanCompleted() callback.
+ * Note that once added to the system MediaStore the content is accessible to any
+ * app on the device.</li>
+ * </ol>
+ * @return this object for method chaining
+ */
+ public Message setData(String dataMimeType, Uri dataUri) {
+ mDataMimeType = dataMimeType;
+ mDataUri = dataUri;
+ return this;
+ }
+
+ /**
+ * Get the text to be used for this message, or the fallback text if a type and content
+ * Uri have been set
+ */
+ public CharSequence getText() {
+ return mText;
+ }
+
+ /**
+ * Get the time at which this message arrived
+ */
+ public long getTimestamp() {
+ return mTimestamp;
+ }
+
+ /**
+ * Get the text used to display the contact's name in the messaging experience
+ */
+ public CharSequence getSender() {
+ return mSender;
+ }
+
+ /**
+ * Get the MIME type of the data pointed to by the Uri
+ */
+ public String getDataMimeType() {
+ return mDataMimeType;
+ }
+
+ /**
+ * Get the the Uri pointing to the content of the message. Can be null, in which case
+ * {@see #getText()} is used.
+ */
+ public Uri getDataUri() {
+ return mDataUri;
+ }
+
+ private Bundle toBundle() {
+ Bundle bundle = new Bundle();
+ if (mText != null) {
+ bundle.putCharSequence(KEY_TEXT, mText);
+ }
+ bundle.putLong(KEY_TIMESTAMP, mTimestamp);
+ if (mSender != null) {
+ bundle.putCharSequence(KEY_SENDER, mSender);
+ }
+ if (mDataMimeType != null) {
+ bundle.putString(KEY_DATA_MIME_TYPE, mDataMimeType);
+ }
+ if (mDataUri != null) {
+ bundle.putParcelable(KEY_DATA_URI, mDataUri);
+ }
+ return bundle;
+ }
+
+ static Bundle[] getBundleArrayForMessages(List<Message> messages) {
+ Bundle[] bundles = new Bundle[messages.size()];
+ final int N = messages.size();
+ for (int i = 0; i < N; i++) {
+ bundles[i] = messages.get(i).toBundle();
+ }
+ return bundles;
+ }
+
+ static List<Message> getMessagesFromBundleArray(Parcelable[] bundles) {
+ List<Message> messages = new ArrayList<>(bundles.length);
+ for (int i = 0; i < bundles.length; i++) {
+ if (bundles[i] instanceof Bundle) {
+ Message message = getMessageFromBundle((Bundle)bundles[i]);
+ if (message != null) {
+ messages.add(message);
+ }
+ }
+ }
+ return messages;
+ }
+
+ static Message getMessageFromBundle(Bundle bundle) {
+ try {
+ if (!bundle.containsKey(KEY_TEXT) || !bundle.containsKey(KEY_TIMESTAMP)) {
+ return null;
+ } else {
+ Message message = new Message(bundle.getCharSequence(KEY_TEXT),
+ bundle.getLong(KEY_TIMESTAMP), bundle.getCharSequence(KEY_SENDER));
+ if (bundle.containsKey(KEY_DATA_MIME_TYPE) &&
+ bundle.containsKey(KEY_DATA_URI)) {
+
+ message.setData(bundle.getString(KEY_DATA_MIME_TYPE),
+ (Uri) bundle.getParcelable(KEY_DATA_URI));
+ }
+ return message;
+ }
+ } catch (ClassCastException e) {
+ return null;
+ }
+ }
+ }
+ }
+
+ /**
* Helper class for generating large-format notifications that include a list of (up to 5) strings.
*
* <br>
@@ -1808,6 +2337,7 @@
public static class Action extends NotificationCompatBase.Action {
private final Bundle mExtras;
private final RemoteInput[] mRemoteInputs;
+ private boolean mAllowGeneratedReplies = false;
/**
* Small icon representing the action.
@@ -1824,16 +2354,17 @@
public PendingIntent actionIntent;
public Action(int icon, CharSequence title, PendingIntent intent) {
- this(icon, title, intent, new Bundle(), null);
+ this(icon, title, intent, new Bundle(), null, false);
}
private Action(int icon, CharSequence title, PendingIntent intent, Bundle extras,
- RemoteInput[] remoteInputs) {
+ RemoteInput[] remoteInputs, boolean allowGeneratedReplies) {
this.icon = icon;
this.title = NotificationCompat.Builder.limitCharSequenceLength(title);
this.actionIntent = intent;
this.mExtras = extras != null ? extras : new Bundle();
this.mRemoteInputs = remoteInputs;
+ this.mAllowGeneratedReplies = allowGeneratedReplies;
}
@Override
@@ -1860,6 +2391,15 @@
}
/**
+ * Return whether the platform should automatically generate possible replies for this
+ * {@link Action}
+ */
+ @Override
+ public boolean getAllowGeneratedReplies() {
+ return mAllowGeneratedReplies;
+ }
+
+ /**
* Get the list of inputs to be collected from the user when this action is sent.
* May return null if no remote inputs were added.
*/
@@ -1875,6 +2415,7 @@
private final int mIcon;
private final CharSequence mTitle;
private final PendingIntent mIntent;
+ private boolean mAllowGeneratedReplies;
private final Bundle mExtras;
private ArrayList<RemoteInput> mRemoteInputs;
@@ -1943,6 +2484,20 @@
}
/**
+ * Set whether the platform should automatically generate possible replies to add to
+ * {@link RemoteInput#getChoices()}. If the {@link Action} doesn't have a
+ * {@link RemoteInput}, this has no effect.
+ * @param allowGeneratedReplies {@code true} to allow generated replies, {@code false}
+ * otherwise
+ * @return this object for method chaining
+ * The default value is {@code false}
+ */
+ public Builder setAllowGeneratedReplies(boolean allowGeneratedReplies) {
+ mAllowGeneratedReplies = allowGeneratedReplies;
+ return this;
+ }
+
+ /**
* Apply an extender to this action builder. Extenders may be used to add
* metadata or change options on this builder.
*/
@@ -1959,7 +2514,8 @@
public Action build() {
RemoteInput[] remoteInputs = mRemoteInputs != null
? mRemoteInputs.toArray(new RemoteInput[mRemoteInputs.size()]) : null;
- return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs);
+ return new Action(mIcon, mTitle, mIntent, mExtras, remoteInputs,
+ mAllowGeneratedReplies);
}
}
@@ -2003,6 +2559,7 @@
// Flags bitwise-ored to mFlags
private static final int FLAG_AVAILABLE_OFFLINE = 0x1;
+ private static final int FLAG_HINT_LAUNCHES_ACTIVITY = 1 << 1;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_AVAILABLE_OFFLINE;
@@ -2165,16 +2722,41 @@
public CharSequence getCancelLabel() {
return mCancelLabel;
}
+
+ /**
+ * Set a hint that this Action will launch an {@link Activity} directly, telling the
+ * platform that it can generate the appropriate transitions.
+ * @param hintLaunchesActivity {@code true} if the content intent will launch
+ * an activity and transitions should be generated, false otherwise.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintLaunchesActivity(
+ boolean hintLaunchesActivity) {
+ setFlag(FLAG_HINT_LAUNCHES_ACTIVITY, hintLaunchesActivity);
+ return this;
+ }
+
+ /**
+ * Get a hint that this Action will launch an {@link Activity} directly, telling the
+ * platform that it can generate the appropriate transitions
+ * @return {@code true} if the content intent will launch an activity and transitions
+ * should be generated, false otherwise. The default value is {@code false} if this was
+ * never set.
+ */
+ public boolean getHintLaunchesActivity() {
+ return (mFlags & FLAG_HINT_LAUNCHES_ACTIVITY) != 0;
+ }
}
/** @hide */
public static final Factory FACTORY = new Factory() {
@Override
- public Action build(int icon, CharSequence title,
+ public NotificationCompatBase.Action build(int icon, CharSequence title,
PendingIntent actionIntent, Bundle extras,
- RemoteInputCompatBase.RemoteInput[] remoteInputs) {
+ RemoteInputCompatBase.RemoteInput[] remoteInputs,
+ boolean allowGeneratedReplies) {
return new Action(icon, title, actionIntent, extras,
- (RemoteInput[]) remoteInputs);
+ (RemoteInput[]) remoteInputs, allowGeneratedReplies);
}
@Override
@@ -2321,6 +2903,7 @@
private static final String KEY_CUSTOM_CONTENT_HEIGHT = "customContentHeight";
private static final String KEY_GRAVITY = "gravity";
private static final String KEY_HINT_SCREEN_TIMEOUT = "hintScreenTimeout";
+ private static final String KEY_DISMISSAL_ID = "dismissalId";
// Flags bitwise-ored to mFlags
private static final int FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE = 0x1;
@@ -2328,6 +2911,8 @@
private static final int FLAG_HINT_SHOW_BACKGROUND_ONLY = 1 << 2;
private static final int FLAG_START_SCROLL_BOTTOM = 1 << 3;
private static final int FLAG_HINT_AVOID_BACKGROUND_CLIPPING = 1 << 4;
+ private static final int FLAG_BIG_PICTURE_AMBIENT = 1 << 5;
+ private static final int FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -2347,6 +2932,7 @@
private int mCustomContentHeight;
private int mGravity = DEFAULT_GRAVITY;
private int mHintScreenTimeout;
+ private String mDismissalId;
/**
* Create a {@link NotificationCompat.WearableExtender} with default
@@ -2386,6 +2972,7 @@
mCustomContentHeight = wearableBundle.getInt(KEY_CUSTOM_CONTENT_HEIGHT);
mGravity = wearableBundle.getInt(KEY_GRAVITY, DEFAULT_GRAVITY);
mHintScreenTimeout = wearableBundle.getInt(KEY_HINT_SCREEN_TIMEOUT);
+ mDismissalId = wearableBundle.getString(KEY_DISMISSAL_ID);
}
}
@@ -2438,6 +3025,9 @@
if (mHintScreenTimeout != 0) {
wearableBundle.putInt(KEY_HINT_SCREEN_TIMEOUT, mHintScreenTimeout);
}
+ if (mDismissalId != null) {
+ wearableBundle.putString(KEY_DISMISSAL_ID, mDismissalId);
+ }
builder.getExtras().putBundle(EXTRA_WEARABLE_EXTENSIONS, wearableBundle);
return builder;
@@ -2458,6 +3048,7 @@
that.mCustomContentHeight = this.mCustomContentHeight;
that.mGravity = this.mGravity;
that.mHintScreenTimeout = this.mHintScreenTimeout;
+ that.mDismissalId = this.mDismissalId;
return that;
}
@@ -2899,6 +3490,75 @@
return mHintScreenTimeout;
}
+ /**
+ * Set a hint that this notification's {@link BigPictureStyle} (if present) should be
+ * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
+ * qr codes, as well as other simple black-and-white tickets.
+ * @param hintAmbientBigPicture {@code true} to enable converstion and ambient.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintAmbientBigPicture(boolean hintAmbientBigPicture) {
+ setFlag(FLAG_BIG_PICTURE_AMBIENT, hintAmbientBigPicture);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's {@link BigPictureStyle} (if present) should be
+ * converted to low-bit and displayed in ambient mode, especially useful for barcodes and
+ * qr codes, as well as other simple black-and-white tickets.
+ * @return {@code true} if it should be displayed in ambient, false otherwise
+ * otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintAmbientBigPicture() {
+ return (mFlags & FLAG_BIG_PICTURE_AMBIENT) != 0;
+ }
+
+ /**
+ * Set a hint that this notification's content intent will launch an {@link Activity}
+ * directly, telling the platform that it can generate the appropriate transitions.
+ * @param hintContentIntentLaunchesActivity {@code true} if the content intent will launch
+ * an activity and transitions should be generated, false otherwise.
+ * @return this object for method chaining
+ */
+ public WearableExtender setHintContentIntentLaunchesActivity(
+ boolean hintContentIntentLaunchesActivity) {
+ setFlag(FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY, hintContentIntentLaunchesActivity);
+ return this;
+ }
+
+ /**
+ * Get a hint that this notification's content intent will launch an {@link Activity}
+ * directly, telling the platform that it can generate the appropriate transitions
+ * @return {@code true} if the content intent will launch an activity and transitions should
+ * be generated, false otherwise. The default value is {@code false} if this was never set.
+ */
+ public boolean getHintContentIntentLaunchesActivity() {
+ return (mFlags & FLAG_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY) != 0;
+ }
+
+ /**
+ * When you post a notification, if you set the dismissal id field, then when that
+ * notification is canceled, notifications on other wearables and the paired Android phone
+ * having that same dismissal id will also be canceled. Note that this only works if you
+ * have notification bridge mode set to NO_BRIDGING in your Wear app manifest. See
+ * <a href="{@docRoot}wear/notifications/index.html">Adding Wearable Features to
+ * Notifications</a> for more information on how to use the bridge mode feature.
+ * @param dismissalId the dismissal id of the notification.
+ * @return this object for method chaining
+ */
+ public WearableExtender setDismissalId(String dismissalId) {
+ mDismissalId = dismissalId;
+ return this;
+ }
+
+ /**
+ * Returns the dismissal id of the notification.
+ * @return the dismissal id of the notification or null if it has not been set.
+ */
+ public String getDismissalId() {
+ return mDismissalId;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
@@ -3022,7 +3682,7 @@
/**
* Gets the accent color.
*
- * @see setColor
+ * @see #setColor
*/
@ColorInt
public int getColor() {
@@ -3309,10 +3969,10 @@
}
/**
- * Get the category of this notification in a backwards compatible
- * manner.
- * @param notif The notification to inspect.
- */
+ * Get the category of this notification in a backwards compatible
+ * manner.
+ * @param notif The notification to inspect.
+ */
public static String getCategory(Notification notif) {
return IMPL.getCategory(notif);
}
diff --git a/v4/java/android/support/v4/app/NotificationManagerCompat.java b/v4/java/android/support/v4/app/NotificationManagerCompat.java
index a848007..de484e9 100644
--- a/v4/java/android/support/v4/app/NotificationManagerCompat.java
+++ b/v4/java/android/support/v4/app/NotificationManagerCompat.java
@@ -34,6 +34,7 @@
import android.os.Message;
import android.os.RemoteException;
import android.provider.Settings;
+import android.support.v4.os.BuildCompat;
import android.util.Log;
import java.util.HashMap;
@@ -51,7 +52,7 @@
* {@link NotificationManagerCompat} object, and then call one of its
* methods to post or cancel notifications.
*/
-public class NotificationManagerCompat {
+public final class NotificationManagerCompat {
private static final String TAG = "NotifManCompat";
/**
@@ -98,6 +99,46 @@
/** Guarded by {@link #sLock} */
private static SideChannelManager sSideChannelManager;
+ /**
+ * Value signifying that the user has not expressed an importance.
+ *
+ * This value is for persisting preferences, and should never be associated with
+ * an actual notification.
+ */
+ public static final int IMPORTANCE_UNSPECIFIED = -1000;
+
+ /**
+ * A notification with no importance: shows nowhere, is blocked.
+ */
+ public static final int IMPORTANCE_NONE = 0;
+
+ /**
+ * Min notification importance: only shows in the shade, below the fold.
+ */
+ public static final int IMPORTANCE_MIN = 1;
+
+ /**
+ * Low notification importance: shows everywhere, but is not intrusive.
+ */
+ public static final int IMPORTANCE_LOW = 2;
+
+ /**
+ * Default notification importance: shows everywhere, allowed to makes noise,
+ * but does not visually intrude.
+ */
+ public static final int IMPORTANCE_DEFAULT = 3;
+
+ /**
+ * Higher notification importance: shows everywhere, allowed to makes noise and peek.
+ */
+ public static final int IMPORTANCE_HIGH = 4;
+
+ /**
+ * Highest notification importance: shows everywhere, allowed to makes noise, peek, and
+ * use full screen intents.
+ */
+ public static final int IMPORTANCE_MAX = 5;
+
/** Get a {@link NotificationManagerCompat} instance for a provided context. */
public static NotificationManagerCompat from(Context context) {
return new NotificationManagerCompat(context);
@@ -118,9 +159,14 @@
Notification notification);
int getSideChannelBindFlags();
+
+ boolean areNotificationsEnabled(Context context, NotificationManager notificationManager);
+
+ int getImportance(NotificationManager notificationManager);
}
static class ImplBase implements Impl {
+
@Override
public void cancelNotification(NotificationManager notificationManager, String tag,
int id) {
@@ -137,6 +183,17 @@
public int getSideChannelBindFlags() {
return Service.BIND_AUTO_CREATE;
}
+
+ @Override
+ public boolean areNotificationsEnabled(Context context,
+ NotificationManager notificationManager) {
+ return true;
+ }
+
+ @Override
+ public int getImportance(NotificationManager notificationManager) {
+ return IMPORTANCE_UNSPECIFIED;
+ }
}
static class ImplEclair extends ImplBase {
@@ -161,8 +218,33 @@
}
}
+ static class ImplKitKat extends ImplIceCreamSandwich {
+ @Override
+ public boolean areNotificationsEnabled(Context context,
+ NotificationManager notificationManager) {
+ return NotificationManagerCompatKitKat.areNotificationsEnabled(context);
+ }
+ }
+
+ static class ImplApi24 extends ImplKitKat {
+ @Override
+ public boolean areNotificationsEnabled(Context context,
+ NotificationManager notificationManager) {
+ return NotificationManagerCompatApi24.areNotificationsEnabled(notificationManager);
+ }
+
+ @Override
+ public int getImportance(NotificationManager notificationManager) {
+ return NotificationManagerCompatApi24.getImportance(notificationManager);
+ }
+ }
+
static {
- if (Build.VERSION.SDK_INT >= 14) {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new ImplApi24();
+ } else if (Build.VERSION.SDK_INT >= 19) {
+ IMPL = new ImplKitKat();
+ } else if (Build.VERSION.SDK_INT >= 14) {
IMPL = new ImplIceCreamSandwich();
} else if (Build.VERSION.SDK_INT >= 5) {
IMPL = new ImplEclair();
@@ -227,29 +309,45 @@
}
/**
+ * Returns whether notifications from the calling package are not blocked.
+ */
+ public boolean areNotificationsEnabled() {
+ return IMPL.areNotificationsEnabled(mContext, mNotificationManager);
+ }
+
+ /**
+ * Returns the user specified importance for notifications from the calling package.
+ *
+ * @return An importance level, such as {@link #IMPORTANCE_DEFAULT}.
+ */
+ public int getImportance() {
+ return IMPL.getImportance(mNotificationManager);
+ }
+
+ /**
* Get the set of packages that have an enabled notification listener component within them.
*/
public static Set<String> getEnabledListenerPackages(Context context) {
final String enabledNotificationListeners = Settings.Secure.getString(
context.getContentResolver(),
SETTING_ENABLED_NOTIFICATION_LISTENERS);
- // Parse the string again if it is different from the last time this method was called.
- if (enabledNotificationListeners != null
- && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) {
- final String[] components = enabledNotificationListeners.split(":");
- Set<String> packageNames = new HashSet<String>(components.length);
- for (String component : components) {
- ComponentName componentName = ComponentName.unflattenFromString(component);
- if (componentName != null) {
- packageNames.add(componentName.getPackageName());
+ synchronized (sEnabledNotificationListenersLock) {
+ // Parse the string again if it is different from the last time this method was called.
+ if (enabledNotificationListeners != null
+ && !enabledNotificationListeners.equals(sEnabledNotificationListeners)) {
+ final String[] components = enabledNotificationListeners.split(":");
+ Set<String> packageNames = new HashSet<String>(components.length);
+ for (String component : components) {
+ ComponentName componentName = ComponentName.unflattenFromString(component);
+ if (componentName != null) {
+ packageNames.add(componentName.getPackageName());
+ }
}
- }
- synchronized (sEnabledNotificationListenersLock) {
sEnabledNotificationListenerPackages = packageNames;
sEnabledNotificationListeners = enabledNotificationListeners;
}
+ return sEnabledNotificationListenerPackages;
}
- return sEnabledNotificationListenerPackages;
}
/**
@@ -268,8 +366,8 @@
if (sSideChannelManager == null) {
sSideChannelManager = new SideChannelManager(mContext.getApplicationContext());
}
+ sSideChannelManager.queueTask(task);
}
- sSideChannelManager.queueTask(task);
}
/**
diff --git a/v4/java/android/support/v4/app/RemoteInput.java b/v4/java/android/support/v4/app/RemoteInput.java
index 38ecad0..eb389d6 100644
--- a/v4/java/android/support/v4/app/RemoteInput.java
+++ b/v4/java/android/support/v4/app/RemoteInput.java
@@ -25,7 +25,7 @@
* Helper for using the {@link android.app.RemoteInput} API
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class RemoteInput extends RemoteInputCompatBase.RemoteInput {
+public final class RemoteInput extends RemoteInputCompatBase.RemoteInput {
private static final String TAG = "RemoteInput";
/** Label used to denote the clip data type used for remote input transport */
@@ -40,7 +40,7 @@
private final boolean mAllowFreeFormInput;
private final Bundle mExtras;
- RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
+ private RemoteInput(String resultKey, CharSequence label, CharSequence[] choices,
boolean allowFreeFormInput, Bundle extras) {
this.mResultKey = resultKey;
this.mLabel = label;
diff --git a/v4/java/android/support/v4/app/ServiceCompat.java b/v4/java/android/support/v4/app/ServiceCompat.java
index f644501..491fa3b 100644
--- a/v4/java/android/support/v4/app/ServiceCompat.java
+++ b/v4/java/android/support/v4/app/ServiceCompat.java
@@ -20,7 +20,7 @@
* Helper for accessing features in {@link android.app.Service}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ServiceCompat {
+public final class ServiceCompat {
private ServiceCompat() {
/* Hide constructor */
diff --git a/v4/java/android/support/v4/app/ShareCompat.java b/v4/java/android/support/v4/app/ShareCompat.java
index 87ebc49..caa2a13 100644
--- a/v4/java/android/support/v4/app/ShareCompat.java
+++ b/v4/java/android/support/v4/app/ShareCompat.java
@@ -56,7 +56,7 @@
* Social apps that enable sharing content are encouraged to use this information
* to call out the app that the content was shared from.
*/
-public class ShareCompat {
+public final class ShareCompat {
/**
* Intent extra that stores the name of the calling package for an ACTION_SEND intent.
* When an activity is started using startActivityForResult this is redundant info.
@@ -158,6 +158,8 @@
}
}
+ private ShareCompat() {}
+
/**
* Retrieve the name of the package that launched calledActivity from a share intent.
* Apps that provide social sharing functionality can use this to provide attribution
@@ -485,7 +487,7 @@
*/
public IntentBuilder addStream(Uri streamUri) {
Uri currentStream = mIntent.getParcelableExtra(Intent.EXTRA_STREAM);
- if (currentStream == null) {
+ if (mStreams == null && currentStream == null) {
return setStream(streamUri);
}
if (mStreams == null) {
diff --git a/v4/java/android/support/v4/app/TaskStackBuilder.java b/v4/java/android/support/v4/app/TaskStackBuilder.java
index 9146872..51b5f1d 100644
--- a/v4/java/android/support/v4/app/TaskStackBuilder.java
+++ b/v4/java/android/support/v4/app/TaskStackBuilder.java
@@ -66,7 +66,7 @@
* from the design guide.
* </div>
*/
-public class TaskStackBuilder implements Iterable<Intent> {
+public final class TaskStackBuilder implements Iterable<Intent> {
private static final String TAG = "TaskStackBuilder";
public interface SupportParentable {
@@ -146,6 +146,7 @@
*
* @deprecated use {@link #create(Context)} instead
*/
+ @Deprecated
public static TaskStackBuilder from(Context context) {
return create(context);
}
@@ -266,6 +267,7 @@
*
* @deprecated Renamed to editIntentAt to better reflect intended usage
*/
+ @Deprecated
public Intent getIntent(int index) {
return editIntentAt(index);
}
@@ -285,6 +287,7 @@
/**
* @deprecated Use editIntentAt instead
*/
+ @Deprecated
public Iterator<Intent> iterator() {
return mIntents.iterator();
}
diff --git a/v4/java/android/support/v4/content/ContentResolverCompat.java b/v4/java/android/support/v4/content/ContentResolverCompat.java
index 3c7f4de..7efe79e 100644
--- a/v4/java/android/support/v4/content/ContentResolverCompat.java
+++ b/v4/java/android/support/v4/content/ContentResolverCompat.java
@@ -27,7 +27,7 @@
* Helper for accessing features in {@link android.content.ContentResolver}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ContentResolverCompat {
+public final class ContentResolverCompat {
interface ContentResolverCompatImpl {
Cursor query(ContentResolver resolver,
Uri uri, String[] projection, String selection, String[] selectionArgs,
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index 29629e7..b61426e 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -20,14 +20,17 @@
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.res.ColorStateList;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.Process;
import android.support.annotation.NonNull;
+import android.support.v4.os.BuildCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
+import android.util.TypedValue;
import java.io.File;
@@ -44,6 +47,10 @@
private static final String DIR_FILES = "files";
private static final String DIR_CACHE = "cache";
+ private static final Object sLock = new Object();
+
+ private static TypedValue sTempValue;
+
/**
* Start a set of activities as a synthesized task stack, if able.
*
@@ -114,6 +121,30 @@
}
/**
+ * Returns the absolute path to the directory on the filesystem where all
+ * private files belonging to this app are stored. Apps should not use this
+ * path directly; they should instead use {@link Context#getFilesDir()},
+ * {@link Context#getCacheDir()}, {@link Context#getDir(String, int)}, or
+ * other storage APIs on {@link Context}.
+ * <p>
+ * The returned path may change over time if the calling app is moved to an
+ * adopted storage device, so only relative paths should be persisted.
+ * <p>
+ * No additional permissions are required for the calling app to read or
+ * write files under the returned path.
+ *
+ * @see ApplicationInfo#dataDir
+ */
+ public static File getDataDir(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return ContextCompatApi24.getDataDir(context);
+ } else {
+ final String dataDir = context.getApplicationInfo().dataDir;
+ return dataDir != null ? new File(dataDir) : null;
+ }
+ }
+
+ /**
* Returns absolute paths to application-specific directories on all
* external storage devices where the application's OBB files (if there are
* any) can be found. Note if the application does not have any OBB files,
@@ -305,22 +336,36 @@
}
/**
- * Return a drawable object associated with a particular resource ID.
+ * Returns a drawable object associated with a particular resource ID.
* <p>
- * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the returned
- * drawable will be styled for the specified Context's theme.
+ * Starting in {@link android.os.Build.VERSION_CODES#LOLLIPOP}, the
+ * returned drawable will be styled for the specified Context's theme.
*
* @param id The desired resource identifier, as generated by the aapt tool.
- * This integer encodes the package, type, and resource entry.
- * The value 0 is an invalid identifier.
+ * This integer encodes the package, type, and resource entry.
+ * The value 0 is an invalid identifier.
* @return Drawable An object that can be used to draw this resource.
*/
public static final Drawable getDrawable(Context context, int id) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return ContextCompatApi21.getDrawable(context, id);
- } else {
+ } else if (version >= 16) {
return context.getResources().getDrawable(id);
+ } else {
+ // Prior to JELLY_BEAN, Resources.getDrawable() would not correctly
+ // retrieve the final configuration density when the resource ID
+ // is a reference another Drawable resource. As a workaround, try
+ // to resolve the drawable reference manually.
+ final int resolvedId;
+ synchronized (sLock) {
+ if (sTempValue == null) {
+ sTempValue = new TypedValue();
+ }
+ context.getResources().getValue(id, sTempValue, true);
+ resolvedId = sTempValue.resourceId;
+ }
+ return context.getResources().getDrawable(resolvedId);
}
}
@@ -403,7 +448,7 @@
*
* @see android.content.Context.getFilesDir
*/
- public static File getNoBackupFilesDir(Context context) {
+ public static final File getNoBackupFilesDir(Context context) {
final int version = Build.VERSION.SDK_INT;
if (version >= 21) {
return ContextCompatApi21.getNoBackupFilesDir(context);
@@ -451,4 +496,75 @@
}
return file;
}
+
+ /**
+ * Return a new Context object for the current Context but whose storage
+ * APIs are backed by device-protected storage.
+ * <p>
+ * On devices with direct boot, data stored in this location is encrypted
+ * with a key tied to the physical device, and it can be accessed
+ * immediately after the device has booted successfully, both
+ * <em>before and after</em> the user has authenticated with their
+ * credentials (such as a lock pattern or PIN).
+ * <p>
+ * Because device-protected data is available without user authentication,
+ * you should carefully limit the data you store using this Context. For
+ * example, storing sensitive authentication tokens or passwords in the
+ * device-protected area is strongly discouraged.
+ * <p>
+ * If the underlying device does not have the ability to store
+ * device-protected and credential-protected data using different keys, then
+ * both storage areas will become available at the same time. They remain as
+ * two distinct storage locations on disk, and only the window of
+ * availability changes.
+ * <p>
+ * Each call to this method returns a new instance of a Context object;
+ * Context objects are not shared, however common state (ClassLoader, other
+ * Resources for the same configuration) may be so the Context itself can be
+ * fairly lightweight.
+ * <p>
+ * Prior to {@link BuildCompat#isAtLeastN()} this method returns
+ * {@code null}, since device-protected storage is not available.
+ *
+ * @see ContextCompat#isDeviceProtectedStorage(Context)
+ */
+ public static Context createDeviceProtectedStorageContext(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return ContextCompatApi24.createDeviceProtectedStorageContext(context);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * @removed
+ * @deprecated Removed. Do not use.
+ */
+ @Deprecated
+ public static Context createDeviceEncryptedStorageContext(Context context) {
+ return createDeviceProtectedStorageContext(context);
+ }
+
+ /**
+ * Indicates if the storage APIs of this Context are backed by
+ * device-encrypted storage.
+ *
+ * @see ContextCompat#createDeviceProtectedStorageContext(Context)
+ */
+ public static boolean isDeviceProtectedStorage(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return ContextCompatApi24.isDeviceProtectedStorage(context);
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * @removed
+ * @deprecated Removed. Do not use.
+ */
+ @Deprecated
+ public static boolean isDeviceEncryptedStorage(Context context) {
+ return isDeviceProtectedStorage(context);
+ }
}
diff --git a/v4/java/android/support/v4/content/FileProvider.java b/v4/java/android/support/v4/content/FileProvider.java
index 9e82d63..12f7a5b 100644
--- a/v4/java/android/support/v4/content/FileProvider.java
+++ b/v4/java/android/support/v4/content/FileProvider.java
@@ -133,6 +133,16 @@
* Represents files in the <code>files/</code> subdirectory of your app's internal storage
* area. This subdirectory is the same as the value returned by {@link Context#getFilesDir()
* Context.getFilesDir()}.
+ * <dt>
+ * <pre class="prettyprint">
+ *<external-path name="<i>name</i>" path="<i>path</i>" />
+ *</pre>
+ * </dt>
+ * <dd>
+ * Represents the root of the external storage. The root path of this subdirectory
+ * is the same that {@link
+ * Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()}
+ * returns.
* </dd>
* <dt>
* <pre>
@@ -144,36 +154,6 @@
* of this subdirectory is the same as the value returned by {@link Context#getCacheDir()
* getCacheDir()}.
* </dd>
- * <dt>
- * <pre class="prettyprint">
- *<external-path name="<i>name</i>" path="<i>path</i>" />
- *</pre>
- * </dt>
- * <dd>
- * Represents files in the root of the external storage area. The root path of this subdirectory
- * is the same as the value returned by
- * {@link Environment#getExternalStorageDirectory() Environment.getExternalStorageDirectory()}.
- * </dd>
- * <dt>
- * <pre class="prettyprint">
- *<external-files-path name="<i>name</i>" path="<i>path</i>" />
- *</pre>
- * </dt>
- * <dd>
- * Represents files in the root of your app's external storage area. The root path of this
- * subdirectory is the same as the value returned by
- * {@code Context#getExternalFilesDir(String) Context.getExternalFilesDir(null)}.
- * </dd>
- * <dt>
- * <pre class="prettyprint">
- *<external-cache-path name="<i>name</i>" path="<i>path</i>" />
- *</pre>
- * </dt>
- * <dd>
- * Represents files in the root of your app's external cache area. The root path of this
- * subdirectory is the same as the value returned by
- * {@link Context#getExternalCacheDir() Context.getExternalCacheDir()}.
- * </dd>
* </dl>
* <p>
* These child elements all use the same attributes:
@@ -331,8 +311,6 @@
private static final String TAG_FILES_PATH = "files-path";
private static final String TAG_CACHE_PATH = "cache-path";
private static final String TAG_EXTERNAL = "external-path";
- private static final String TAG_EXTERNAL_FILES = "external-files-path";
- private static final String TAG_EXTERNAL_CACHE = "external-cache-path";
private static final String ATTR_NAME = "name";
private static final String ATTR_PATH = "path";
@@ -597,27 +575,17 @@
File target = null;
if (TAG_ROOT_PATH.equals(tag)) {
- target = DEVICE_ROOT;
+ target = buildPath(DEVICE_ROOT, path);
} else if (TAG_FILES_PATH.equals(tag)) {
- target = context.getFilesDir();
+ target = buildPath(context.getFilesDir(), path);
} else if (TAG_CACHE_PATH.equals(tag)) {
- target = context.getCacheDir();
+ target = buildPath(context.getCacheDir(), path);
} else if (TAG_EXTERNAL.equals(tag)) {
- target = Environment.getExternalStorageDirectory();
- } else if (TAG_EXTERNAL_FILES.equals(tag)) {
- File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(context, null);
- if (externalFilesDirs.length > 0) {
- target = externalFilesDirs[0];
- }
- } else if (TAG_EXTERNAL_CACHE.equals(tag)) {
- File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(context);
- if (externalCacheDirs.length > 0) {
- target = externalCacheDirs[0];
- }
+ target = buildPath(Environment.getExternalStorageDirectory(), path);
}
if (target != null) {
- strat.addRoot(name, buildPath(target, path));
+ strat.addRoot(name, target);
}
}
}
diff --git a/v4/java/android/support/v4/content/IntentCompat.java b/v4/java/android/support/v4/content/IntentCompat.java
index 11d3334..eaf7b1f 100644
--- a/v4/java/android/support/v4/content/IntentCompat.java
+++ b/v4/java/android/support/v4/content/IntentCompat.java
@@ -25,7 +25,7 @@
* Helper for accessing features in {@link android.content.Intent}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class IntentCompat {
+public final class IntentCompat {
interface IntentCompatImpl {
Intent makeMainActivity(ComponentName componentName);
@@ -174,6 +174,12 @@
public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
/**
+ * Indicates an activity optimized for Leanback mode, and that should
+ * be displayed in the Leanback launcher.
+ */
+ public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
+
+ /**
* If set in an Intent passed to {@link Context#startActivity Context.startActivity()},
* this flag will cause a newly launching task to be placed on top of the current
* home activity task (if there is one). That is, pressing back from the task
diff --git a/v4/java/android/support/v4/content/Loader.java b/v4/java/android/support/v4/content/Loader.java
index cf6cc99..40b459f 100644
--- a/v4/java/android/support/v4/content/Loader.java
+++ b/v4/java/android/support/v4/content/Loader.java
@@ -466,7 +466,7 @@
*/
public void rollbackContentChanged() {
if (mProcessingChange) {
- mContentChanged = true;
+ onContentChanged();
}
}
diff --git a/v4/java/android/support/v4/content/LocalBroadcastManager.java b/v4/java/android/support/v4/content/LocalBroadcastManager.java
index 7a7c50f..299bc8e 100644
--- a/v4/java/android/support/v4/content/LocalBroadcastManager.java
+++ b/v4/java/android/support/v4/content/LocalBroadcastManager.java
@@ -43,7 +43,7 @@
* system.
* </ul>
*/
-public class LocalBroadcastManager {
+public final class LocalBroadcastManager {
private static class ReceiverRecord {
final IntentFilter filter;
final BroadcastReceiver receiver;
diff --git a/v4/java/android/support/v4/content/ParallelExecutorCompat.java b/v4/java/android/support/v4/content/ParallelExecutorCompat.java
index c23470b..20ef3d5 100644
--- a/v4/java/android/support/v4/content/ParallelExecutorCompat.java
+++ b/v4/java/android/support/v4/content/ParallelExecutorCompat.java
@@ -24,7 +24,7 @@
* Helper for accessing a shared parallel Executor instance
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ParallelExecutorCompat {
+public final class ParallelExecutorCompat {
public static Executor getParallelExecutor() {
if (Build.VERSION.SDK_INT >= 11) {
// From API 11 onwards, return AsyncTask.THREAD_POOL_EXECUTOR
@@ -33,4 +33,6 @@
return ModernAsyncTask.THREAD_POOL_EXECUTOR;
}
}
+
+ private ParallelExecutorCompat() {}
}
diff --git a/v4/java/android/support/v4/content/SharedPreferencesCompat.java b/v4/java/android/support/v4/content/SharedPreferencesCompat.java
index dca99a8..da5f1fb 100644
--- a/v4/java/android/support/v4/content/SharedPreferencesCompat.java
+++ b/v4/java/android/support/v4/content/SharedPreferencesCompat.java
@@ -20,9 +20,9 @@
import android.os.Build;
import android.support.annotation.NonNull;
-public class SharedPreferencesCompat {
+public final class SharedPreferencesCompat {
- public static class EditorCompat {
+ public final static class EditorCompat {
private static EditorCompat sInstance;
@@ -68,4 +68,6 @@
}
}
+ private SharedPreferencesCompat() {}
+
}
diff --git a/v4/java/android/support/v4/content/pm/ActivityInfoCompat.java b/v4/java/android/support/v4/content/pm/ActivityInfoCompat.java
index 5c27923..401440467 100644
--- a/v4/java/android/support/v4/content/pm/ActivityInfoCompat.java
+++ b/v4/java/android/support/v4/content/pm/ActivityInfoCompat.java
@@ -20,7 +20,7 @@
* Helper for accessing features in {@link android.content.pm.ActivityInfo}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ActivityInfoCompat {
+public final class ActivityInfoCompat {
private ActivityInfoCompat() {
/* Hide constructor */
diff --git a/v4/java/android/support/v4/content/res/ConfigurationHelper.java b/v4/java/android/support/v4/content/res/ConfigurationHelper.java
new file mode 100644
index 0000000..2ee20d0
--- /dev/null
+++ b/v4/java/android/support/v4/content/res/ConfigurationHelper.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.NonNull;
+
+/**
+ * Helper class which allows access to properties of {@link android.content.res.Configuration} in
+ * a backward compatible fashion.
+ */
+public final class ConfigurationHelper {
+
+ private static final ConfigurationHelperImpl IMPL;
+
+ static {
+ final int sdk = Build.VERSION.SDK_INT;
+ if (sdk >= 17) {
+ IMPL = new JellybeanMr1Impl();
+ } else if (sdk >= 13) {
+ IMPL = new HoneycombMr2Impl();
+ } else {
+ IMPL = new DonutImpl();
+ }
+ }
+
+ private ConfigurationHelper() {}
+
+ private interface ConfigurationHelperImpl {
+ int getScreenHeightDp(@NonNull Resources resources);
+ int getScreenWidthDp(@NonNull Resources resources);
+ int getSmallestScreenWidthDp(@NonNull Resources resources);
+ int getDensityDpi(@NonNull Resources resources);
+ }
+
+ private static class DonutImpl implements ConfigurationHelperImpl {
+ @Override
+ public int getScreenHeightDp(@NonNull Resources resources) {
+ return ConfigurationHelperDonut.getScreenHeightDp(resources);
+ }
+
+ @Override
+ public int getScreenWidthDp(@NonNull Resources resources) {
+ return ConfigurationHelperDonut.getScreenWidthDp(resources);
+ }
+
+ @Override
+ public int getSmallestScreenWidthDp(@NonNull Resources resources) {
+ return ConfigurationHelperDonut.getSmallestScreenWidthDp(resources);
+ }
+
+ @Override
+ public int getDensityDpi(@NonNull Resources resources) {
+ return ConfigurationHelperDonut.getDensityDpi(resources);
+ }
+ }
+
+ private static class HoneycombMr2Impl extends DonutImpl {
+ @Override
+ public int getScreenHeightDp(@NonNull Resources resources) {
+ return ConfigurationHelperHoneycombMr2.getScreenHeightDp(resources);
+ }
+
+ @Override
+ public int getScreenWidthDp(@NonNull Resources resources) {
+ return ConfigurationHelperHoneycombMr2.getScreenWidthDp(resources);
+ }
+
+ @Override
+ public int getSmallestScreenWidthDp(@NonNull Resources resources) {
+ return ConfigurationHelperHoneycombMr2.getSmallestScreenWidthDp(resources);
+ }
+ }
+
+ private static class JellybeanMr1Impl extends HoneycombMr2Impl {
+ @Override
+ public int getDensityDpi(@NonNull Resources resources) {
+ return ConfigurationHelperJellybeanMr1.getDensityDpi(resources);
+ }
+ }
+
+ /**
+ * Returns the current height of the available screen space, in dp units.
+ *
+ * <p>Uses {@code Configuration.screenHeightDp} when available, otherwise an approximation
+ * is computed and returned.</p>
+ */
+ public static int getScreenHeightDp(@NonNull Resources resources) {
+ return IMPL.getScreenHeightDp(resources);
+ }
+
+ /**
+ * Returns the current width of the available screen space, in dp units.
+ *
+ * <p>Uses {@code Configuration.screenWidthDp} when available, otherwise an approximation
+ * is computed and returned.</p>
+ */
+ public static int getScreenWidthDp(@NonNull Resources resources) {
+ return IMPL.getScreenWidthDp(resources);
+ }
+
+ /**
+ * Returns The smallest screen size an application will see in normal operation, in dp units.
+ *
+ * <p>Uses {@code Configuration.smallestScreenWidthDp} when available, otherwise an
+ * approximation is computed and returned.</p>
+ */
+ public static int getSmallestScreenWidthDp(@NonNull Resources resources) {
+ return IMPL.getSmallestScreenWidthDp(resources);
+ }
+
+ /**
+ * Returns the target screen density being rendered to.
+ *
+ * <p>Uses {@code Configuration.densityDpi} when available, otherwise an approximation
+ * is computed and returned.</p>
+ */
+ public static int getDensityDpi(@NonNull Resources resources) {
+ return IMPL.getDensityDpi(resources);
+ }
+}
diff --git a/v4/java/android/support/v4/content/res/ResourcesCompat.java b/v4/java/android/support/v4/content/res/ResourcesCompat.java
index ce5f658..0f4b800 100644
--- a/v4/java/android/support/v4/content/res/ResourcesCompat.java
+++ b/v4/java/android/support/v4/content/res/ResourcesCompat.java
@@ -33,7 +33,7 @@
* Helper for accessing features in {@link android.content.res.Resources}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ResourcesCompat {
+public final class ResourcesCompat {
/**
* Return a drawable object associated with a particular resource ID and
* styled for the specified theme. Various types of objects will be
@@ -155,4 +155,6 @@
return res.getColorStateList(id);
}
}
+
+ private ResourcesCompat() {}
}
diff --git a/v4/java/android/support/v4/content/res/TypedArrayUtils.java b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
index 79e4ac8..0cc5885 100644
--- a/v4/java/android/support/v4/content/res/TypedArrayUtils.java
+++ b/v4/java/android/support/v4/content/res/TypedArrayUtils.java
@@ -15,10 +15,12 @@
*/
package android.support.v4.content.res;
+import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.annotation.AnyRes;
import android.support.annotation.StyleableRes;
+import android.util.TypedValue;
/**
* Compat methods for accessing TypedArray values.
@@ -70,4 +72,13 @@
}
return val;
}
+
+ public static int getAttr(Context context, int attr, int fallbackAttr) {
+ TypedValue value = new TypedValue();
+ context.getTheme().resolveAttribute(attr, value, true);
+ if (value.resourceId != 0) {
+ return attr;
+ }
+ return fallbackAttr;
+ }
}
diff --git a/v4/java/android/support/v4/database/DatabaseUtilsCompat.java b/v4/java/android/support/v4/database/DatabaseUtilsCompat.java
index 9a825e8..dd53ced1 100644
--- a/v4/java/android/support/v4/database/DatabaseUtilsCompat.java
+++ b/v4/java/android/support/v4/database/DatabaseUtilsCompat.java
@@ -22,7 +22,7 @@
* Helper for accessing features in {@link android.database.DatabaseUtils}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class DatabaseUtilsCompat {
+public final class DatabaseUtilsCompat {
private DatabaseUtilsCompat() {
/* Hide constructor */
diff --git a/v4/java/android/support/v4/graphics/BitmapCompat.java b/v4/java/android/support/v4/graphics/BitmapCompat.java
index 5b1cb68..b01c4f5 100644
--- a/v4/java/android/support/v4/graphics/BitmapCompat.java
+++ b/v4/java/android/support/v4/graphics/BitmapCompat.java
@@ -21,7 +21,7 @@
* Helper for accessing features in {@link android.graphics.Bitmap}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class BitmapCompat {
+public final class BitmapCompat {
/**
* Interface for the full API.
*/
@@ -108,4 +108,6 @@
public static int getAllocationByteCount(Bitmap bitmap) {
return IMPL.getAllocationByteCount(bitmap);
}
+
+ private BitmapCompat() {}
}
\ No newline at end of file
diff --git a/v4/java/android/support/v4/graphics/ColorUtils.java b/v4/java/android/support/v4/graphics/ColorUtils.java
index 4d9d9b2..85768f6 100644
--- a/v4/java/android/support/v4/graphics/ColorUtils.java
+++ b/v4/java/android/support/v4/graphics/ColorUtils.java
@@ -21,15 +21,24 @@
import android.support.annotation.FloatRange;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
/**
* A set of color-related utility methods, building upon those available in {@code Color}.
*/
-public class ColorUtils {
+public final class ColorUtils {
+
+ private static final double XYZ_WHITE_REFERENCE_X = 95.047;
+ private static final double XYZ_WHITE_REFERENCE_Y = 100;
+ private static final double XYZ_WHITE_REFERENCE_Z = 108.883;
+ private static final double XYZ_EPSILON = 0.008856;
+ private static final double XYZ_KAPPA = 903.3;
private static final int MIN_ALPHA_SEARCH_MAX_ITERATIONS = 10;
private static final int MIN_ALPHA_SEARCH_PRECISION = 1;
+ private static final ThreadLocal<double[]> TEMP_ARRAY = new ThreadLocal<>();
+
private ColorUtils() {}
/**
@@ -60,24 +69,15 @@
}
/**
- * Returns the luminance of a color.
- * <p>
- * Formula defined
- * <a href="http://www.w3.org/TR/2008/REC-WCAG20-20081211/#relativeluminancedef">here</a>.
- * </p>
+ * Returns the luminance of a color as a float between {@code 0.0} and {@code 1.0}.
+ * <p>Defined as the Y component in the XYZ representation of {@code color}.</p>
*/
@FloatRange(from = 0.0, to = 1.0)
public static double calculateLuminance(@ColorInt int color) {
- double red = Color.red(color) / 255d;
- red = red < 0.03928 ? red / 12.92 : Math.pow((red + 0.055) / 1.055, 2.4);
-
- double green = Color.green(color) / 255d;
- green = green < 0.03928 ? green / 12.92 : Math.pow((green + 0.055) / 1.055, 2.4);
-
- double blue = Color.blue(color) / 255d;
- blue = blue < 0.03928 ? blue / 12.92 : Math.pow((blue + 0.055) / 1.055, 2.4);
-
- return (0.2126 * red) + (0.7152 * green) + (0.0722 * blue);
+ final double[] result = getTempDouble3Array();
+ colorToXYZ(color, result);
+ // Luminance is the Y component
+ return result[1] / 100;
}
/**
@@ -109,10 +109,10 @@
* have a contrast value of at least {@code minContrastRatio} when compared to
* {@code background}.
*
- * @param foreground the foreground color.
- * @param background the background color. Should be opaque.
- * @param minContrastRatio the minimum contrast ratio.
- * @return the alpha value in the range 0-255, or -1 if no value could be calculated.
+ * @param foreground the foreground color
+ * @param background the opaque background color
+ * @param minContrastRatio the minimum contrast ratio
+ * @return the alpha value in the range 0-255, or -1 if no value could be calculated
*/
public static int calculateMinimumAlpha(@ColorInt int foreground, @ColorInt int background,
float minContrastRatio) {
@@ -157,19 +157,19 @@
/**
* Convert RGB components to HSL (hue-saturation-lightness).
* <ul>
- * <li>hsl[0] is Hue [0 .. 360)</li>
- * <li>hsl[1] is Saturation [0...1]</li>
- * <li>hsl[2] is Lightness [0...1]</li>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
* </ul>
*
- * @param r red component value [0..255]
- * @param g green component value [0..255]
- * @param b blue component value [0..255]
- * @param hsl 3 element array which holds the resulting HSL components.
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outHsl 3-element array which holds the resulting HSL components
*/
public static void RGBToHSL(@IntRange(from = 0x0, to = 0xFF) int r,
@IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
- @NonNull float[] hsl) {
+ @NonNull float[] outHsl) {
final float rf = r / 255f;
final float gf = g / 255f;
final float bf = b / 255f;
@@ -201,24 +201,24 @@
h += 360f;
}
- hsl[0] = constrain(h, 0f, 360f);
- hsl[1] = constrain(s, 0f, 1f);
- hsl[2] = constrain(l, 0f, 1f);
+ outHsl[0] = constrain(h, 0f, 360f);
+ outHsl[1] = constrain(s, 0f, 1f);
+ outHsl[2] = constrain(l, 0f, 1f);
}
/**
* Convert the ARGB color to its HSL (hue-saturation-lightness) components.
* <ul>
- * <li>hsl[0] is Hue [0 .. 360)</li>
- * <li>hsl[1] is Saturation [0...1]</li>
- * <li>hsl[2] is Lightness [0...1]</li>
+ * <li>outHsl[0] is Hue [0 .. 360)</li>
+ * <li>outHsl[1] is Saturation [0...1]</li>
+ * <li>outHsl[2] is Lightness [0...1]</li>
* </ul>
*
- * @param color the ARGB color to convert. The alpha component is ignored.
- * @param hsl 3 element array which holds the resulting HSL components.
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outHsl 3-element array which holds the resulting HSL components
*/
- public static void colorToHSL(@ColorInt int color, @NonNull float[] hsl) {
- RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), hsl);
+ public static void colorToHSL(@ColorInt int color, @NonNull float[] outHsl) {
+ RGBToHSL(Color.red(color), Color.green(color), Color.blue(color), outHsl);
}
/**
@@ -230,7 +230,7 @@
* </ul>
* If hsv values are out of range, they are pinned.
*
- * @param hsl 3 element array which holds the input HSL components.
+ * @param hsl 3-element array which holds the input HSL components
* @return the resulting RGB color
*/
@ColorInt
@@ -300,6 +300,219 @@
return (color & 0x00ffffff) | (alpha << 24);
}
+ /**
+ * Convert the ARGB color to its CIE Lab representative components.
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void colorToLAB(@ColorInt int color, @NonNull double[] outLab) {
+ RGBToLAB(Color.red(color), Color.green(color), Color.blue(color), outLab);
+ }
+
+ /**
+ * Convert RGB components to its CIE Lab representative components.
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outLab 3-element array which holds the resulting LAB components
+ */
+ public static void RGBToLAB(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull double[] outLab) {
+ // First we convert RGB to XYZ
+ RGBToXYZ(r, g, b, outLab);
+ // outLab now contains XYZ
+ XYZToLAB(outLab[0], outLab[1], outLab[2], outLab);
+ // outLab now contains LAB representation
+ }
+
+ /**
+ * Convert the ARGB color to it's CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param color the ARGB color to convert. The alpha component is ignored
+ * @param outXyz 3-element array which holds the resulting LAB components
+ */
+ public static void colorToXYZ(@ColorInt int color, @NonNull double[] outXyz) {
+ RGBToXYZ(Color.red(color), Color.green(color), Color.blue(color), outXyz);
+ }
+
+ /**
+ * Convert RGB components to it's CIE XYZ representative components.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param r red component value [0..255]
+ * @param g green component value [0..255]
+ * @param b blue component value [0..255]
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void RGBToXYZ(@IntRange(from = 0x0, to = 0xFF) int r,
+ @IntRange(from = 0x0, to = 0xFF) int g, @IntRange(from = 0x0, to = 0xFF) int b,
+ @NonNull double[] outXyz) {
+ if (outXyz.length != 3) {
+ throw new IllegalArgumentException("outXyz must have a length of 3.");
+ }
+
+ double sr = r / 255.0;
+ sr = sr < 0.04045 ? sr / 12.92 : Math.pow((sr + 0.055) / 1.055, 2.4);
+ double sg = g / 255.0;
+ sg = sg < 0.04045 ? sg / 12.92 : Math.pow((sg + 0.055) / 1.055, 2.4);
+ double sb = b / 255.0;
+ sb = sb < 0.04045 ? sb / 12.92 : Math.pow((sb + 0.055) / 1.055, 2.4);
+
+ outXyz[0] = 100 * (sr * 0.4124 + sg * 0.3576 + sb * 0.1805);
+ outXyz[1] = 100 * (sr * 0.2126 + sg * 0.7152 + sb * 0.0722);
+ outXyz[2] = 100 * (sr * 0.0193 + sg * 0.1192 + sb * 0.9505);
+ }
+
+ /**
+ * Converts a color from CIE XYZ to CIE Lab representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outLab[0] is L [0 ...1)</li>
+ * <li>outLab[1] is a [-128...127)</li>
+ * <li>outLab[2] is b [-128...127)</li>
+ * </ul>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @param outLab 3-element array which holds the resulting Lab components
+ */
+ public static void XYZToLAB(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z,
+ @NonNull double[] outLab) {
+ if (outLab.length != 3) {
+ throw new IllegalArgumentException("outLab must have a length of 3.");
+ }
+ x = pivotXyzComponent(x / XYZ_WHITE_REFERENCE_X);
+ y = pivotXyzComponent(y / XYZ_WHITE_REFERENCE_Y);
+ z = pivotXyzComponent(z / XYZ_WHITE_REFERENCE_Z);
+ outLab[0] = Math.max(0, 116 * y - 16);
+ outLab[1] = 500 * (x - y);
+ outLab[2] = 200 * (y - z);
+ }
+
+ /**
+ * Converts a color from CIE Lab to CIE XYZ representation.
+ *
+ * <p>The resulting XYZ representation will use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * <ul>
+ * <li>outXyz[0] is X [0 ...95.047)</li>
+ * <li>outXyz[1] is Y [0...100)</li>
+ * <li>outXyz[2] is Z [0...108.883)</li>
+ * </ul>
+ *
+ * @param l L component value [0...100)
+ * @param a A component value [-128...127)
+ * @param b B component value [-128...127)
+ * @param outXyz 3-element array which holds the resulting XYZ components
+ */
+ public static void LABToXYZ(@FloatRange(from = 0f, to = 100) final double l,
+ @FloatRange(from = -128, to = 127) final double a,
+ @FloatRange(from = -128, to = 127) final double b,
+ @NonNull double[] outXyz) {
+ final double fy = (l + 16) / 116;
+ final double fx = a / 500 + fy;
+ final double fz = fy - b / 200;
+
+ double tmp = Math.pow(fx, 3);
+ final double xr = tmp > XYZ_EPSILON ? tmp : (116 * fx - 16) / XYZ_KAPPA;
+ final double yr = l > XYZ_KAPPA * XYZ_EPSILON ? Math.pow(fy, 3) : l / XYZ_KAPPA;
+
+ tmp = Math.pow(fz, 3);
+ final double zr = tmp > XYZ_EPSILON ? tmp : (116 * fz - 16) / XYZ_KAPPA;
+
+ outXyz[0] = xr * XYZ_WHITE_REFERENCE_X;
+ outXyz[1] = yr * XYZ_WHITE_REFERENCE_Y;
+ outXyz[2] = zr * XYZ_WHITE_REFERENCE_Z;
+ }
+
+ /**
+ * Converts a color from CIE XYZ to its RGB representation.
+ *
+ * <p>This method expects the XYZ representation to use the D65 illuminant and the CIE
+ * 2° Standard Observer (1931).</p>
+ *
+ * @param x X component value [0...95.047)
+ * @param y Y component value [0...100)
+ * @param z Z component value [0...108.883)
+ * @return int containing the RGB representation
+ */
+ @ColorInt
+ public static int XYZToColor(@FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_X) double x,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Y) double y,
+ @FloatRange(from = 0f, to = XYZ_WHITE_REFERENCE_Z) double z) {
+ double r = (x * 3.2406 + y * -1.5372 + z * -0.4986) / 100;
+ double g = (x * -0.9689 + y * 1.8758 + z * 0.0415) / 100;
+ double b = (x * 0.0557 + y * -0.2040 + z * 1.0570) / 100;
+
+ r = r > 0.0031308 ? 1.055 * Math.pow(r, 1 / 2.4) - 0.055 : 12.92 * r;
+ g = g > 0.0031308 ? 1.055 * Math.pow(g, 1 / 2.4) - 0.055 : 12.92 * g;
+ b = b > 0.0031308 ? 1.055 * Math.pow(b, 1 / 2.4) - 0.055 : 12.92 * b;
+
+ return Color.rgb(
+ constrain((int) Math.round(r * 255), 0, 255),
+ constrain((int) Math.round(g * 255), 0, 255),
+ constrain((int) Math.round(b * 255), 0, 255));
+ }
+
+ /**
+ * Converts a color from CIE Lab to its RGB representation.
+ *
+ * @param l L component value [0...100]
+ * @param a A component value [-128...127]
+ * @param b B component value [-128...127]
+ * @return int containing the RGB representation
+ */
+ @ColorInt
+ public static int LABToColor(@FloatRange(from = 0f, to = 100) final double l,
+ @FloatRange(from = -128, to = 127) final double a,
+ @FloatRange(from = -128, to = 127) final double b) {
+ final double[] result = getTempDouble3Array();
+ LABToXYZ(l, a, b, result);
+ return XYZToColor(result[0], result[1], result[2]);
+ }
+
+ /**
+ * Returns the euclidean distance between two LAB colors.
+ */
+ public static double distanceEuclidean(@NonNull double[] labX, @NonNull double[] labY) {
+ return Math.sqrt(Math.pow(labX[0] - labY[0], 2)
+ + Math.pow(labX[1] - labY[1], 2)
+ + Math.pow(labX[2] - labY[2], 2));
+ }
+
private static float constrain(float amount, float low, float high) {
return amount < low ? low : (amount > high ? high : amount);
}
@@ -308,4 +521,98 @@
return amount < low ? low : (amount > high ? high : amount);
}
+ private static double pivotXyzComponent(double component) {
+ return component > XYZ_EPSILON
+ ? Math.pow(component, 1 / 3.0)
+ : (XYZ_KAPPA * component + 16) / 116;
+ }
+
+ /**
+ * Blend between two ARGB colors using the given ratio.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code color1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code color2}.</p>
+ *
+ * @param color1 the first ARGB color
+ * @param color2 the second ARGB color
+ * @param ratio the blend ratio of {@code color1} to {@code color2}
+ */
+ @ColorInt
+ public static int blendARGB(@ColorInt int color1, @ColorInt int color2,
+ @FloatRange(from = 0.0, to = 1.0) float ratio) {
+ final float inverseRatio = 1 - ratio;
+ float a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio;
+ float r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio;
+ float g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio;
+ float b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio;
+ return Color.argb((int) a, (int) r, (int) g, (int) b);
+ }
+
+ /**
+ * Blend between {@code hsl1} and {@code hsl2} using the given ratio. This will interpolate
+ * the hue using the shortest angle.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code hsl1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code hsl2}.</p>
+ *
+ * @param hsl1 3-element array which holds the first HSL color
+ * @param hsl2 3-element array which holds the second HSL color
+ * @param ratio the blend ratio of {@code hsl1} to {@code hsl2}
+ * @param outResult 3-element array which holds the resulting HSL components
+ */
+ public static void blendHSL(@NonNull float[] hsl1, @NonNull float[] hsl2,
+ @FloatRange(from = 0.0, to = 1.0) float ratio, @NonNull float[] outResult) {
+ if (outResult.length != 3) {
+ throw new IllegalArgumentException("result must have a length of 3.");
+ }
+ final float inverseRatio = 1 - ratio;
+ // Since hue is circular we will need to interpolate carefully
+ outResult[0] = circularInterpolate(hsl1[0], hsl2[0], ratio);
+ outResult[1] = hsl1[1] * inverseRatio + hsl2[1] * ratio;
+ outResult[2] = hsl1[2] * inverseRatio + hsl2[2] * ratio;
+ }
+
+ /**
+ * Blend between two CIE-LAB colors using the given ratio.
+ *
+ * <p>A blend ratio of 0.0 will result in {@code lab1}, 0.5 will give an even blend,
+ * 1.0 will result in {@code lab2}.</p>
+ *
+ * @param lab1 3-element array which holds the first LAB color
+ * @param lab2 3-element array which holds the second LAB color
+ * @param ratio the blend ratio of {@code lab1} to {@code lab2}
+ * @param outResult 3-element array which holds the resulting LAB components
+ */
+ public static void blendLAB(@NonNull double[] lab1, @NonNull double[] lab2,
+ @FloatRange(from = 0.0, to = 1.0) double ratio, @NonNull double[] outResult) {
+ if (outResult.length != 3) {
+ throw new IllegalArgumentException("outResult must have a length of 3.");
+ }
+ final double inverseRatio = 1 - ratio;
+ outResult[0] = lab1[0] * inverseRatio + lab2[0] * ratio;
+ outResult[1] = lab1[1] * inverseRatio + lab2[1] * ratio;
+ outResult[2] = lab1[2] * inverseRatio + lab2[2] * ratio;
+ }
+
+ @VisibleForTesting
+ static float circularInterpolate(float a, float b, float f) {
+ if (Math.abs(b - a) > 180) {
+ if (b > a) {
+ a += 360;
+ } else {
+ b += 360;
+ }
+ }
+ return (a + ((b - a) * f)) % 360;
+ }
+
+ private static double[] getTempDouble3Array() {
+ double[] result = TEMP_ARRAY.get();
+ if (result == null) {
+ result = new double[3];
+ TEMP_ARRAY.set(result);
+ }
+ return result;
+ }
+
}
diff --git a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
index 64ae075..b28c7d9 100644
--- a/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
+++ b/v4/java/android/support/v4/graphics/drawable/DrawableCompat.java
@@ -17,15 +17,25 @@
package android.support.v4.graphics.drawable;
import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.ColorFilter;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import java.io.IOException;
/**
* Helper for accessing features in {@link android.graphics.drawable.Drawable}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class DrawableCompat {
+public final class DrawableCompat {
/**
* Interface for the full API.
*/
@@ -39,8 +49,14 @@
void setTintList(Drawable drawable, ColorStateList tint);
void setTintMode(Drawable drawable, PorterDuff.Mode tintMode);
Drawable wrap(Drawable drawable);
- void setLayoutDirection(Drawable drawable, int layoutDirection);
+ boolean setLayoutDirection(Drawable drawable, int layoutDirection);
int getLayoutDirection(Drawable drawable);
+ int getAlpha(Drawable drawable);
+ void applyTheme(Drawable drawable, Resources.Theme t);
+ boolean canApplyTheme(Drawable drawable);
+ ColorFilter getColorFilter(Drawable drawable);
+ void inflate(Drawable drawable, Resources res, XmlPullParser parser, AttributeSet attrs,
+ Resources.Theme t) throws IOException, XmlPullParserException;
}
/**
@@ -89,20 +105,57 @@
}
@Override
- public void setLayoutDirection(Drawable drawable, int layoutDirection) {
+ public boolean setLayoutDirection(Drawable drawable, int layoutDirection) {
// No op for API < 23
+ return false;
}
@Override
public int getLayoutDirection(Drawable drawable) {
return ViewCompat.LAYOUT_DIRECTION_LTR;
}
+
+ @Override
+ public int getAlpha(Drawable drawable) {
+ return 0;
+ }
+
+ @Override
+ public void applyTheme(Drawable drawable, Resources.Theme t) {
+ }
+
+ @Override
+ public boolean canApplyTheme(Drawable drawable) {
+ return false;
+ }
+
+ @Override
+ public ColorFilter getColorFilter(Drawable drawable) {
+ return null;
+ }
+
+ @Override
+ public void inflate(Drawable drawable, Resources res, XmlPullParser parser,
+ AttributeSet attrs, Resources.Theme t)
+ throws IOException, XmlPullParserException {
+ DrawableCompatBase.inflate(drawable, res, parser, attrs, t);
+ }
+ }
+
+ /**
+ * Interface implementation for devices with at least v5 APIs.
+ */
+ static class EclairDrawableImpl extends BaseDrawableImpl {
+ @Override
+ public Drawable wrap(Drawable drawable) {
+ return DrawableCompatEclair.wrapForTinting(drawable);
+ }
}
/**
* Interface implementation for devices with at least v11 APIs.
*/
- static class HoneycombDrawableImpl extends BaseDrawableImpl {
+ static class HoneycombDrawableImpl extends EclairDrawableImpl {
@Override
public void jumpToCurrentState(Drawable drawable) {
DrawableCompatHoneycomb.jumpToCurrentState(drawable);
@@ -116,8 +169,8 @@
static class JellybeanMr1DrawableImpl extends HoneycombDrawableImpl {
@Override
- public void setLayoutDirection(Drawable drawable, int layoutDirection) {
- DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection);
+ public boolean setLayoutDirection(Drawable drawable, int layoutDirection) {
+ return DrawableCompatJellybeanMr1.setLayoutDirection(drawable, layoutDirection);
}
@Override
@@ -145,6 +198,11 @@
public Drawable wrap(Drawable drawable) {
return DrawableCompatKitKat.wrapForTinting(drawable);
}
+
+ @Override
+ public int getAlpha(Drawable drawable) {
+ return DrawableCompatKitKat.getAlpha(drawable);
+ }
}
/**
@@ -180,31 +238,49 @@
public Drawable wrap(Drawable drawable) {
return DrawableCompatLollipop.wrapForTinting(drawable);
}
- }
- /**
- * Interface implementation for devices with at least L APIs.
- */
- static class LollipopMr1DrawableImpl extends LollipopDrawableImpl {
@Override
- public Drawable wrap(Drawable drawable) {
- return DrawableCompatApi22.wrapForTinting(drawable);
+ public void applyTheme(Drawable drawable, Resources.Theme t) {
+ DrawableCompatLollipop.applyTheme(drawable, t);
+ }
+
+ @Override
+ public boolean canApplyTheme(Drawable drawable) {
+ return DrawableCompatLollipop.canApplyTheme(drawable);
+ }
+
+ @Override
+ public ColorFilter getColorFilter(Drawable drawable) {
+ return DrawableCompatLollipop.getColorFilter(drawable);
+ }
+
+ @Override
+ public void inflate(Drawable drawable, Resources res, XmlPullParser parser,
+ AttributeSet attrs, Resources.Theme t)
+ throws IOException, XmlPullParserException {
+ DrawableCompatLollipop.inflate(drawable, res, parser, attrs, t);
}
}
/**
* Interface implementation for devices with at least M APIs.
*/
- static class MDrawableImpl extends LollipopMr1DrawableImpl {
+ static class MDrawableImpl extends LollipopDrawableImpl {
@Override
- public void setLayoutDirection(Drawable drawable, int layoutDirection) {
- DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection);
+ public boolean setLayoutDirection(Drawable drawable, int layoutDirection) {
+ return DrawableCompatApi23.setLayoutDirection(drawable, layoutDirection);
}
@Override
public int getLayoutDirection(Drawable drawable) {
return DrawableCompatApi23.getLayoutDirection(drawable);
}
+
+ @Override
+ public Drawable wrap(Drawable drawable) {
+ // No need to wrap on M+
+ return drawable;
+ }
}
/**
@@ -215,8 +291,6 @@
final int version = android.os.Build.VERSION.SDK_INT;
if (version >= 23) {
IMPL = new MDrawableImpl();
- } else if (version >= 22) {
- IMPL = new LollipopMr1DrawableImpl();
} else if (version >= 21) {
IMPL = new LollipopDrawableImpl();
} else if (version >= 19) {
@@ -225,6 +299,8 @@
IMPL = new JellybeanMr1DrawableImpl();
} else if (version >= 11) {
IMPL = new HoneycombDrawableImpl();
+ } else if (version >= 5) {
+ IMPL = new EclairDrawableImpl();
} else {
IMPL = new BaseDrawableImpl();
}
@@ -238,7 +314,7 @@
*
* @param drawable The Drawable against which to invoke the method.
*/
- public static void jumpToCurrentState(Drawable drawable) {
+ public static void jumpToCurrentState(@NonNull Drawable drawable) {
IMPL.jumpToCurrentState(drawable);
}
@@ -254,7 +330,7 @@
* @param mirrored Set to true if the Drawable should be mirrored, false if
* not.
*/
- public static void setAutoMirrored(Drawable drawable, boolean mirrored) {
+ public static void setAutoMirrored(@NonNull Drawable drawable, boolean mirrored) {
IMPL.setAutoMirrored(drawable, mirrored);
}
@@ -269,7 +345,7 @@
* @return boolean Returns true if this Drawable will be automatically
* mirrored.
*/
- public static boolean isAutoMirrored(Drawable drawable) {
+ public static boolean isAutoMirrored(@NonNull Drawable drawable) {
return IMPL.isAutoMirrored(drawable);
}
@@ -280,7 +356,7 @@
* @param x The X coordinate of the center of the hotspot
* @param y The Y coordinate of the center of the hotspot
*/
- public static void setHotspot(Drawable drawable, float x, float y) {
+ public static void setHotspot(@NonNull Drawable drawable, float x, float y) {
IMPL.setHotspot(drawable, x, y);
}
@@ -290,7 +366,7 @@
*
* @param drawable The Drawable against which to invoke the method.
*/
- public static void setHotspotBounds(Drawable drawable, int left, int top,
+ public static void setHotspotBounds(@NonNull Drawable drawable, int left, int top,
int right, int bottom) {
IMPL.setHotspotBounds(drawable, left, top, right, bottom);
}
@@ -301,7 +377,7 @@
* @param drawable The Drawable against which to invoke the method.
* @param tint Color to use for tinting this drawable
*/
- public static void setTint(Drawable drawable, int tint) {
+ public static void setTint(@NonNull Drawable drawable, @ColorInt int tint) {
IMPL.setTint(drawable, tint);
}
@@ -311,7 +387,7 @@
* @param drawable The Drawable against which to invoke the method.
* @param tint Color state list to use for tinting this drawable, or null to clear the tint
*/
- public static void setTintList(Drawable drawable, ColorStateList tint) {
+ public static void setTintList(@NonNull Drawable drawable, @Nullable ColorStateList tint) {
IMPL.setTintList(drawable, tint);
}
@@ -321,16 +397,78 @@
* @param drawable The Drawable against which to invoke the method.
* @param tintMode A Porter-Duff blending mode
*/
- public static void setTintMode(Drawable drawable, PorterDuff.Mode tintMode) {
+ public static void setTintMode(@NonNull Drawable drawable, @Nullable PorterDuff.Mode tintMode) {
IMPL.setTintMode(drawable, tintMode);
}
/**
+ * Get the alpha value of the {@code drawable}.
+ * 0 means fully transparent, 255 means fully opaque.
+ *
+ * @param drawable The Drawable against which to invoke the method.
+ */
+ public static int getAlpha(@NonNull Drawable drawable) {
+ return IMPL.getAlpha(drawable);
+ }
+
+ /**
+ * Applies the specified theme to this Drawable and its children.
+ */
+ public static void applyTheme(Drawable drawable, Resources.Theme t) {
+ IMPL.applyTheme(drawable, t);
+ }
+
+ /**
+ * Whether a theme can be applied to this Drawable and its children.
+ */
+ public static boolean canApplyTheme(Drawable drawable) {
+ return IMPL.canApplyTheme(drawable);
+ }
+
+ /**
+ * Returns the current color filter, or {@code null} if none set.
+ *
+ * @return the current color filter, or {@code null} if none set
+ */
+ public static ColorFilter getColorFilter(Drawable drawable) {
+ return IMPL.getColorFilter(drawable);
+ }
+
+ /**
+ * Inflate this Drawable from an XML resource optionally styled by a theme.
+ *
+ * @param res Resources used to resolve attribute values
+ * @param parser XML parser from which to inflate this Drawable
+ * @param attrs Base set of attribute values
+ * @param theme Theme to apply, may be null
+ * @throws XmlPullParserException
+ * @throws IOException
+ */
+ public static void inflate(Drawable drawable, Resources res, XmlPullParser parser,
+ AttributeSet attrs, Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ IMPL.inflate(drawable, res, parser, attrs, theme);
+ }
+
+ /**
* Potentially wrap {@code drawable} so that it may be used for tinting across the
* different API levels, via the tinting methods in this class.
- * <p>
- * If you need to get hold of the original {@link android.graphics.drawable.Drawable} again,
- * you can use the value returned from {@link #unwrap(Drawable)}.
+ *
+ * <p>You must use the result of this call. If the given drawable is being used by a view
+ * (as it's background for instance), you must replace the original drawable with
+ * the result of this call:</p>
+ *
+ * <pre>
+ * Drawable bg = DrawableCompat.wrap(view.getBackground());
+ * // Need to set the background with the wrapped drawable
+ * view.setBackground(bg);
+ *
+ * // You can now tint the drawable
+ * DrawableCompat.setTint(bg, ...);
+ * </pre>
+ *
+ * <p>If you need to get hold of the original {@link android.graphics.drawable.Drawable} again,
+ * you can use the value returned from {@link #unwrap(Drawable)}.</p>
*
* @param drawable The Drawable to process
* @return A drawable capable of being tinted across all API levels.
@@ -340,7 +478,7 @@
* @see #setTintMode(Drawable, PorterDuff.Mode)
* @see #unwrap(Drawable)
*/
- public static Drawable wrap(Drawable drawable) {
+ public static Drawable wrap(@NonNull Drawable drawable) {
return IMPL.wrap(drawable);
}
@@ -354,7 +492,7 @@
*
* @see #wrap(Drawable)
*/
- public static <T extends Drawable> T unwrap(Drawable drawable) {
+ public static <T extends Drawable> T unwrap(@NonNull Drawable drawable) {
if (drawable instanceof DrawableWrapper) {
return (T) ((DrawableWrapper) drawable).getWrappedDrawable();
}
@@ -369,10 +507,13 @@
* @param layoutDirection the resolved layout direction for the drawable,
* either {@link ViewCompat#LAYOUT_DIRECTION_LTR}
* or {@link ViewCompat#LAYOUT_DIRECTION_RTL}
+ * @return {@code true} if the layout direction change has caused the
+ * appearance of the drawable to change such that it needs to be
+ * re-drawn, {@code false} otherwise
* @see #getLayoutDirection(Drawable)
*/
- public static void setLayoutDirection(Drawable drawable, int layoutDirection) {
- IMPL.setLayoutDirection(drawable, layoutDirection);
+ public static boolean setLayoutDirection(@NonNull Drawable drawable, int layoutDirection) {
+ return IMPL.setLayoutDirection(drawable, layoutDirection);
}
/**
@@ -382,7 +523,9 @@
* {@link ViewCompat#LAYOUT_DIRECTION_RTL}
* @see #setLayoutDirection(Drawable, int)
*/
- public static int getLayoutDirection(Drawable drawable) {
+ public static int getLayoutDirection(@NonNull Drawable drawable) {
return IMPL.getLayoutDirection(drawable);
}
+
+ private DrawableCompat() {}
}
diff --git a/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java b/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
index ad1aedc..5efc74b 100644
--- a/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
+++ b/v4/java/android/support/v4/graphics/drawable/RoundedBitmapDrawableFactory.java
@@ -30,7 +30,7 @@
* Constructs {@link RoundedBitmapDrawable RoundedBitmapDrawable} objects,
* either from Bitmaps directly, or from streams and files.
*/
-public class RoundedBitmapDrawableFactory {
+public final class RoundedBitmapDrawableFactory {
private static final String TAG = "RoundedBitmapDrawableFactory";
private static class DefaultRoundedBitmapDrawable extends RoundedBitmapDrawable {
@@ -95,4 +95,6 @@
return drawable;
}
+ private RoundedBitmapDrawableFactory() {}
+
}
diff --git a/v4/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java b/v4/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
index dc7bb74..10663a2 100644
--- a/v4/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
+++ b/v4/java/android/support/v4/hardware/fingerprint/FingerprintManagerCompat.java
@@ -34,7 +34,7 @@
* On platforms before {@link android.os.Build.VERSION_CODES#M}, this class behaves as there would
* be no fingerprint hardware available.
*/
-public class FingerprintManagerCompat {
+public final class FingerprintManagerCompat {
private Context mContext;
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index dcce7f5..4b5c80d 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -13,13 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package android.support.v4.media;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
+import android.os.Binder;
+import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -34,6 +35,7 @@
import android.support.v4.app.BundleCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.os.BuildCompat;
import android.support.v4.os.ResultReceiver;
import android.support.v4.util.ArrayMap;
import android.text.TextUtils;
@@ -41,8 +43,11 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
+import java.lang.ref.WeakReference;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import static android.support.v4.media.MediaBrowserProtocol.*;
@@ -55,6 +60,25 @@
*/
public final class MediaBrowserCompat {
private static final String TAG = "MediaBrowserCompat";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+ /**
+ * Used as an int extra field to denote the page number to subscribe.
+ * The value of {@code EXTRA_PAGE} should be greater than or equal to 1.
+ *
+ * @see android.service.media.MediaBrowserService.BrowserRoot
+ * @see #EXTRA_PAGE_SIZE
+ */
+ public static final String EXTRA_PAGE = "android.media.browse.extra.PAGE";
+
+ /**
+ * Used as an int extra field to denote the number of media items in a page.
+ * The value of {@code EXTRA_PAGE_SIZE} should be greater than or equal to 1.
+ *
+ * @see android.service.media.MediaBrowserService.BrowserRoot
+ * @see #EXTRA_PAGE
+ */
+ public static final String EXTRA_PAGE_SIZE = "android.media.browse.extra.PAGE_SIZE";
private final MediaBrowserImpl mImpl;
@@ -66,14 +90,19 @@
* @param callback The connection callback.
* @param rootHints An optional bundle of service-specific arguments to send
* to the media browse service when connecting and retrieving the root id
- * for browsing, or null if none. The contents of this bundle may affect
+ * for browsing, or null if none. The contents of this bundle may affect
* the information returned when browsing.
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
*/
public MediaBrowserCompat(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
- if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 24 || BuildCompat.isAtLeastN()) {
+ mImpl = new MediaBrowserImplApi24(context, serviceComponent, callback, rootHints);
+ } else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
- } else if (android.os.Build.VERSION.SDK_INT >= 21) {
+ } else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
} else {
mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
@@ -148,7 +177,7 @@
*
* @throws IllegalStateException if not connected.
*/
- public @NonNull MediaSessionCompat.Token getSessionToken() {
+ public @NonNull MediaSessionCompat.Token getSessionToken() {
return mImpl.getSessionToken();
}
@@ -157,7 +186,7 @@
* the specified id and subscribes to receive updates when they change.
* <p>
* The list of subscriptions is maintained even when not connected and is
- * restored after reconnection. It is ok to subscribe while not connected
+ * restored after the reconnection. It is ok to subscribe while not connected
* but the results will not be returned until the connection completes.
* </p>
* <p>
@@ -171,7 +200,51 @@
* @param callback The callback to receive the list of children.
*/
public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
- mImpl.subscribe(parentId, callback);
+ // Check arguments.
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+ mImpl.subscribe(parentId, null, callback);
+ }
+
+ /**
+ * Queries with service-specific arguments for information about the media items
+ * that are contained within the specified id and subscribes to receive updates
+ * when they change.
+ * <p>
+ * The list of subscriptions is maintained even when not connected and is
+ * restored after the reconnection. It is ok to subscribe while not connected
+ * but the results will not be returned until the connection completes.
+ * </p>
+ * <p>
+ * If the id is already subscribed with a different callback then the new
+ * callback will replace the previous one and the child data will be
+ * reloaded.
+ * </p>
+ *
+ * @param parentId The id of the parent media item whose list of children
+ * will be subscribed.
+ * @param options A bundle of service-specific arguments to send to the media
+ * browse service. The contents of this bundle may affect the
+ * information returned when browsing.
+ * @param callback The callback to receive the list of children.
+ */
+ public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+ @NonNull SubscriptionCallback callback) {
+ // Check arguments.
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+ if (options == null) {
+ throw new IllegalArgumentException("options are null");
+ }
+ mImpl.subscribe(parentId, options, callback);
}
/**
@@ -182,10 +255,36 @@
* </p>
*
* @param parentId The id of the parent media item whose list of children
- * will be unsubscribed.
+ * will be unsubscribed.
*/
public void unsubscribe(@NonNull String parentId) {
- mImpl.unsubscribe(parentId);
+ // Check arguments.
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty");
+ }
+ mImpl.unsubscribe(parentId, null);
+ }
+
+ /**
+ * Unsubscribes for changes to the children of the specified media id.
+ * <p>
+ * The query callback will no longer be invoked for results associated with
+ * this id once this method returns.
+ * </p>
+ *
+ * @param parentId The id of the parent media item whose list of children
+ * will be unsubscribed.
+ * @param callback A callback sent to the media browse service to subscribe.
+ */
+ public void unsubscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
+ // Check arguments.
+ if (TextUtils.isEmpty(parentId)) {
+ throw new IllegalArgumentException("parentId is empty");
+ }
+ if (callback == null) {
+ throw new IllegalArgumentException("callback is null");
+ }
+ mImpl.unsubscribe(parentId, callback);
}
/**
@@ -200,6 +299,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;
@@ -320,15 +422,15 @@
}
}
-
/**
* Callbacks for connection related events.
*/
public static class ConnectionCallback {
final Object mConnectionCallbackObj;
+ private ConnectionCallbackInternal mConnectionCallbackInternal;
public ConnectionCallback() {
- if (android.os.Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= 21) {
mConnectionCallbackObj =
MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
} else {
@@ -355,20 +457,38 @@
public void onConnectionFailed() {
}
+ void setInternalConnectionCallback(ConnectionCallbackInternal connectionCallbackInternal) {
+ mConnectionCallbackInternal = connectionCallbackInternal;
+ }
+
+ interface ConnectionCallbackInternal {
+ void onConnected();
+ void onConnectionSuspended();
+ void onConnectionFailed();
+ }
private class StubApi21 implements MediaBrowserCompatApi21.ConnectionCallback {
@Override
public void onConnected() {
+ if (mConnectionCallbackInternal != null) {
+ mConnectionCallbackInternal.onConnected();
+ }
ConnectionCallback.this.onConnected();
}
@Override
public void onConnectionSuspended() {
+ if (mConnectionCallbackInternal != null) {
+ mConnectionCallbackInternal.onConnectionSuspended();
+ }
ConnectionCallback.this.onConnectionSuspended();
}
@Override
public void onConnectionFailed() {
+ if (mConnectionCallbackInternal != null) {
+ mConnectionCallbackInternal.onConnectionFailed();
+ }
ConnectionCallback.this.onConnectionFailed();
}
}
@@ -378,14 +498,22 @@
* Callbacks for subscription related events.
*/
public static abstract class SubscriptionCallback {
- final Object mSubscriptionCallbackObj;
+ private final Object mSubscriptionCallbackObj;
+ private final IBinder mToken;
+ private WeakReference<Subscription> mSubscriptionRef;
public SubscriptionCallback() {
- if (android.os.Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= 24 || BuildCompat.isAtLeastN()) {
+ mSubscriptionCallbackObj =
+ MediaBrowserCompatApi24.createSubscriptionCallback(new StubApi24());
+ mToken = null;
+ } else if (Build.VERSION.SDK_INT >= 21) {
mSubscriptionCallbackObj =
MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
+ mToken = new Binder();
} else {
mSubscriptionCallbackObj = null;
+ mToken = new Binder();
}
}
@@ -399,6 +527,31 @@
}
/**
+ * Called when the list of children is loaded or updated.
+ *
+ * @param parentId The media id of the parent media item.
+ * @param children The children which were loaded, or null if the id is invalid.
+ * @param options A bundle of service-specific arguments to send to the media
+ * browse service. The contents of this bundle may affect the
+ * information returned when browsing.
+ */
+ public void onChildrenLoaded(@NonNull String parentId, List<MediaItem> children,
+ @NonNull Bundle options) {
+ }
+
+ /**
+ * Called when the id doesn't exist or other errors in subscribing.
+ * <p>
+ * If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
+ * called, because some errors may heal themselves.
+ * </p>
+ *
+ * @param parentId The media id of the parent media item whose children could not be loaded.
+ */
+ public void onError(@NonNull String parentId) {
+ }
+
+ /**
* Called when the id doesn't exist or other errors in subscribing.
* <p>
* If this is called, the subscription remains until {@link MediaBrowserCompat#unsubscribe}
@@ -406,31 +559,95 @@
* </p>
*
* @param parentId The media id of the parent media item whose children could
- * not be loaded.
+ * not be loaded.
+ * @param options A bundle of service-specific arguments sent to the media
+ * browse service.
*/
- public void onError(@NonNull String parentId) {
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ }
+
+ private void setSubscription(Subscription subscription) {
+ mSubscriptionRef = new WeakReference(subscription);
}
private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
@Override
- public void onChildrenLoaded(@NonNull String parentId, @NonNull List<Parcel> children) {
- List<MediaBrowserCompat.MediaItem> mediaItems = null;
- if (children != null) {
- mediaItems = new ArrayList<>();
- for (Parcel parcel : children) {
- parcel.setDataPosition(0);
- mediaItems.add(
- MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
+ public void onChildrenLoaded(@NonNull String parentId, List<Parcel> children) {
+ Subscription sub = mSubscriptionRef == null ? null : mSubscriptionRef.get();
+ if (sub == null) {
+ SubscriptionCallback.this.onChildrenLoaded(
+ parentId, parcelListToItemList(children));
+ } else {
+ List<MediaBrowserCompat.MediaItem> itemList = parcelListToItemList(children);
+ final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+ final List<Bundle> optionsList = sub.getOptionsList();
+ for (int i = 0; i < callbacks.size(); ++i) {
+ Bundle options = optionsList.get(i);
+ if (options == null) {
+ SubscriptionCallback.this.onChildrenLoaded(parentId, itemList);
+ } else {
+ SubscriptionCallback.this.onChildrenLoaded(
+ parentId, applyOptions(itemList, options), options);
+ }
}
}
- SubscriptionCallback.this.onChildrenLoaded(parentId, mediaItems);
}
@Override
public void onError(@NonNull String parentId) {
SubscriptionCallback.this.onError(parentId);
}
+
+ List<MediaBrowserCompat.MediaItem> parcelListToItemList(
+ List<Parcel> parcelList) {
+ if (parcelList == null) {
+ return null;
+ }
+ List<MediaBrowserCompat.MediaItem> items = new ArrayList<>();
+ for (Parcel parcel : parcelList) {
+ parcel.setDataPosition(0);
+ items.add(MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ return items;
+ }
+
+ List<MediaBrowserCompat.MediaItem> applyOptions(List<MediaBrowserCompat.MediaItem> list,
+ final Bundle options) {
+ if (list == null) {
+ return null;
+ }
+ int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+ if (page == -1 && pageSize == -1) {
+ return list;
+ }
+ int fromIndex = pageSize * page;
+ int toIndex = fromIndex + pageSize;
+ if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+ return Collections.EMPTY_LIST;
+ }
+ if (toIndex > list.size()) {
+ toIndex = list.size();
+ }
+ return list.subList(fromIndex, toIndex);
+ }
+
+ }
+
+ private class StubApi24 extends StubApi21
+ implements MediaBrowserCompatApi24.SubscriptionCallback {
+ @Override
+ public void onChildrenLoaded(@NonNull String parentId, List<Parcel> children,
+ @NonNull Bundle options) {
+ SubscriptionCallback.this.onChildrenLoaded(
+ parentId, parcelListToItemList(children), options);
+ }
+
+ @Override
+ public void onError(@NonNull String parentId, @NonNull Bundle options) {
+ SubscriptionCallback.this.onError(parentId, options);
+ }
}
}
@@ -441,7 +658,7 @@
final Object mItemCallbackObj;
public ItemCallback() {
- if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 23) {
mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
} else {
mItemCallbackObj = null;
@@ -488,14 +705,21 @@
@NonNull String getRoot();
@Nullable Bundle getExtras();
@NonNull MediaSessionCompat.Token getSessionToken();
- void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback);
- void unsubscribe(@NonNull String parentId);
+ void subscribe(@NonNull String parentId, Bundle options,
+ @NonNull SubscriptionCallback callback);
+ void unsubscribe(@NonNull String parentId, SubscriptionCallback callback);
void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb);
}
- static class MediaBrowserImplBase implements MediaBrowserImpl {
- private static final boolean DBG = false;
+ interface MediaBrowserServiceCallbackImpl {
+ void onServiceConnected(Messenger callback, String root, MediaSessionCompat.Token session,
+ Bundle extra);
+ void onConnectionFailed(Messenger callback);
+ void onLoadChildren(Messenger callback, String parentId, List list, Bundle options);
+ }
+ static class MediaBrowserImplBase
+ implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl {
private static final int CONNECT_STATE_DISCONNECTED = 0;
private static final int CONNECT_STATE_CONNECTING = 1;
private static final int CONNECT_STATE_CONNECTED = 2;
@@ -505,8 +729,8 @@
private final ComponentName mServiceComponent;
private final ConnectionCallback mCallback;
private final Bundle mRootHints;
- private final CallbackHandler mHandler = new CallbackHandler();
- private final ArrayMap<String,Subscription> mSubscriptions = new ArrayMap<>();
+ private final CallbackHandler mHandler = new CallbackHandler(this);
+ private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
private int mState = CONNECT_STATE_DISCONNECTED;
private MediaServiceConnection mServiceConnection;
@@ -530,16 +754,17 @@
mContext = context;
mServiceComponent = serviceComponent;
mCallback = callback;
- mRootHints = rootHints;
+ mRootHints = rootHints == null ? null : new Bundle(rootHints);
}
+ @Override
public void connect() {
if (mState != CONNECT_STATE_DISCONNECTED) {
throw new IllegalStateException("connect() called while not disconnected (state="
+ getStateLabel(mState) + ")");
}
// TODO: remove this extra check.
- if (DBG) {
+ if (DEBUG) {
if (mServiceConnection != null) {
throw new RuntimeException("mServiceConnection should be null. Instead it is "
+ mServiceConnection);
@@ -570,9 +795,9 @@
}
if (!bound) {
- // Tell them that it didn't work. We are already on the main thread,
- // but we don't want to do callbacks inside of connect(). So post it,
- // and then check that we are on the same ServiceConnection. We know
+ // Tell them that it didn't work. We are already on the main thread,
+ // but we don't want to do callbacks inside of connect(). So post it,
+ // and then check that we are on the same ServiceConnection. We know
// we won't also get an onServiceConnected or onServiceDisconnected,
// so we won't be doing double callbacks.
mHandler.post(new Runnable() {
@@ -587,40 +812,41 @@
});
}
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "connect...");
dump();
}
}
+ @Override
public void disconnect() {
// It's ok to call this any state, because allowing this lets apps not have
- // to check isConnected() unnecessarily. They won't appreciate the extra
- // assertions for this. We do everything we can here to go back to a sane state.
+ // to check isConnected() unnecessarily. They won't appreciate the extra
+ // assertions for this. We do everything we can here to go back to a sane state.
if (mCallbacksMessenger != null) {
try {
- mServiceBinderWrapper.disconnect();
+ mServiceBinderWrapper.disconnect(mCallbacksMessenger);
} catch (RemoteException ex) {
- // We are disconnecting anyway. Log, just for posterity but it's not
+ // We are disconnecting anyway. Log, just for posterity but it's not
// a big problem.
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
}
}
forceCloseConnection();
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "disconnect...");
dump();
}
}
/**
- * Null out the variables and unbind from the service. This doesn't include
+ * Null out the variables and unbind from the service. This doesn't include
* calling disconnect on the service, because we only try to do that in the
* clean shutdown cases.
* <p>
* Everywhere that calls this EXCEPT for disconnect() should follow it with
- * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
+ * a call to mCallback.onConnectionFailed(). Disconnect doesn't do that callback
* for a clean shutdown, but everywhere else is a dirty shutdown and should
* notify the app.
*/
@@ -632,16 +858,18 @@
mServiceConnection = null;
mServiceBinderWrapper = null;
mCallbacksMessenger = null;
+ mHandler.setCallbacksMessenger(null);
mRootId = null;
mMediaSessionToken = null;
}
+ @Override
public boolean isConnected() {
return mState == CONNECT_STATE_CONNECTED;
}
- public @NonNull
- ComponentName getServiceComponent() {
+ @Override
+ public @NonNull ComponentName getServiceComponent() {
if (!isConnected()) {
throw new IllegalStateException("getServiceComponent() called while not connected" +
" (state=" + mState + ")");
@@ -649,6 +877,7 @@
return mServiceComponent;
}
+ @Override
public @NonNull String getRoot() {
if (!isConnected()) {
throw new IllegalStateException("getRoot() called while not connected"
@@ -657,8 +886,8 @@
return mRootId;
}
- public @Nullable
- Bundle getExtras() {
+ @Override
+ public @Nullable Bundle getExtras() {
if (!isConnected()) {
throw new IllegalStateException("getExtras() called while not connected (state="
+ getStateLabel(mState) + ")");
@@ -666,6 +895,7 @@
return mExtras;
}
+ @Override
public @NonNull MediaSessionCompat.Token getSessionToken() {
if (!isConnected()) {
throw new IllegalStateException("getSessionToken() called while not connected"
@@ -674,65 +904,77 @@
return mMediaSessionToken;
}
- public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
- // Check arguments.
- if (parentId == null) {
- throw new IllegalArgumentException("parentId is null");
- }
- if (callback == null) {
- throw new IllegalArgumentException("callback is null");
- }
-
+ @Override
+ public void subscribe(@NonNull String parentId, Bundle options,
+ @NonNull SubscriptionCallback callback) {
// Update or create the subscription.
Subscription sub = mSubscriptions.get(parentId);
- boolean newSubscription = sub == null;
- if (newSubscription) {
- sub = new Subscription(parentId);
+ if (sub == null) {
+ sub = new Subscription();
mSubscriptions.put(parentId, sub);
}
- sub.callback = callback;
+ sub.putCallback(options, callback);
- // If we are connected, tell the service that we are watching. If we aren't
+ // If we are connected, tell the service that we are watching. If we aren't
// connected, the service will be told when we connect.
if (mState == CONNECT_STATE_CONNECTED) {
try {
- mServiceBinderWrapper.addSubscription(parentId);
- } catch (RemoteException ex) {
- // Process is crashing. We will disconnect, and upon reconnect we will
+ mServiceBinderWrapper.addSubscription(parentId, callback.mToken, options,
+ mCallbacksMessenger);
+ } catch (RemoteException e) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
// automatically reregister. So nothing to do here.
Log.d(TAG, "addSubscription failed with RemoteException parentId=" + parentId);
}
}
}
- public void unsubscribe(@NonNull String parentId) {
- // Check arguments.
- if (TextUtils.isEmpty(parentId)) {
- throw new IllegalArgumentException("parentId is empty.");
+ @Override
+ public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+ Subscription sub = mSubscriptions.get(parentId);
+ if (sub == null) {
+ return;
}
- // Remove from our list.
- final Subscription sub = mSubscriptions.remove(parentId);
-
// Tell the service if necessary.
- if (mState == CONNECT_STATE_CONNECTED && sub != null) {
- try {
- mServiceBinderWrapper.removeSubscription(parentId);
- } catch (RemoteException ex) {
- // Process is crashing. We will disconnect, and upon reconnect we will
- // automatically reregister. So nothing to do here.
- Log.d(TAG, "removeSubscription failed with RemoteException parentId="
- + parentId);
+ try {
+ if (callback == null) {
+ if (mState == CONNECT_STATE_CONNECTED) {
+ mServiceBinderWrapper.removeSubscription(parentId, null,
+ mCallbacksMessenger);
+ }
+ } else {
+ final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+ final List<Bundle> optionsList = sub.getOptionsList();
+ for (int i = callbacks.size() - 1; i >= 0; --i) {
+ if (callbacks.get(i) == callback) {
+ if (mState == CONNECT_STATE_CONNECTED) {
+ mServiceBinderWrapper.removeSubscription(
+ parentId, callback.mToken, mCallbacksMessenger);
+ }
+ callbacks.remove(i);
+ optionsList.remove(i);
+ }
+ }
}
+ } catch (RemoteException ex) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reregister. So nothing to do here.
+ Log.d(TAG, "removeSubscription failed with RemoteException parentId=" + parentId);
+ }
+
+ if (sub.isEmpty() || callback == null) {
+ mSubscriptions.remove(parentId);
}
}
- public void getItem(final @NonNull String mediaId, @NonNull final ItemCallback cb) {
+ @Override
+ public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
if (TextUtils.isEmpty(mediaId)) {
- throw new IllegalArgumentException("mediaId is empty.");
+ throw new IllegalArgumentException("mediaId is empty");
}
if (cb == null) {
- throw new IllegalArgumentException("cb is null.");
+ throw new IllegalArgumentException("cb is null");
}
if (mState != CONNECT_STATE_CONNECTED) {
Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
@@ -744,25 +986,9 @@
});
return;
}
- ResultReceiver receiver = new ResultReceiver(mHandler) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != 0 || resultData == null
- || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
- cb.onError(mediaId);
- return;
- }
- Parcelable item =
- resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
- if (!(item instanceof MediaItem)) {
- cb.onError(mediaId);
- return;
- }
- cb.onItemLoaded((MediaItem)item);
- }
- };
+ ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
try {
- mServiceBinderWrapper.getMediaItem(mediaId, receiver);
+ mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
} catch (RemoteException e) {
Log.i(TAG, "Remote error getting media item.");
mHandler.post(new Runnable() {
@@ -774,6 +1000,106 @@
}
}
+ @Override
+ public void onServiceConnected(final Messenger callback, final String root,
+ final MediaSessionCompat.Token session, final Bundle extra) {
+ // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+ if (!isCurrent(callback, "onConnect")) {
+ return;
+ }
+ // Don't allow them to call us twice.
+ if (mState != CONNECT_STATE_CONNECTING) {
+ Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+ + "... ignoring");
+ return;
+ }
+ mRootId = root;
+ mMediaSessionToken = session;
+ mExtras = extra;
+ mState = CONNECT_STATE_CONNECTED;
+
+ if (DEBUG) {
+ Log.d(TAG, "ServiceCallbacks.onConnect...");
+ dump();
+ }
+ mCallback.onConnected();
+
+ // we may receive some subscriptions before we are connected, so re-subscribe
+ // everything now
+ try {
+ for (Map.Entry<String, Subscription> subscriptionEntry
+ : mSubscriptions.entrySet()) {
+ String id = subscriptionEntry.getKey();
+ Subscription sub = subscriptionEntry.getValue();
+ List<SubscriptionCallback> callbackList = sub.getCallbacks();
+ List<Bundle> optionsList = sub.getOptionsList();
+ for (int i = 0; i < callbackList.size(); ++i) {
+ mServiceBinderWrapper.addSubscription(id, callbackList.get(i).mToken,
+ optionsList.get(i), mCallbacksMessenger);
+ }
+ }
+ } catch (RemoteException ex) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reregister. So nothing to do here.
+ Log.d(TAG, "addSubscription failed with RemoteException.");
+ }
+ }
+
+ @Override
+ public void onConnectionFailed(final Messenger callback) {
+ Log.e(TAG, "onConnectFailed for " + mServiceComponent);
+
+ // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
+ if (!isCurrent(callback, "onConnectFailed")) {
+ return;
+ }
+ // Don't allow them to call us twice.
+ if (mState != CONNECT_STATE_CONNECTING) {
+ Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
+ + "... ignoring");
+ return;
+ }
+
+ // Clean up
+ forceCloseConnection();
+
+ // Tell the app.
+ mCallback.onConnectionFailed();
+ }
+
+ @Override
+ public void onLoadChildren(final Messenger callback, final String parentId,
+ final List list, final Bundle options) {
+ // Check that there hasn't been a disconnect or a different ServiceConnection.
+ if (!isCurrent(callback, "onLoadChildren")) {
+ return;
+ }
+
+ List<MediaItem> data = list;
+ if (DEBUG) {
+ Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
+ }
+
+ // Check that the subscription is still subscribed.
+ final Subscription subscription = mSubscriptions.get(parentId);
+ if (subscription == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+ }
+ return;
+ }
+
+ // Tell the app.
+ SubscriptionCallback subscriptionCallback = subscription.getCallback(options);
+ if (subscriptionCallback != null) {
+ if (options == null) {
+ subscriptionCallback.onChildrenLoaded(parentId, data);
+ } else {
+ subscriptionCallback.onChildrenLoaded(parentId, data, options);
+ }
+ }
+ }
+
/**
* For debugging.
*/
@@ -792,90 +1118,8 @@
}
}
- private final void onServiceConnected(final Messenger callback, final String root,
- final MediaSessionCompat.Token session, final Bundle extra) {
- // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
- if (!isCurrent(callback, "onConnect")) {
- return;
- }
- // Don't allow them to call us twice.
- if (mState != CONNECT_STATE_CONNECTING) {
- Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
- + "... ignoring");
- return;
- }
- mRootId = root;
- mMediaSessionToken = session;
- mExtras = extra;
- mState = CONNECT_STATE_CONNECTED;
-
- if (DBG) {
- Log.d(TAG, "ServiceCallbacks.onConnect...");
- dump();
- }
- mCallback.onConnected();
-
- // we may receive some subscriptions before we are connected, so re-subscribe
- // everything now
- for (String id : mSubscriptions.keySet()) {
- try {
- mServiceBinderWrapper.addSubscription(id);
- } catch (RemoteException ex) {
- // Process is crashing. We will disconnect, and upon reconnect we will
- // automatically reregister. So nothing to do here.
- Log.d(TAG, "addSubscription failed with RemoteException parentId=" + id);
- }
- }
- }
-
- private final void onConnectionFailed(final Messenger callback) {
- Log.e(TAG, "onConnectFailed for " + mServiceComponent);
-
- // Check to make sure there hasn't been a disconnect or a different ServiceConnection.
- if (!isCurrent(callback, "onConnectFailed")) {
- return;
- }
- // Don't allow them to call us twice.
- if (mState != CONNECT_STATE_CONNECTING) {
- Log.w(TAG, "onConnect from service while mState=" + getStateLabel(mState)
- + "... ignoring");
- return;
- }
-
- // Clean up
- forceCloseConnection();
-
- // Tell the app.
- mCallback.onConnectionFailed();
- }
-
- private final void onLoadChildren(final Messenger callback, final String parentId,
- final List list) {
- // Check that there hasn't been a disconnect or a different ServiceConnection.
- if (!isCurrent(callback, "onLoadChildren")) {
- return;
- }
-
- List<MediaItem> data = list;
- if (DBG) {
- Log.d(TAG, "onLoadChildren for " + mServiceComponent + " id=" + parentId);
- }
-
- // Check that the subscription is still subscribed.
- final Subscription subscription = mSubscriptions.get(parentId);
- if (subscription == null) {
- if (DBG) {
- Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
- }
- return;
- }
-
- // Tell the app.
- subscription.callback.onChildrenLoaded(parentId, data);
- }
-
/**
- * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
+ * Return true if {@code callback} is the current ServiceCallbacks. Also logs if it's not.
*/
private boolean isCurrent(Messenger callback, String funcName) {
if (mCallbacksMessenger != callback) {
@@ -905,48 +1149,6 @@
Log.d(TAG, " mMediaSessionToken=" + mMediaSessionToken);
}
- private class ServiceBinderWrapper {
- private Messenger mMessenger;
-
- public ServiceBinderWrapper(IBinder target) {
- mMessenger = new Messenger(target);
- }
-
- void connect() throws RemoteException {
- sendRequest(CLIENT_MSG_CONNECT, mContext.getPackageName(), mRootHints,
- mCallbacksMessenger);
- }
-
- void disconnect() throws RemoteException {
- sendRequest(CLIENT_MSG_DISCONNECT, null, null, mCallbacksMessenger);
- }
-
- void addSubscription(String parentId) throws RemoteException {
- sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, parentId, null, mCallbacksMessenger);
- }
-
- void removeSubscription(String parentId) throws RemoteException {
- sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, parentId, null, mCallbacksMessenger);
- }
-
- void getMediaItem(String mediaId, ResultReceiver receiver) throws RemoteException {
- Bundle data = new Bundle();
- data.putParcelable(SERVICE_DATA_RESULT_RECEIVER, receiver);
- sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, mediaId, data, null);
- }
-
- private void sendRequest(int what, Object obj, Bundle data, Messenger cbMessenger)
- throws RemoteException {
- Message msg = Message.obtain();
- msg.what = what;
- msg.arg1 = CLIENT_VERSION_CURRENT;
- msg.obj = obj;
- msg.setData(data);
- msg.replyTo = cbMessenger;
- mMessenger.send(msg);
- }
- }
-
/**
* ServiceConnection to the other app.
*/
@@ -956,7 +1158,7 @@
postOrRun(new Runnable() {
@Override
public void run() {
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "MediaServiceConnection.onServiceConnected name=" + name
+ " binder=" + binder);
dump();
@@ -969,29 +1171,30 @@
}
// Save their binder
- mServiceBinderWrapper = new ServiceBinderWrapper(binder);
+ mServiceBinderWrapper = new ServiceBinderWrapper(binder, mRootHints);
// We make a new mServiceCallbacks each time we connect so that we can drop
// responses from previous connections.
mCallbacksMessenger = new Messenger(mHandler);
+ mHandler.setCallbacksMessenger(mCallbacksMessenger);
mState = CONNECT_STATE_CONNECTING;
// Call connect, which is async. When we get a response from that we will
// say that we're connected.
try {
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "ServiceCallbacks.onConnect...");
dump();
}
- mServiceBinderWrapper.connect();
+ mServiceBinderWrapper.connect(mContext, mCallbacksMessenger);
} catch (RemoteException ex) {
// Connect failed, which isn't good. But the auto-reconnect on the
- // service will take over and we will come back. We will also get the
- // onServiceDisconnected, which has all the cleanup code. So let that
+ // service will take over and we will come back. We will also get the
+ // onServiceDisconnected, which has all the cleanup code. So let that
// do it.
Log.w(TAG, "RemoteException during connect for " + mServiceComponent);
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "ServiceCallbacks.onConnect...");
dump();
}
@@ -1005,7 +1208,7 @@
postOrRun(new Runnable() {
@Override
public void run() {
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "MediaServiceConnection.onServiceDisconnected name=" + name
+ " this=" + this + " mServiceConnection=" +
mServiceConnection);
@@ -1021,6 +1224,7 @@
// Clear out what we set in onServiceConnected
mServiceBinderWrapper = null;
mCallbacksMessenger = null;
+ mHandler.setCallbacksMessenger(null);
// And tell the app that it's suspended.
mState = CONNECT_STATE_SUSPENDED;
@@ -1038,7 +1242,7 @@
}
/**
- * Return true if this is the current ServiceConnection. Also logs if it's not.
+ * Return true if this is the current ServiceConnection. Also logs if it's not.
*/
private boolean isCurrent(String funcName) {
if (mServiceConnection != this) {
@@ -1052,52 +1256,34 @@
return true;
}
}
-
- private class CallbackHandler extends Handler {
- @Override
- public void handleMessage(Message msg) {
- Bundle data = msg.getData();
- switch (msg.what) {
- case SERVICE_MSG_ON_CONNECT:
- onServiceConnected(mCallbacksMessenger, (String) msg.obj,
- (MediaSessionCompat.Token) data.getParcelable(
- SERVICE_DATA_MEDIA_SESSION_TOKEN),
- data.getBundle(SERVICE_DATA_EXTRAS));
- break;
- case SERVICE_MSG_ON_CONNECT_FAILED:
- onConnectionFailed(mCallbacksMessenger);
- break;
- case SERVICE_MSG_ON_LOAD_CHILDREN:
- onLoadChildren(mCallbacksMessenger, (String) msg.obj,
- data.getParcelableArrayList(SERVICE_DATA_MEDIA_ITEM_LIST));
- break;
- default:
- Log.w(TAG, "Unhandled message: " + msg
- + "\n Client version: " + CLIENT_VERSION_CURRENT
- + "\n Service version: " + msg.arg1);
- }
- }
- }
-
- private static class Subscription {
- final String id;
- SubscriptionCallback callback;
-
- Subscription(String id) {
- this.id = id;
- }
- }
}
- static class MediaBrowserImplApi21 implements MediaBrowserImpl {
- protected Object mBrowserObj;
- protected Messenger mMessenger;
- protected Handler mHandler = new Handler();
+ static class MediaBrowserImplApi21 implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl,
+ ConnectionCallback.ConnectionCallbackInternal {
+ protected final Object mBrowserObj;
+ protected final Bundle mRootHints;
+ protected final CallbackHandler mHandler = new CallbackHandler(this);
+ private final ArrayMap<String, Subscription> mSubscriptions = new ArrayMap<>();
+
+ protected ServiceBinderWrapper mServiceBinderWrapper;
+ protected Messenger mCallbacksMessenger;
public MediaBrowserImplApi21(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
+ // Do not send the client version for API 24 and higher, since we don't need to use
+ // EXTRA_MESSENGER_BINDER for API 24 and higher.
+ if (Build.VERSION.SDK_INT < 24 && !BuildCompat.isAtLeastN()) {
+ if (rootHints == null) {
+ rootHints = new Bundle();
+ }
+ rootHints.putInt(EXTRA_CLIENT_VERSION, CLIENT_VERSION_CURRENT);
+ mRootHints = new Bundle(rootHints);
+ } else {
+ mRootHints = rootHints == null ? null : new Bundle(rootHints);
+ }
+ callback.setInternalConnectionCallback(this);
mBrowserObj = MediaBrowserCompatApi21.createBrowser(context, serviceComponent,
- callback.mConnectionCallbackObj, rootHints);
+ callback.mConnectionCallbackObj, mRootHints);
}
@Override
@@ -1107,6 +1293,13 @@
@Override
public void disconnect() {
+ if (mServiceBinderWrapper != null && mCallbacksMessenger != null) {
+ try {
+ mServiceBinderWrapper.unregisterCallbackMessenger(mCallbacksMessenger);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Remote error unregistering client messenger." );
+ }
+ }
MediaBrowserCompatApi21.disconnect(mBrowserObj);
}
@@ -1140,23 +1333,93 @@
}
@Override
- public void subscribe(@NonNull String parentId, @NonNull SubscriptionCallback callback) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ public void subscribe(@NonNull final String parentId, final Bundle options,
+ @NonNull final SubscriptionCallback callback) {
+ // Update or create the subscription.
+ Subscription sub = mSubscriptions.get(parentId);
+ if (sub == null) {
+ sub = new Subscription();
+ mSubscriptions.put(parentId, sub);
+ }
+ callback.setSubscription(sub);
+ sub.putCallback(options, callback);
+
+ if (mServiceBinderWrapper == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ } else {
+ try {
+ mServiceBinderWrapper.addSubscription(
+ parentId, callback.mToken, options, mCallbacksMessenger);
+ } catch (RemoteException e) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reregister. So nothing to do here.
+ Log.i(TAG, "Remote error subscribing media item: " + parentId);
+ }
+ }
}
@Override
- public void unsubscribe(@NonNull String parentId) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+ Subscription sub = mSubscriptions.get(parentId);
+ if (sub == null) {
+ return;
+ }
+
+ if (mServiceBinderWrapper == null) {
+ if (callback == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+ final List<Bundle> optionsList = sub.getOptionsList();
+ for (int i = callbacks.size() - 1; i >= 0; --i) {
+ if (callbacks.get(i) == callback) {
+ callbacks.remove(i);
+ optionsList.remove(i);
+ }
+ }
+ if (callbacks.size() == 0) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ }
+ }
+ } else {
+ // Tell the service if necessary.
+ try {
+ if (callback == null) {
+ mServiceBinderWrapper.removeSubscription(parentId, null,
+ mCallbacksMessenger);
+ } else {
+ final List<SubscriptionCallback> callbacks = sub.getCallbacks();
+ final List<Bundle> optionsList = sub.getOptionsList();
+ for (int i = callbacks.size() - 1; i >= 0; --i) {
+ if (callbacks.get(i) == callback) {
+ mServiceBinderWrapper.removeSubscription(
+ parentId, callback.mToken, mCallbacksMessenger);
+ callbacks.remove(i);
+ optionsList.remove(i);
+ }
+ }
+ }
+ } catch (RemoteException ex) {
+ // Process is crashing. We will disconnect, and upon reconnect we will
+ // automatically reregister. So nothing to do here.
+ Log.d(TAG, "removeSubscription failed with RemoteException parentId="
+ + parentId);
+ }
+ }
+
+ if (sub.isEmpty() || callback == null) {
+ mSubscriptions.remove(parentId);
+ }
}
@Override
public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
if (TextUtils.isEmpty(mediaId)) {
- throw new IllegalArgumentException("mediaId is empty.");
+ throw new IllegalArgumentException("mediaId is empty");
}
if (cb == null) {
- throw new IllegalArgumentException("cb is null.");
+ throw new IllegalArgumentException("cb is null");
}
if (!MediaBrowserCompatApi21.isConnected(mBrowserObj)) {
Log.i(TAG, "Not connected, unable to retrieve the MediaItem.");
@@ -1168,14 +1431,7 @@
});
return;
}
- if (mMessenger == null) {
- Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
- IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
- if (serviceBinder != null) {
- mMessenger = new Messenger(serviceBinder);
- }
- }
- if (mMessenger == null) {
+ if (mServiceBinderWrapper == null) {
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -1185,29 +1441,11 @@
});
return;
}
- ResultReceiver receiver = new ResultReceiver(mHandler) {
- @Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- if (resultCode != 0 || resultData == null
- || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
- cb.onError(mediaId);
- return;
- }
- Parcelable item =
- resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
- if (!(item instanceof MediaItem)) {
- cb.onError(mediaId);
- return;
- }
- cb.onItemLoaded((MediaItem)item);
- }
- };
+ ResultReceiver receiver = new ItemReceiver(mediaId, cb, mHandler);
try {
- Bundle data = new Bundle();
- data.putParcelable(SERVICE_DATA_RESULT_RECEIVER, receiver);
- sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, mediaId, data, null);
+ mServiceBinderWrapper.getMediaItem(mediaId, receiver, mCallbacksMessenger);
} catch (RemoteException e) {
- Log.i(TAG, "Remote error getting media item.");
+ Log.i(TAG, "Remote error getting media item: " + mediaId);
mHandler.post(new Runnable() {
@Override
public void run() {
@@ -1217,15 +1455,72 @@
}
}
- private void sendRequest(int what, Object obj, Bundle data, Messenger cbMessenger)
- throws RemoteException {
- Message msg = Message.obtain();
- msg.what = what;
- msg.arg1 = CLIENT_VERSION_CURRENT;
- msg.obj = obj;
- msg.setData(data);
- msg.replyTo = cbMessenger;
- mMessenger.send(msg);
+ @Override
+ public void onConnected() {
+ Bundle extras = MediaBrowserCompatApi21.getExtras(mBrowserObj);
+ if (extras == null) {
+ return;
+ }
+ IBinder serviceBinder = BundleCompat.getBinder(extras, EXTRA_MESSENGER_BINDER);
+ if (serviceBinder != null) {
+ mServiceBinderWrapper = new ServiceBinderWrapper(serviceBinder, mRootHints);
+ mCallbacksMessenger = new Messenger(mHandler);
+ mHandler.setCallbacksMessenger(mCallbacksMessenger);
+ try {
+ mServiceBinderWrapper.registerCallbackMessenger(mCallbacksMessenger);
+ } catch (RemoteException e) {
+ Log.i(TAG, "Remote error registering client messenger." );
+ }
+ }
+ }
+
+ @Override
+ public void onConnectionSuspended() {
+ mServiceBinderWrapper = null;
+ mCallbacksMessenger = null;
+ mHandler.setCallbacksMessenger(null);
+ }
+
+ @Override
+ public void onConnectionFailed() {
+ // Do noting
+ }
+
+ @Override
+ public void onServiceConnected(final Messenger callback, final String root,
+ final MediaSessionCompat.Token session, final Bundle extra) {
+ // This method will not be called.
+ }
+
+ @Override
+ public void onConnectionFailed(Messenger callback) {
+ // This method will not be called.
+ }
+
+ @Override
+ public void onLoadChildren(Messenger callback, String parentId, List list, Bundle options) {
+ if (mCallbacksMessenger != callback) {
+ return;
+ }
+
+ // Check that the subscription is still subscribed.
+ Subscription subscription = mSubscriptions.get(parentId);
+ if (subscription == null) {
+ if (DEBUG) {
+ Log.d(TAG, "onLoadChildren for id that isn't subscribed id=" + parentId);
+ }
+ return;
+ }
+
+ // Tell the app.
+ SubscriptionCallback subscriptionCallback = subscription.getCallback(options);
+ if (subscriptionCallback != null) {
+ if (options == null) {
+ subscriptionCallback.onChildrenLoaded(parentId, list);
+ } else {
+ subscriptionCallback.onChildrenLoaded(parentId, list, options);
+ }
+ }
}
}
@@ -1236,8 +1531,229 @@
}
@Override
- public void getItem(@NonNull String mediaId, @NonNull ItemCallback cb) {
+ public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
+ if (mServiceBinderWrapper == null) {
+ MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
+ } else {
+ super.getItem(mediaId, cb);
+ }
+ }
+ }
+
+ static class MediaBrowserImplApi24 extends MediaBrowserImplApi23 {
+ public MediaBrowserImplApi24(Context context, ComponentName serviceComponent,
+ ConnectionCallback callback, Bundle rootHints) {
+ super(context, serviceComponent, callback, rootHints);
+ }
+
+ @Override
+ public void subscribe(@NonNull String parentId, @NonNull Bundle options,
+ @NonNull SubscriptionCallback callback) {
+ if (options == null) {
+ MediaBrowserCompatApi21.subscribe(
+ mBrowserObj, parentId, callback.mSubscriptionCallbackObj);
+ } else {
+ MediaBrowserCompatApi24.subscribe(
+ mBrowserObj, parentId, options, callback.mSubscriptionCallbackObj);
+ }
+ }
+
+ @Override
+ public void unsubscribe(@NonNull String parentId, SubscriptionCallback callback) {
+ if (callback == null) {
+ MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
+ } else {
+ MediaBrowserCompatApi24.unsubscribe(mBrowserObj, parentId,
+ callback.mSubscriptionCallbackObj);
+ }
+ }
+
+ @Override
+ public void getItem(@NonNull final String mediaId, @NonNull final ItemCallback cb) {
MediaBrowserCompatApi23.getItem(mBrowserObj, mediaId, cb.mItemCallbackObj);
}
}
+
+ private static class Subscription {
+ private final List<SubscriptionCallback> mCallbacks;
+ private final List<Bundle> mOptionsList;
+
+ public Subscription() {
+ mCallbacks = new ArrayList();
+ mOptionsList = new ArrayList();
+ }
+
+ public boolean isEmpty() {
+ return mCallbacks.isEmpty();
+ }
+
+ public List<Bundle> getOptionsList() {
+ return mOptionsList;
+ }
+
+ public List<SubscriptionCallback> getCallbacks() {
+ return mCallbacks;
+ }
+
+ public SubscriptionCallback getCallback(Bundle options) {
+ for (int i = 0; i < mOptionsList.size(); ++i) {
+ if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
+ return mCallbacks.get(i);
+ }
+ }
+ return null;
+ }
+
+ public void putCallback(Bundle options, SubscriptionCallback callback) {
+ for (int i = 0; i < mOptionsList.size(); ++i) {
+ if (MediaBrowserCompatUtils.areSameOptions(mOptionsList.get(i), options)) {
+ mCallbacks.set(i, callback);
+ return;
+ }
+ }
+ mCallbacks.add(callback);
+ mOptionsList.add(options);
+ }
+ }
+
+ private static class CallbackHandler extends Handler {
+ private final WeakReference<MediaBrowserServiceCallbackImpl> mCallbackImplRef;
+ private WeakReference<Messenger> mCallbacksMessengerRef;
+
+ CallbackHandler(MediaBrowserServiceCallbackImpl callbackImpl) {
+ super();
+ mCallbackImplRef = new WeakReference<>(callbackImpl);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ if (mCallbacksMessengerRef == null || mCallbacksMessengerRef.get() == null ||
+ mCallbackImplRef.get() == null) {
+ return;
+ }
+ Bundle data = msg.getData();
+ data.setClassLoader(MediaSessionCompat.class.getClassLoader());
+ switch (msg.what) {
+ case SERVICE_MSG_ON_CONNECT:
+ mCallbackImplRef.get().onServiceConnected(mCallbacksMessengerRef.get(),
+ data.getString(DATA_MEDIA_ITEM_ID),
+ (MediaSessionCompat.Token) data.getParcelable(DATA_MEDIA_SESSION_TOKEN),
+ data.getBundle(DATA_ROOT_HINTS));
+ break;
+ case SERVICE_MSG_ON_CONNECT_FAILED:
+ mCallbackImplRef.get().onConnectionFailed(mCallbacksMessengerRef.get());
+ break;
+ case SERVICE_MSG_ON_LOAD_CHILDREN:
+ mCallbackImplRef.get().onLoadChildren(mCallbacksMessengerRef.get(),
+ data.getString(DATA_MEDIA_ITEM_ID),
+ data.getParcelableArrayList(DATA_MEDIA_ITEM_LIST),
+ data.getBundle(DATA_OPTIONS));
+ break;
+ default:
+ Log.w(TAG, "Unhandled message: " + msg
+ + "\n Client version: " + CLIENT_VERSION_CURRENT
+ + "\n Service version: " + msg.arg1);
+ }
+ }
+
+ void setCallbacksMessenger(Messenger callbacksMessenger) {
+ mCallbacksMessengerRef = new WeakReference<>(callbacksMessenger);
+ }
+ }
+
+ private static class ServiceBinderWrapper {
+ private Messenger mMessenger;
+ private Bundle mRootHints;
+
+ public ServiceBinderWrapper(IBinder target, Bundle rootHints) {
+ mMessenger = new Messenger(target);
+ mRootHints = rootHints;
+ }
+
+ void connect(Context context, Messenger callbacksMessenger)
+ throws RemoteException {
+ Bundle data = new Bundle();
+ data.putString(DATA_PACKAGE_NAME, context.getPackageName());
+ data.putBundle(DATA_ROOT_HINTS, mRootHints);
+ sendRequest(CLIENT_MSG_CONNECT, data, callbacksMessenger);
+ }
+
+ void disconnect(Messenger callbacksMessenger) throws RemoteException {
+ sendRequest(CLIENT_MSG_DISCONNECT, null, callbacksMessenger);
+ }
+
+ void addSubscription(String parentId, IBinder callbackToken, Bundle options,
+ Messenger callbacksMessenger)
+ throws RemoteException {
+ Bundle data = new Bundle();
+ data.putString(DATA_MEDIA_ITEM_ID, parentId);
+ BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+ data.putBundle(DATA_OPTIONS, options);
+ sendRequest(CLIENT_MSG_ADD_SUBSCRIPTION, data, callbacksMessenger);
+ }
+
+ void removeSubscription(String parentId, IBinder callbackToken,
+ Messenger callbacksMessenger)
+ throws RemoteException {
+ Bundle data = new Bundle();
+ data.putString(DATA_MEDIA_ITEM_ID, parentId);
+ BundleCompat.putBinder(data, DATA_CALLBACK_TOKEN, callbackToken);
+ sendRequest(CLIENT_MSG_REMOVE_SUBSCRIPTION, data, callbacksMessenger);
+ }
+
+ void getMediaItem(String mediaId, ResultReceiver receiver, Messenger callbacksMessenger)
+ throws RemoteException {
+ Bundle data = new Bundle();
+ data.putString(DATA_MEDIA_ITEM_ID, mediaId);
+ data.putParcelable(DATA_RESULT_RECEIVER, receiver);
+ sendRequest(CLIENT_MSG_GET_MEDIA_ITEM, data, callbacksMessenger);
+ }
+
+ void registerCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
+ Bundle data = new Bundle();
+ data.putBundle(DATA_ROOT_HINTS, mRootHints);
+ sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, data, callbackMessenger);
+ }
+
+ void unregisterCallbackMessenger(Messenger callbackMessenger) throws RemoteException {
+ sendRequest(CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER, null, callbackMessenger);
+ }
+
+ private void sendRequest(int what, Bundle data, Messenger cbMessenger)
+ throws RemoteException {
+ Message msg = Message.obtain();
+ msg.what = what;
+ msg.arg1 = CLIENT_VERSION_CURRENT;
+ msg.setData(data);
+ msg.replyTo = cbMessenger;
+ mMessenger.send(msg);
+ }
+ }
+
+ private static class ItemReceiver extends ResultReceiver {
+ private final String mMediaId;
+ private final ItemCallback mCallback;
+
+ ItemReceiver(String mediaId, ItemCallback callback, Handler handler) {
+ super(handler);
+ mMediaId = mediaId;
+ mCallback = callback;
+ }
+
+ @Override
+ protected void onReceiveResult(int resultCode, Bundle resultData) {
+ resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader());
+ if (resultCode != 0 || resultData == null
+ || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) {
+ mCallback.onError(mMediaId);
+ return;
+ }
+ Parcelable item = resultData.getParcelable(MediaBrowserServiceCompat.KEY_MEDIA_ITEM);
+ if (item instanceof MediaItem) {
+ mCallback.onItemLoaded((MediaItem) item);
+ } else {
+ mCallback.onError(mMediaId);
+ }
+ }
+ }
}
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompatUtils.java b/v4/java/android/support/v4/media/MediaBrowserCompatUtils.java
new file mode 100644
index 0000000..5db9eb4
--- /dev/null
+++ b/v4/java/android/support/v4/media/MediaBrowserCompatUtils.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.os.Bundle;
+
+/**
+ * @hide
+ */
+public class MediaBrowserCompatUtils {
+ public static boolean areSameOptions(Bundle options1, Bundle options2) {
+ if (options1 == options2) {
+ return true;
+ } else if (options1 == null) {
+ return options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1) == -1
+ && options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1) == -1;
+ } else if (options2 == null) {
+ return options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1) == -1
+ && options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1) == -1;
+ } else {
+ return options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1)
+ == options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1)
+ && options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1)
+ == options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+ }
+ }
+
+ public static boolean hasDuplicatedItems(Bundle options1, Bundle options2) {
+ int page1 = options1 == null ? -1 : options1.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ int page2 = options2 == null ? -1 :options2.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ int pageSize1 = options1 == null
+ ? -1 :options1.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+ int pageSize2 = options2 == null
+ ? -1 :options2.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+
+ int startIndex1, startIndex2, endIndex1, endIndex2;
+ if (page1 == -1 || pageSize1 == -1) {
+ startIndex1 = 0;
+ endIndex1 = Integer.MAX_VALUE;
+ } else {
+ startIndex1 = pageSize1 * page1;
+ endIndex1 = startIndex1 + pageSize1 - 1;
+ }
+
+ if (page2 == -1 || pageSize2 == -1) {
+ startIndex2 = 0;
+ endIndex2 = Integer.MAX_VALUE;
+ } else {
+ startIndex2 = pageSize2 * page2;
+ endIndex2 = startIndex2 + pageSize2 - 1;
+ }
+
+ if (startIndex1 <= startIndex2 && startIndex2 <= endIndex1) {
+ return true;
+ } else if (startIndex1 <= endIndex2 && endIndex2 <= endIndex1) {
+ return true;
+ }
+ return false;
+ }
+}
diff --git a/v4/java/android/support/v4/media/MediaBrowserProtocol.java b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
index 7c8feb3..7ebfe9b 100644
--- a/v4/java/android/support/v4/media/MediaBrowserProtocol.java
+++ b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
@@ -15,12 +15,26 @@
*/
package android.support.v4.media;
-/***
+/**
* Defines the communication protocol for media browsers and media browser services.
* @hide
*/
class MediaBrowserProtocol {
+ public static final String DATA_CALLBACK_TOKEN = "data_callback_token";
+ public static final String DATA_CALLING_UID = "data_calling_uid";
+ public static final String DATA_MEDIA_ITEM_ID = "data_media_item_id";
+ public static final String DATA_MEDIA_ITEM_LIST = "data_media_item_list";
+ public static final String DATA_MEDIA_SESSION_TOKEN = "data_media_session_token";
+ public static final String DATA_OPTIONS = "data_options";
+ public static final String DATA_PACKAGE_NAME = "data_package_name";
+ public static final String DATA_RESULT_RECEIVER = "data_result_receiver";
+ public static final String DATA_ROOT_HINTS = "data_root_hints";
+
+ public static final String EXTRA_CLIENT_VERSION = "extra_client_version";
+ public static final String EXTRA_SERVICE_VERSION = "extra_service_version";
+ public static final String EXTRA_MESSENGER_BINDER = "extra_messenger";
+
/**
* MediaBrowserCompat will check the version of the connected MediaBrowserServiceCompat,
* and it will not send messages if they are introduced in the higher version of the
@@ -39,10 +53,10 @@
* Sent after {@link MediaBrowserCompat#connect()} when the request has successfully
* completed.
* - arg1 : The service version
- * - obj : The root media item id
* - data
- * SERVICE_DATA_MEDIA_SESSION_TOKEN : Media session token
- * SERVICE_DATA_EXTRAS : An extras bundle which contains EXTRA_SERVICE_VERSION
+ * DATA_MEDIA_ITEM_ID : A string for the root media item id
+ * DATA_MEDIA_SESSION_TOKEN : Media session token
+ * DATA_ROOT_HINTS : An optional root hints bundle of service-specific arguments
*/
public static final int SERVICE_MSG_ON_CONNECT = 1;
@@ -56,20 +70,14 @@
/** (service v1)
* Sent when the list of children is loaded or updated.
* - arg1 : The service version
- * - obj : The parent media item id
* - data
- * SERVICE_DATA_MEDIA_ITEM_LIST : An array list for the media item children
+ * DATA_MEDIA_ITEM_ID : A string for the parent media item id
+ * DATA_MEDIA_ITEM_LIST : An array list for the media item children
+ * DATA_OPTIONS : A bundle of service-specific arguments sent from the media browse to
+ * the media browser service
*/
public static final int SERVICE_MSG_ON_LOAD_CHILDREN = 3;
- public static final String SERVICE_DATA_MEDIA_SESSION_TOKEN = "data_media_session_token";
- public static final String SERVICE_DATA_EXTRAS = "data_extras";
- public static final String SERVICE_DATA_MEDIA_ITEM_LIST = "data_media_item_list";
- public static final String SERVICE_DATA_RESULT_RECEIVER = "data_result_receiver";
-
- public static final String EXTRA_SERVICE_VERSION = "extra_service_version";
- public static final String EXTRA_MESSENGER_BINDER = "extra_messenger";
-
/**
* MediaBrowserServiceCompat will check the version of the MediaBrowserCompat, and it will not
* send messages if they are introduced in the higher version of the MediaBrowserCompat.
@@ -86,41 +94,67 @@
/** (client v1)
* Sent to connect to the media browse service compat.
* - arg1 : The client version
- * - obj : The package name
- * - data : An optional root hints bundle of service-specific arguments
- * - replayTo : Client messenger
+ * - data
+ * DATA_PACKAGE_NAME : A string for the package name of MediaBrowserCompat
+ * DATA_ROOT_HINTS : An optional root hints bundle of service-specific arguments
+ * - replyTo : Callback messenger
*/
public static final int CLIENT_MSG_CONNECT = 1;
/** (client v1)
* Sent to disconnect from the media browse service compat.
* - arg1 : The client version
- * - replayTo : Client messenger
+ * - replyTo : Callback messenger
*/
public static final int CLIENT_MSG_DISCONNECT = 2;
/** (client v1)
* Sent to subscribe for changes to the children of the specified media id.
* - arg1 : The client version
- * - obj : The media item id
- * - replayTo : Client messenger
+ * - data
+ * DATA_MEDIA_ITEM_ID : A string for a media item id
+ * DATA_OPTIONS : A bundle of service-specific arguments sent from the media browser to
+ * the media browser service
+ * DATA_CALLBACK_TOKEN : An IBinder of service-specific arguments sent from the media
+ * browser to the media browser service
+ * - replyTo : Callback messenger
*/
public static final int CLIENT_MSG_ADD_SUBSCRIPTION = 3;
/** (client v1)
* Sent to unsubscribe for changes to the children of the specified media id.
* - arg1 : The client version
- * - obj : The media item id
- * - replayTo : Client messenger
+ * - data
+ * DATA_MEDIA_ITEM_ID : A string for a media item id
+ * DATA_CALLBACK_TOKEN : An IBinder of service-specific arguments sent from the media
+ * browser to the media browser service
+ * - replyTo : Callback messenger
*/
public static final int CLIENT_MSG_REMOVE_SUBSCRIPTION = 4;
/** (client v1)
- * Sent to retrieves a specific media item from the connected service.
+ * Sent to retrieve a specific media item from the connected service.
* - arg1 : The client version
- * - obj : The media item id
* - data
- * SERVICE_DATA_RESULT_RECEIVER : Result receiver to get the result
+ * DATA_MEDIA_ITEM_ID : A string for a media item id
+ * DATA_RESULT_RECEIVER : Result receiver to get the result
+ * - replyTo : Callback messenger
*/
public static final int CLIENT_MSG_GET_MEDIA_ITEM = 5;
+
+ /** (client v1)
+ * Sent to register the client messenger
+ * - arg1 : The client version
+ * - data
+ * DATA_ROOT_HINTS : An optional root hints bundle of service-specific arguments
+ * - replyTo : Callback messenger
+ */
+ public static final int CLIENT_MSG_REGISTER_CALLBACK_MESSENGER = 6;
+
+ /** (client v1)
+ * Sent to unregister the client messenger
+ * - arg1 : The client version
+ * - replyTo : Callback messenger
+ */
+ public static final int CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER = 7;
}
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 205e7899..9163cd7 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -16,6 +16,30 @@
package android.support.v4.media;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_ADD_SUBSCRIPTION;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_CONNECT;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_DISCONNECT;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_GET_MEDIA_ITEM;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_REGISTER_CALLBACK_MESSENGER;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_REMOVE_SUBSCRIPTION;
+import static android.support.v4.media.MediaBrowserProtocol.CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_CALLBACK_TOKEN;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_CALLING_UID;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_ID;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_MEDIA_ITEM_LIST;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_MEDIA_SESSION_TOKEN;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_OPTIONS;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_PACKAGE_NAME;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_RESULT_RECEIVER;
+import static android.support.v4.media.MediaBrowserProtocol.DATA_ROOT_HINTS;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_CLIENT_VERSION;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_MESSENGER_BINDER;
+import static android.support.v4.media.MediaBrowserProtocol.EXTRA_SERVICE_VERSION;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_CONNECT_FAILED;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_MSG_ON_LOAD_CHILDREN;
+import static android.support.v4.media.MediaBrowserProtocol.SERVICE_VERSION_CURRENT;
+
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
@@ -28,28 +52,32 @@
import android.os.Messenger;
import android.os.Parcel;
import android.os.RemoteException;
+import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.app.BundleCompat;
import android.support.v4.media.session.MediaSessionCompat;
+import android.support.v4.os.BuildCompat;
import android.support.v4.os.ResultReceiver;
import android.support.v4.util.ArrayMap;
+import android.support.v4.util.Pair;
import android.text.TextUtils;
import android.util.Log;
import java.io.FileDescriptor;
import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
-import java.util.HashSet;
+import java.util.Collections;
+import java.util.HashMap;
import java.util.List;
-import static android.support.v4.media.MediaBrowserProtocol.*;
-
/**
* Base class for media browse services.
* <p>
* Media browse services enable applications to browse media content provided by an application
- * and ask the application to start playing it. They may also be used to control content that
+ * and ask the application to start playing it. They may also be used to control content that
* is already playing by way of a {@link MediaSessionCompat}.
* </p>
*
@@ -67,8 +95,8 @@
* </pre>
*/
public abstract class MediaBrowserServiceCompat extends Service {
- private static final String TAG = "MediaBrowserServiceCompat";
- private static final boolean DBG = false;
+ private static final String TAG = "MBServiceCompat";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
private MediaBrowserServiceImpl mImpl;
@@ -84,13 +112,24 @@
*/
public static final String KEY_MEDIA_ITEM = "media_item";
- private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap();
+ private static final int RESULT_FLAG_OPTION_NOT_HANDLED = 0x00000001;
+
+ /** @hide */
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef(flag=true, value = { RESULT_FLAG_OPTION_NOT_HANDLED })
+ private @interface ResultFlags { }
+
+ private final ArrayMap<IBinder, ConnectionRecord> mConnections = new ArrayMap<>();
+ private ConnectionRecord mCurConnection;
private final ServiceHandler mHandler = new ServiceHandler();
MediaSessionCompat.Token mSession;
interface MediaBrowserServiceImpl {
void onCreate();
IBinder onBind(Intent intent);
+ void setSessionToken(MediaSessionCompat.Token token);
+ void notifyChildrenChanged(final String parentId, final Bundle options);
+ Bundle getBrowserRootHints();
}
class MediaBrowserServiceImplBase implements MediaBrowserServiceImpl {
@@ -108,62 +147,291 @@
}
return null;
}
+
+ @Override
+ public void setSessionToken(final MediaSessionCompat.Token token) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder key : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(key);
+ try {
+ connection.callbacks.onConnect(connection.root.getRootId(), token,
+ connection.root.getExtras());
+ } catch (RemoteException e) {
+ Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
+ mConnections.remove(key);
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public void notifyChildrenChanged(@NonNull final String parentId, final Bundle options) {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ List<Pair<IBinder, Bundle>> callbackList =
+ connection.subscriptions.get(parentId);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (MediaBrowserCompatUtils.hasDuplicatedItems(
+ options, callback.second)) {
+ performLoadChildren(parentId, connection, callback.second);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+
+ @Override
+ public Bundle getBrowserRootHints() {
+ if (mCurConnection == null) {
+ throw new IllegalStateException("This should be called inside of onLoadChildren or"
+ + " onLoadItem methods");
+ }
+ return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints);
+ }
}
- class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl {
- private Object mServiceObj;
+ class MediaBrowserServiceImplApi21 implements MediaBrowserServiceImpl,
+ MediaBrowserServiceCompatApi21.ServiceCompatProxy {
+ Object mServiceObj;
+ Messenger mMessenger;
@Override
public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi21.createService();
- MediaBrowserServiceCompatApi21.onCreate(mServiceObj, new ServiceImplApi21());
+ mServiceObj = MediaBrowserServiceCompatApi21.createService(
+ MediaBrowserServiceCompat.this, this);
+ MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
}
@Override
public IBinder onBind(Intent intent) {
return MediaBrowserServiceCompatApi21.onBind(mServiceObj, intent);
}
- }
-
- class MediaBrowserServiceImplApi23 implements MediaBrowserServiceImpl {
- private Object mServiceObj;
@Override
- public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi23.createService();
- MediaBrowserServiceCompatApi23.onCreate(mServiceObj, new ServiceImplApi23());
+ public void setSessionToken(MediaSessionCompat.Token token) {
+ MediaBrowserServiceCompatApi21.setSessionToken(mServiceObj, token.getToken());
}
@Override
- public IBinder onBind(Intent intent) {
- return MediaBrowserServiceCompatApi23.onBind(mServiceObj, intent);
+ public void notifyChildrenChanged(final String parentId, final Bundle options) {
+ if (mMessenger == null) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ } else {
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ for (IBinder binder : mConnections.keySet()) {
+ ConnectionRecord connection = mConnections.get(binder);
+ List<Pair<IBinder, Bundle>> callbackList =
+ connection.subscriptions.get(parentId);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (MediaBrowserCompatUtils.hasDuplicatedItems(
+ options, callback.second)) {
+ performLoadChildren(parentId, connection, callback.second);
+ }
+ }
+ }
+ }
+ }
+ });
+ }
+ }
+
+ @Override
+ public Bundle getBrowserRootHints() {
+ if (mMessenger == null) {
+ // TODO: Handle getBrowserRootHints when connected with framework MediaBrowser.
+ return null;
+ }
+ if (mCurConnection == null) {
+ throw new IllegalStateException("This should be called inside of onLoadChildren or"
+ + " onLoadItem methods");
+ }
+ return mCurConnection.rootHints == null ? null : new Bundle(mCurConnection.rootHints);
+ }
+
+ @Override
+ public MediaBrowserServiceCompatApi21.BrowserRoot onGetRoot(
+ String clientPackageName, int clientUid, Bundle rootHints) {
+ Bundle rootExtras = null;
+ if (rootHints != null && rootHints.getInt(EXTRA_CLIENT_VERSION, 0) != 0) {
+ rootHints.remove(EXTRA_CLIENT_VERSION);
+ mMessenger = new Messenger(mHandler);
+ rootExtras = new Bundle();
+ rootExtras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
+ BundleCompat.putBinder(rootExtras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
+ }
+ BrowserRoot root = MediaBrowserServiceCompat.this.onGetRoot(
+ clientPackageName, clientUid, rootHints);
+ if (root == null) {
+ return null;
+ }
+ if (rootExtras == null) {
+ rootExtras = root.getExtras();
+ } else if (root.getExtras() != null) {
+ rootExtras.putAll(root.getExtras());
+ }
+ return new MediaBrowserServiceCompatApi21.BrowserRoot(
+ root.getRootId(), rootExtras);
+ }
+
+ @Override
+ public void onLoadChildren(String parentId,
+ final MediaBrowserServiceCompatApi21.ResultWrapper<List<Parcel>> resultWrapper) {
+ final Result<List<MediaBrowserCompat.MediaItem>> result
+ = new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
+ @Override
+ void onResultSent(List<MediaBrowserCompat.MediaItem> list, @ResultFlags int flags) {
+ List<Parcel> parcelList = null;
+ if (list != null) {
+ parcelList = new ArrayList<>();
+ for (MediaBrowserCompat.MediaItem item : list) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ }
+ resultWrapper.sendResult(parcelList);
+ }
+
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
+ MediaBrowserServiceCompat.this.onLoadChildren(parentId, result);
+ }
+ }
+
+ class MediaBrowserServiceImplApi23 extends MediaBrowserServiceImplApi21 implements
+ MediaBrowserServiceCompatApi23.ServiceCompatProxy {
+ @Override
+ public void onCreate() {
+ mServiceObj = MediaBrowserServiceCompatApi23.createService(
+ MediaBrowserServiceCompat.this, this);
+ MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
+ }
+
+ @Override
+ public void onLoadItem(String itemId,
+ final MediaBrowserServiceCompatApi21.ResultWrapper<Parcel> resultWrapper) {
+ final Result<MediaBrowserCompat.MediaItem> result
+ = new Result<MediaBrowserCompat.MediaItem>(itemId) {
+ @Override
+ void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flags) {
+ Parcel parcelItem = Parcel.obtain();
+ item.writeToParcel(parcelItem, 0);
+ resultWrapper.sendResult(parcelItem);
+ }
+
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
+ MediaBrowserServiceCompat.this.onLoadItem(itemId, result);
+ }
+ }
+
+ class MediaBrowserServiceImplApi24 extends MediaBrowserServiceImplApi23 implements
+ MediaBrowserServiceCompatApi24.ServiceCompatProxy {
+ @Override
+ public void onCreate() {
+ mServiceObj = MediaBrowserServiceCompatApi24.createService(
+ MediaBrowserServiceCompat.this, this);
+ MediaBrowserServiceCompatApi21.onCreate(mServiceObj);
+ }
+
+ @Override
+ public void notifyChildrenChanged(final String parentId, final Bundle options) {
+ if (options == null) {
+ MediaBrowserServiceCompatApi21.notifyChildrenChanged(mServiceObj, parentId);
+ } else {
+ MediaBrowserServiceCompatApi24.notifyChildrenChanged(mServiceObj, parentId,
+ options);
+ }
+ }
+
+ @Override
+ public void onLoadChildren(String parentId,
+ final MediaBrowserServiceCompatApi24.ResultWrapper resultWrapper, Bundle options) {
+ final Result<List<MediaBrowserCompat.MediaItem>> result
+ = new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
+ @Override
+ void onResultSent(List<MediaBrowserCompat.MediaItem> list, @ResultFlags int flags) {
+ List<Parcel> parcelList = null;
+ if (list != null) {
+ parcelList = new ArrayList<>();
+ for (MediaBrowserCompat.MediaItem item : list) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ }
+ resultWrapper.sendResult(parcelList, flags);
+ }
+
+ @Override
+ public void detach() {
+ resultWrapper.detach();
+ }
+ };
+ MediaBrowserServiceCompat.this.onLoadChildren(parentId, result, options);
+ }
+
+ @Override
+ public Bundle getBrowserRootHints() {
+ return MediaBrowserServiceCompatApi24.getBrowserRootHints(mServiceObj);
}
}
private final class ServiceHandler extends Handler {
- private final ServiceImpl mServiceImpl = new ServiceImpl();
+ private final ServiceBinderImpl mServiceBinderImpl = new ServiceBinderImpl();
@Override
public void handleMessage(Message msg) {
+ Bundle data = msg.getData();
switch (msg.what) {
case CLIENT_MSG_CONNECT:
- mServiceImpl.connect((String) msg.obj, msg.getData(),
+ mServiceBinderImpl.connect(data.getString(DATA_PACKAGE_NAME),
+ data.getInt(DATA_CALLING_UID), data.getBundle(DATA_ROOT_HINTS),
new ServiceCallbacksCompat(msg.replyTo));
break;
case CLIENT_MSG_DISCONNECT:
- mServiceImpl.disconnect(new ServiceCallbacksCompat(msg.replyTo));
+ mServiceBinderImpl.disconnect(new ServiceCallbacksCompat(msg.replyTo));
break;
case CLIENT_MSG_ADD_SUBSCRIPTION:
- mServiceImpl.addSubscription((String) msg.obj,
+ mServiceBinderImpl.addSubscription(data.getString(DATA_MEDIA_ITEM_ID),
+ BundleCompat.getBinder(data, DATA_CALLBACK_TOKEN),
+ data.getBundle(DATA_OPTIONS),
new ServiceCallbacksCompat(msg.replyTo));
break;
case CLIENT_MSG_REMOVE_SUBSCRIPTION:
- mServiceImpl.removeSubscription((String) msg.obj,
+ mServiceBinderImpl.removeSubscription(data.getString(DATA_MEDIA_ITEM_ID),
+ BundleCompat.getBinder(data, DATA_CALLBACK_TOKEN),
new ServiceCallbacksCompat(msg.replyTo));
break;
case CLIENT_MSG_GET_MEDIA_ITEM:
- mServiceImpl.getMediaItem((String) msg.obj, (ResultReceiver) msg.getData()
- .getParcelable(SERVICE_DATA_RESULT_RECEIVER));
+ mServiceBinderImpl.getMediaItem(data.getString(DATA_MEDIA_ITEM_ID),
+ (ResultReceiver) data.getParcelable(DATA_RESULT_RECEIVER),
+ new ServiceCallbacksCompat(msg.replyTo));
+ break;
+ case CLIENT_MSG_REGISTER_CALLBACK_MESSENGER:
+ mServiceBinderImpl.registerCallbacks(new ServiceCallbacksCompat(msg.replyTo),
+ data.getBundle(DATA_ROOT_HINTS));
+ break;
+ case CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER:
+ mServiceBinderImpl.unregisterCallbacks(new ServiceCallbacksCompat(msg.replyTo));
break;
default:
Log.w(TAG, "Unhandled message: " + msg
@@ -172,6 +440,16 @@
}
}
+ @Override
+ public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
+ // Binder.getCallingUid() in handleMessage will return the uid of this process.
+ // In order to get the right calling uid, Binder.getCallingUid() should be called here.
+ Bundle data = msg.getData();
+ data.setClassLoader(MediaBrowserCompat.class.getClassLoader());
+ data.putInt(DATA_CALLING_UID, Binder.getCallingUid());
+ return super.sendMessageAtTime(msg, uptimeMillis);
+ }
+
public void postOrRun(Runnable r) {
if (Thread.currentThread() == getLooper().getThread()) {
r.run();
@@ -179,10 +457,6 @@
post(r);
}
}
-
- public ServiceImpl getServiceImpl() {
- return mServiceImpl;
- }
}
/**
@@ -193,17 +467,17 @@
Bundle rootHints;
ServiceCallbacks callbacks;
BrowserRoot root;
- HashSet<String> subscriptions = new HashSet();
+ HashMap<String, List<Pair<IBinder, Bundle>>> subscriptions = new HashMap();
}
/**
* Completion handler for asynchronous callback methods in {@link MediaBrowserServiceCompat}.
* <p>
* Each of the methods that takes one of these to send the result must call
- * {@link #sendResult} to respond to the caller with the given results. If those
+ * {@link #sendResult} to respond to the caller with the given results. If those
* functions return without calling {@link #sendResult}, they must instead call
* {@link #detach} before returning, and then may call {@link #sendResult} when
- * they are done. If more than one of those methods is called, an exception will
+ * they are done. If more than one of those methods is called, an exception will
* be thrown.
*
* @see MediaBrowserServiceCompat#onLoadChildren
@@ -213,6 +487,7 @@
private Object mDebug;
private boolean mDetachCalled;
private boolean mSendResultCalled;
+ private int mFlags;
Result(Object debug) {
mDebug = debug;
@@ -226,7 +501,7 @@
throw new IllegalStateException("sendResult() called twice for: " + mDebug);
}
mSendResultCalled = true;
- onResultSent(result);
+ onResultSent(result, mFlags);
}
/**
@@ -249,19 +524,22 @@
return mDetachCalled || mSendResultCalled;
}
+ void setFlags(@ResultFlags int flags) {
+ mFlags = flags;
+ }
+
/**
* Called when the result is sent, after assertions about not being called twice
* have happened.
*/
- void onResultSent(T result) {
+ void onResultSent(T result, @ResultFlags int flags) {
}
}
- private class ServiceImpl {
- public void connect(final String pkg, final Bundle rootHints,
+ private class ServiceBinderImpl {
+ public void connect(final String pkg, final int uid, final Bundle rootHints,
final ServiceCallbacks callbacks) {
- final int uid = Binder.getCallingUid();
if (!isValidPackage(pkg, uid)) {
throw new IllegalArgumentException("Package/uid mismatch: uid=" + uid
+ " package=" + pkg);
@@ -272,7 +550,7 @@
public void run() {
final IBinder b = callbacks.asBinder();
- // Clear out the old subscriptions. We are getting new ones.
+ // Clear out the old subscriptions. We are getting new ones.
mConnections.remove(b);
final ConnectionRecord connection = new ConnectionRecord();
@@ -316,7 +594,7 @@
public void run() {
final IBinder b = callbacks.asBinder();
- // Clear out the old subscriptions. We are getting new ones.
+ // Clear out the old subscriptions. We are getting new ones.
final ConnectionRecord old = mConnections.remove(b);
if (old != null) {
// TODO
@@ -325,8 +603,8 @@
});
}
-
- public void addSubscription(final String id, final ServiceCallbacks callbacks) {
+ public void addSubscription(final String id, final IBinder token, final Bundle options,
+ final ServiceCallbacks callbacks) {
mHandler.postOrRun(new Runnable() {
@Override
public void run() {
@@ -340,12 +618,13 @@
return;
}
- MediaBrowserServiceCompat.this.addSubscription(id, connection);
+ MediaBrowserServiceCompat.this.addSubscription(id, connection, token, options);
}
});
}
- public void removeSubscription(final String id, final ServiceCallbacks callbacks) {
+ public void removeSubscription(final String id, final IBinder token,
+ final ServiceCallbacks callbacks) {
mHandler.postOrRun(new Runnable() {
@Override
public void run() {
@@ -357,7 +636,8 @@
+ id);
return;
}
- if (!connection.subscriptions.remove(id)) {
+ if (!MediaBrowserServiceCompat.this.removeSubscription(
+ id, connection, token)) {
Log.w(TAG, "removeSubscription called for " + id
+ " which is not subscribed");
}
@@ -365,7 +645,8 @@
});
}
- public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
+ public void getMediaItem(final String mediaId, final ResultReceiver receiver,
+ final ServiceCallbacks callbacks) {
if (TextUtils.isEmpty(mediaId) || receiver == null) {
return;
}
@@ -373,62 +654,44 @@
mHandler.postOrRun(new Runnable() {
@Override
public void run() {
- performLoadItem(mediaId, receiver);
+ final IBinder b = callbacks.asBinder();
+
+ ConnectionRecord connection = mConnections.get(b);
+ if (connection == null) {
+ Log.w(TAG, "getMediaItem for callback that isn't registered id=" + mediaId);
+ return;
+ }
+ performLoadItem(mediaId, connection, receiver);
}
});
}
- }
- private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImplApi21 {
- final ServiceImpl mServiceImpl;
-
- ServiceImplApi21() {
- mServiceImpl = mHandler.getServiceImpl();
- }
-
- @Override
- public void connect(final String pkg, final Bundle rootHints,
- final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
- mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
- }
-
- @Override
- public void disconnect(final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
- mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
- }
-
-
- @Override
- public void addSubscription(
- final String id, final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
- mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
- }
-
- @Override
- public void removeSubscription(final String id,
- final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
- mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
- }
- }
-
- private class ServiceImplApi23 extends ServiceImplApi21
- implements MediaBrowserServiceCompatApi23.ServiceImplApi23 {
- @Override
- public void getMediaItem(final String mediaId,
- final MediaBrowserServiceCompatApi23.ItemCallback cb) {
- ResultReceiver receiverCompat = new ResultReceiver(mHandler) {
+ // Used when {@link MediaBrowserProtocol#EXTRA_MESSENGER_BINDER} is used.
+ public void registerCallbacks(final ServiceCallbacks callbacks, final Bundle rootHints) {
+ mHandler.postOrRun(new Runnable() {
@Override
- protected void onReceiveResult(int resultCode, Bundle resultData) {
- MediaBrowserCompat.MediaItem item = resultData.getParcelable(KEY_MEDIA_ITEM);
- Parcel itemParcel = null;
- if (item != null) {
- itemParcel = Parcel.obtain();
- item.writeToParcel(itemParcel, 0);
- }
- cb.onItemLoaded(resultCode, resultData, itemParcel);
+ public void run() {
+ final IBinder b = callbacks.asBinder();
+ // Clear out the old subscriptions. We are getting new ones.
+ mConnections.remove(b);
+
+ final ConnectionRecord connection = new ConnectionRecord();
+ connection.callbacks = callbacks;
+ connection.rootHints = rootHints;
+ mConnections.put(b, connection);
}
- };
- mServiceImpl.getMediaItem(mediaId, receiverCompat);
+ });
+ }
+
+ // Used when {@link MediaBrowserProtocol#EXTRA_MESSENGER_BINDER} is used.
+ public void unregisterCallbacks(final ServiceCallbacks callbacks) {
+ mHandler.postOrRun(new Runnable() {
+ @Override
+ public void run() {
+ final IBinder b = callbacks.asBinder();
+ mConnections.remove(b);
+ }
+ });
}
}
@@ -437,7 +700,7 @@
void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
throws RemoteException;
void onConnectFailed() throws RemoteException;
- void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list)
+ void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list, Bundle options)
throws RemoteException;
}
@@ -459,83 +722,43 @@
}
extras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
Bundle data = new Bundle();
- data.putParcelable(SERVICE_DATA_MEDIA_SESSION_TOKEN, session);
- data.putBundle(SERVICE_DATA_EXTRAS, extras);
- sendRequest(SERVICE_MSG_ON_CONNECT, root, data);
+ data.putString(DATA_MEDIA_ITEM_ID, root);
+ data.putParcelable(DATA_MEDIA_SESSION_TOKEN, session);
+ data.putBundle(DATA_ROOT_HINTS, extras);
+ sendRequest(SERVICE_MSG_ON_CONNECT, data);
}
public void onConnectFailed() throws RemoteException {
- sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null, null);
+ sendRequest(SERVICE_MSG_ON_CONNECT_FAILED, null);
}
- public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list)
- throws RemoteException {
- Bundle data = null;
+ public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list,
+ Bundle options) throws RemoteException {
+ Bundle data = new Bundle();
+ data.putString(DATA_MEDIA_ITEM_ID, mediaId);
+ data.putBundle(DATA_OPTIONS, options);
if (list != null) {
- data = new Bundle();
- data.putParcelableArrayList(SERVICE_DATA_MEDIA_ITEM_LIST,
+ data.putParcelableArrayList(DATA_MEDIA_ITEM_LIST,
list instanceof ArrayList ? (ArrayList) list : new ArrayList<>(list));
}
- sendRequest(SERVICE_MSG_ON_LOAD_CHILDREN, mediaId, data);
+ sendRequest(SERVICE_MSG_ON_LOAD_CHILDREN, data);
}
- private void sendRequest(int what, Object obj, Bundle data)
- throws RemoteException {
+ private void sendRequest(int what, Bundle data) throws RemoteException {
Message msg = Message.obtain();
msg.what = what;
msg.arg1 = SERVICE_VERSION_CURRENT;
- msg.obj = obj;
msg.setData(data);
mCallbacks.send(msg);
}
}
- private class ServiceCallbacksApi21 implements ServiceCallbacks {
- final MediaBrowserServiceCompatApi21.ServiceCallbacks mCallbacks;
- Messenger mMessenger;
-
- ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
- mCallbacks = callbacks;
- }
-
- public IBinder asBinder() {
- return mCallbacks.asBinder();
- }
-
- public void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
- throws RemoteException {
- if (extras == null) {
- extras = new Bundle();
- }
- mMessenger = new Messenger(mHandler);
- BundleCompat.putBinder(extras, EXTRA_MESSENGER_BINDER, mMessenger.getBinder());
- extras.putInt(EXTRA_SERVICE_VERSION, SERVICE_VERSION_CURRENT);
- mCallbacks.onConnect(root, session.getToken(), extras);
- }
-
- public void onConnectFailed() throws RemoteException {
- mCallbacks.onConnectFailed();
- }
-
- public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list)
- throws RemoteException {
- List<Parcel> parcelList = null;
- if (list != null) {
- parcelList = new ArrayList<>();
- for (MediaBrowserCompat.MediaItem item : list) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
- }
- }
- mCallbacks.onLoadChildren(mediaId, parcelList);
- }
- }
-
@Override
public void onCreate() {
super.onCreate();
- if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 24 || BuildCompat.isAtLeastN()) {
+ mImpl = new MediaBrowserServiceImplApi24();
+ } else if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();
@@ -572,6 +795,9 @@
* root id for browsing, or null if none. The contents of this
* bundle may affect the information returned when browsing.
* @return The {@link BrowserRoot} for accessing this app's content or null.
+ * @see BrowserRoot#EXTRA_RECENT
+ * @see BrowserRoot#EXTRA_OFFLINE
+ * @see BrowserRoot#EXTRA_SUGGESTED
*/
public abstract @Nullable BrowserRoot onGetRoot(@NonNull String clientPackageName,
int clientUid, @Nullable Bundle rootHints);
@@ -595,6 +821,33 @@
@NonNull Result<List<MediaBrowserCompat.MediaItem>> result);
/**
+ * Called to get information about the children of a media item.
+ * <p>
+ * Implementations must call {@link Result#sendResult result.sendResult}
+ * with the list of children. If loading the children will be an expensive
+ * operation that should be performed on another thread,
+ * {@link Result#detach result.detach} may be called before returning from
+ * this function, and then {@link Result#sendResult result.sendResult}
+ * called when the loading is complete.
+ *
+ * @param parentId The id of the parent media item whose children are to be
+ * queried.
+ * @param result The Result to send the list of children to, or null if the
+ * id is invalid.
+ * @param options A bundle of service-specific arguments sent from the media
+ * browse. The information returned through the result should be
+ * affected by the contents of this bundle.
+ */
+ public void onLoadChildren(@NonNull String parentId,
+ @NonNull Result<List<MediaBrowserCompat.MediaItem>> result, @NonNull Bundle options) {
+ // To support backward compatibility, when the implementation of MediaBrowserService doesn't
+ // override onLoadChildren() with options, onLoadChildren() without options will be used
+ // instead, and the options will be applied in the implementation of result.onResultSent().
+ result.setFlags(RESULT_FLAG_OPTION_NOT_HANDLED);
+ onLoadChildren(parentId, result);
+ }
+
+ /**
* Called to get information about a specific media item.
* <p>
* Implementations must call {@link Result#sendResult result.sendResult}. If
@@ -605,8 +858,7 @@
* <p>
* The default implementation sends a null result.
*
- * @param itemId The id for the specific
- * {@link MediaBrowserCompat.MediaItem}.
+ * @param itemId The id for the specific {@link MediaBrowserCompat.MediaItem}.
* @param result The Result to send the item to, or null if the id is
* invalid.
*/
@@ -622,7 +874,7 @@
*
* @param token The token for the service's {@link MediaSessionCompat}.
*/
- public void setSessionToken(final MediaSessionCompat.Token token) {
+ public void setSessionToken(MediaSessionCompat.Token token) {
if (token == null) {
throw new IllegalArgumentException("Session token may not be null.");
}
@@ -630,21 +882,7 @@
throw new IllegalStateException("The session token has already been set.");
}
mSession = token;
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (IBinder key : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(key);
- try {
- connection.callbacks.onConnect(connection.root.getRootId(), token,
- connection.root.getExtras());
- } catch (RemoteException e) {
- Log.w(TAG, "Connection for " + connection.pkg + " is no longer valid.");
- mConnections.remove(key);
- }
- }
- }
- });
+ mImpl.setSessionToken(token);
}
/**
@@ -656,6 +894,25 @@
}
/**
+ * Gets the root hints sent from the currently connected {@link MediaBrowserCompat}.
+ * The root hints are service-specific arguments included in an optional bundle sent to the
+ * media browser service when connecting and retrieving the root id for browsing, or null if
+ * none. The contents of this bundle may affect the information returned when browsing.
+ * <p>
+ * Note that this will return null when connected to {@link android.media.browse.MediaBrowser}
+ * and running on API 23 or lower.
+ *
+ * @throws IllegalStateException If this method is called outside of {@link #onLoadChildren}
+ * or {@link #onLoadItem}
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_RECENT
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_OFFLINE
+ * @see MediaBrowserServiceCompat.BrowserRoot#EXTRA_SUGGESTED
+ */
+ public final Bundle getBrowserRootHints() {
+ return mImpl.getBrowserRootHints();
+ }
+
+ /**
* Notifies all connected media browsers that the children of
* the specified parent id have changed in some way.
* This will cause browsers to fetch subscribed content again.
@@ -663,21 +920,32 @@
* @param parentId The id of the parent media item whose
* children changed.
*/
- public void notifyChildrenChanged(@NonNull final String parentId) {
+ public void notifyChildrenChanged(@NonNull String parentId) {
if (parentId == null) {
throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
}
- mHandler.post(new Runnable() {
- @Override
- public void run() {
- for (IBinder binder : mConnections.keySet()) {
- ConnectionRecord connection = mConnections.get(binder);
- if (connection.subscriptions.contains(parentId)) {
- performLoadChildren(parentId, connection);
- }
- }
- }
- });
+ mImpl.notifyChildrenChanged(parentId, null);
+ }
+
+ /**
+ * Notifies all connected media browsers that the children of
+ * the specified parent id have changed in some way.
+ * This will cause browsers to fetch subscribed content again.
+ *
+ * @param parentId The id of the parent media item whose
+ * children changed.
+ * @param options A bundle of service-specific arguments to send
+ * to the media browse. The contents of this bundle may
+ * contain the information about the change.
+ */
+ public void notifyChildrenChanged(@NonNull String parentId, @NonNull Bundle options) {
+ if (parentId == null) {
+ throw new IllegalArgumentException("parentId cannot be null in notifyChildrenChanged");
+ }
+ if (options == null) {
+ throw new IllegalArgumentException("options cannot be null in notifyChildrenChanged");
+ }
+ mImpl.notifyChildrenChanged(parentId, options);
}
/**
@@ -701,12 +969,46 @@
/**
* Save the subscription and if it is a new subscription send the results.
*/
- private void addSubscription(String id, ConnectionRecord connection) {
+ private void addSubscription(String id, ConnectionRecord connection, IBinder token,
+ Bundle options) {
// Save the subscription
- connection.subscriptions.add(id);
-
+ List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
+ if (callbackList == null) {
+ callbackList = new ArrayList<>();
+ }
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (token == callback.first
+ && MediaBrowserCompatUtils.areSameOptions(options, callback.second)) {
+ return;
+ }
+ }
+ callbackList.add(new Pair<>(token, options));
+ connection.subscriptions.put(id, callbackList);
// send the results
- performLoadChildren(id, connection);
+ performLoadChildren(id, connection, options);
+ }
+
+ /**
+ * Remove the subscription.
+ */
+ private boolean removeSubscription(String id, ConnectionRecord connection, IBinder token) {
+ if (token == null) {
+ return connection.subscriptions.remove(id) != null;
+ }
+ boolean removed = false;
+ List<Pair<IBinder, Bundle>> callbackList = connection.subscriptions.get(id);
+ if (callbackList != null) {
+ for (Pair<IBinder, Bundle> callback : callbackList) {
+ if (token == callback.first) {
+ removed = true;
+ callbackList.remove(callback);
+ }
+ }
+ if (callbackList.size() == 0) {
+ connection.subscriptions.remove(id);
+ }
+ }
+ return removed;
}
/**
@@ -714,21 +1016,25 @@
* <p>
* Callers must make sure that this connection is still connected.
*/
- private void performLoadChildren(final String parentId, final ConnectionRecord connection) {
+ private void performLoadChildren(final String parentId, final ConnectionRecord connection,
+ final Bundle options) {
final Result<List<MediaBrowserCompat.MediaItem>> result
= new Result<List<MediaBrowserCompat.MediaItem>>(parentId) {
@Override
- void onResultSent(List<MediaBrowserCompat.MediaItem> list) {
+ void onResultSent(List<MediaBrowserCompat.MediaItem> list, @ResultFlags int flags) {
if (mConnections.get(connection.callbacks.asBinder()) != connection) {
- if (DBG) {
+ if (DEBUG) {
Log.d(TAG, "Not sending onLoadChildren result for connection that has"
+ " been disconnected. pkg=" + connection.pkg + " id=" + parentId);
}
return;
}
+ List<MediaBrowserCompat.MediaItem> filteredList =
+ (flags & RESULT_FLAG_OPTION_NOT_HANDLED) != 0
+ ? applyOptions(list, options) : list;
try {
- connection.callbacks.onLoadChildren(parentId, list);
+ connection.callbacks.onLoadChildren(parentId, filteredList, options);
} catch (RemoteException ex) {
// The other side is in the process of crashing.
Log.w(TAG, "Calling onLoadChildren() failed for id=" + parentId
@@ -737,7 +1043,13 @@
}
};
- onLoadChildren(parentId, result);
+ mCurConnection = connection;
+ if (options == null) {
+ onLoadChildren(parentId, result);
+ } else {
+ onLoadChildren(parentId, result, options);
+ }
+ mCurConnection = null;
if (!result.isDone()) {
throw new IllegalStateException("onLoadChildren must call detach() or sendResult()"
@@ -745,18 +1057,42 @@
}
}
- private void performLoadItem(String itemId, final ResultReceiver receiver) {
+ private List<MediaBrowserCompat.MediaItem> applyOptions(List<MediaBrowserCompat.MediaItem> list,
+ final Bundle options) {
+ if (list == null) {
+ return null;
+ }
+ int page = options.getInt(MediaBrowserCompat.EXTRA_PAGE, -1);
+ int pageSize = options.getInt(MediaBrowserCompat.EXTRA_PAGE_SIZE, -1);
+ if (page == -1 && pageSize == -1) {
+ return list;
+ }
+ int fromIndex = pageSize * page;
+ int toIndex = fromIndex + pageSize;
+ if (page < 0 || pageSize < 1 || fromIndex >= list.size()) {
+ return Collections.EMPTY_LIST;
+ }
+ if (toIndex > list.size()) {
+ toIndex = list.size();
+ }
+ return list.subList(fromIndex, toIndex);
+ }
+
+ private void performLoadItem(String itemId, ConnectionRecord connection,
+ final ResultReceiver receiver) {
final Result<MediaBrowserCompat.MediaItem> result =
new Result<MediaBrowserCompat.MediaItem>(itemId) {
- @Override
- void onResultSent(MediaBrowserCompat.MediaItem item) {
- Bundle bundle = new Bundle();
- bundle.putParcelable(KEY_MEDIA_ITEM, item);
- receiver.send(0, bundle);
- }
- };
+ @Override
+ void onResultSent(MediaBrowserCompat.MediaItem item, @ResultFlags int flags) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(KEY_MEDIA_ITEM, item);
+ receiver.send(0, bundle);
+ }
+ };
- MediaBrowserServiceCompat.this.onLoadItem(itemId, result);
+ mCurConnection = connection;
+ onLoadItem(itemId, result);
+ mCurConnection = null;
if (!result.isDone()) {
throw new IllegalStateException("onLoadItem must call detach() or sendResult()"
@@ -769,6 +1105,57 @@
* when first connected.
*/
public static final class BrowserRoot {
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for recently played media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are recently played.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_OFFLINE
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_RECENT = "android.service.media.extra.RECENT";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for offline media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving media items that are can be played without an
+ * internet connection.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_SUGGESTED
+ */
+ public static final String EXTRA_OFFLINE = "android.service.media.extra.OFFLINE";
+
+ /**
+ * The lookup key for a boolean that indicates whether the browser service should return a
+ * browser root for suggested media items.
+ *
+ * <p>When creating a media browser for a given media browser service, this key can be
+ * supplied as a root hint for retrieving the media items suggested by the media browser
+ * service. The list of media items passed in {@link android.support.v4.media.MediaBrowserCompat.SubscriptionCallback#onChildrenLoaded(String, List)}
+ * is considered ordered by relevance, first being the top suggestion.
+ * If the media browser service can provide such media items, the implementation must return
+ * the key in the root hint when {@link #onGetRoot(String, int, Bundle)} is called back.
+ *
+ * <p>The root hint may contain multiple keys.
+ *
+ * @see #EXTRA_RECENT
+ * @see #EXTRA_OFFLINE
+ */
+ public static final String EXTRA_SUGGESTED = "android.service.media.extra.SUGGESTED";
+
final private String mRootId;
final private Bundle mExtras;
@@ -794,7 +1181,7 @@
}
/**
- * Gets any extras about the brwoser service.
+ * Gets any extras about the browser service.
*/
public Bundle getExtras() {
return mExtras;
diff --git a/v4/java/android/support/v4/media/MediaDescriptionCompat.java b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
index 9d12713..c9b627f 100644
--- a/v4/java/android/support/v4/media/MediaDescriptionCompat.java
+++ b/v4/java/android/support/v4/media/MediaDescriptionCompat.java
@@ -31,6 +31,21 @@
*/
public final class MediaDescriptionCompat implements Parcelable {
/**
+ * Custom key to store a media URI on API 21-22 devices (before it became part of the
+ * framework class) when parceling/converting to and from framework objects.
+ *
+ * @hide
+ */
+ public static final String DESCRIPTION_KEY_MEDIA_URI =
+ "android.support.v4.media.description.MEDIA_URI";
+ /**
+ * Custom key to store whether the original Bundle provided by the developer was null
+ *
+ * @hide
+ */
+ public static final String DESCRIPTION_KEY_NULL_BUNDLE_FLAG =
+ "android.support.v4.media.description.NULL_BUNDLE_FLAG";
+ /**
* A unique persistent id for the content or null.
*/
private final String mMediaId;
@@ -185,6 +200,7 @@
dest.writeParcelable(mIcon, flags);
dest.writeParcelable(mIconUri, flags);
dest.writeBundle(mExtras);
+ dest.writeParcelable(mMediaUri, flags);
} else {
MediaDescriptionCompatApi21.writeToParcel(getMediaDescription(), dest, flags);
}
@@ -217,7 +233,19 @@
MediaDescriptionCompatApi21.Builder.setDescription(bob, mDescription);
MediaDescriptionCompatApi21.Builder.setIconBitmap(bob, mIcon);
MediaDescriptionCompatApi21.Builder.setIconUri(bob, mIconUri);
- MediaDescriptionCompatApi21.Builder.setExtras(bob, mExtras);
+ // Media URI was not added until API 23, so add it to the Bundle of extras to
+ // ensure the data is not lost - this ensures that
+ // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)) returns
+ // an equivalent MediaDescriptionCompat on all API levels
+ Bundle extras = mExtras;
+ if (Build.VERSION.SDK_INT < 23 && mMediaUri != null) {
+ if (extras == null) {
+ extras = new Bundle();
+ extras.putBoolean(DESCRIPTION_KEY_NULL_BUNDLE_FLAG, true);
+ }
+ extras.putParcelable(DESCRIPTION_KEY_MEDIA_URI, mMediaUri);
+ }
+ MediaDescriptionCompatApi21.Builder.setExtras(bob, extras);
if (Build.VERSION.SDK_INT >= 23) {
MediaDescriptionCompatApi23.Builder.setMediaUri(bob, mMediaUri);
}
@@ -250,8 +278,27 @@
bob.setDescription(MediaDescriptionCompatApi21.getDescription(descriptionObj));
bob.setIconBitmap(MediaDescriptionCompatApi21.getIconBitmap(descriptionObj));
bob.setIconUri(MediaDescriptionCompatApi21.getIconUri(descriptionObj));
- bob.setExtras(MediaDescriptionCompatApi21.getExtras(descriptionObj));
- if (Build.VERSION.SDK_INT >= 23) {
+ Bundle extras = MediaDescriptionCompatApi21.getExtras(descriptionObj);
+ Uri mediaUri = extras == null ? null :
+ (Uri) extras.getParcelable(DESCRIPTION_KEY_MEDIA_URI);
+ if (mediaUri != null) {
+ if (extras.containsKey(DESCRIPTION_KEY_NULL_BUNDLE_FLAG) && extras.size() == 2) {
+ // The extras were only created for the media URI, so we set it back to null to
+ // ensure mediaDescriptionCompat.getExtras() equals
+ // fromMediaDescription(getMediaDescription(mediaDescriptionCompat)).getExtras()
+ extras = null;
+ } else {
+ // Remove media URI keys to ensure mediaDescriptionCompat.getExtras().keySet()
+ // equals fromMediaDescription(getMediaDescription(mediaDescriptionCompat))
+ // .getExtras().keySet()
+ extras.remove(DESCRIPTION_KEY_MEDIA_URI);
+ extras.remove(DESCRIPTION_KEY_NULL_BUNDLE_FLAG);
+ }
+ }
+ bob.setExtras(extras);
+ if (mediaUri != null) {
+ bob.setMediaUri(mediaUri);
+ } else if (Build.VERSION.SDK_INT >= 23) {
bob.setMediaUri(MediaDescriptionCompatApi23.getMediaUri(descriptionObj));
}
MediaDescriptionCompat descriptionCompat = bob.build();
diff --git a/v4/java/android/support/v4/media/MediaMetadataCompat.java b/v4/java/android/support/v4/media/MediaMetadataCompat.java
index 0d07826..4cb2c65 100644
--- a/v4/java/android/support/v4/media/MediaMetadataCompat.java
+++ b/v4/java/android/support/v4/media/MediaMetadataCompat.java
@@ -361,8 +361,8 @@
public RatingCompat getRating(@RatingKey String key) {
RatingCompat rating = null;
try {
- if (Build.VERSION.SDK_INT >= 21) {
- // On platform version 21 or higher, mBundle stores a Rating object. Convert it to
+ if (Build.VERSION.SDK_INT >= 19) {
+ // On platform version 19 or higher, mBundle stores a Rating object. Convert it to
// RatingCompat.
rating = RatingCompat.fromRating(mBundle.getParcelable(key));
} else {
@@ -708,8 +708,8 @@
+ " key cannot be used to put a Rating");
}
}
- if (Build.VERSION.SDK_INT >= 21) {
- // On platform version 21 or higher, use Rating instead of RatingCompat so mBundle
+ if (Build.VERSION.SDK_INT >= 19) {
+ // On platform version 19 or higher, use Rating instead of RatingCompat so mBundle
// can be unmarshalled.
mBundle.putParcelable(key, (Parcelable) value.getRating());
} else {
diff --git a/v4/java/android/support/v4/media/RatingCompat.java b/v4/java/android/support/v4/media/RatingCompat.java
index d6c6033..ee25ad4 100644
--- a/v4/java/android/support/v4/media/RatingCompat.java
+++ b/v4/java/android/support/v4/media/RatingCompat.java
@@ -312,35 +312,35 @@
/**
* Creates an instance from a framework {@link android.media.Rating} object.
* <p>
- * This method is only supported on API 21+.
+ * This method is only supported on API 19+.
* </p>
*
* @param ratingObj A {@link android.media.Rating} object, or null if none.
* @return An equivalent {@link RatingCompat} object, or null if none.
*/
public static RatingCompat fromRating(Object ratingObj) {
- if (ratingObj == null || Build.VERSION.SDK_INT < 21) {
+ if (ratingObj == null || Build.VERSION.SDK_INT < 19) {
return null;
}
- final int ratingStyle = RatingCompatApi21.getRatingStyle(ratingObj);
+ final int ratingStyle = RatingCompatKitkat.getRatingStyle(ratingObj);
final RatingCompat rating;
- if (RatingCompatApi21.isRated(ratingObj)) {
+ if (RatingCompatKitkat.isRated(ratingObj)) {
switch (ratingStyle) {
case RATING_HEART:
- rating = newHeartRating(RatingCompatApi21.hasHeart(ratingObj));
+ rating = newHeartRating(RatingCompatKitkat.hasHeart(ratingObj));
break;
case RATING_THUMB_UP_DOWN:
- rating = newThumbRating(RatingCompatApi21.isThumbUp(ratingObj));
+ rating = newThumbRating(RatingCompatKitkat.isThumbUp(ratingObj));
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
rating = newStarRating(ratingStyle,
- RatingCompatApi21.getStarRating(ratingObj));
+ RatingCompatKitkat.getStarRating(ratingObj));
break;
case RATING_PERCENTAGE:
- rating = newPercentageRating(RatingCompatApi21.getPercentRating(ratingObj));
+ rating = newPercentageRating(RatingCompatKitkat.getPercentRating(ratingObj));
break;
default:
return null;
@@ -355,36 +355,36 @@
/**
* Gets the underlying framework {@link android.media.Rating} object.
* <p>
- * This method is only supported on API 21+.
+ * This method is only supported on API 19+.
* </p>
*
* @return An equivalent {@link android.media.Rating} object, or null if none.
*/
public Object getRating() {
- if (mRatingObj != null || Build.VERSION.SDK_INT < 21) {
+ if (mRatingObj != null || Build.VERSION.SDK_INT < 19) {
return mRatingObj;
}
if (isRated()) {
switch (mRatingStyle) {
case RATING_HEART:
- mRatingObj = RatingCompatApi21.newHeartRating(hasHeart());
+ mRatingObj = RatingCompatKitkat.newHeartRating(hasHeart());
break;
case RATING_THUMB_UP_DOWN:
- mRatingObj = RatingCompatApi21.newThumbRating(isThumbUp());
+ mRatingObj = RatingCompatKitkat.newThumbRating(isThumbUp());
break;
case RATING_3_STARS:
case RATING_4_STARS:
case RATING_5_STARS:
- mRatingObj = RatingCompatApi21.newStarRating(mRatingStyle, getStarRating());
+ mRatingObj = RatingCompatKitkat.newStarRating(mRatingStyle, getStarRating());
break;
case RATING_PERCENTAGE:
- mRatingObj = RatingCompatApi21.newPercentageRating(getPercentRating());
+ mRatingObj = RatingCompatKitkat.newPercentageRating(getPercentRating());
default:
return null;
}
} else {
- mRatingObj = RatingCompatApi21.newUnratedRating(mRatingStyle);
+ mRatingObj = RatingCompatKitkat.newUnratedRating(mRatingStyle);
}
return mRatingObj;
}
diff --git a/v4/java/android/support/v4/media/session/IMediaSession.aidl b/v4/java/android/support/v4/media/session/IMediaSession.aidl
index 305dd0b..718b870 100644
--- a/v4/java/android/support/v4/media/session/IMediaSession.aidl
+++ b/v4/java/android/support/v4/media/session/IMediaSession.aidl
@@ -67,4 +67,8 @@
CharSequence getQueueTitle();
Bundle getExtras();
int getRatingType();
+ void prepare();
+ void prepareFromMediaId(String uri, in Bundle extras);
+ void prepareFromSearch(String string, in Bundle extras);
+ void prepareFromUri(in Uri uri, in Bundle extras);
}
diff --git a/v4/java/android/support/v4/media/session/MediaButtonReceiver.java b/v4/java/android/support/v4/media/session/MediaButtonReceiver.java
index d5eb0e8..b03e558 100644
--- a/v4/java/android/support/v4/media/session/MediaButtonReceiver.java
+++ b/v4/java/android/support/v4/media/session/MediaButtonReceiver.java
@@ -23,6 +23,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.support.v4.media.MediaBrowserServiceCompat;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.view.KeyEvent;
@@ -44,8 +45,11 @@
* </receiver>
* </pre>
* This class assumes you have a {@link Service} in your app that controls
- * media playback via a {@link MediaSessionCompat}. That {@link Service} must
- * include an intent filter that also handles {@link Intent#ACTION_MEDIA_BUTTON}:
+ * media playback via a {@link MediaSessionCompat} - all {@link Intent}s received by
+ * the MediaButtonReceiver will be forwarded to that service.
+ * <p />
+ * First priority is given to a {@link Service}
+ * that includes an intent filter that handles {@link Intent#ACTION_MEDIA_BUTTON}:
* <pre>
* <service android:name="com.example.android.MediaPlaybackService" >
* <intent-filter>
@@ -54,9 +58,12 @@
* </service>
* </pre>
*
- * All {@link Intent}s sent to this MediaButtonReceiver will then be forwarded
- * to the {@link Service}. Events can then be handled in
- * {@link Service#onStartCommand(Intent, int, int)} by calling
+ * If such a {@link Service} is not found, MediaButtonReceiver will attempt to
+ * find a media browser service implementation.
+ * If neither is available or more than one valid service/media browser service is found, an
+ * {@link IllegalStateException} will be thrown.
+ * <p />
+ * Events can then be handled in {@link Service#onStartCommand(Intent, int, int)} by calling
* {@link MediaButtonReceiver#handleIntent(MediaSessionCompat, Intent)}, passing in
* your current {@link MediaSessionCompat}:
* <pre>
@@ -78,9 +85,17 @@
queryIntent.setPackage(context.getPackageName());
PackageManager pm = context.getPackageManager();
List<ResolveInfo> resolveInfos = pm.queryIntentServices(queryIntent, 0);
- if (resolveInfos.size() != 1) {
+ if (resolveInfos.isEmpty()) {
+ // Fall back to looking for any available media browser service
+ queryIntent.setAction(MediaBrowserServiceCompat.SERVICE_INTERFACE);
+ resolveInfos = pm.queryIntentServices(queryIntent, 0);
+ }
+ if (resolveInfos.isEmpty()) {
+ throw new IllegalStateException("Could not find any Service that handles " +
+ Intent.ACTION_MEDIA_BUTTON + " or a media browser service implementation");
+ } else if (resolveInfos.size() != 1) {
throw new IllegalStateException("Expected 1 Service that handles " +
- Intent.ACTION_MEDIA_BUTTON + ", found " + resolveInfos.size());
+ queryIntent.getAction() + ", found " + resolveInfos.size() );
}
ResolveInfo resolveInfo = resolveInfos.get(0);
ComponentName componentName = new ComponentName(resolveInfo.serviceInfo.packageName,
diff --git a/v4/java/android/support/v4/media/session/MediaControllerCompat.java b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
index a6a5c88..0c04c73f 100644
--- a/v4/java/android/support/v4/media/session/MediaControllerCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaControllerCompat.java
@@ -69,7 +69,9 @@
}
mToken = session.getSessionToken();
- if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (android.os.Build.VERSION.SDK_INT >= 24) {
+ mImpl = new MediaControllerImplApi24(context, session);
+ } else if (android.os.Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaControllerImplApi23(context, session);
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, session);
@@ -92,7 +94,9 @@
}
mToken = sessionToken;
- if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (android.os.Build.VERSION.SDK_INT >= 24) {
+ mImpl = new MediaControllerImplApi24(context, sessionToken);
+ } else if (android.os.Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaControllerImplApi23(context, sessionToken);
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaControllerImplApi21(context, sessionToken);
@@ -573,6 +577,62 @@
}
/**
+ * Request that the player prepare its playback without audio focus. In other words, other
+ * session can continue to play during the preparation of this session. This method can be
+ * used to speed up the start of the playback. Once the preparation is done, the session
+ * will change its playback state to {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards,
+ * {@link #play} can be called to start playback. If the preparation is not needed,
+ * {@link #play} can be directly called without this method.
+ */
+ public abstract void prepare();
+
+ /**
+ * Request that the player prepare playback for a specific media id. In other words, other
+ * session can continue to play during the preparation of this session. This method can be
+ * used to speed up the start of the playback. Once the preparation is
+ * done, the session will change its playback state to
+ * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback. If the preparation is not needed, {@link #playFromMediaId} can
+ * be directly called without this method.
+ *
+ * @param mediaId The id of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public abstract void prepareFromMediaId(String mediaId, Bundle extras);
+
+ /**
+ * Request that the player prepare playback for a specific search query.
+ * An empty or null query should be treated as a request to prepare any
+ * music. In other words, other session can continue to play during
+ * the preparation of this session. This method can be used to speed up the start of the
+ * playback. Once the preparation is done, the session will change its playback state to
+ * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback. If the preparation is not needed, {@link #playFromSearch} can be directly
+ * called without this method.
+ *
+ * @param query The search query.
+ * @param extras Optional extras that can include extra information
+ * about the query.
+ */
+ public abstract void prepareFromSearch(String query, Bundle extras);
+
+ /**
+ * Request that the player prepare playback for a specific {@link Uri}.
+ * In other words, other session can continue to play during the preparation of this
+ * session. This method can be used to speed up the start of the playback.
+ * Once the preparation is done, the session will change its playback state to
+ * {@link PlaybackStateCompat#STATE_PAUSED}. Afterwards, {@link #play} can be called to
+ * start playback. If the preparation is not needed, {@link #playFromUri} can be directly
+ * called without this method.
+ *
+ * @param uri The URI of the requested media.
+ * @param extras Optional extras that can include extra information about the media item
+ * to be prepared.
+ */
+ public abstract void prepareFromUri(Uri uri, Bundle extras);
+
+ /**
* Request that the player start its playback at its current position.
*/
public abstract void play();
@@ -599,9 +659,6 @@
/**
* Request that the player start playback for a specific {@link Uri}.
- * <p>
- * This method is not supported on API 21-22.
- * </p>
*
* @param uri The URI of the requested media.
* @param extras Optional extras that can include extra information about the media item
@@ -1010,6 +1067,42 @@
}
@Override
+ public void prepare() {
+ try {
+ mBinder.prepare();
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in prepare. " + e);
+ }
+ }
+
+ @Override
+ public void prepareFromMediaId(String mediaId, Bundle extras) {
+ try {
+ mBinder.prepareFromMediaId(mediaId, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in prepareFromMediaId. " + e);
+ }
+ }
+
+ @Override
+ public void prepareFromSearch(String query, Bundle extras) {
+ try {
+ mBinder.prepareFromSearch(query, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in prepareFromSearch. " + e);
+ }
+ }
+
+ @Override
+ public void prepareFromUri(Uri uri, Bundle extras) {
+ try {
+ mBinder.prepareFromUri(uri, extras);
+ } catch (RemoteException e) {
+ Log.e(TAG, "Dead object in prepareFromUri. " + e);
+ }
+ }
+
+ @Override
public void play() {
try {
mBinder.play();
@@ -1273,6 +1366,35 @@
}
@Override
+ public void prepare() {
+ sendCustomAction(MediaSessionCompat.ACTION_PREPARE, null);
+ }
+
+ @Override
+ public void prepareFromMediaId(String mediaId, Bundle extras) {
+ Bundle bundle = new Bundle();
+ bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_MEDIA_ID, mediaId);
+ bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+ sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_MEDIA_ID, bundle);
+ }
+
+ @Override
+ public void prepareFromSearch(String query, Bundle extras) {
+ Bundle bundle = new Bundle();
+ bundle.putString(MediaSessionCompat.ACTION_ARGUMENT_QUERY, query);
+ bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+ sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_SEARCH, bundle);
+ }
+
+ @Override
+ public void prepareFromUri(Uri uri, Bundle extras) {
+ Bundle bundle = new Bundle();
+ bundle.putParcelable(MediaSessionCompat.ACTION_ARGUMENT_URI, uri);
+ bundle.putBundle(MediaSessionCompat.ACTION_ARGUMENT_EXTRAS, extras);
+ sendCustomAction(MediaSessionCompat.ACTION_PREPARE_FROM_URI, bundle);
+ }
+
+ @Override
public void play() {
MediaControllerCompatApi21.TransportControls.play(mControlsObj);
}
@@ -1390,4 +1512,52 @@
extras);
}
}
+
+ static class MediaControllerImplApi24 extends MediaControllerImplApi23 {
+
+ public MediaControllerImplApi24(Context context, MediaSessionCompat session) {
+ super(context, session);
+ }
+
+ public MediaControllerImplApi24(Context context, MediaSessionCompat.Token sessionToken)
+ throws RemoteException {
+ super(context, sessionToken);
+ }
+
+ @Override
+ public TransportControls getTransportControls() {
+ Object controlsObj = MediaControllerCompatApi21.getTransportControls(mControllerObj);
+ return controlsObj != null ? new TransportControlsApi24(controlsObj) : null;
+ }
+ }
+
+ static class TransportControlsApi24 extends TransportControlsApi23 {
+
+ public TransportControlsApi24(Object controlsObj) {
+ super(controlsObj);
+ }
+
+ @Override
+ public void prepare() {
+ MediaControllerCompatApi24.TransportControls.prepare(mControlsObj);
+ }
+
+ @Override
+ public void prepareFromMediaId(String mediaId, Bundle extras) {
+ MediaControllerCompatApi24.TransportControls.prepareFromMediaId(
+ mControlsObj, mediaId, extras);
+ }
+
+ @Override
+ public void prepareFromSearch(String query, Bundle extras) {
+ MediaControllerCompatApi24.TransportControls.prepareFromSearch(
+ mControlsObj, query, extras);
+ }
+
+ @Override
+ public void prepareFromUri(Uri uri, Bundle extras) {
+ MediaControllerCompatApi24.TransportControls.prepareFromUri(mControlsObj, uri, extras);
+ }
+ }
+
}
diff --git a/v4/java/android/support/v4/media/session/MediaSessionCompat.java b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
index d35ada6..4c6c305 100644
--- a/v4/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/v4/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -25,6 +25,7 @@
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
+import android.graphics.Bitmap;
import android.media.AudioManager;
import android.net.Uri;
import android.os.Bundle;
@@ -49,7 +50,6 @@
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
-import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.List;
@@ -84,8 +84,7 @@
private final MediaSessionImpl mImpl;
private final MediaControllerCompat mController;
- private final ArrayList<OnActiveChangeListener>
- mActiveListeners = new ArrayList<OnActiveChangeListener>();
+ private final ArrayList<OnActiveChangeListener> mActiveListeners = new ArrayList<>();
/**
* @hide
@@ -108,34 +107,70 @@
/**
* Custom action to invoke playFromUri() for the forward compatibility.
- *
- * @hide
*/
- public static final String ACTION_PLAY_FROM_URI =
+ static final String ACTION_PLAY_FROM_URI =
"android.support.v4.media.session.action.PLAY_FROM_URI";
/**
- * Argument for use with {@link #ACTION_PLAY_FROM_URI} indicating URI to play.
- *
- * @hide
+ * Custom action to invoke prepare() for the forward compatibility.
*/
- public static final String ACTION_ARGUMENT_URI =
+ static final String ACTION_PREPARE = "android.support.v4.media.session.action.PREPARE";
+
+ /**
+ * Custom action to invoke prepareFromMediaId() for the forward compatibility.
+ */
+ static final String ACTION_PREPARE_FROM_MEDIA_ID =
+ "android.support.v4.media.session.action.PREPARE_FROM_MEDIA_ID";
+
+ /**
+ * Custom action to invoke prepareFromSearch() for the forward compatibility.
+ */
+ static final String ACTION_PREPARE_FROM_SEARCH =
+ "android.support.v4.media.session.action.PREPARE_FROM_SEARCH";
+
+ /**
+ * Custom action to invoke prepareFromUri() for the forward compatibility.
+ */
+ static final String ACTION_PREPARE_FROM_URI =
+ "android.support.v4.media.session.action.PREPARE_FROM_URI";
+
+ /**
+ * Argument for use with {@link #ACTION_PREPARE_FROM_MEDIA_ID} indicating media id to play.
+ */
+ static final String ACTION_ARGUMENT_MEDIA_ID =
+ "android.support.v4.media.session.action.ARGUMENT_MEDIA_ID";
+
+ /**
+ * Argument for use with {@link #ACTION_PREPARE_FROM_SEARCH} indicating search query.
+ */
+ static final String ACTION_ARGUMENT_QUERY =
+ "android.support.v4.media.session.action.ARGUMENT_QUERY";
+
+ /**
+ * Argument for use with {@link #ACTION_PREPARE_FROM_URI} and {@link #ACTION_PLAY_FROM_URI}
+ * indicating URI to play.
+ */
+ static final String ACTION_ARGUMENT_URI =
"android.support.v4.media.session.action.ARGUMENT_URI";
/**
- * Argument for use with {@link #ACTION_PLAY_FROM_URI} indicating extra bundle.
- *
- * @hide
+ * Argument for use with various actions indicating extra bundle.
*/
- public static final String ACTION_ARGUMENT_EXTRAS =
+ static final String ACTION_ARGUMENT_EXTRAS =
"android.support.v4.media.session.action.ARGUMENT_EXTRAS";
/**
- * Creates a new session using a media button receiver from your manifest.
- * Note that a media button receiver is required to support platform versions
- * earlier than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
- *
- * @param context The context.
+ * Creates a new session. You must call {@link #release()} when finished with the session.
+ * <p>
+ * The session will automatically be registered with the system but will not be published
+ * until {@link #setActive(boolean) setActive(true)} is called.
+ * </p><p>
+ * For API 20 or earlier, note that a media button receiver is required for handling
+ * {@link Intent#ACTION_MEDIA_BUTTON}. This constructor will attempt to find an appropriate
+ * {@link BroadcastReceiver} from your manifest. See {@link MediaButtonReceiver} for more
+ * details.
+ * </p>
+ * @param context The context to use to create the session.
* @param tag A short name for debugging purposes.
*/
public MediaSessionCompat(Context context, String tag) {
@@ -143,22 +178,29 @@
}
/**
- * Creates a new session.
- *
- * @param context The context.
+ * Creates a new session with a specified media button receiver (a component name and/or
+ * a pending intent). You must call {@link #release()} when finished with the session.
+ * <p>
+ * The session will automatically be registered with the system but will not be published
+ * until {@link #setActive(boolean) setActive(true)} is called. Note that {@code mbrComponent}
+ * and {@code mrbIntent} are only used for API 20 or earlier. If you want to set a media button
+ * receiver in API 21 or later, call {@link #setMediaButtonReceiver}.
+ * </p><p>
+ * For API 20 or earlier, the new session will use the given {@code mbrComponent}.
+ * If null, this will attempt to find an appropriate {@link BroadcastReceiver} that handles
+ * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest. See {@link MediaButtonReceiver} for
+ * more details.
+ * </p>
+ * @param context The context to use to create the session.
* @param tag A short name for debugging purposes.
- * @param mediaButtonEventReceiver The component name for your receiver.
- * If null, this will attempt to find an appropriate
- * {@link BroadcastReceiver} that handles
- * {@link Intent#ACTION_MEDIA_BUTTON} from your manifest.
- * A receiver is required to support platform versions earlier
- * than {@link android.os.Build.VERSION_CODES#LOLLIPOP}.
- * @param mbrIntent The PendingIntent for your receiver component that
- * handles media button events. This is optional and will be used
- * on {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
- * later instead of the component name.
+ * @param mbrComponent The component name for your media button receiver.
+ * @param mbrIntent The PendingIntent for your receiver component that handles
+ * media button events. This is optional and will be used on between
+ * {@link android.os.Build.VERSION_CODES#JELLY_BEAN_MR2} and
+ * {@link android.os.Build.VERSION_CODES#KITKAT_WATCH} instead of the
+ * component name.
*/
- public MediaSessionCompat(Context context, String tag, ComponentName mediaButtonEventReceiver,
+ public MediaSessionCompat(Context context, String tag, ComponentName mbrComponent,
PendingIntent mbrIntent) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
@@ -167,37 +209,10 @@
throw new IllegalArgumentException("tag must not be null or empty");
}
- if (mediaButtonEventReceiver == null) {
- Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- queryIntent.setPackage(context.getPackageName());
- PackageManager pm = context.getPackageManager();
- List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
- // If none are found, assume we are running on a newer platform version that does
- // not require a media button receiver ComponentName. Later code will double check
- // this assumption and throw an error if needed
- if (resolveInfos.size() == 1) {
- ResolveInfo resolveInfo = resolveInfos.get(0);
- mediaButtonEventReceiver = new ComponentName(resolveInfo.activityInfo.packageName,
- resolveInfo.activityInfo.name);
- } else if (resolveInfos.size() > 1) {
- Log.w(TAG, "More than one BroadcastReceiver that handles " +
- Intent.ACTION_MEDIA_BUTTON + " was found, using null. Provide a " +
- "specific ComponentName to use as this session's media button receiver");
- }
- }
- if (mediaButtonEventReceiver != null && mbrIntent == null) {
- // construct a PendingIntent for the media button
- Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
- // the associated intent will be handled by the component being registered
- mediaButtonIntent.setComponent(mediaButtonEventReceiver);
- mbrIntent = PendingIntent.getBroadcast(context,
- 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
- }
if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaSessionImplApi21(context, tag);
- mImpl.setMediaButtonReceiver(mbrIntent);
} else {
- mImpl = new MediaSessionImplBase(context, tag, mediaButtonEventReceiver, mbrIntent);
+ mImpl = new MediaSessionImplBase(context, tag, mbrComponent, mbrIntent);
}
mController = new MediaControllerCompat(context, this);
}
@@ -488,6 +503,17 @@
}
/**
+ * Returns the name of the package that sent the last media button, transport control, or
+ * command from controllers and the system. This is only valid while in a request callback, such
+ * as {@link Callback#onPlay}. This method is not available and returns null on pre-N devices.
+ *
+ * @hide
+ */
+ public String getCallingPackage() {
+ return mImpl.getCallingPackage();
+ }
+
+ /**
* Adds a listener to be notified when the active status of this session
* changes. This is primarily used by the support library and should not be
* needed by apps.
@@ -533,7 +559,9 @@
final Object mCallbackObj;
public Callback() {
- if (android.os.Build.VERSION.SDK_INT >= 23) {
+ if (android.os.Build.VERSION.SDK_INT >= 24) {
+ mCallbackObj = MediaSessionCompatApi24.createCallback(new StubApi24());
+ } else if (android.os.Build.VERSION.SDK_INT >= 23) {
mCallbackObj = MediaSessionCompatApi23.createCallback(new StubApi23());
} else if (android.os.Build.VERSION.SDK_INT >= 21) {
mCallbackObj = MediaSessionCompatApi21.createCallback(new StubApi21());
@@ -565,6 +593,51 @@
}
/**
+ * Override to handle requests to prepare playback. During the preparation, a session
+ * should not hold audio focus in order to allow other session play seamlessly.
+ * The state of playback should be updated to {@link PlaybackStateCompat#STATE_PAUSED}
+ * after the preparation is done.
+ */
+ public void onPrepare() {
+ }
+
+ /**
+ * Override to handle requests to prepare for playing a specific mediaId that was provided
+ * by your app. During the preparation, a session should not hold audio focus in order to
+ * allow other session play seamlessly. The state of playback should be updated to
+ * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback
+ * of the prepared content should start in the implementation of {@link #onPlay}. Override
+ * {@link #onPlayFromMediaId} to handle requests for starting playback without preparation.
+ */
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ }
+
+ /**
+ * Override to handle requests to prepare playback from a search query. An
+ * empty query indicates that the app may prepare any music. The
+ * implementation should attempt to make a smart choice about what to
+ * play. During the preparation, a session should not hold audio focus in order to allow
+ * other session play seamlessly. The state of playback should be updated to
+ * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done.
+ * The playback of the prepared content should start in the implementation of
+ * {@link #onPlay}. Override {@link #onPlayFromSearch} to handle requests for
+ * starting playback without preparation.
+ */
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ }
+
+ /**
+ * Override to handle requests to prepare a specific media item represented by a URI.
+ * During the preparation, a session should not hold audio focus in order to allow other
+ * session play seamlessly. The state of playback should be updated to
+ * {@link PlaybackStateCompat#STATE_PAUSED} after the preparation is done. The playback of
+ * the prepared content should start in the implementation of {@link #onPlay}. Override
+ * {@link #onPlayFromUri} to handle requests for starting playback without preparation.
+ */
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ }
+
+ /**
* Override to handle requests to begin playback.
*/
public void onPlay() {
@@ -738,9 +811,23 @@
@Override
public void onCustomAction(String action, Bundle extras) {
if (action.equals(ACTION_PLAY_FROM_URI)) {
- Uri uri = (Uri) extras.getParcelable(ACTION_ARGUMENT_URI);
- Bundle bundle = (Bundle) extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
+ Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
+ Bundle bundle = extras.getParcelable(ACTION_ARGUMENT_EXTRAS);
Callback.this.onPlayFromUri(uri, bundle);
+ } else if (action.equals(ACTION_PREPARE)) {
+ Callback.this.onPrepare();
+ } else if (action.equals(ACTION_PREPARE_FROM_MEDIA_ID)) {
+ String mediaId = extras.getString(ACTION_ARGUMENT_MEDIA_ID);
+ Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+ Callback.this.onPrepareFromMediaId(mediaId, bundle);
+ } else if (action.equals(ACTION_PREPARE_FROM_SEARCH)) {
+ String query = extras.getString(ACTION_ARGUMENT_QUERY);
+ Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+ Callback.this.onPrepareFromSearch(query, bundle);
+ } else if (action.equals(ACTION_PREPARE_FROM_URI)) {
+ Uri uri = extras.getParcelable(ACTION_ARGUMENT_URI);
+ Bundle bundle = extras.getBundle(ACTION_ARGUMENT_EXTRAS);
+ Callback.this.onPrepareFromUri(uri, bundle);
} else {
Callback.this.onCustomAction(action, extras);
}
@@ -754,6 +841,29 @@
Callback.this.onPlayFromUri(uri, extras);
}
}
+
+ private class StubApi24 extends StubApi23 implements MediaSessionCompatApi24.Callback {
+
+ @Override
+ public void onPrepare() {
+ Callback.this.onPrepare();
+ }
+
+ @Override
+ public void onPrepareFromMediaId(String mediaId, Bundle extras) {
+ Callback.this.onPrepareFromMediaId(mediaId, extras);
+ }
+
+ @Override
+ public void onPrepareFromSearch(String query, Bundle extras) {
+ Callback.this.onPrepareFromSearch(query, extras);
+ }
+
+ @Override
+ public void onPrepareFromUri(Uri uri, Bundle extras) {
+ Callback.this.onPrepareFromUri(uri, extras);
+ }
+ }
}
/**
@@ -935,19 +1045,19 @@
return new QueueItem(queueItem, description, id);
}
- public static final Creator<MediaSessionCompat.QueueItem>
- CREATOR = new Creator<MediaSessionCompat.QueueItem>() {
+ public static final Creator<MediaSessionCompat.QueueItem> CREATOR
+ = new Creator<MediaSessionCompat.QueueItem>() {
- @Override
- public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
- return new MediaSessionCompat.QueueItem(p);
- }
+ @Override
+ public MediaSessionCompat.QueueItem createFromParcel(Parcel p) {
+ return new MediaSessionCompat.QueueItem(p);
+ }
- @Override
- public MediaSessionCompat.QueueItem[] newArray(int size) {
- return new MediaSessionCompat.QueueItem[size];
- }
- };
+ @Override
+ public MediaSessionCompat.QueueItem[] newArray(int size) {
+ return new MediaSessionCompat.QueueItem[size];
+ }
+ };
@Override
public String toString() {
@@ -1026,29 +1136,31 @@
Object getMediaSession();
Object getRemoteControlClient();
+
+ String getCallingPackage();
}
static class MediaSessionImplBase implements MediaSessionImpl {
private final Context mContext;
- private final ComponentName mComponentName;
- private final PendingIntent mMediaButtonEventReceiver;
+ private final ComponentName mMediaButtonReceiverComponentName;
+ private final PendingIntent mMediaButtonReceiverIntent;
private final Object mRccObj;
private final MediaSessionStub mStub;
private final Token mToken;
- private final MessageHandler mHandler;
private final String mPackageName;
private final String mTag;
private final AudioManager mAudioManager;
private final Object mLock = new Object();
private final RemoteCallbackList<IMediaControllerCallback> mControllerCallbacks
- = new RemoteCallbackList<IMediaControllerCallback>();
+ = new RemoteCallbackList<>();
+ private MessageHandler mHandler;
private boolean mDestroyed = false;
private boolean mIsActive = false;
private boolean mIsRccRegistered = false;
private boolean mIsMbrRegistered = false;
- private Callback mCallback;
+ private volatile Callback mCallback;
private @SessionFlags int mFlags;
@@ -1079,7 +1191,34 @@
};
public MediaSessionImplBase(Context context, String tag, ComponentName mbrComponent,
- PendingIntent mbr) {
+ PendingIntent mbrIntent) {
+ if (mbrComponent == null) {
+ Intent queryIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ queryIntent.setPackage(context.getPackageName());
+ PackageManager pm = context.getPackageManager();
+ List<ResolveInfo> resolveInfos = pm.queryBroadcastReceivers(queryIntent, 0);
+ // If none are found, assume we are running on a newer platform version that does
+ // not require a media button receiver ComponentName. Later code will double check
+ // this assumption and throw an error if needed
+ if (resolveInfos.size() == 1) {
+ ResolveInfo resolveInfo = resolveInfos.get(0);
+ mbrComponent = new ComponentName(resolveInfo.activityInfo.packageName,
+ resolveInfo.activityInfo.name);
+ } else if (resolveInfos.size() > 1) {
+ Log.w(TAG, "More than one BroadcastReceiver that handles "
+ + Intent.ACTION_MEDIA_BUTTON + " was found, using null. Provide a "
+ + "specific ComponentName to use as this session's media button "
+ + "receiver");
+ }
+ }
+ if (mbrComponent != null && mbrIntent == null) {
+ // construct a PendingIntent for the media button
+ Intent mediaButtonIntent = new Intent(Intent.ACTION_MEDIA_BUTTON);
+ // the associated intent will be handled by the component being registered
+ mediaButtonIntent.setComponent(mbrComponent);
+ mbrIntent = PendingIntent.getBroadcast(context,
+ 0/* requestCode, ignored */, mediaButtonIntent, 0/* flags */);
+ }
if (mbrComponent == null) {
throw new IllegalArgumentException(
"MediaButtonReceiver component may not be null.");
@@ -1088,29 +1227,26 @@
mPackageName = context.getPackageName();
mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
mTag = tag;
- mComponentName = mbrComponent;
- mMediaButtonEventReceiver = mbr;
+ mMediaButtonReceiverComponentName = mbrComponent;
+ mMediaButtonReceiverIntent = mbrIntent;
mStub = new MediaSessionStub();
mToken = new Token(mStub);
- mHandler = new MessageHandler(Looper.myLooper());
mRatingType = RatingCompat.RATING_NONE;
mVolumeType = MediaControllerCompat.PlaybackInfo.PLAYBACK_TYPE_LOCAL;
mLocalStream = AudioManager.STREAM_MUSIC;
if (android.os.Build.VERSION.SDK_INT >= 14) {
- mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbr);
+ mRccObj = MediaSessionCompatApi14.createRemoteControlClient(mbrIntent);
} else {
mRccObj = null;
}
}
@Override
- public void setCallback(final Callback callback, Handler handler) {
- if (callback == mCallback) {
- return;
- }
- if (callback == null || android.os.Build.VERSION.SDK_INT < 18) {
- // There's nothing to register on API < 18 since media buttons
+ public void setCallback(Callback callback, Handler handler) {
+ mCallback = callback;
+ if (callback == null) {
+ // There's nothing to unregister on API < 18 since media buttons
// all go through the media button receiver
if (android.os.Build.VERSION.SDK_INT >= 18) {
MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj, null);
@@ -1122,76 +1258,50 @@
if (handler == null) {
handler = new Handler();
}
- MediaSessionCompatApi14.Callback cb14 = new MediaSessionCompatApi14.Callback() {
- @Override
- public void onStop() {
- callback.onStop();
- }
-
- @Override
- public void onSkipToPrevious() {
- callback.onSkipToPrevious();
- }
-
- @Override
- public void onSkipToNext() {
- callback.onSkipToNext();
- }
-
+ synchronized (mLock) {
+ mHandler = new MessageHandler(handler.getLooper());
+ }
+ MediaSessionCompatApi19.Callback cb19 = new MediaSessionCompatApi19.Callback() {
@Override
public void onSetRating(Object ratingObj) {
- callback.onSetRating(RatingCompat.fromRating(ratingObj));
+ postToHandler(MessageHandler.MSG_RATE,
+ RatingCompat.fromRating(ratingObj));
}
@Override
public void onSeekTo(long pos) {
- callback.onSeekTo(pos);
- }
-
- @Override
- public void onRewind() {
- callback.onRewind();
- }
-
- @Override
- public void onPlay() {
- callback.onPlay();
- }
-
- @Override
- public void onPause() {
- callback.onPause();
- }
-
- @Override
- public boolean onMediaButtonEvent(Intent mediaButtonIntent) {
- return callback.onMediaButtonEvent(mediaButtonIntent);
- }
-
- @Override
- public void onFastForward() {
- callback.onFastForward();
- }
-
- @Override
- public void onCommand(String command, Bundle extras, ResultReceiver cb) {
- callback.onCommand(command, extras, cb);
+ postToHandler(MessageHandler.MSG_SEEK_TO, pos);
}
};
if (android.os.Build.VERSION.SDK_INT >= 18) {
Object onPositionUpdateObj = MediaSessionCompatApi18
- .createPlaybackPositionUpdateListener(cb14);
+ .createPlaybackPositionUpdateListener(cb19);
MediaSessionCompatApi18.setOnPlaybackPositionUpdateListener(mRccObj,
onPositionUpdateObj);
}
if (android.os.Build.VERSION.SDK_INT >= 19) {
Object onMetadataUpdateObj = MediaSessionCompatApi19
- .createMetadataUpdateListener(cb14);
+ .createMetadataUpdateListener(cb19);
MediaSessionCompatApi19.setOnMetadataUpdateListener(mRccObj,
onMetadataUpdateObj);
}
}
- mCallback = callback;
+ }
+
+ private void postToHandler(int what) {
+ postToHandler(what, null);
+ }
+
+ private void postToHandler(int what, Object obj) {
+ postToHandler(what, obj, null);
+ }
+
+ private void postToHandler(int what, Object obj, Bundle extras) {
+ synchronized (mLock) {
+ if (mHandler != null) {
+ mHandler.post(what, obj, extras);
+ }
+ }
}
@Override
@@ -1303,8 +1413,42 @@
}
}
+ /**
+ * Clones the given {@link MediaMetadataCompat}, deep-copying bitmaps in the metadata if
+ * they exist. If there is no bitmap in the metadata, this method just returns the given
+ * metadata.
+ *
+ * @param metadata A {@link MediaMetadataCompat} to be cloned.
+ * @return A newly cloned metadata if it contains bitmaps. Otherwise, the given metadata
+ * will be returned.
+ */
+ private MediaMetadataCompat cloneMetadataIfNeeded(MediaMetadataCompat metadata) {
+ if (metadata == null) {
+ return null;
+ } else if (!metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ART)
+ && !metadata.containsKey(MediaMetadataCompat.METADATA_KEY_ALBUM_ART)) {
+ return metadata;
+ }
+ MediaMetadataCompat.Builder builder = new MediaMetadataCompat.Builder(metadata);
+ Bitmap artBitmap = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ART);
+ if (artBitmap != null) {
+ builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ART,
+ artBitmap.copy(artBitmap.getConfig(), false));
+ }
+ Bitmap albumArtBitmap = metadata.getBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART);
+ if (albumArtBitmap != null) {
+ builder.putBitmap(MediaMetadataCompat.METADATA_KEY_ALBUM_ART,
+ albumArtBitmap.copy(albumArtBitmap.getConfig(), false));
+ }
+ return builder.build();
+ }
+
@Override
public void setMetadata(MediaMetadataCompat metadata) {
+ if (android.os.Build.VERSION.SDK_INT >= 14 && metadata != null) {
+ // Clone bitmaps in metadata for protecting them to be recycled by RCC.
+ metadata = cloneMetadataIfNeeded(metadata);
+ }
synchronized (mLock) {
mMetadata = metadata;
}
@@ -1358,6 +1502,11 @@
}
@Override
+ public String getCallingPackage() {
+ return null;
+ }
+
+ @Override
public void setRatingType(@RatingCompat.Style int type) {
mRatingType = type;
}
@@ -1377,19 +1526,21 @@
if (!mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) != 0) {
if (android.os.Build.VERSION.SDK_INT >= 18) {
MediaSessionCompatApi18.registerMediaButtonEventReceiver(mContext,
- mMediaButtonEventReceiver, mComponentName);
+ mMediaButtonReceiverIntent,
+ mMediaButtonReceiverComponentName);
} else {
MediaSessionCompatApi8.registerMediaButtonEventReceiver(mContext,
- mComponentName);
+ mMediaButtonReceiverComponentName);
}
mIsMbrRegistered = true;
} else if (mIsMbrRegistered && (mFlags & FLAG_HANDLES_MEDIA_BUTTONS) == 0) {
if (android.os.Build.VERSION.SDK_INT >= 18) {
MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
- mMediaButtonEventReceiver, mComponentName);
+ mMediaButtonReceiverIntent,
+ mMediaButtonReceiverComponentName);
} else {
MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
- mComponentName);
+ mMediaButtonReceiverComponentName);
}
mIsMbrRegistered = false;
}
@@ -1416,10 +1567,10 @@
if (mIsMbrRegistered) {
if (android.os.Build.VERSION.SDK_INT >= 18) {
MediaSessionCompatApi18.unregisterMediaButtonEventReceiver(mContext,
- mMediaButtonEventReceiver, mComponentName);
+ mMediaButtonReceiverIntent, mMediaButtonReceiverComponentName);
} else {
MediaSessionCompatApi8.unregisterMediaButtonEventReceiver(mContext,
- mComponentName);
+ mMediaButtonReceiverComponentName);
}
mIsMbrRegistered = false;
}
@@ -1509,7 +1660,7 @@
for (int i = size - 1; i >= 0; i--) {
IMediaControllerCallback cb = mControllerCallbacks.getBroadcastItem(i);
try {
- cb.onSessionDestroyed();;
+ cb.onSessionDestroyed();
} catch (RemoteException e) {
}
}
@@ -1580,7 +1731,7 @@
class MediaSessionStub extends IMediaSession.Stub {
@Override
public void sendCommand(String command, Bundle args, ResultReceiverWrapper cb) {
- mHandler.post(MessageHandler.MSG_COMMAND,
+ postToHandler(MessageHandler.MSG_COMMAND,
new Command(command, args, cb.mResultReceiver));
}
@@ -1589,7 +1740,7 @@
boolean handlesMediaButtons =
(mFlags & MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS) != 0;
if (handlesMediaButtons) {
- mHandler.post(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
+ postToHandler(MessageHandler.MSG_MEDIA_BUTTON, mediaButton);
}
return handlesMediaButtons;
}
@@ -1676,74 +1827,94 @@
}
@Override
+ public void prepare() throws RemoteException {
+ postToHandler(MessageHandler.MSG_PREPARE);
+ }
+
+ @Override
+ public void prepareFromMediaId(String mediaId, Bundle extras) throws RemoteException {
+ postToHandler(MessageHandler.MSG_PREPARE_MEDIA_ID, mediaId, extras);
+ }
+
+ @Override
+ public void prepareFromSearch(String query, Bundle extras) throws RemoteException {
+ postToHandler(MessageHandler.MSG_PREPARE_SEARCH, query, extras);
+ }
+
+ @Override
+ public void prepareFromUri(Uri uri, Bundle extras) throws RemoteException {
+ postToHandler(MessageHandler.MSG_PREPARE_URI, uri, extras);
+ }
+
+ @Override
public void play() throws RemoteException {
- mHandler.post(MessageHandler.MSG_PLAY);
+ postToHandler(MessageHandler.MSG_PLAY);
}
@Override
public void playFromMediaId(String mediaId, Bundle extras) throws RemoteException {
- mHandler.post(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
+ postToHandler(MessageHandler.MSG_PLAY_MEDIA_ID, mediaId, extras);
}
@Override
public void playFromSearch(String query, Bundle extras) throws RemoteException {
- mHandler.post(MessageHandler.MSG_PLAY_SEARCH, query, extras);
+ postToHandler(MessageHandler.MSG_PLAY_SEARCH, query, extras);
}
@Override
public void playFromUri(Uri uri, Bundle extras) throws RemoteException {
- mHandler.post(MessageHandler.MSG_PLAY_URI, uri, extras);
+ postToHandler(MessageHandler.MSG_PLAY_URI, uri, extras);
}
@Override
public void skipToQueueItem(long id) {
- mHandler.post(MessageHandler.MSG_SKIP_TO_ITEM, id);
+ postToHandler(MessageHandler.MSG_SKIP_TO_ITEM, id);
}
@Override
public void pause() throws RemoteException {
- mHandler.post(MessageHandler.MSG_PAUSE);
+ postToHandler(MessageHandler.MSG_PAUSE);
}
@Override
public void stop() throws RemoteException {
- mHandler.post(MessageHandler.MSG_STOP);
+ postToHandler(MessageHandler.MSG_STOP);
}
@Override
public void next() throws RemoteException {
- mHandler.post(MessageHandler.MSG_NEXT);
+ postToHandler(MessageHandler.MSG_NEXT);
}
@Override
public void previous() throws RemoteException {
- mHandler.post(MessageHandler.MSG_PREVIOUS);
+ postToHandler(MessageHandler.MSG_PREVIOUS);
}
@Override
public void fastForward() throws RemoteException {
- mHandler.post(MessageHandler.MSG_FAST_FORWARD);
+ postToHandler(MessageHandler.MSG_FAST_FORWARD);
}
@Override
public void rewind() throws RemoteException {
- mHandler.post(MessageHandler.MSG_REWIND);
+ postToHandler(MessageHandler.MSG_REWIND);
}
@Override
public void seekTo(long pos) throws RemoteException {
- mHandler.post(MessageHandler.MSG_SEEK_TO, pos);
+ postToHandler(MessageHandler.MSG_SEEK_TO, pos);
}
@Override
public void rate(RatingCompat rating) throws RemoteException {
- mHandler.post(MessageHandler.MSG_RATE, rating);
+ postToHandler(MessageHandler.MSG_RATE, rating);
}
@Override
public void sendCustomAction(String action, Bundle args)
throws RemoteException {
- mHandler.post(MessageHandler.MSG_CUSTOM_ACTION, action, args);
+ postToHandler(MessageHandler.MSG_CUSTOM_ACTION, action, args);
}
@Override
@@ -1801,24 +1972,28 @@
private class MessageHandler extends Handler {
- private static final int MSG_PLAY = 1;
- private static final int MSG_PLAY_MEDIA_ID = 2;
- private static final int MSG_PLAY_SEARCH = 3;
- private static final int MSG_SKIP_TO_ITEM = 4;
- private static final int MSG_PAUSE = 5;
- private static final int MSG_STOP = 6;
- private static final int MSG_NEXT = 7;
- private static final int MSG_PREVIOUS = 8;
- private static final int MSG_FAST_FORWARD = 9;
- private static final int MSG_REWIND = 10;
- private static final int MSG_SEEK_TO = 11;
- private static final int MSG_RATE = 12;
- private static final int MSG_CUSTOM_ACTION = 13;
- private static final int MSG_MEDIA_BUTTON = 14;
- private static final int MSG_COMMAND = 15;
- private static final int MSG_ADJUST_VOLUME = 16;
- private static final int MSG_SET_VOLUME = 17;
- private static final int MSG_PLAY_URI = 18;
+ private static final int MSG_COMMAND = 1;
+ private static final int MSG_ADJUST_VOLUME = 2;
+ private static final int MSG_PREPARE = 3;
+ private static final int MSG_PREPARE_MEDIA_ID = 4;
+ private static final int MSG_PREPARE_SEARCH = 5;
+ private static final int MSG_PREPARE_URI = 6;
+ private static final int MSG_PLAY = 7;
+ private static final int MSG_PLAY_MEDIA_ID = 8;
+ private static final int MSG_PLAY_SEARCH = 9;
+ private static final int MSG_PLAY_URI = 10;
+ private static final int MSG_SKIP_TO_ITEM = 11;
+ private static final int MSG_PAUSE = 12;
+ private static final int MSG_STOP = 13;
+ private static final int MSG_NEXT = 14;
+ private static final int MSG_PREVIOUS = 15;
+ private static final int MSG_FAST_FORWARD = 16;
+ private static final int MSG_REWIND = 17;
+ private static final int MSG_SEEK_TO = 18;
+ private static final int MSG_RATE = 19;
+ private static final int MSG_CUSTOM_ACTION = 20;
+ private static final int MSG_MEDIA_BUTTON = 21;
+ private static final int MSG_SET_VOLUME = 22;
// KeyEvent constants only available on API 11+
private static final int KEYCODE_MEDIA_PAUSE = 127;
@@ -1848,64 +2023,77 @@
@Override
public void handleMessage(Message msg) {
- if (mCallback == null) {
+ MediaSessionCompat.Callback cb = mCallback;
+ if (cb == null) {
return;
}
switch (msg.what) {
- case MSG_PLAY:
- mCallback.onPlay();
- break;
- case MSG_PLAY_MEDIA_ID:
- mCallback.onPlayFromMediaId((String) msg.obj, msg.getData());
- break;
- case MSG_PLAY_SEARCH:
- mCallback.onPlayFromSearch((String) msg.obj, msg.getData());
- break;
- case MSG_PLAY_URI:
- mCallback.onPlayFromUri((Uri) msg.obj, msg.getData());
- break;
- case MSG_SKIP_TO_ITEM:
- mCallback.onSkipToQueueItem((Long) msg.obj);
- break;
- case MSG_PAUSE:
- mCallback.onPause();
- break;
- case MSG_STOP:
- mCallback.onStop();
- break;
- case MSG_NEXT:
- mCallback.onSkipToNext();
- break;
- case MSG_PREVIOUS:
- mCallback.onSkipToPrevious();
- break;
- case MSG_FAST_FORWARD:
- mCallback.onFastForward();
- break;
- case MSG_REWIND:
- mCallback.onRewind();
- break;
- case MSG_SEEK_TO:
- mCallback.onSeekTo((Long) msg.obj);
- break;
- case MSG_RATE:
- mCallback.onSetRating((RatingCompat) msg.obj);
- break;
- case MSG_CUSTOM_ACTION:
- mCallback.onCustomAction((String) msg.obj, msg.getData());
+ case MSG_COMMAND:
+ Command cmd = (Command) msg.obj;
+ cb.onCommand(cmd.command, cmd.extras, cmd.stub);
break;
case MSG_MEDIA_BUTTON:
KeyEvent keyEvent = (KeyEvent) msg.obj;
Intent intent = new Intent(Intent.ACTION_MEDIA_BUTTON);
intent.putExtra(Intent.EXTRA_KEY_EVENT, keyEvent);
// Let the Callback handle events first before using the default behavior
- if (!mCallback.onMediaButtonEvent(intent)) {
- onMediaButtonEvent(keyEvent);
+ if (!cb.onMediaButtonEvent(intent)) {
+ onMediaButtonEvent(keyEvent, cb);
}
break;
- case MSG_COMMAND:
- Command cmd = (Command) msg.obj;
- mCallback.onCommand(cmd.command, cmd.extras, cmd.stub);
+ case MSG_PREPARE:
+ cb.onPrepare();
+ break;
+ case MSG_PREPARE_MEDIA_ID:
+ cb.onPrepareFromMediaId((String) msg.obj, msg.getData());
+ break;
+ case MSG_PREPARE_SEARCH:
+ cb.onPrepareFromSearch((String) msg.obj, msg.getData());
+ break;
+ case MSG_PREPARE_URI:
+ cb.onPrepareFromUri((Uri) msg.obj, msg.getData());
+ break;
+ case MSG_PLAY:
+ cb.onPlay();
+ break;
+ case MSG_PLAY_MEDIA_ID:
+ cb.onPlayFromMediaId((String) msg.obj, msg.getData());
+ break;
+ case MSG_PLAY_SEARCH:
+ cb.onPlayFromSearch((String) msg.obj, msg.getData());
+ break;
+ case MSG_PLAY_URI:
+ cb.onPlayFromUri((Uri) msg.obj, msg.getData());
+ break;
+ case MSG_SKIP_TO_ITEM:
+ cb.onSkipToQueueItem((Long) msg.obj);
+ break;
+ case MSG_PAUSE:
+ cb.onPause();
+ break;
+ case MSG_STOP:
+ cb.onStop();
+ break;
+ case MSG_NEXT:
+ cb.onSkipToNext();
+ break;
+ case MSG_PREVIOUS:
+ cb.onSkipToPrevious();
+ break;
+ case MSG_FAST_FORWARD:
+ cb.onFastForward();
+ break;
+ case MSG_REWIND:
+ cb.onRewind();
+ break;
+ case MSG_SEEK_TO:
+ cb.onSeekTo((Long) msg.obj);
+ break;
+ case MSG_RATE:
+ cb.onSetRating((RatingCompat) msg.obj);
+ break;
+ case MSG_CUSTOM_ACTION:
+ cb.onCustomAction((String) msg.obj, msg.getData());
break;
case MSG_ADJUST_VOLUME:
adjustVolume((int) msg.obj, 0);
@@ -1916,7 +2104,7 @@
}
}
- private void onMediaButtonEvent(KeyEvent ke) {
+ private void onMediaButtonEvent(KeyEvent ke, MediaSessionCompat.Callback cb) {
if (ke == null || ke.getAction() != KeyEvent.ACTION_DOWN) {
return;
}
@@ -1925,38 +2113,38 @@
// Note KeyEvent.KEYCODE_MEDIA_PLAY is API 11+
case KEYCODE_MEDIA_PLAY:
if ((validActions & PlaybackStateCompat.ACTION_PLAY) != 0) {
- mCallback.onPlay();
+ cb.onPlay();
}
break;
// Note KeyEvent.KEYCODE_MEDIA_PAUSE is API 11+
case KEYCODE_MEDIA_PAUSE:
if ((validActions & PlaybackStateCompat.ACTION_PAUSE) != 0) {
- mCallback.onPause();
+ cb.onPause();
}
break;
case KeyEvent.KEYCODE_MEDIA_NEXT:
if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
- mCallback.onSkipToNext();
+ cb.onSkipToNext();
}
break;
case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
if ((validActions & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
- mCallback.onSkipToPrevious();
+ cb.onSkipToPrevious();
}
break;
case KeyEvent.KEYCODE_MEDIA_STOP:
if ((validActions & PlaybackStateCompat.ACTION_STOP) != 0) {
- mCallback.onStop();
+ cb.onStop();
}
break;
case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
if ((validActions & PlaybackStateCompat.ACTION_FAST_FORWARD) != 0) {
- mCallback.onFastForward();
+ cb.onFastForward();
}
break;
case KeyEvent.KEYCODE_MEDIA_REWIND:
if ((validActions & PlaybackStateCompat.ACTION_REWIND) != 0) {
- mCallback.onRewind();
+ cb.onRewind();
}
break;
case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
@@ -1968,9 +2156,9 @@
boolean canPause = (validActions & (PlaybackStateCompat.ACTION_PLAY_PAUSE
| PlaybackStateCompat.ACTION_PAUSE)) != 0;
if (isPlaying && canPause) {
- mCallback.onPause();
+ cb.onPause();
} else if (!isPlaying && canPlay) {
- mCallback.onPlay();
+ cb.onPlay();
}
break;
}
@@ -2068,7 +2256,7 @@
public void setQueue(List<QueueItem> queue) {
List<Object> queueObjs = null;
if (queue != null) {
- queueObjs = new ArrayList<Object>();
+ queueObjs = new ArrayList<>();
for (QueueItem item : queue) {
queueObjs.add(item.getQueueItem());
}
@@ -2104,5 +2292,14 @@
public Object getRemoteControlClient() {
return null;
}
+
+ @Override
+ public String getCallingPackage() {
+ if (android.os.Build.VERSION.SDK_INT < 24) {
+ return null;
+ } else {
+ return MediaSessionCompatApi24.getCallingPackage(mSessionObj);
+ }
+ }
}
}
diff --git a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
index 05ec2783..e3bda8c 100644
--- a/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
+++ b/v4/java/android/support/v4/media/session/PlaybackStateCompat.java
@@ -43,7 +43,8 @@
@IntDef(flag=true, value={ACTION_STOP, ACTION_PAUSE, ACTION_PLAY, ACTION_REWIND,
ACTION_SKIP_TO_PREVIOUS, ACTION_SKIP_TO_NEXT, ACTION_FAST_FORWARD, ACTION_SET_RATING,
ACTION_SEEK_TO, ACTION_PLAY_PAUSE, ACTION_PLAY_FROM_MEDIA_ID, ACTION_PLAY_FROM_SEARCH,
- ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI})
+ ACTION_SKIP_TO_QUEUE_ITEM, ACTION_PLAY_FROM_URI, ACTION_PREPARE,
+ ACTION_PREPARE_FROM_MEDIA_ID, ACTION_PREPARE_FROM_SEARCH, ACTION_PREPARE_FROM_URI})
@Retention(RetentionPolicy.SOURCE)
public @interface Actions {}
@@ -137,6 +138,7 @@
* @see Builder#setActions(long)
*/
public static final long ACTION_SKIP_TO_QUEUE_ITEM = 1 << 12;
+
/**
* Indicates this session supports the play from URI command.
*
@@ -145,6 +147,34 @@
public static final long ACTION_PLAY_FROM_URI = 1 << 13;
/**
+ * Indicates this session supports the prepare command.
+ *
+ * @see Builder#setActions(long)
+ */
+ public static final long ACTION_PREPARE = 1 << 14;
+
+ /**
+ * Indicates this session supports the prepare from media id command.
+ *
+ * @see Builder#setActions(long)
+ */
+ public static final long ACTION_PREPARE_FROM_MEDIA_ID = 1 << 15;
+
+ /**
+ * Indicates this session supports the prepare from search command.
+ *
+ * @see Builder#setActions(long)
+ */
+ public static final long ACTION_PREPARE_FROM_SEARCH = 1 << 16;
+
+ /**
+ * Indicates this session supports the prepare from URI command.
+ *
+ * @see Builder#setActions(long)
+ */
+ public static final long ACTION_PREPARE_FROM_URI = 1 << 17;
+
+ /**
* @hide
*/
@IntDef({STATE_NONE, STATE_STOPPED, STATE_PAUSED, STATE_PLAYING, STATE_FAST_FORWARDING,
@@ -398,6 +428,10 @@
* <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
* <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
* <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
* </ul>
*/
@Actions
@@ -904,6 +938,10 @@
* <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_SEARCH}</li>
* <li> {@link PlaybackStateCompat#ACTION_SKIP_TO_QUEUE_ITEM}</li>
* <li> {@link PlaybackStateCompat#ACTION_PLAY_FROM_URI}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_MEDIA_ID}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_SEARCH}</li>
+ * <li> {@link PlaybackStateCompat#ACTION_PREPARE_FROM_URI}</li>
* </ul>
*
* @return this
diff --git a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
index 14f150a..785a8f3 100644
--- a/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
+++ b/v4/java/android/support/v4/net/ConnectivityManagerCompat.java
@@ -28,7 +28,7 @@
* Helper for accessing features in {@link ConnectivityManager} introduced after
* API level 16 in a backwards compatible fashion.
*/
-public class ConnectivityManagerCompat {
+public final class ConnectivityManagerCompat {
interface ConnectivityManagerCompatImpl {
boolean isActiveNetworkMetered(ConnectivityManager cm);
@@ -118,4 +118,6 @@
return null;
}
}
+
+ private ConnectivityManagerCompat() {}
}
diff --git a/v4/java/android/support/v4/net/TrafficStatsCompat.java b/v4/java/android/support/v4/net/TrafficStatsCompat.java
index cfc5c0d..57b8a7e 100644
--- a/v4/java/android/support/v4/net/TrafficStatsCompat.java
+++ b/v4/java/android/support/v4/net/TrafficStatsCompat.java
@@ -18,6 +18,7 @@
import android.os.Build;
+import java.net.DatagramSocket;
import java.net.Socket;
import java.net.SocketException;
@@ -25,7 +26,7 @@
* Helper for accessing features in TrafficStats introduced after API level 14
* in a backwards compatible fashion.
*/
-public class TrafficStatsCompat {
+public final class TrafficStatsCompat {
interface TrafficStatsCompatImpl {
void clearThreadStatsTag();
@@ -35,6 +36,8 @@
void setThreadStatsTag(int tag);
void tagSocket(Socket socket) throws SocketException;
void untagSocket(Socket socket) throws SocketException;
+ void tagDatagramSocket(DatagramSocket socket) throws SocketException;
+ void untagDatagramSocket(DatagramSocket socket) throws SocketException;
}
static class BaseTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -79,6 +82,14 @@
@Override
public void untagSocket(Socket socket) {
}
+
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) {
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) {
+ }
}
static class IcsTrafficStatsCompatImpl implements TrafficStatsCompatImpl {
@@ -116,12 +127,36 @@
public void untagSocket(Socket socket) throws SocketException {
TrafficStatsCompatIcs.untagSocket(socket);
}
+
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatIcs.tagDatagramSocket(socket);
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatIcs.untagDatagramSocket(socket);
+ }
+ }
+
+ static class Api24TrafficStatsCompatImpl extends IcsTrafficStatsCompatImpl {
+ @Override
+ public void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatApi24.tagDatagramSocket(socket);
+ }
+
+ @Override
+ public void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ TrafficStatsCompatApi24.untagDatagramSocket(socket);
+ }
}
private static final TrafficStatsCompatImpl IMPL;
static {
- if (Build.VERSION.SDK_INT >= 14) {
+ if ("N".equals(Build.VERSION.CODENAME)) {
+ IMPL = new Api24TrafficStatsCompatImpl();
+ } else if (Build.VERSION.SDK_INT >= 14) {
IMPL = new IcsTrafficStatsCompatImpl();
} else {
IMPL = new BaseTrafficStatsCompatImpl();
@@ -200,4 +235,26 @@
public static void untagSocket(Socket socket) throws SocketException {
IMPL.untagSocket(socket);
}
+
+ /**
+ * Tag the given {@link DatagramSocket} with any statistics parameters
+ * active for the current thread. Subsequent calls always replace any
+ * existing parameters. When finished, call
+ * {@link #untagDatagramSocket(DatagramSocket)} to remove statistics
+ * parameters.
+ *
+ * @see #setThreadStatsTag(int)
+ */
+ public static void tagDatagramSocket(DatagramSocket socket) throws SocketException {
+ IMPL.tagDatagramSocket(socket);
+ }
+
+ /**
+ * Remove any statistics parameters from the given {@link DatagramSocket}.
+ */
+ public static void untagDatagramSocket(DatagramSocket socket) throws SocketException {
+ IMPL.untagDatagramSocket(socket);
+ }
+
+ private TrafficStatsCompat() {}
}
diff --git a/v4/java/android/support/v4/os/AsyncTaskCompat.java b/v4/java/android/support/v4/os/AsyncTaskCompat.java
index 2af8905..2423c73 100644
--- a/v4/java/android/support/v4/os/AsyncTaskCompat.java
+++ b/v4/java/android/support/v4/os/AsyncTaskCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in {@link android.os.AsyncTask}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class AsyncTaskCompat {
+public final class AsyncTaskCompat {
/**
* Executes the task with the specified parameters, allowing multiple tasks to run in parallel
@@ -51,4 +51,6 @@
return task;
}
+ private AsyncTaskCompat() {}
+
}
diff --git a/v4/java/android/support/v4/os/EnvironmentCompat.java b/v4/java/android/support/v4/os/EnvironmentCompat.java
index 66a2a8e..454065d 100644
--- a/v4/java/android/support/v4/os/EnvironmentCompat.java
+++ b/v4/java/android/support/v4/os/EnvironmentCompat.java
@@ -27,7 +27,7 @@
* Helper for accessing features in {@link Environment} introduced after API
* level 4 in a backwards compatible fashion.
*/
-public class EnvironmentCompat {
+public final class EnvironmentCompat {
private static final String TAG = "EnvironmentCompat";
/**
@@ -72,4 +72,6 @@
return MEDIA_UNKNOWN;
}
+
+ private EnvironmentCompat() {}
}
diff --git a/v4/java/android/support/v4/os/ParcelableCompat.java b/v4/java/android/support/v4/os/ParcelableCompat.java
index fbb064f..10c03b5 100644
--- a/v4/java/android/support/v4/os/ParcelableCompat.java
+++ b/v4/java/android/support/v4/os/ParcelableCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in {@link android.os.Parcelable}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ParcelableCompat {
+public final class ParcelableCompat {
/**
* Factory method for {@link Parcelable.Creator}.
@@ -56,4 +56,6 @@
return mCallbacks.newArray(size);
}
}
+
+ private ParcelableCompat() {}
}
diff --git a/v4/java/android/support/v4/os/TraceCompat.java b/v4/java/android/support/v4/os/TraceCompat.java
index 82b0fd5e..a8b62ab 100644
--- a/v4/java/android/support/v4/os/TraceCompat.java
+++ b/v4/java/android/support/v4/os/TraceCompat.java
@@ -26,7 +26,7 @@
* href="{@docRoot}tools/debugging/systrace.html">Analyzing Display and Performance
* with Systrace</a>.
*/
-public class TraceCompat {
+public final class TraceCompat {
/**
* Writes a trace message to indicate that a given section of code has begun. This call must
* be followed by a corresponding call to {@link #endSection()} on the same thread.
@@ -57,4 +57,6 @@
TraceJellybeanMR2.endSection();
}
}
+
+ private TraceCompat() {}
}
diff --git a/v4/java/android/support/v4/os/UserManagerCompat.java b/v4/java/android/support/v4/os/UserManagerCompat.java
new file mode 100644
index 0000000..9ad0643
--- /dev/null
+++ b/v4/java/android/support/v4/os/UserManagerCompat.java
@@ -0,0 +1,70 @@
+/*
+ * 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.v4.os;
+
+import android.content.Context;
+
+/**
+ * Helper for accessing features in {@link android.os.UserManager}
+ * introduced after API level 4 in a backwards compatible fashion.
+ */
+public class UserManagerCompat {
+ private UserManagerCompat() {
+ }
+
+ /**
+ * Return whether the calling user is running in a "locked" state. A user is
+ * unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ *
+ * @removed
+ * @deprecated Removed. Do not use.
+ */
+ @Deprecated
+ public static boolean isUserRunningAndLocked(Context context) {
+ return !isUserUnlocked(context);
+ }
+
+ /**
+ * Return whether the calling user is running in an "unlocked" state. A user
+ * is unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ *
+ * @removed
+ * @deprecated Removed. Do not use.
+ */
+ @Deprecated
+ public static boolean isUserRunningAndUnlocked(Context context) {
+ return isUserUnlocked(context);
+ }
+
+ /**
+ * Return whether the calling user is running in an "unlocked" state. A user
+ * is unlocked only after they've entered their credentials (such as a lock
+ * pattern or PIN), and credential-encrypted private app data storage is
+ * available.
+ */
+ public static boolean isUserUnlocked(Context context) {
+ if (BuildCompat.isAtLeastN()) {
+ return UserManagerCompatApi24.isUserUnlocked(context);
+ } else {
+ return true;
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/print/PrintHelper.java b/v4/java/android/support/v4/print/PrintHelper.java
index 23429ec..88d5387 100644
--- a/v4/java/android/support/v4/print/PrintHelper.java
+++ b/v4/java/android/support/v4/print/PrintHelper.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.graphics.Bitmap;
import android.os.Build;
+
import android.net.Uri;
import java.io.FileNotFoundException;
@@ -151,13 +152,14 @@
}
/**
- * Implementation used on KitKat (and above)
+ * Generic implementation for KitKat to Api24
*/
- private static final class PrintHelperKitkatImpl implements PrintHelperVersionImpl {
- private final PrintHelperKitkat mPrintHelper;
+ private static class PrintHelperImpl<RealHelper extends PrintHelperKitkat>
+ implements PrintHelperVersionImpl {
+ private final RealHelper mPrintHelper;
- PrintHelperKitkatImpl(Context context) {
- mPrintHelper = new PrintHelperKitkat(context);
+ protected PrintHelperImpl(RealHelper helper) {
+ mPrintHelper = helper;
}
@Override
@@ -193,9 +195,9 @@
@Override
public void printBitmap(String jobName, Bitmap bitmap,
final OnPrintFinishCallback callback) {
- PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+ RealHelper.OnPrintFinishCallback delegateCallback = null;
if (callback != null) {
- delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+ delegateCallback = new RealHelper.OnPrintFinishCallback() {
@Override
public void onFinish() {
callback.onFinish();
@@ -208,9 +210,9 @@
@Override
public void printBitmap(String jobName, Uri imageFile,
final OnPrintFinishCallback callback) throws FileNotFoundException {
- PrintHelperKitkat.OnPrintFinishCallback delegateCallback = null;
+ RealHelper.OnPrintFinishCallback delegateCallback = null;
if (callback != null) {
- delegateCallback = new PrintHelperKitkat.OnPrintFinishCallback() {
+ delegateCallback = new RealHelper.OnPrintFinishCallback() {
@Override
public void onFinish() {
callback.onFinish();
@@ -222,6 +224,43 @@
}
/**
+ * Implementation used on KitKat
+ */
+ private static final class PrintHelperKitkatImpl extends PrintHelperImpl<PrintHelperKitkat> {
+ PrintHelperKitkatImpl(Context context) {
+ super(new PrintHelperKitkat(context));
+ }
+ }
+
+ /**
+ * Implementation used on Api20 to Api22
+ */
+ private static final class PrintHelperApi20Impl extends PrintHelperImpl<PrintHelperApi20> {
+ PrintHelperApi20Impl(Context context) {
+ super(new PrintHelperApi20(context));
+ }
+ }
+
+ /**
+ * Implementation used on Api23
+ */
+ private static final class PrintHelperApi23Impl extends PrintHelperImpl<PrintHelperApi23> {
+ PrintHelperApi23Impl(Context context) {
+ super(new PrintHelperApi23(context));
+ }
+ }
+
+
+ /**
+ * Implementation used on Api24 and above
+ */
+ private static final class PrintHelperApi24Impl extends PrintHelperImpl<PrintHelperApi24> {
+ PrintHelperApi24Impl(Context context) {
+ super(new PrintHelperApi24(context));
+ }
+ }
+
+ /**
* Returns the PrintHelper that can be used to print images.
*
* @param context A context for accessing system resources.
@@ -229,7 +268,15 @@
*/
public PrintHelper(Context context) {
if (systemSupportsPrint()) {
- mImpl = new PrintHelperKitkatImpl(context);
+ if (Build.VERSION.SDK_INT >= 24) {
+ mImpl = new PrintHelperApi24Impl(context);
+ } else if (Build.VERSION.SDK_INT >= 23) {
+ mImpl = new PrintHelperApi23Impl(context);
+ } else if (Build.VERSION.SDK_INT >= 20) {
+ mImpl = new PrintHelperApi20Impl(context);
+ } else {
+ mImpl = new PrintHelperKitkatImpl(context);
+ }
} else {
mImpl = new PrintHelperStubImpl();
}
@@ -350,4 +397,4 @@
throws FileNotFoundException {
mImpl.printBitmap(jobName, imageFile, callback);
}
-}
\ No newline at end of file
+}
diff --git a/v4/java/android/support/v4/text/ICUCompat.java b/v4/java/android/support/v4/text/ICUCompat.java
index 43ada52..0632513 100644
--- a/v4/java/android/support/v4/text/ICUCompat.java
+++ b/v4/java/android/support/v4/text/ICUCompat.java
@@ -20,7 +20,7 @@
import java.util.Locale;
-public class ICUCompat {
+public final class ICUCompat {
interface ICUCompatImpl {
public String maximizeAndGetScript(Locale locale);
@@ -86,4 +86,6 @@
public static String maximizeAndGetScript(Locale locale) {
return IMPL.maximizeAndGetScript(locale);
}
+
+ private ICUCompat() {}
}
diff --git a/v4/java/android/support/v4/text/TextDirectionHeuristicsCompat.java b/v4/java/android/support/v4/text/TextDirectionHeuristicsCompat.java
index 1fddfd1..b0ede87 100644
--- a/v4/java/android/support/v4/text/TextDirectionHeuristicsCompat.java
+++ b/v4/java/android/support/v4/text/TextDirectionHeuristicsCompat.java
@@ -25,7 +25,7 @@
* Some objects that implement TextDirectionHeuristic.
*
*/
-public class TextDirectionHeuristicsCompat {
+public final class TextDirectionHeuristicsCompat {
/**
* Always decides that the direction is left to right.
@@ -254,4 +254,6 @@
public static final TextDirectionHeuristicLocale INSTANCE =
new TextDirectionHeuristicLocale();
}
+
+ private TextDirectionHeuristicsCompat() {}
}
diff --git a/v4/java/android/support/v4/text/TextUtilsCompat.java b/v4/java/android/support/v4/text/TextUtilsCompat.java
index 72b6408..c8207e4 100644
--- a/v4/java/android/support/v4/text/TextUtilsCompat.java
+++ b/v4/java/android/support/v4/text/TextUtilsCompat.java
@@ -23,7 +23,7 @@
import java.util.Locale;
-public class TextUtilsCompat {
+public final class TextUtilsCompat {
private static class TextUtilsCompatImpl {
@NonNull
public String htmlEncode(@NonNull String s) {
@@ -147,4 +147,6 @@
private static String ARAB_SCRIPT_SUBTAG = "Arab";
private static String HEBR_SCRIPT_SUBTAG = "Hebr";
+
+ private TextUtilsCompat() {}
}
diff --git a/v4/java/android/support/v4/util/AtomicFile.java b/v4/java/android/support/v4/util/AtomicFile.java
index 69b255b..275f4e2 100644
--- a/v4/java/android/support/v4/util/AtomicFile.java
+++ b/v4/java/android/support/v4/util/AtomicFile.java
@@ -100,7 +100,7 @@
str = new FileOutputStream(mBaseName);
} catch (FileNotFoundException e) {
File parent = mBaseName.getParentFile();
- if (!parent.mkdir()) {
+ if (!parent.mkdirs()) {
throw new IOException("Couldn't create directory " + mBaseName);
}
try {
diff --git a/v4/java/android/support/v4/util/CircularArray.java b/v4/java/android/support/v4/util/CircularArray.java
index e2ac26b..9533136 100644
--- a/v4/java/android/support/v4/util/CircularArray.java
+++ b/v4/java/android/support/v4/util/CircularArray.java
@@ -41,27 +41,35 @@
}
/**
- * Create a CircularArray with default capacity.
+ * Creates a circular array with default capacity.
*/
public CircularArray() {
this(8);
}
/**
- * Create a CircularArray with capacity for at least minCapacity elements.
+ * Creates a circular array with capacity for at least {@code minCapacity}
+ * elements.
*
- * @param minCapacity The minimum capacity required for the CircularArray.
+ * @param minCapacity the minimum capacity, between 1 and 2^30 inclusive
*/
public CircularArray(int minCapacity) {
- if (minCapacity <= 0) {
- throw new IllegalArgumentException("capacity must be positive");
+ if (minCapacity < 1) {
+ throw new IllegalArgumentException("capacity must be >= 1");
}
- int arrayCapacity = minCapacity;
- // If minCapacity isn't a power of 2, round up to the next highest power
- // of 2.
+ if (minCapacity > (2 << 29)) {
+ throw new IllegalArgumentException("capacity must be <= 2^30");
+ }
+
+ // If minCapacity isn't a power of 2, round up to the next highest
+ // power of 2.
+ final int arrayCapacity;
if (Integer.bitCount(minCapacity) != 1) {
- arrayCapacity = 1 << (Integer.highestOneBit(minCapacity) + 1);
+ arrayCapacity = Integer.highestOneBit(minCapacity - 1) << 1;
+ } else {
+ arrayCapacity = minCapacity;
}
+
mCapacityBitmask = arrayCapacity - 1;
mElements = (E[]) new Object[arrayCapacity];
}
diff --git a/v4/java/android/support/v4/util/CircularIntArray.java b/v4/java/android/support/v4/util/CircularIntArray.java
index 706d73b..b82107f 100644
--- a/v4/java/android/support/v4/util/CircularIntArray.java
+++ b/v4/java/android/support/v4/util/CircularIntArray.java
@@ -42,27 +42,35 @@
}
/**
- * Create a CircularIntArray with default capacity.
+ * Creates a circular array with default capacity.
*/
public CircularIntArray() {
this(8);
}
/**
- * Create a CircularIntArray with capacity for at least minCapacity elements.
+ * Creates a circular array with capacity for at least {@code minCapacity}
+ * elements.
*
- * @param minCapacity The minimum capacity required for the CircularIntArray.
+ * @param minCapacity the minimum capacity, between 1 and 2^30 inclusive
*/
public CircularIntArray(int minCapacity) {
- if (minCapacity <= 0) {
- throw new IllegalArgumentException("capacity must be positive");
+ if (minCapacity < 1) {
+ throw new IllegalArgumentException("capacity must be >= 1");
}
- int arrayCapacity = minCapacity;
- // If minCapacity isn't a power of 2, round up to the next highest power
- // of 2.
+ if (minCapacity > (2 << 29)) {
+ throw new IllegalArgumentException("capacity must be <= 2^30");
+ }
+
+ // If minCapacity isn't a power of 2, round up to the next highest
+ // power of 2.
+ final int arrayCapacity;
if (Integer.bitCount(minCapacity) != 1) {
- arrayCapacity = 1 << (Integer.highestOneBit(minCapacity) + 1);
+ arrayCapacity = Integer.highestOneBit(minCapacity - 1) << 1;
+ } else {
+ arrayCapacity = minCapacity;
}
+
mCapacityBitmask = arrayCapacity - 1;
mElements = new int[arrayCapacity];
}
diff --git a/v4/java/android/support/v4/util/SimpleArrayMap.java b/v4/java/android/support/v4/util/SimpleArrayMap.java
index 1b1577a..c7d4b5d 100644
--- a/v4/java/android/support/v4/util/SimpleArrayMap.java
+++ b/v4/java/android/support/v4/util/SimpleArrayMap.java
@@ -522,17 +522,42 @@
/**
* {@inheritDoc}
*
- * <p>This implementation returns false if the object is not a map, or
- * if the maps have different sizes. Otherwise, for each key in this map,
- * values of both maps are compared. If the values for any key are not
- * equal, the method returns false, otherwise it returns true.
+ * <p>This implementation returns false if the object is not a Map or
+ * SimpleArrayMap, or if the maps have different sizes. Otherwise, for each
+ * key in this map, values of both maps are compared. If the values for any
+ * key are not equal, the method returns false, otherwise it returns true.
*/
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
- if (object instanceof Map) {
+ if (object instanceof SimpleArrayMap) {
+ SimpleArrayMap<?, ?> map = (SimpleArrayMap<?, ?>) object;
+ if (size() != map.size()) {
+ return false;
+ }
+
+ try {
+ for (int i=0; i<mSize; i++) {
+ K key = keyAt(i);
+ V mine = valueAt(i);
+ Object theirs = map.get(key);
+ if (mine == null) {
+ if (theirs != null || !map.containsKey(key)) {
+ return false;
+ }
+ } else if (!mine.equals(theirs)) {
+ return false;
+ }
+ }
+ } catch (NullPointerException ignored) {
+ return false;
+ } catch (ClassCastException ignored) {
+ return false;
+ }
+ return true;
+ } else if (object instanceof Map) {
Map<?, ?> map = (Map<?, ?>) object;
if (size() != map.size()) {
return false;
diff --git a/v4/java/android/support/v4/util/TimeUtils.java b/v4/java/android/support/v4/util/TimeUtils.java
index a57c94c..36a1964 100644
--- a/v4/java/android/support/v4/util/TimeUtils.java
+++ b/v4/java/android/support/v4/util/TimeUtils.java
@@ -24,7 +24,7 @@
*
* @hide
*/
-public class TimeUtils {
+public final class TimeUtils {
/** @hide Field length that can hold 999 days of time */
public static final int HUNDRED_DAY_FIELD_LEN = 19;
@@ -172,4 +172,6 @@
}
formatDuration(time-now, pw, 0);
}
+
+ private TimeUtils() {}
}
diff --git a/v4/java/android/support/v4/view/AbsSavedState.java b/v4/java/android/support/v4/view/AbsSavedState.java
new file mode 100644
index 0000000..e5aba098
--- /dev/null
+++ b/v4/java/android/support/v4/view/AbsSavedState.java
@@ -0,0 +1,100 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+
+/**
+ * A {@link Parcelable} implementation that should be used by inheritance
+ * hierarchies to ensure the state of all classes along the chain is saved.
+ */
+public abstract class AbsSavedState implements Parcelable {
+ public static final AbsSavedState EMPTY_STATE = new AbsSavedState() {};
+
+ private final Parcelable mSuperState;
+
+ /**
+ * Constructor used to make the EMPTY_STATE singleton
+ */
+ private AbsSavedState() {
+ mSuperState = null;
+ }
+
+ /**
+ * Constructor called by derived classes when creating their SavedState objects
+ *
+ * @param superState The state of the superclass of this view
+ */
+ protected AbsSavedState(Parcelable superState) {
+ if (superState == null) {
+ throw new IllegalArgumentException("superState must not be null");
+ }
+ mSuperState = superState != EMPTY_STATE ? superState : null;
+ }
+
+ /**
+ * Constructor used when reading from a parcel. Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ */
+ protected AbsSavedState(Parcel source) {
+ this(source, null);
+ }
+
+ /**
+ * Constructor used when reading from a parcel. Reads the state of the superclass.
+ *
+ * @param source parcel to read from
+ * @param loader ClassLoader to use for reading
+ */
+ protected AbsSavedState(Parcel source, ClassLoader loader) {
+ Parcelable superState = source.readParcelable(loader);
+ mSuperState = superState != null ? superState : EMPTY_STATE;
+ }
+
+ final public Parcelable getSuperState() {
+ return mSuperState;
+ }
+
+ public int describeContents() {
+ return 0;
+ }
+
+ public void writeToParcel(Parcel dest, int flags) {
+ dest.writeParcelable(mSuperState, flags);
+ }
+
+ public static final Parcelable.Creator<AbsSavedState> CREATOR
+ = ParcelableCompat.newCreator(new ParcelableCompatCreatorCallbacks<AbsSavedState>() {
+ @Override
+ public AbsSavedState createFromParcel(Parcel in, ClassLoader loader) {
+ Parcelable superState = in.readParcelable(loader);
+ if (superState != null) {
+ throw new IllegalStateException("superState must be null");
+ }
+ return EMPTY_STATE;
+ }
+
+ @Override
+ public AbsSavedState[] newArray(int size) {
+ return new AbsSavedState[size];
+ }
+ });
+}
diff --git a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
index 0ea755e..4d0281e 100644
--- a/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
+++ b/v4/java/android/support/v4/view/AccessibilityDelegateCompat.java
@@ -27,6 +27,18 @@
/**
* Helper for accessing {@link View.AccessibilityDelegate} introduced after
* API level 4 in a backwards compatible fashion.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*/
public class AccessibilityDelegateCompat {
diff --git a/v4/java/android/support/v4/view/AsyncLayoutInflater.java b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
new file mode 100644
index 0000000..6e88f59
--- /dev/null
+++ b/v4/java/android/support/v4/view/AsyncLayoutInflater.java
@@ -0,0 +1,222 @@
+/*
+ * 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.v4.view;
+
+import android.content.Context;
+import android.os.Handler;
+import android.os.Handler.Callback;
+import android.os.Looper;
+import android.os.Message;
+import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.UiThread;
+import android.support.v4.util.Pools.SynchronizedPool;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.ArrayBlockingQueue;
+
+/**
+ * <p>Helper class for inflating layouts asynchronously. To use, construct
+ * an instance of {@link AsyncLayoutInflater} on the UI thread and call
+ * {@link #inflate(int, ViewGroup, OnInflateFinishedListener)}. The
+ * {@link OnInflateFinishedListener} will be invoked on the UI thread
+ * when the inflate request has completed.
+ *
+ * <p>This is intended for parts of the UI that are created lazily or in
+ * response to user interactions. This allows the UI thread to continue
+ * to be responsive & animate while the relatively heavy inflate
+ * is being performed.
+ *
+ * <p>For a layout to be inflated asynchronously it needs to have a parent
+ * whose {@link ViewGroup#generateLayoutParams(AttributeSet)} is thread-safe
+ * and all the Views being constructed as part of inflation must not create
+ * any {@link Handler}s or otherwise call {@link Looper#myLooper()}. If the
+ * layout that is trying to be inflated cannot be constructed
+ * asynchronously for whatever reason, {@link AsyncLayoutInflater} will
+ * automatically fall back to inflating on the UI thread.
+ *
+ * <p>NOTE that the inflated View hierarchy is NOT added to the parent. It is
+ * equivalent to calling {@link LayoutInflater#inflate(int, ViewGroup, boolean)}
+ * with attachToRoot set to false. Callers will likely want to call
+ * {@link ViewGroup#addView(View)} in the {@link OnInflateFinishedListener}
+ * callback at a minimum.
+ *
+ * <p>This inflater does not support setting a {@link LayoutInflater.Factory}
+ * nor {@link LayoutInflater.Factory2}. Similarly it does not support inflating
+ * layouts that contain fragments.
+ */
+public final class AsyncLayoutInflater {
+ private static final String TAG = "AsyncLayoutInflater";
+
+ private LayoutInflater mInflater;
+ private Handler mHandler;
+ private InflateThread mInflateThread;
+
+ public AsyncLayoutInflater(@NonNull Context context) {
+ mInflater = new BasicInflater(context);
+ mHandler = new Handler(mHandlerCallback);
+ mInflateThread = InflateThread.getInstance();
+ }
+
+ @UiThread
+ public void inflate(@LayoutRes int resid, @Nullable ViewGroup parent,
+ @NonNull OnInflateFinishedListener callback) {
+ if (callback == null) {
+ throw new NullPointerException("callback argument may not be null!");
+ }
+ InflateRequest request = mInflateThread.obtainRequest();
+ request.inflater = this;
+ request.resid = resid;
+ request.parent = parent;
+ request.callback = callback;
+ mInflateThread.enqueue(request);
+ }
+
+ private Callback mHandlerCallback = new Callback() {
+ @Override
+ public boolean handleMessage(Message msg) {
+ InflateRequest request = (InflateRequest) msg.obj;
+ if (request.view == null) {
+ request.view = mInflater.inflate(
+ request.resid, request.parent, false);
+ }
+ request.callback.onInflateFinished(
+ request.view, request.resid, request.parent);
+ mInflateThread.releaseRequest(request);
+ return true;
+ }
+ };
+
+ public interface OnInflateFinishedListener {
+ public void onInflateFinished(View view, int resid, ViewGroup parent);
+ }
+
+ private static class InflateRequest {
+ AsyncLayoutInflater inflater;
+ ViewGroup parent;
+ int resid;
+ View view;
+ OnInflateFinishedListener callback;
+ }
+
+ private static class BasicInflater extends LayoutInflater {
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.webkit.",
+ "android.app."
+ };
+
+ public BasicInflater(Context context) {
+ super(context);
+ }
+
+ @Override
+ public LayoutInflater cloneInContext(Context newContext) {
+ return new BasicInflater(newContext);
+ }
+
+ @Override
+ protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
+ for (String prefix : sClassPrefixList) {
+ try {
+ View view = createView(name, prefix, attrs);
+ if (view != null) {
+ return view;
+ }
+ } catch (ClassNotFoundException e) {
+ // In this case we want to let the base class take a crack
+ // at it.
+ }
+ }
+
+ return super.onCreateView(name, attrs);
+ }
+ }
+
+ private static class InflateThread extends Thread {
+ private static final InflateThread sInstance;
+ static {
+ sInstance = new InflateThread();
+ sInstance.start();
+ }
+
+ public static InflateThread getInstance() {
+ return sInstance;
+ }
+
+ private ArrayBlockingQueue<InflateRequest> mQueue
+ = new ArrayBlockingQueue<>(10);
+ private SynchronizedPool<InflateRequest> mRequestPool
+ = new SynchronizedPool<>(10);
+
+ @Override
+ public void run() {
+ while (true) {
+ InflateRequest request;
+ try {
+ request = mQueue.take();
+ } catch (InterruptedException ex) {
+ // Odd, just continue
+ Log.w(TAG, ex);
+ continue;
+ }
+
+ try {
+ request.view = request.inflater.mInflater.inflate(
+ request.resid, request.parent, false);
+ } catch (RuntimeException ex) {
+ // Probably a Looper failure, retry on the UI thread
+ Log.w(TAG, "Failed to inflate resource in the background! Retrying on the UI thread",
+ ex);
+ }
+ Message.obtain(request.inflater.mHandler, 0, request)
+ .sendToTarget();
+ }
+ }
+
+ public InflateRequest obtainRequest() {
+ InflateRequest obj = mRequestPool.acquire();
+ if (obj == null) {
+ obj = new InflateRequest();
+ }
+ return obj;
+ }
+
+ public void releaseRequest(InflateRequest obj) {
+ obj.callback = null;
+ obj.inflater = null;
+ obj.parent = null;
+ obj.resid = 0;
+ obj.view = null;
+ mRequestPool.release(obj);
+ }
+
+ public void enqueue(InflateRequest request) {
+ try {
+ mQueue.put(request);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(
+ "Failed to enqueue async inflate request", e);
+ }
+ }
+ }
+}
diff --git a/v4/java/android/support/v4/view/GestureDetectorCompat.java b/v4/java/android/support/v4/view/GestureDetectorCompat.java
index bcd1a0a..9d9ddb9 100644
--- a/v4/java/android/support/v4/view/GestureDetectorCompat.java
+++ b/v4/java/android/support/v4/view/GestureDetectorCompat.java
@@ -45,7 +45,7 @@
* will be executed when the events occur.
* </ul>
*/
-public class GestureDetectorCompat {
+public final class GestureDetectorCompat {
interface GestureDetectorCompatImpl {
boolean isLongpressEnabled();
boolean onTouchEvent(MotionEvent ev);
diff --git a/v4/java/android/support/v4/view/GravityCompat.java b/v4/java/android/support/v4/view/GravityCompat.java
index 0e8558e..cfcdd50 100644
--- a/v4/java/android/support/v4/view/GravityCompat.java
+++ b/v4/java/android/support/v4/view/GravityCompat.java
@@ -24,7 +24,7 @@
/**
* Compatibility shim for accessing newer functionality from {@link android.view.Gravity}.
*/
-public class GravityCompat {
+public final class GravityCompat {
interface GravityCompatImpl {
int getAbsoluteGravity(int gravity, int layoutDirection);
void apply(int gravity, int w, int h, Rect container, Rect outRect, int layoutDirection);
@@ -200,4 +200,6 @@
public static int getAbsoluteGravity(int gravity, int layoutDirection) {
return IMPL.getAbsoluteGravity(gravity, layoutDirection);
}
+
+ private GravityCompat() {}
}
diff --git a/v4/java/android/support/v4/view/InputDeviceCompat.java b/v4/java/android/support/v4/view/InputDeviceCompat.java
index 7a202cd..6db92e8 100644
--- a/v4/java/android/support/v4/view/InputDeviceCompat.java
+++ b/v4/java/android/support/v4/view/InputDeviceCompat.java
@@ -19,7 +19,7 @@
/**
* Helper class for accessing values in {@link android.view.InputDevice}.
*/
-public class InputDeviceCompat {
+public final class InputDeviceCompat {
/**
* A mask for input source classes.
@@ -208,4 +208,6 @@
* to match devices that provide any type of input source.
*/
public static final int SOURCE_ANY = 0xffffff00;
+
+ private InputDeviceCompat() {}
}
diff --git a/v4/java/android/support/v4/view/KeyEventCompat.java b/v4/java/android/support/v4/view/KeyEventCompat.java
index fc0ee2e..ee5a914 100644
--- a/v4/java/android/support/v4/view/KeyEventCompat.java
+++ b/v4/java/android/support/v4/view/KeyEventCompat.java
@@ -23,19 +23,19 @@
* Helper for accessing features in {@link KeyEvent} introduced after
* API level 4 in a backwards compatible fashion.
*/
-public class KeyEventCompat {
+public final class KeyEventCompat {
/**
* Interface for the full API.
*/
interface KeyEventVersionImpl {
- public int normalizeMetaState(int metaState);
- public boolean metaStateHasModifiers(int metaState, int modifiers);
- public boolean metaStateHasNoModifiers(int metaState);
- public void startTracking(KeyEvent event);
- public boolean isTracking(KeyEvent event);
- public Object getKeyDispatcherState(View view);
- public boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state,
- Object target);
+ int normalizeMetaState(int metaState);
+ boolean metaStateHasModifiers(int metaState, int modifiers);
+ boolean metaStateHasNoModifiers(int metaState);
+ void startTracking(KeyEvent event);
+ boolean isTracking(KeyEvent event);
+ Object getKeyDispatcherState(View view);
+ boolean dispatch(KeyEvent event, KeyEvent.Callback receiver, Object state, Object target);
+ boolean isCtrlPressed(KeyEvent event);
}
/**
@@ -113,6 +113,11 @@
Object target) {
return event.dispatch(receiver);
}
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return false;
+ }
}
static class EclairKeyEventVersionImpl extends BaseKeyEventVersionImpl {
@@ -156,6 +161,11 @@
public boolean metaStateHasNoModifiers(int metaState) {
return KeyEventCompatHoneycomb.metaStateHasNoModifiers(metaState);
}
+
+ @Override
+ public boolean isCtrlPressed(KeyEvent event) {
+ return KeyEventCompatHoneycomb.isCtrlPressed(event);
+ }
}
/**
@@ -208,4 +218,10 @@
Object target) {
return IMPL.dispatch(event, receiver, state, target);
}
+
+ public static boolean isCtrlPressed(KeyEvent event) {
+ return IMPL.isCtrlPressed(event);
+ }
+
+ private KeyEventCompat() {}
}
diff --git a/v4/java/android/support/v4/view/LayoutInflaterCompat.java b/v4/java/android/support/v4/view/LayoutInflaterCompat.java
index d67a739..d61896d 100644
--- a/v4/java/android/support/v4/view/LayoutInflaterCompat.java
+++ b/v4/java/android/support/v4/view/LayoutInflaterCompat.java
@@ -23,10 +23,11 @@
* Helper for accessing features in {@link android.view.LayoutInflater}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class LayoutInflaterCompat {
+public final class LayoutInflaterCompat {
interface LayoutInflaterCompatImpl {
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory);
+ public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater);
}
static class LayoutInflaterCompatImplBase implements LayoutInflaterCompatImpl {
@@ -34,6 +35,11 @@
public void setFactory(LayoutInflater layoutInflater, LayoutInflaterFactory factory) {
LayoutInflaterCompatBase.setFactory(layoutInflater, factory);
}
+
+ @Override
+ public LayoutInflaterFactory getFactory(LayoutInflater layoutInflater) {
+ return LayoutInflaterCompatBase.getFactory(layoutInflater);
+ }
}
static class LayoutInflaterCompatImplV11 extends LayoutInflaterCompatImplBase {
@@ -79,4 +85,18 @@
IMPL.setFactory(inflater, factory);
}
+ /**
+ * Return the current {@link LayoutInflaterFactory} (or null). This is
+ * called on each element name. If the factory returns a View, add that
+ * to the hierarchy. If it returns null, proceed to call onCreateView(name).
+ *
+ * @return The {@link LayoutInflaterFactory} associated with the
+ * {@link LayoutInflater}. Will be {@code null} if the inflater does not
+ * have a {@link LayoutInflaterFactory} but a raw {@link LayoutInflater.Factory}.
+ * @see LayoutInflater#getFactory()
+ */
+ public static LayoutInflaterFactory getFactory(LayoutInflater inflater) {
+ return IMPL.getFactory(inflater);
+ }
+
}
diff --git a/v4/java/android/support/v4/view/MarginLayoutParamsCompat.java b/v4/java/android/support/v4/view/MarginLayoutParamsCompat.java
index 86072c0..4e5851e 100644
--- a/v4/java/android/support/v4/view/MarginLayoutParamsCompat.java
+++ b/v4/java/android/support/v4/view/MarginLayoutParamsCompat.java
@@ -24,7 +24,7 @@
* Helper for accessing API features in
* {@link android.view.ViewGroup.MarginLayoutParams MarginLayoutParams} added after API 4.
*/
-public class MarginLayoutParamsCompat {
+public final class MarginLayoutParamsCompat {
interface MarginLayoutParamsCompatImpl {
int getMarginStart(ViewGroup.MarginLayoutParams lp);
int getMarginEnd(ViewGroup.MarginLayoutParams lp);
@@ -202,13 +202,20 @@
}
/**
- * Retuns the layout direction. Can be either {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
+ * Returns the layout direction. Can be either {@link ViewCompat#LAYOUT_DIRECTION_LTR} or
* {@link ViewCompat#LAYOUT_DIRECTION_RTL}.
*
* @return the layout direction.
*/
public static int getLayoutDirection(ViewGroup.MarginLayoutParams lp) {
- return IMPL.getLayoutDirection(lp);
+ int result = IMPL.getLayoutDirection(lp);
+ if ((result != ViewCompat.LAYOUT_DIRECTION_LTR)
+ && (result != ViewCompat.LAYOUT_DIRECTION_RTL)) {
+ // This can happen on older platform releases where the default (unset) layout direction
+ // is -1
+ result = ViewCompat.LAYOUT_DIRECTION_LTR;
+ }
+ return result;
}
/**
@@ -230,4 +237,6 @@
int layoutDirection) {
IMPL.resolveLayoutDirection(lp, layoutDirection);
}
+
+ private MarginLayoutParamsCompat() {}
}
diff --git a/v4/java/android/support/v4/view/MenuCompat.java b/v4/java/android/support/v4/view/MenuCompat.java
index 73c26ee..a03207f 100644
--- a/v4/java/android/support/v4/view/MenuCompat.java
+++ b/v4/java/android/support/v4/view/MenuCompat.java
@@ -22,7 +22,7 @@
* Helper for accessing features in {@link android.view.Menu}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class MenuCompat {
+public final class MenuCompat {
/**
* Call {@link MenuItem#setShowAsAction(int) MenuItem.setShowAsAction()}.
*
@@ -33,4 +33,6 @@
public static void setShowAsAction(MenuItem item, int actionEnum) {
MenuItemCompat.setShowAsAction(item, actionEnum);
}
+
+ private MenuCompat() {}
}
diff --git a/v4/java/android/support/v4/view/MenuItemCompat.java b/v4/java/android/support/v4/view/MenuItemCompat.java
index 8a52e9e..1467754 100644
--- a/v4/java/android/support/v4/view/MenuItemCompat.java
+++ b/v4/java/android/support/v4/view/MenuItemCompat.java
@@ -29,7 +29,7 @@
* android.view.MenuItem}, but take a {@link android.view.MenuItem} object as an additional
* argument.</p>
*/
-public class MenuItemCompat {
+public final class MenuItemCompat {
private static final String TAG = "MenuItemCompat";
/**
@@ -437,4 +437,6 @@
}
return IMPL.setOnActionExpandListener(item, listener);
}
+
+ private MenuItemCompat() {}
}
diff --git a/v4/java/android/support/v4/view/MotionEventCompat.java b/v4/java/android/support/v4/view/MotionEventCompat.java
index 8c7d07d..81d1d05 100644
--- a/v4/java/android/support/v4/view/MotionEventCompat.java
+++ b/v4/java/android/support/v4/view/MotionEventCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in {@link MotionEvent} introduced
* after API level 4 in a backwards compatible fashion.
*/
-public class MotionEventCompat {
+public final class MotionEventCompat {
/**
* Interface for the full API.
*/
@@ -36,6 +36,7 @@
public int getSource(MotionEvent event);
float getAxisValue(MotionEvent event, int axis);
float getAxisValue(MotionEvent event, int axis, int pointerIndex);
+ int getButtonState(MotionEvent event);
}
/**
@@ -91,6 +92,11 @@
public float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
return 0;
}
+
+ @Override
+ public int getButtonState(MotionEvent event) {
+ return 0;
+ }
}
/**
@@ -145,12 +151,25 @@
}
}
+
+ /**
+ * Interface implementation for devices with at least v14 APIs.
+ */
+ private static class ICSMotionEventVersionImpl extends HoneycombMr1MotionEventVersionImpl {
+ @Override
+ public int getButtonState(MotionEvent event) {
+ return MotionEventCompatICS.getButtonState(event);
+ }
+ }
+
/**
* Select the correct implementation to use for the current platform.
*/
static final MotionEventVersionImpl IMPL;
static {
- if (Build.VERSION.SDK_INT >= 12) {
+ if (Build.VERSION.SDK_INT >= 14) {
+ IMPL = new ICSMotionEventVersionImpl();
+ } else if (Build.VERSION.SDK_INT >= 12) {
IMPL = new HoneycombMr1MotionEventVersionImpl();
} else if (Build.VERSION.SDK_INT >= 9) {
IMPL = new GingerbreadMotionEventVersionImpl();
@@ -339,6 +358,16 @@
public static final int AXIS_TILT = 25;
/**
+ * Synonym for {@link MotionEvent#AXIS_RELATIVE_X}.
+ */
+ public static final int AXIS_RELATIVE_X = 27;
+
+ /**
+ * Synonym for {@link MotionEvent#AXIS_RELATIVE_Y}.
+ */
+ public static final int AXIS_RELATIVE_Y = 28;
+
+ /**
* Synonym for {@link MotionEvent#AXIS_GENERIC_1}.
*/
public static final int AXIS_GENERIC_1 = 32;
@@ -419,6 +448,11 @@
public static final int AXIS_GENERIC_16 = 47;
/**
+ * Synonym for {@link MotionEvent#BUTTON_PRIMARY}.
+ */
+ public static final int BUTTON_PRIMARY = 1;
+
+ /**
* Call {@link MotionEvent#getAction}, returning only the {@link #ACTION_MASK}
* portion.
*/
@@ -489,6 +523,15 @@
}
/**
+ * Determines whether the event is from the given source.
+ * @param source The input source to check against.
+ * @return Whether the event is from the given source.
+ */
+ public static boolean isFromSource(MotionEvent event, int source) {
+ return (getSource(event) & source) == source;
+ }
+
+ /**
* Get axis value for the first pointer index (may be an
* arbitrary pointer identifier).
*
@@ -516,4 +559,15 @@
public static float getAxisValue(MotionEvent event, int axis, int pointerIndex) {
return IMPL.getAxisValue(event, axis, pointerIndex);
}
+
+ /**
+ *
+ * @param event
+ * @return
+ */
+ public static int getButtonState(MotionEvent event) {
+ return IMPL.getButtonState(event);
+ }
+
+ private MotionEventCompat() {}
}
diff --git a/v4/java/android/support/v4/view/PagerAdapter.java b/v4/java/android/support/v4/view/PagerAdapter.java
index 55fb9c1..1b1dc3d 100644
--- a/v4/java/android/support/v4/view/PagerAdapter.java
+++ b/v4/java/android/support/v4/view/PagerAdapter.java
@@ -156,6 +156,7 @@
*
* @deprecated Use {@link #startUpdate(ViewGroup)}
*/
+ @Deprecated
public void startUpdate(View container) {
}
@@ -172,6 +173,7 @@
*
* @deprecated Use {@link #instantiateItem(ViewGroup, int)}
*/
+ @Deprecated
public Object instantiateItem(View container, int position) {
throw new UnsupportedOperationException(
"Required method instantiateItem was not overridden");
@@ -189,6 +191,7 @@
*
* @deprecated Use {@link #destroyItem(ViewGroup, int, Object)}
*/
+ @Deprecated
public void destroyItem(View container, int position, Object object) {
throw new UnsupportedOperationException("Required method destroyItem was not overridden");
}
@@ -204,6 +207,7 @@
*
* @deprecated Use {@link #setPrimaryItem(ViewGroup, int, Object)}
*/
+ @Deprecated
public void setPrimaryItem(View container, int position, Object object) {
}
@@ -216,6 +220,7 @@
*
* @deprecated Use {@link #finishUpdate(ViewGroup)}
*/
+ @Deprecated
public void finishUpdate(View container) {
}
diff --git a/v4/java/android/support/v4/view/PagerTitleStrip.java b/v4/java/android/support/v4/view/PagerTitleStrip.java
index 6edbed8..288e1a0 100644
--- a/v4/java/android/support/v4/view/PagerTitleStrip.java
+++ b/v4/java/android/support/v4/view/PagerTitleStrip.java
@@ -44,7 +44,8 @@
*
* <p>For an interactive indicator, see {@link PagerTabStrip}.</p>
*/
-public class PagerTitleStrip extends ViewGroup implements ViewPager.Decor {
+@ViewPager.DecorView
+public class PagerTitleStrip extends ViewGroup {
private static final String TAG = "PagerTitleStrip";
ViewPager mPager;
@@ -253,7 +254,7 @@
final PagerAdapter adapter = pager.getAdapter();
pager.setInternalPageChangeListener(mPageListener);
- pager.setOnAdapterChangeListener(mPageListener);
+ pager.addOnAdapterChangeListener(mPageListener);
mPager = pager;
updateAdapter(mWatchingAdapter != null ? mWatchingAdapter.get() : null, adapter);
}
@@ -264,7 +265,7 @@
if (mPager != null) {
updateAdapter(mPager.getAdapter(), null);
mPager.setInternalPageChangeListener(null);
- mPager.setOnAdapterChangeListener(null);
+ mPager.removeOnAdapterChangeListener(mPageListener);
mPager = null;
}
}
@@ -503,7 +504,8 @@
}
@Override
- public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter) {
+ public void onAdapterChanged(ViewPager viewPager, PagerAdapter oldAdapter,
+ PagerAdapter newAdapter) {
updateAdapter(oldAdapter, newAdapter);
}
diff --git a/v4/java/android/support/v4/view/PointerIconCompat.java b/v4/java/android/support/v4/view/PointerIconCompat.java
new file mode 100644
index 0000000..70344b8
--- /dev/null
+++ b/v4/java/android/support/v4/view/PointerIconCompat.java
@@ -0,0 +1,216 @@
+/**
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Bitmap;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Helper for accessing features in {@link android.view.PointerIcon} introduced after API
+ * level 4 in a backwards compatible fashion.
+ */
+public final class PointerIconCompat {
+ /** Synonym for {@link android.view.PointerIcon#TYPE_NULL} */
+ public static final int TYPE_NULL = 0;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_ARROW} */
+ public static final int TYPE_ARROW = 1000;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_CONTEXT_MENU} */
+ public static final int TYPE_CONTEXT_MENU = 1001;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_HAND} */
+ public static final int TYPE_HAND = 1002;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_HELP} */
+ public static final int TYPE_HELP = 1003;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_WAIT} */
+ public static final int TYPE_WAIT = 1004;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_CELL} */
+ public static final int TYPE_CELL = 1006;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_CROSSHAIR} */
+ public static final int TYPE_CROSSHAIR = 1007;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_TEXT} */
+ public static final int TYPE_TEXT = 1008;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_VERTICAL_TEXT} */
+ public static final int TYPE_VERTICAL_TEXT = 1009;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_ALIAS} */
+ public static final int TYPE_ALIAS = 1010;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_COPY} */
+ public static final int TYPE_COPY = 1011;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_NO_DROP} */
+ public static final int TYPE_NO_DROP = 1012;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_ALL_SCROLL} */
+ public static final int TYPE_ALL_SCROLL = 1013;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_HORIZONTAL_DOUBLE_ARROW} */
+ public static final int TYPE_HORIZONTAL_DOUBLE_ARROW = 1014;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_VERTICAL_DOUBLE_ARROW} */
+ public static final int TYPE_VERTICAL_DOUBLE_ARROW = 1015;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW} */
+ public static final int TYPE_TOP_RIGHT_DIAGONAL_DOUBLE_ARROW = 1016;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW} */
+ public static final int TYPE_TOP_LEFT_DIAGONAL_DOUBLE_ARROW = 1017;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_ZOOM_IN} */
+ public static final int TYPE_ZOOM_IN = 1018;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_ZOOM_OUT} */
+ public static final int TYPE_ZOOM_OUT = 1019;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_GRAB} */
+ public static final int TYPE_GRAB = 1020;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_GRABBING} */
+ public static final int TYPE_GRABBING = 1021;
+
+ /** Synonym for {@link android.view.PointerIcon#TYPE_DEFAULT} */
+ public static final int TYPE_DEFAULT = TYPE_ARROW;
+
+
+ private Object mPointerIcon;
+
+ private PointerIconCompat(Object pointerIcon) {
+ mPointerIcon = pointerIcon;
+ }
+
+ /**
+ * @hide
+ */
+ public Object getPointerIcon() {
+ return mPointerIcon;
+ }
+
+ interface PointerIconCompatImpl {
+ Object getSystemIcon(Context context, int style);
+ Object create(Bitmap bitmap, float hotSpotX, float hotSpotY);
+ Object load(Resources resources, int resourceId);
+ }
+
+ static class BasePointerIconCompatImpl implements PointerIconCompatImpl {
+ @Override
+ public Object getSystemIcon(Context context, int style) {
+ return null;
+ }
+
+ @Override
+ public Object create(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return null;
+ }
+
+ @Override
+ public Object load(Resources resources, int resourceId) {
+ return null;
+ }
+ }
+
+ static class Api24PointerIconCompatImpl extends BasePointerIconCompatImpl {
+ @Override
+ public Object getSystemIcon(Context context, int style) {
+ return PointerIconCompatApi24.getSystemIcon(context, style);
+ }
+
+ @Override
+ public Object create(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return PointerIconCompatApi24.create(bitmap, hotSpotX, hotSpotY);
+ }
+
+ @Override
+ public Object load(Resources resources, int resourceId) {
+ return PointerIconCompatApi24.load(resources, resourceId);
+ }
+ }
+
+ static final PointerIconCompatImpl IMPL;
+ static {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24PointerIconCompatImpl();
+ } else {
+ IMPL = new BasePointerIconCompatImpl();
+ }
+ }
+
+ /**
+ * Gets a system pointer icon for the given style.
+ * If style is not recognized, returns the default pointer icon.
+ *
+ * @param context The context.
+ * @param style The pointer icon style.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if context is null.
+ */
+ public static PointerIconCompat getSystemIcon(Context context, int style) {
+ return new PointerIconCompat(IMPL.getSystemIcon(context, style));
+ }
+
+ /**
+ * Creates a custom pointer from the given bitmap and hotspot information.
+ *
+ * @param bitmap The bitmap for the icon.
+ * @param hotSpotX The X offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getWidth()) range.
+ * @param hotSpotY The Y offset of the pointer icon hotspot in the bitmap.
+ * Must be within the [0, bitmap.getHeight()) range.
+ * @return A pointer icon for this bitmap.
+ *
+ * @throws IllegalArgumentException if bitmap is null, or if the x/y hotspot
+ * parameters are invalid.
+ */
+ public static PointerIconCompat create(Bitmap bitmap, float hotSpotX, float hotSpotY) {
+ return new PointerIconCompat(IMPL.create(bitmap, hotSpotX, hotSpotY));
+ }
+
+ /**
+ * Loads a custom pointer icon from an XML resource.
+ * <p>
+ * The XML resource should have the following form:
+ * <code>
+ * <?xml version="1.0" encoding="utf-8"?>
+ * <pointer-icon xmlns:android="http://schemas.android.com/apk/res/android"
+ * android:bitmap="@drawable/my_pointer_bitmap"
+ * android:hotSpotX="24"
+ * android:hotSpotY="24" />
+ * </code>
+ * </p>
+ *
+ * @param resources The resources object.
+ * @param resourceId The resource id.
+ * @return The pointer icon.
+ *
+ * @throws IllegalArgumentException if resources is null.
+ * @throws Resources.NotFoundException if the resource was not found or the drawable
+ * linked in the resource was not found.
+ */
+ public static PointerIconCompat load(Resources resources, int resourceId) {
+ return new PointerIconCompat(IMPL.load(resources, resourceId));
+ }
+}
diff --git a/v4/java/android/support/v4/view/ScaleGestureDetectorCompat.java b/v4/java/android/support/v4/view/ScaleGestureDetectorCompat.java
index ba8d0ec..fe24cc7 100644
--- a/v4/java/android/support/v4/view/ScaleGestureDetectorCompat.java
+++ b/v4/java/android/support/v4/view/ScaleGestureDetectorCompat.java
@@ -20,7 +20,7 @@
* Helper for accessing features in <code>ScaleGestureDetector</code> introduced
* after API level 19 (KitKat) in a backwards compatible fashion.
*/
-public class ScaleGestureDetectorCompat {
+public final class ScaleGestureDetectorCompat {
static final ScaleGestureDetectorImpl IMPL;
interface ScaleGestureDetectorImpl {
diff --git a/v4/java/android/support/v4/view/VelocityTrackerCompat.java b/v4/java/android/support/v4/view/VelocityTrackerCompat.java
index 99286d0..3a02c37 100644
--- a/v4/java/android/support/v4/view/VelocityTrackerCompat.java
+++ b/v4/java/android/support/v4/view/VelocityTrackerCompat.java
@@ -22,7 +22,7 @@
* Helper for accessing features in {@link VelocityTracker}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class VelocityTrackerCompat {
+public final class VelocityTrackerCompat {
/**
* Interface for the full API.
*/
@@ -90,4 +90,6 @@
public static float getYVelocity(VelocityTracker tracker, int pointerId) {
return IMPL.getYVelocity(tracker, pointerId);
}
+
+ private VelocityTrackerCompat() {}
}
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index b88d7e5..422d9d1 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -17,18 +17,19 @@
package android.support.v4.view;
import android.content.res.ColorStateList;
+import android.graphics.Matrix;
import android.graphics.Paint;
import android.graphics.PixelFormat;
import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.os.Bundle;
import android.support.annotation.FloatRange;
import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.util.Log;
@@ -53,6 +54,21 @@
public class ViewCompat {
private static final String TAG = "ViewCompat";
+ /** @hide */
+ @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN,
+ View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusDirection {}
+
+ /** @hide */
+ @IntDef({View.FOCUS_LEFT, View.FOCUS_UP, View.FOCUS_RIGHT, View.FOCUS_DOWN})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRealDirection {}
+
+ /** @hide */
+ @IntDef({View.FOCUS_FORWARD, View.FOCUS_BACKWARD})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface FocusRelativeDirection {}
/** @hide */
@IntDef({OVER_SCROLL_ALWAYS, OVER_SCROLL_IF_CONTENT_SCROLLS, OVER_SCROLL_NEVER})
@@ -293,36 +309,36 @@
/**
* Scroll indicator direction for the top edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_TOP = 0x1;
/**
* Scroll indicator direction for the bottom edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_BOTTOM = 0x2;
/**
* Scroll indicator direction for the left edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_LEFT = 0x4;
/**
* Scroll indicator direction for the right edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_RIGHT = 0x8;
@@ -338,93 +354,94 @@
/**
* Scroll indicator direction for the ending edge of the view.
*
- * @see #setScrollIndicators(int)
- * @see #setScrollIndicators(int, int)
- * @see #getScrollIndicators()
+ * @see #setScrollIndicators(View, int)
+ * @see #setScrollIndicators(View, int, int)
+ * @see #getScrollIndicators(View)
*/
public static final int SCROLL_INDICATOR_END = 0x20;
interface ViewCompatImpl {
- public boolean canScrollHorizontally(View v, int direction);
- public boolean canScrollVertically(View v, int direction);
- public int getOverScrollMode(View v);
- public void setOverScrollMode(View v, int mode);
- public void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
- public void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
- public void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
- public void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
- public boolean hasAccessibilityDelegate(View v);
- public boolean hasTransientState(View view);
- public void setHasTransientState(View view, boolean hasTransientState);
- public void postInvalidateOnAnimation(View view);
- public void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
- public void postOnAnimation(View view, Runnable action);
- public void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
- public int getImportantForAccessibility(View view);
- public void setImportantForAccessibility(View view, int mode);
- public boolean isImportantForAccessibility(View view);
- public boolean performAccessibilityAction(View view, int action, Bundle arguments);
- public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
- public float getAlpha(View view);
- public void setLayerType(View view, int layerType, Paint paint);
- public int getLayerType(View view);
- public int getLabelFor(View view);
- public void setLabelFor(View view, int id);
- public void setLayerPaint(View view, Paint paint);
- public int getLayoutDirection(View view);
- public void setLayoutDirection(View view, int layoutDirection);
- public ViewParent getParentForAccessibility(View view);
- public boolean isOpaque(View view);
- public int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
- public int getMeasuredWidthAndState(View view);
- public int getMeasuredHeightAndState(View view);
- public int getMeasuredState(View view);
- public int getAccessibilityLiveRegion(View view);
- public void setAccessibilityLiveRegion(View view, int mode);
- public int getPaddingStart(View view);
- public int getPaddingEnd(View view);
- public void setPaddingRelative(View view, int start, int top, int end, int bottom);
- public void dispatchStartTemporaryDetach(View view);
- public void dispatchFinishTemporaryDetach(View view);
- public float getX(View view);
- public float getY(View view);
- public float getRotation(View view);
- public float getRotationX(View view);
- public float getRotationY(View view);
- public float getScaleX(View view);
- public float getScaleY(View view);
- public float getTranslationX(View view);
- public float getTranslationY(View view);
- public int getMinimumWidth(View view);
- public int getMinimumHeight(View view);
- public ViewPropertyAnimatorCompat animate(View view);
- public void setRotation(View view, float value);
- public void setRotationX(View view, float value);
- public void setRotationY(View view, float value);
- public void setScaleX(View view, float value);
- public void setScaleY(View view, float value);
- public void setTranslationX(View view, float value);
- public void setTranslationY(View view, float value);
- public void setX(View view, float value);
- public void setY(View view, float value);
- public void setAlpha(View view, float value);
- public void setPivotX(View view, float value);
- public void setPivotY(View view, float value);
- public float getPivotX(View view);
- public float getPivotY(View view);
- public void setElevation(View view, float elevation);
- public float getElevation(View view);
- public void setTranslationZ(View view, float translationZ);
- public float getTranslationZ(View view);
- public void setClipBounds(View view, Rect clipBounds);
- public Rect getClipBounds(View view);
- public void setTransitionName(View view, String transitionName);
- public String getTransitionName(View view);
- public int getWindowSystemUiVisibility(View view);
- public void requestApplyInsets(View view);
- public void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
- public boolean getFitsSystemWindows(View view);
- public boolean hasOverlappingRendering(View view);
+ boolean canScrollHorizontally(View v, int direction);
+ boolean canScrollVertically(View v, int direction);
+ int getOverScrollMode(View v);
+ void setOverScrollMode(View v, int mode);
+ void onInitializeAccessibilityEvent(View v, AccessibilityEvent event);
+ void onPopulateAccessibilityEvent(View v, AccessibilityEvent event);
+ void onInitializeAccessibilityNodeInfo(View v, AccessibilityNodeInfoCompat info);
+ void setAccessibilityDelegate(View v, @Nullable AccessibilityDelegateCompat delegate);
+ boolean hasAccessibilityDelegate(View v);
+ boolean hasTransientState(View view);
+ void setHasTransientState(View view, boolean hasTransientState);
+ void postInvalidateOnAnimation(View view);
+ void postInvalidateOnAnimation(View view, int left, int top, int right, int bottom);
+ void postOnAnimation(View view, Runnable action);
+ void postOnAnimationDelayed(View view, Runnable action, long delayMillis);
+ int getImportantForAccessibility(View view);
+ void setImportantForAccessibility(View view, int mode);
+ boolean isImportantForAccessibility(View view);
+ boolean performAccessibilityAction(View view, int action, Bundle arguments);
+ AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View view);
+ float getAlpha(View view);
+ void setLayerType(View view, int layerType, Paint paint);
+ int getLayerType(View view);
+ int getLabelFor(View view);
+ void setLabelFor(View view, int id);
+ void setLayerPaint(View view, Paint paint);
+ int getLayoutDirection(View view);
+ void setLayoutDirection(View view, int layoutDirection);
+ ViewParent getParentForAccessibility(View view);
+ boolean isOpaque(View view);
+ int resolveSizeAndState(int size, int measureSpec, int childMeasuredState);
+ int getMeasuredWidthAndState(View view);
+ int getMeasuredHeightAndState(View view);
+ int getMeasuredState(View view);
+ int getAccessibilityLiveRegion(View view);
+ void setAccessibilityLiveRegion(View view, int mode);
+ int getPaddingStart(View view);
+ int getPaddingEnd(View view);
+ void setPaddingRelative(View view, int start, int top, int end, int bottom);
+ void dispatchStartTemporaryDetach(View view);
+ void dispatchFinishTemporaryDetach(View view);
+ float getX(View view);
+ float getY(View view);
+ float getRotation(View view);
+ float getRotationX(View view);
+ float getRotationY(View view);
+ float getScaleX(View view);
+ float getScaleY(View view);
+ float getTranslationX(View view);
+ float getTranslationY(View view);
+ @Nullable Matrix getMatrix(View view);
+ int getMinimumWidth(View view);
+ int getMinimumHeight(View view);
+ ViewPropertyAnimatorCompat animate(View view);
+ void setRotation(View view, float value);
+ void setRotationX(View view, float value);
+ void setRotationY(View view, float value);
+ void setScaleX(View view, float value);
+ void setScaleY(View view, float value);
+ void setTranslationX(View view, float value);
+ void setTranslationY(View view, float value);
+ void setX(View view, float value);
+ void setY(View view, float value);
+ void setAlpha(View view, float value);
+ void setPivotX(View view, float value);
+ void setPivotY(View view, float value);
+ float getPivotX(View view);
+ float getPivotY(View view);
+ void setElevation(View view, float elevation);
+ float getElevation(View view);
+ void setTranslationZ(View view, float translationZ);
+ float getTranslationZ(View view);
+ void setClipBounds(View view, Rect clipBounds);
+ Rect getClipBounds(View view);
+ void setTransitionName(View view, String transitionName);
+ String getTransitionName(View view);
+ int getWindowSystemUiVisibility(View view);
+ void requestApplyInsets(View view);
+ void setChildrenDrawingOrderEnabled(ViewGroup viewGroup, boolean enabled);
+ boolean getFitsSystemWindows(View view);
+ boolean hasOverlappingRendering(View view);
void setFitsSystemWindows(View view, boolean fitSystemWindows);
void jumpDrawablesToCurrentState(View v);
void setOnApplyWindowInsetsListener(View view, OnApplyWindowInsetsListener listener);
@@ -448,14 +465,20 @@
int[] offsetInWindow);
boolean dispatchNestedFling(View view, float velocityX, float velocityY, boolean consumed);
boolean dispatchNestedPreFling(View view, float velocityX, float velocityY);
+ boolean isInLayout(View view);
boolean isLaidOut(View view);
+ boolean isLayoutDirectionResolved(View view);
int combineMeasuredStates(int curState, int newState);
- public float getZ(View view);
- public boolean isAttachedToWindow(View view);
- public boolean hasOnClickListeners(View view);
- public void setScrollIndicators(View view, int indicators);
- public void setScrollIndicators(View view, int indicators, int mask);
- public int getScrollIndicators(View view);
+ float getZ(View view);
+ void setZ(View view, float z);
+ boolean isAttachedToWindow(View view);
+ boolean hasOnClickListeners(View view);
+ void setScrollIndicators(View view, int indicators);
+ void setScrollIndicators(View view, int indicators, int mask);
+ int getScrollIndicators(View view);
+ void offsetTopAndBottom(View view, int offset);
+ void offsetLeftAndRight(View view, int offset);
+ void setPointerIcon(View view, PointerIconCompat pointerIcon);
}
static class BaseViewCompatImpl implements ViewCompatImpl {
@@ -718,6 +741,11 @@
}
@Override
+ public Matrix getMatrix(View view) {
+ return null;
+ }
+
+ @Override
public int getMinimumWidth(View view) {
return ViewCompatBase.getMinimumWidth(view);
}
@@ -1017,11 +1045,21 @@
}
@Override
+ public boolean isInLayout(View view) {
+ return false;
+ }
+
+ @Override
public boolean isLaidOut(View view) {
return ViewCompatBase.isLaidOut(view);
}
@Override
+ public boolean isLayoutDirectionResolved(View view) {
+ return false;
+ }
+
+ @Override
public int combineMeasuredStates(int curState, int newState) {
return curState | newState;
}
@@ -1031,6 +1069,10 @@
return getTranslationZ(view) + getElevation(view);
}
+ public void setZ(View view, float z) {
+ // no-op
+ }
+
@Override
public boolean isAttachedToWindow(View view) {
return ViewCompatBase.isAttachedToWindow(view);
@@ -1055,6 +1097,21 @@
public void setScrollIndicators(View view, int indicators, int mask) {
// no-op
}
+
+ @Override
+ public void offsetLeftAndRight(View view, int offset) {
+ ViewCompatBase.offsetLeftAndRight(view, offset);
+ }
+
+ @Override
+ public void offsetTopAndBottom(View view, int offset) {
+ ViewCompatBase.offsetTopAndBottom(view, offset);
+ }
+
+ @Override
+ public void setPointerIcon(View view, PointerIconCompat pointerIcon) {
+ // no-op
+ }
}
static class EclairMr1ViewCompatImpl extends BaseViewCompatImpl {
@@ -1129,6 +1186,12 @@
public float getTranslationY(View view) {
return ViewCompatHC.getTranslationY(view);
}
+
+ @Override
+ public Matrix getMatrix(View view) {
+ return ViewCompatHC.getMatrix(view);
+ }
+
@Override
public void setTranslationX(View view, float value) {
ViewCompatHC.setTranslationX(view, value);
@@ -1239,6 +1302,16 @@
public int combineMeasuredStates(int curState, int newState) {
return ViewCompatHC.combineMeasuredStates(curState, newState);
}
+
+ @Override
+ public void offsetLeftAndRight(View view, int offset) {
+ ViewCompatHC.offsetLeftAndRight(view, offset);
+ }
+
+ @Override
+ public void offsetTopAndBottom(View view, int offset) {
+ ViewCompatHC.offsetTopAndBottom(view, offset);
+ }
}
static class ICSViewCompatImpl extends HCViewCompatImpl {
@@ -1297,8 +1370,7 @@
@Override
public ViewPropertyAnimatorCompat animate(View view) {
if (mViewPropertyAnimatorCompatMap == null) {
- mViewPropertyAnimatorCompatMap =
- new WeakHashMap<View, ViewPropertyAnimatorCompat>();
+ mViewPropertyAnimatorCompatMap = new WeakHashMap<>();
}
ViewPropertyAnimatorCompat vpa = mViewPropertyAnimatorCompatMap.get(view);
if (vpa == null) {
@@ -1467,6 +1539,11 @@
public Rect getClipBounds(View view) {
return ViewCompatJellybeanMr2.getClipBounds(view);
}
+
+ @Override
+ public boolean isInLayout(View view) {
+ return ViewCompatJellybeanMr2.isInLayout(view);
+ }
}
static class KitKatViewCompatImpl extends JbMr2ViewCompatImpl {
@@ -1491,6 +1568,11 @@
}
@Override
+ public boolean isLayoutDirectionResolved(View view) {
+ return ViewCompatKitKat.isLayoutDirectionResolved(view);
+ }
+
+ @Override
public boolean isAttachedToWindow(View view) {
return ViewCompatKitKat.isAttachedToWindow(view);
}
@@ -1626,6 +1708,21 @@
public float getZ(View view) {
return ViewCompatLollipop.getZ(view);
}
+
+ @Override
+ public void setZ(View view, float z) {
+ ViewCompatLollipop.setZ(view, z);
+ }
+
+ @Override
+ public void offsetLeftAndRight(View view, int offset) {
+ ViewCompatLollipop.offsetLeftAndRight(view, offset);
+ }
+
+ @Override
+ public void offsetTopAndBottom(View view, int offset) {
+ ViewCompatLollipop.offsetTopAndBottom(view, offset);
+ }
}
static class MarshmallowViewCompatImpl extends LollipopViewCompatImpl {
@@ -1643,17 +1740,39 @@
public int getScrollIndicators(View view) {
return ViewCompatMarshmallow.getScrollIndicators(view);
}
+
+
+ @Override
+ public void offsetLeftAndRight(View view, int offset) {
+ ViewCompatMarshmallow.offsetLeftAndRight(view, offset);
+ }
+
+ @Override
+ public void offsetTopAndBottom(View view, int offset) {
+ ViewCompatMarshmallow.offsetTopAndBottom(view, offset);
+ }
+ }
+
+ static class Api24ViewCompatImpl extends MarshmallowViewCompatImpl {
+ @Override
+ public void setPointerIcon(View view, PointerIconCompat pointerIconCompat) {
+ ViewCompatApi24.setPointerIcon(view, pointerIconCompat.getPointerIcon());
+ }
}
static final ViewCompatImpl IMPL;
static {
final int version = android.os.Build.VERSION.SDK_INT;
- if (version >= 23) {
+ if (BuildCompat.isAtLeastN()) {
+ IMPL = new Api24ViewCompatImpl();
+ } else if (version >= 23) {
IMPL = new MarshmallowViewCompatImpl();
} else if (version >= 21) {
IMPL = new LollipopViewCompatImpl();
} else if (version >= 19) {
IMPL = new KitKatViewCompatImpl();
+ } else if (version >= 18) {
+ IMPL = new JbMr2ViewCompatImpl();
} else if (version >= 17) {
IMPL = new JbMr1ViewCompatImpl();
} else if (version >= 16) {
@@ -1706,6 +1825,7 @@
*/
@OverScroll
public static int getOverScrollMode(View v) {
+ //noinspection ResourceType
return IMPL.getOverScrollMode(v);
}
@@ -1742,10 +1862,9 @@
* event.getText().add(selectedDateUtterance);
* }</pre>
* <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onPopulateAccessibilityEvent(View,
- * AccessibilityEvent)}
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)} its
+ * {@link AccessibilityDelegateCompat#onPopulateAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
* </p>
* <p class="note"><strong>Note:</strong> Always call the super implementation before adding
@@ -1775,15 +1894,10 @@
* event.setPassword(true);
* }</pre>
* <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityEvent(View,
- * AccessibilityEvent)}
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityEvent(View, AccessibilityEvent)}
* is responsible for handling this call.
- * </p>
- * <p class="note"><strong>Note:</strong> Always call the super implementation before adding
- * information to the event, in case the default implementation has basic information to add.
- * </p>
*
* @param v The View against which to invoke the method.
* @param event The event to initialize.
@@ -1796,33 +1910,27 @@
}
/**
- * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+ * Initializes an {@link AccessibilityNodeInfoCompat} with information
* about this view. The base implementation sets:
* <ul>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setParent(View)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInParent(Rect)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setBoundsInScreen(Rect)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setPackageName(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClassName(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setContentDescription(CharSequence)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setEnabled(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setClickable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocusable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setFocused(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setLongClickable(boolean)},</li>
- * <li>{@link android.view.accessibility.AccessibilityNodeInfo#setSelected(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setParent(View)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setPackageName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setClassName(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setEnabled(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setFocusable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setFocused(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setLongClickable(boolean)},</li>
+ * <li>{@link AccessibilityNodeInfoCompat#setSelected(boolean)},</li>
* </ul>
* <p>
- * Subclasses should override this method, call the super implementation,
- * and set additional attributes.
- * </p>
- * <p>
- * If an {@link android.view.View.AccessibilityDelegate} has been specified via calling
- * {@link View#setAccessibilityDelegate(android.view.View.AccessibilityDelegate)} its
- * {@link android.view.View.AccessibilityDelegate#onInitializeAccessibilityNodeInfo(View,
- * android.view.accessibility.AccessibilityNodeInfo)}
- * is responsible for handling this call.
- * </p>
+ * If an {@link AccessibilityDelegateCompat} has been specified via calling
+ * {@link ViewCompat#setAccessibilityDelegate(View, AccessibilityDelegateCompat)}, its
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)}
+ * method is responsible for handling this call.
*
* @param v The View against which to invoke the method.
* @param info The instance to initialize.
@@ -1832,15 +1940,27 @@
}
/**
- * Sets a delegate for implementing accessibility support via compositon as
- * opposed to inheritance. The delegate's primary use is for implementing
- * backwards compatible widgets. For more details see
- * {@link android.view.View.AccessibilityDelegate}.
+ * Sets a delegate for implementing accessibility support via composition
+ * (as opposed to inheritance). For more details, see
+ * {@link AccessibilityDelegateCompat}.
+ * <p>
+ * On platform versions prior to API 14, this method is a no-op.
+ * <p>
+ * <strong>Note:</strong> On platform versions prior to
+ * {@link android.os.Build.VERSION_CODES#M API 23}, delegate methods on
+ * views in the {@code android.widget.*} package are called <i>before</i>
+ * host methods. This prevents certain properties such as class name from
+ * being modified by overriding
+ * {@link AccessibilityDelegateCompat#onInitializeAccessibilityNodeInfo(View, AccessibilityNodeInfoCompat)},
+ * as any changes will be overwritten by the host class.
+ * <p>
+ * Starting in {@link android.os.Build.VERSION_CODES#M API 23}, delegate
+ * methods are called <i>after</i> host methods, which all properties to be
+ * modified without being overwritten by the host class.
*
- * @param v The View against which to invoke the method.
- * @param delegate The delegate instance.
- *
- * @see android.view.View.AccessibilityDelegate
+ * @param delegate the object to which accessibility method calls should be
+ * delegated
+ * @see AccessibilityDelegateCompat
*/
public static void setAccessibilityDelegate(View v, AccessibilityDelegateCompat delegate) {
IMPL.setAccessibilityDelegate(v, delegate);
@@ -1956,6 +2076,7 @@
*/
@ImportantForAccessibility
public static int getImportantForAccessibility(View view) {
+ //noinspection ResourceType
return IMPL.getImportantForAccessibility(view);
}
@@ -2063,13 +2184,6 @@
* {@link #LAYER_TYPE_SOFTWARE software} and {@link #LAYER_TYPE_HARDWARE hardware}
* for more information on when and how to use layers.</p>
*
- * @param layerType The ype of layer to use with this view, must be one of
- * {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
- * {@link #LAYER_TYPE_HARDWARE}
- * @param paint The paint used to compose the layer. This argument is optional
- * and can be null. It is ignored when the layer type is
- * {@link #LAYER_TYPE_NONE}
- *
* @param view View to set the layer type for
* @param layerType The type of layer to use with this view, must be one of
* {@link #LAYER_TYPE_NONE}, {@link #LAYER_TYPE_SOFTWARE} or
@@ -2100,6 +2214,7 @@
*/
@LayerType
public static int getLayerType(View view) {
+ //noinspection ResourceType
return IMPL.getLayerType(view);
}
@@ -2171,6 +2286,7 @@
*/
@ResolvedLayoutDirectionMode
public static int getLayoutDirection(View view) {
+ //noinspection ResourceType
return IMPL.getLayoutDirection(view);
}
@@ -2297,6 +2413,7 @@
*/
@AccessibilityLiveRegion
public static int getAccessibilityLiveRegion(View view) {
+ //noinspection ResourceType
return IMPL.getAccessibilityLiveRegion(view);
}
@@ -2401,7 +2518,7 @@
}
/**
- * The vertical location of this view relative to its {@link View#getTop() left} position.
+ * The vertical location of this view relative to its {@link View#getTop() top} position.
* This position is post-layout, in addition to wherever the object's
* layout placed it.
*
@@ -2414,6 +2531,26 @@
}
/**
+ * The transform matrix of this view, which is calculated based on the current
+ * rotation, scale, and pivot properties.
+ * <p>
+ * Prior to 11, this method will return {@code null}.
+ *
+ * @param view The view whose Matrix will be returned
+ * @return The current transform matrix for the view
+ *
+ * @see #getRotation(View)
+ * @see #getScaleX(View)
+ * @see #getScaleY(View)
+ * @see #getPivotX(View)
+ * @see #getPivotY(View)
+ */
+ @Nullable
+ public static Matrix getMatrix(View view) {
+ return IMPL.getMatrix(view);
+ }
+
+ /**
* Returns the minimum width of the view.
*
* <p>Prior to API 16 this will return 0.</p>
@@ -2471,7 +2608,7 @@
* @param value The vertical position of this view relative to its top position,
* in pixels.
*
- * @attr ref android.R.styleable#View_translationY
+ * @attr name android:translationY
*/
public static void setTranslationY(View view, float value) {
IMPL.setTranslationY(view, value);
@@ -3127,6 +3264,22 @@
}
/**
+ * Returns whether the view hierarchy is currently undergoing a layout pass. This
+ * information is useful to avoid situations such as calling {@link View#requestLayout()}
+ * during a layout pass.
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>API < 18: Always returns {@code false}</li>
+ * </ul>
+ *
+ * @return whether the view hierarchy is currently undergoing a layout pass
+ */
+ public static boolean isInLayout(View view) {
+ return IMPL.isInLayout(view);
+ }
+
+ /**
* Returns true if {@code view} has been through at least one layout since it
* was last attached to or detached from a window.
*/
@@ -3135,6 +3288,20 @@
}
/**
+ * Returns whether layout direction has been resolved.
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>API < 19: Always returns {@code false}</li>
+ * </ul>
+ *
+ * @return true if layout direction has been resolved.
+ */
+ public static boolean isLayoutDirectionResolved(View view) {
+ return IMPL.isLayoutDirectionResolved(view);
+ }
+
+ /**
* The visual z position of this view, in pixels. This is equivalent to the
* {@link #setTranslationZ(View, float) translationZ} property plus the current
* {@link #getElevation(View) elevation} property.
@@ -3146,30 +3313,37 @@
}
/**
+ * Sets the visual z position of this view, in pixels. This is equivalent to setting the
+ * {@link #setTranslationZ(View, float) translationZ} property to be the difference between
+ * the x value passed in and the current {@link #getElevation(View) elevation} property.
+ * <p>
+ * Compatibility:
+ * <ul>
+ * <li>API < 21: No-op
+ * </ul>
+ *
+ * @param z The visual z position of this view, in pixels.
+ */
+ public static void setZ(View view, float z) {
+ IMPL.setZ(view, z);
+ }
+
+ /**
* Offset this view's vertical location by the specified number of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public static void offsetTopAndBottom(View view, int offset) {
- view.offsetTopAndBottom(offset);
-
- if (offset != 0 && Build.VERSION.SDK_INT < 11) {
- // We need to manually invalidate pre-honeycomb
- view.invalidate();
- }
+ IMPL.offsetTopAndBottom(view, offset);
}
+
/**
* Offset this view's horizontal location by the specified amount of pixels.
*
* @param offset the number of pixels to offset the view by
*/
public static void offsetLeftAndRight(View view, int offset) {
- view.offsetLeftAndRight(offset);
-
- if (offset != 0 && Build.VERSION.SDK_INT < 11) {
- // We need to manually invalidate pre-honeycomb
- view.invalidate();
- }
+ IMPL.offsetLeftAndRight(view, offset);
}
/**
@@ -3277,4 +3451,14 @@
public static int getScrollIndicators(@NonNull View view) {
return IMPL.getScrollIndicators(view);
}
+
+ /**
+ * Set the pointer icon for the current view.
+ * @param pointerIcon A PointerIconCompat instance which will be shown when the mouse hovers.
+ */
+ public static void setPointerIcon(@NonNull View view, PointerIconCompat pointerIcon) {
+ IMPL.setPointerIcon(view, pointerIcon);
+ }
+
+ protected ViewCompat() {}
}
diff --git a/v4/java/android/support/v4/view/ViewConfigurationCompat.java b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
index 86d19bbc..c51eb41 100644
--- a/v4/java/android/support/v4/view/ViewConfigurationCompat.java
+++ b/v4/java/android/support/v4/view/ViewConfigurationCompat.java
@@ -22,7 +22,7 @@
* Helper for accessing features in {@link ViewConfiguration}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ViewConfigurationCompat {
+public final class ViewConfigurationCompat {
/**
* Interface for the full API.
*/
@@ -112,4 +112,6 @@
public static boolean hasPermanentMenuKey(ViewConfiguration config) {
return IMPL.hasPermanentMenuKey(config);
}
+
+ private ViewConfigurationCompat() {}
}
diff --git a/v4/java/android/support/v4/view/ViewGroupCompat.java b/v4/java/android/support/v4/view/ViewGroupCompat.java
index 2254c4a..fbb75c7 100644
--- a/v4/java/android/support/v4/view/ViewGroupCompat.java
+++ b/v4/java/android/support/v4/view/ViewGroupCompat.java
@@ -25,7 +25,7 @@
* Helper for accessing features in {@link ViewGroup}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ViewGroupCompat {
+public final class ViewGroupCompat {
/**
* This constant is a {@link #setLayoutMode(ViewGroup, int) layoutMode}.
@@ -155,9 +155,7 @@
/*
* Hide the constructor.
*/
- private ViewGroupCompat() {
-
- }
+ private ViewGroupCompat() {}
/**
* Called when a child has requested sending an {@link AccessibilityEvent} and
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index c6f3648..ce18817 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -30,6 +30,8 @@
import android.os.SystemClock;
import android.support.annotation.CallSuper;
import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
@@ -52,6 +54,10 @@
import android.view.animation.Interpolator;
import android.widget.Scroller;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
@@ -63,20 +69,32 @@
* through pages of data. You supply an implementation of a
* {@link PagerAdapter} to generate the pages that the view shows.
*
- * <p>Note this class is currently under early design and
- * development. The API will likely change in later updates of
- * the compatibility library, requiring changes to the source code
- * of apps when they are compiled against the newer version.</p>
- *
* <p>ViewPager is most often used in conjunction with {@link android.app.Fragment},
* which is a convenient way to supply and manage the lifecycle of each page.
* There are standard adapters implemented for using fragments with the ViewPager,
* which cover the most common use cases. These are
- * {@link android.support.v4.app.FragmentPagerAdapter} and
+ * {@link android.support.v4.app.FragmentPagerAdapter} and
* {@link android.support.v4.app.FragmentStatePagerAdapter}; each of these
* classes have simple code showing how to build a full user interface
* with them.
*
+ * <p>Views which are annotated with the {@link DecorView} annotation are treated as
+ * part of the view pagers 'decor'. Each decor view's position can be controlled via
+ * its {@code android:layout_gravity} attribute. For example:
+ *
+ * <pre>
+ * <android.support.v4.view.ViewPager
+ * android:layout_width="match_parent"
+ * android:layout_height="match_parent">
+ *
+ * <android.support.v4.view.PagerTitleStrip
+ * android:layout_width="match_parent"
+ * android:layout_height="wrap_content"
+ * android:layout_gravity="top" />
+ *
+ * </android.support.v4.view.ViewPager>
+ * </pre>
+ *
* <p>For more information about how to use ViewPager, read <a
* href="{@docRoot}training/implementing-navigation/lateral.html">Creating Swipe Views with
* Tabs</a>.</p>
@@ -221,7 +239,7 @@
private List<OnPageChangeListener> mOnPageChangeListeners;
private OnPageChangeListener mOnPageChangeListener;
private OnPageChangeListener mInternalPageChangeListener;
- private OnAdapterChangeListener mAdapterChangeListener;
+ private List<OnAdapterChangeListener> mAdapterChangeListeners;
private PageTransformer mPageTransformer;
private Method mSetChildrenDrawingOrderEnabled;
@@ -338,17 +356,34 @@
}
/**
- * Used internally to monitor when adapters are switched.
+ * Callback interface for responding to adapter changes.
*/
- interface OnAdapterChangeListener {
- public void onAdapterChanged(PagerAdapter oldAdapter, PagerAdapter newAdapter);
+ public interface OnAdapterChangeListener {
+ /**
+ * Called when the adapter for the given view pager has changed.
+ *
+ * @param viewPager ViewPager where the adapter change has happened
+ * @param oldAdapter the previously set adapter
+ * @param newAdapter the newly set adapter
+ */
+ void onAdapterChanged(@NonNull ViewPager viewPager,
+ @Nullable PagerAdapter oldAdapter, @Nullable PagerAdapter newAdapter);
}
/**
- * Used internally to tag special types of child views that should be added as
- * pager decorations by default.
+ * Annotation which allows marking of views to be decoration views when added to a view
+ * pager.
+ *
+ * <p>Views marked with this annotation can be added to the view pager with a layout resource.
+ * An example being {@link PagerTitleStrip}.</p>
+ *
+ * <p>You can also control whether a view is a decor view but setting
+ * {@link LayoutParams#isDecor} on the child's layout params.</p>
*/
- interface Decor {}
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface DecorView {
+ }
public ViewPager(Context context) {
super(context);
@@ -386,6 +421,55 @@
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ ViewCompat.setOnApplyWindowInsetsListener(this,
+ new android.support.v4.view.OnApplyWindowInsetsListener() {
+ private final Rect mTempRect = new Rect();
+
+ @Override
+ public WindowInsetsCompat onApplyWindowInsets(final View v,
+ final WindowInsetsCompat originalInsets) {
+ // First let the ViewPager itself try and consume them...
+ final WindowInsetsCompat applied =
+ ViewCompat.onApplyWindowInsets(v, originalInsets);
+ if (applied.isConsumed()) {
+ // If the ViewPager consumed all insets, return now
+ return applied;
+ }
+
+ // Now we'll manually dispatch the insets to our children. Since ViewPager
+ // children are always full-height, we do not want to use the standard
+ // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,
+ // the rest of the children will not receive any insets. To workaround this
+ // we manually dispatch the applied insets, not allowing children to
+ // consume them from each other. We do however keep track of any insets
+ // which are consumed, returning the union of our children's consumption
+ final Rect res = mTempRect;
+ res.left = applied.getSystemWindowInsetLeft();
+ res.top = applied.getSystemWindowInsetTop();
+ res.right = applied.getSystemWindowInsetRight();
+ res.bottom = applied.getSystemWindowInsetBottom();
+
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ final WindowInsetsCompat childInsets = ViewCompat
+ .dispatchApplyWindowInsets(getChildAt(i), applied);
+ // Now keep track of any consumed by tracking each dimension's min
+ // value
+ res.left = Math.min(childInsets.getSystemWindowInsetLeft(),
+ res.left);
+ res.top = Math.min(childInsets.getSystemWindowInsetTop(),
+ res.top);
+ res.right = Math.min(childInsets.getSystemWindowInsetRight(),
+ res.right);
+ res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),
+ res.bottom);
+ }
+
+ // Now return a new WindowInsets, using the consumed window insets
+ return applied.replaceSystemWindowInsets(
+ res.left, res.top, res.right, res.bottom);
+ }
+ });
}
@Override
@@ -457,8 +541,11 @@
}
}
- if (mAdapterChangeListener != null && oldAdapter != adapter) {
- mAdapterChangeListener.onAdapterChanged(oldAdapter, adapter);
+ // Dispatch the change to any listeners
+ if (mAdapterChangeListeners != null && !mAdapterChangeListeners.isEmpty()) {
+ for (int i = 0, count = mAdapterChangeListeners.size(); i < count; i++) {
+ mAdapterChangeListeners.get(i).onAdapterChanged(this, oldAdapter, adapter);
+ }
}
}
@@ -482,8 +569,28 @@
return mAdapter;
}
- void setOnAdapterChangeListener(OnAdapterChangeListener listener) {
- mAdapterChangeListener = listener;
+ /**
+ * Add a listener that will be invoked whenever the adapter for this ViewPager changes.
+ *
+ * @param listener listener to add
+ */
+ public void addOnAdapterChangeListener(@NonNull OnAdapterChangeListener listener) {
+ if (mAdapterChangeListeners == null) {
+ mAdapterChangeListeners = new ArrayList<>();
+ }
+ mAdapterChangeListeners.add(listener);
+ }
+
+ /**
+ * Remove a listener that was previously added via
+ * {@link #addOnAdapterChangeListener(OnAdapterChangeListener)}.
+ *
+ * @param listener listener to remove
+ */
+ public void removeOnAdapterChangeListener(@NonNull OnAdapterChangeListener listener) {
+ if (mAdapterChangeListeners != null) {
+ mAdapterChangeListeners.remove(listener);
+ }
}
private int getClientWidth() {
@@ -978,9 +1085,7 @@
void populate(int newCurrentItem) {
ItemInfo oldCurInfo = null;
- int focusDirection = View.FOCUS_FORWARD;
if (mCurItem != newCurrentItem) {
- focusDirection = mCurItem < newCurrentItem ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
oldCurInfo = infoForPosition(mCurItem);
mCurItem = newCurrentItem;
}
@@ -1155,7 +1260,7 @@
View child = getChildAt(i);
ii = infoForChild(child);
if (ii != null && ii.position == mCurItem) {
- if (child.requestFocus(focusDirection)) {
+ if (child.requestFocus(View.FOCUS_FORWARD)) {
break;
}
}
@@ -1272,7 +1377,7 @@
* state, in which case it should implement a subclass of this which
* contains that state.
*/
- public static class SavedState extends BaseSavedState {
+ public static class SavedState extends AbsSavedState {
int position;
Parcelable adapterState;
ClassLoader loader;
@@ -1308,7 +1413,7 @@
});
SavedState(Parcel in, ClassLoader loader) {
- super(in);
+ super(in, loader);
if (loader == null) {
loader = getClass().getClassLoader();
}
@@ -1355,7 +1460,8 @@
params = generateLayoutParams(params);
}
final LayoutParams lp = (LayoutParams) params;
- lp.isDecor |= child instanceof Decor;
+ // Any views added via inflation should be classed as part of the decor
+ lp.isDecor |= isDecorView(child);
if (mInLayout) {
if (lp != null && lp.isDecor) {
throw new IllegalStateException("Cannot add pager decor view during layout");
@@ -1375,6 +1481,11 @@
}
}
+ private static boolean isDecorView(@NonNull View view) {
+ Class<?> clazz = view.getClass();
+ return clazz.getAnnotation(DecorView.class) != null;
+ }
+
@Override
public void removeView(View view) {
if (mInLayout) {
@@ -1528,20 +1639,17 @@
private void recomputeScrollPosition(int width, int oldWidth, int margin, int oldMargin) {
if (oldWidth > 0 && !mItems.isEmpty()) {
- final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
- final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
- + oldMargin;
- final int xpos = getScrollX();
- final float pageOffset = (float) xpos / oldWidthWithMargin;
- final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
-
- scrollTo(newOffsetPixels, getScrollY());
if (!mScroller.isFinished()) {
- // We now return to your regularly scheduled scroll, already in progress.
- final int newDuration = mScroller.getDuration() - mScroller.timePassed();
- ItemInfo targetInfo = infoForPosition(mCurItem);
- mScroller.startScroll(newOffsetPixels, 0,
- (int) (targetInfo.offset * width), 0, newDuration);
+ mScroller.setFinalX(getCurrentItem() * getClientWidth());
+ } else {
+ final int widthWithMargin = width - getPaddingLeft() - getPaddingRight() + margin;
+ final int oldWidthWithMargin = oldWidth - getPaddingLeft() - getPaddingRight()
+ + oldMargin;
+ final int xpos = getScrollX();
+ final float pageOffset = (float) xpos / oldWidthWithMargin;
+ final int newOffsetPixels = (int) (pageOffset * widthWithMargin);
+
+ scrollTo(newOffsetPixels, getScrollY());
}
} else {
final ItemInfo ii = infoForPosition(mCurItem);
@@ -1692,6 +1800,11 @@
private boolean pageScrolled(int xpos) {
if (mItems.size() == 0) {
+ if (mFirstLayout) {
+ // If we haven't been laid out yet, we probably just haven't been populated yet.
+ // Let's skip this call since it doesn't make sense in this state
+ return false;
+ }
mCalledSuper = false;
onPageScrolled(0, 0, 0);
if (!mCalledSuper) {
@@ -1782,7 +1895,6 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (lp.isDecor) continue;
-
final float transformPos = (float) (child.getLeft() - scrollX) / getClientWidth();
mPageTransformer.transformPage(child, transformPos);
}
@@ -2120,8 +2232,10 @@
final int width = getClientWidth();
final int scrollX = getScrollX();
final ItemInfo ii = infoForCurrentScrollPosition();
+ final float marginOffset = (float) mPageMargin / width;
final int currentPage = ii.position;
- final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+ final float pageOffset = (((float) scrollX / width) - ii.offset)
+ / (ii.widthFactor + marginOffset);
final int activePointerIndex =
MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, activePointerIndex);
@@ -2272,7 +2386,7 @@
targetPage = velocity > 0 ? currentPage : currentPage + 1;
} else {
final float truncator = currentPage >= mCurItem ? 0.4f : 0.6f;
- targetPage = (int) (currentPage + pageOffset + truncator);
+ targetPage = currentPage + (int) (pageOffset + truncator);
}
if (mItems.size() > 0) {
@@ -2360,8 +2474,8 @@
}
if (drawAt + mPageMargin > scrollX) {
- mMarginDrawable.setBounds((int) drawAt, mTopPageBounds,
- (int) (drawAt + mPageMargin + 0.5f), mBottomPageBounds);
+ mMarginDrawable.setBounds(Math.round(drawAt), mTopPageBounds,
+ Math.round(drawAt + mPageMargin), mBottomPageBounds);
mMarginDrawable.draw(canvas);
}
@@ -2420,20 +2534,22 @@
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
}
- final VelocityTracker velocityTracker = mVelocityTracker;
- velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
- int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
- velocityTracker, mActivePointerId);
- mPopulatePending = true;
- final int width = getClientWidth();
- final int scrollX = getScrollX();
- final ItemInfo ii = infoForCurrentScrollPosition();
- final int currentPage = ii.position;
- final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
- final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
- int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
- totalDelta);
- setCurrentItemInternal(nextPage, true, true, initialVelocity);
+ if (mAdapter != null) {
+ final VelocityTracker velocityTracker = mVelocityTracker;
+ velocityTracker.computeCurrentVelocity(1000, mMaximumVelocity);
+ int initialVelocity = (int) VelocityTrackerCompat.getXVelocity(
+ velocityTracker, mActivePointerId);
+ mPopulatePending = true;
+ final int width = getClientWidth();
+ final int scrollX = getScrollX();
+ final ItemInfo ii = infoForCurrentScrollPosition();
+ final int currentPage = ii.position;
+ final float pageOffset = (((float) scrollX / width) - ii.offset) / ii.widthFactor;
+ final int totalDelta = (int) (mLastMotionX - mInitialMotionX);
+ int nextPage = determineTargetPage(currentPage, pageOffset, initialVelocity,
+ totalDelta);
+ setCurrentItemInternal(nextPage, true, true, initialVelocity);
+ }
endDrag();
mFakeDragging = false;
@@ -2451,6 +2567,10 @@
throw new IllegalStateException("No fake drag in progress. Call beginFakeDrag first.");
}
+ if (mAdapter == null) {
+ return;
+ }
+
mLastMotionX += xOffset;
float oldScrollX = getScrollX();
@@ -2883,7 +3003,8 @@
public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
super.onInitializeAccessibilityEvent(host, event);
event.setClassName(ViewPager.class.getName());
- final AccessibilityRecordCompat recordCompat = AccessibilityRecordCompat.obtain();
+ final AccessibilityRecordCompat recordCompat =
+ AccessibilityEventCompat.asRecord(event);
recordCompat.setScrollable(canScroll());
if (event.getEventType() == AccessibilityEventCompat.TYPE_VIEW_SCROLLED
&& mAdapter != null) {
diff --git a/v4/java/android/support/v4/view/ViewParentCompat.java b/v4/java/android/support/v4/view/ViewParentCompat.java
index b95b332f..ec97988 100644
--- a/v4/java/android/support/v4/view/ViewParentCompat.java
+++ b/v4/java/android/support/v4/view/ViewParentCompat.java
@@ -30,7 +30,7 @@
* Helper for accessing features in {@link ViewParent}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class ViewParentCompat {
+public final class ViewParentCompat {
interface ViewParentCompatImpl {
public boolean requestSendAccessibilityEvent(
@@ -215,9 +215,7 @@
/*
* Hide the constructor.
*/
- private ViewParentCompat() {
-
- }
+ private ViewParentCompat() {}
/**
* Called by a child to request from its parent to send an {@link AccessibilityEvent}.
diff --git a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
index c4a2145..3427e26 100644
--- a/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
+++ b/v4/java/android/support/v4/view/ViewPropertyAnimatorCompat.java
@@ -15,13 +15,14 @@
*/
package android.support.v4.view;
+import android.os.Build;
import android.view.View;
import android.view.animation.Interpolator;
import java.lang.ref.WeakReference;
import java.util.WeakHashMap;
-public class ViewPropertyAnimatorCompat {
+public final class ViewPropertyAnimatorCompat {
private static final String TAG = "ViewAnimatorCompat";
private WeakReference<View> mView;
private Runnable mStartAction = null;
@@ -31,7 +32,6 @@
// internally and apps should use ids higher than it
static final int LISTENER_TAG_ID = 0x7e000000;
-
ViewPropertyAnimatorCompat(View view) {
mView = new WeakReference<View>(view);
}
@@ -298,6 +298,8 @@
}
Runnable startAction = vpa.mStartAction;
Runnable endAction = vpa.mEndAction;
+ vpa.mStartAction = null;
+ vpa.mEndAction = null;
if (startAction != null) {
startAction.run();
}
@@ -522,8 +524,8 @@
}
static class MyVpaListener implements ViewPropertyAnimatorListener {
-
ViewPropertyAnimatorCompat mVpa;
+ boolean mAnimEndCalled;
MyVpaListener(ViewPropertyAnimatorCompat vpa) {
mVpa = vpa;
@@ -531,11 +533,16 @@
@Override
public void onAnimationStart(View view) {
+ // Reset our end called flag, since this is a new animation...
+ mAnimEndCalled = false;
+
if (mVpa.mOldLayerType >= 0) {
ViewCompat.setLayerType(view, ViewCompat.LAYER_TYPE_HARDWARE, null);
}
if (mVpa.mStartAction != null) {
- mVpa.mStartAction.run();
+ Runnable startAction = mVpa.mStartAction;
+ mVpa.mStartAction = null;
+ startAction.run();
}
Object listenerTag = view.getTag(LISTENER_TAG_ID);
ViewPropertyAnimatorListener listener = null;
@@ -553,16 +560,23 @@
ViewCompat.setLayerType(view, mVpa.mOldLayerType, null);
mVpa.mOldLayerType = -1;
}
- if (mVpa.mEndAction != null) {
- mVpa.mEndAction.run();
- }
- Object listenerTag = view.getTag(LISTENER_TAG_ID);
- ViewPropertyAnimatorListener listener = null;
- if (listenerTag instanceof ViewPropertyAnimatorListener) {
- listener = (ViewPropertyAnimatorListener) listenerTag;
- }
- if (listener != null) {
- listener.onAnimationEnd(view);
+ if (Build.VERSION.SDK_INT >= 16 || !mAnimEndCalled) {
+ // Pre-v16 seems to have a bug where onAnimationEnd is called
+ // twice, therefore we only dispatch on the first call
+ if (mVpa.mEndAction != null) {
+ Runnable endAction = mVpa.mEndAction;
+ mVpa.mEndAction = null;
+ endAction.run();
+ }
+ Object listenerTag = view.getTag(LISTENER_TAG_ID);
+ ViewPropertyAnimatorListener listener = null;
+ if (listenerTag instanceof ViewPropertyAnimatorListener) {
+ listener = (ViewPropertyAnimatorListener) listenerTag;
+ }
+ if (listener != null) {
+ listener.onAnimationEnd(view);
+ }
+ mAnimEndCalled = true;
}
}
diff --git a/v4/java/android/support/v4/view/WindowCompat.java b/v4/java/android/support/v4/view/WindowCompat.java
index 15fe715..9a0773e 100644
--- a/v4/java/android/support/v4/view/WindowCompat.java
+++ b/v4/java/android/support/v4/view/WindowCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in {@link Window} introduced after API
* level 4 in a backwards compatible fashion.
*/
-public class WindowCompat {
+public final class WindowCompat {
/**
* Flag for enabling the Action Bar.
* This is enabled by default for some devices. The Action Bar
@@ -58,4 +58,6 @@
* If overlay is enabled, the action mode UI will be allowed to cover existing window content.
*/
public static final int FEATURE_ACTION_MODE_OVERLAY = 10;
+
+ private WindowCompat() {}
}
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityEventCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityEventCompat.java
index 297d646..7032f39 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityEventCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityEventCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in {@link AccessibilityEvent}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class AccessibilityEventCompat {
+public final class AccessibilityEventCompat {
static interface AccessibilityEventVersionImpl {
int getRecordCount(AccessibilityEvent event);
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityManagerCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
index ee2da331..19d893d 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityManagerCompat.java
@@ -28,7 +28,7 @@
* Helper for accessing features in {@link AccessibilityManager}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class AccessibilityManagerCompat {
+public final class AccessibilityManagerCompat {
interface AccessibilityManagerVersionImpl {
public Object newAccessiblityStateChangeListener(
@@ -213,4 +213,6 @@
*/
public abstract void onAccessibilityStateChanged(boolean enabled);
}
+
+ private AccessibilityManagerCompat() {}
}
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
index 2752ebc..5bd94e4 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityNodeInfoCompat.java
@@ -19,6 +19,7 @@
import android.graphics.Rect;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.Nullable;
import android.support.v4.accessibilityservice.AccessibilityServiceInfoCompat;
import android.support.v4.view.ViewCompat;
import android.text.InputType;
@@ -608,9 +609,15 @@
public void setDismissable(Object info, boolean dismissable);
public boolean isEditable(Object info);
public void setEditable(Object info, boolean editable);
+ public int getDrawingOrder(Object info);
+ public void setDrawingOrder(Object info, int drawingOrderInParent);
+ public boolean isImportantForAccessibility(Object info);
+ public void setImportantForAccessibility(Object info, boolean importantForAccessibility);
public boolean isMultiLine(Object info);
public void setMultiLine(Object info, boolean multiLine);
public boolean refresh(Object info);
+ public CharSequence getRoleDescription(Object info);
+ public void setRoleDescription(Object info, CharSequence roleDescription);
}
static class AccessibilityNodeInfoStubImpl implements AccessibilityNodeInfoImpl {
@@ -1215,6 +1222,33 @@
public boolean refresh(Object info) {
return false;
}
+
+ @Override
+ public CharSequence getRoleDescription(Object info) {
+ return null;
+ }
+
+ @Override
+ public void setRoleDescription(Object info, CharSequence roleDescription) {
+ }
+
+ @Override
+ public int getDrawingOrder(Object info) {
+ return 0;
+ }
+
+ @Override
+ public void setDrawingOrder(Object info, int drawingOrderInParent) {
+ }
+
+ @Override
+ public boolean isImportantForAccessibility(Object info) {
+ return true;
+ }
+
+ @Override
+ public void setImportantForAccessibility(Object info, boolean importantForAccessibility) {
+ }
}
static class AccessibilityNodeInfoIcsImpl extends AccessibilityNodeInfoStubImpl {
@@ -1753,6 +1787,16 @@
public void setMultiLine(Object info, boolean multiLine) {
AccessibilityNodeInfoCompatKitKat.setMultiLine(info, multiLine);
}
+
+ @Override
+ public CharSequence getRoleDescription(Object info) {
+ return AccessibilityNodeInfoCompatKitKat.getRoleDescription(info);
+ }
+
+ @Override
+ public void setRoleDescription(Object info, CharSequence roleDescription) {
+ AccessibilityNodeInfoCompatKitKat.setRoleDescription(info, roleDescription);
+ }
}
static class AccessibilityNodeInfoApi21Impl extends AccessibilityNodeInfoKitKatImpl {
@@ -1873,8 +1917,34 @@
}
}
+ static class AccessibilityNodeInfoApi24Impl extends AccessibilityNodeInfoApi22Impl {
+ @Override
+ public int getDrawingOrder(Object info) {
+ return AccessibilityNodeInfoCompatApi24.getDrawingOrder(info);
+ }
+
+ @Override
+ public void setDrawingOrder(Object info, int drawingOrderInParent) {
+ AccessibilityNodeInfoCompatApi24.setDrawingOrder(info, drawingOrderInParent);
+ }
+
+ @Override
+ public boolean isImportantForAccessibility(Object info) {
+ return AccessibilityNodeInfoCompatApi24.isImportantForAccessibility(info);
+ }
+
+ @Override
+ public void setImportantForAccessibility(Object info, boolean importantForAccessibility) {
+ AccessibilityNodeInfoCompatApi24.setImportantForAccessibility(
+ info, importantForAccessibility);
+ }
+
+ }
+
static {
- if (Build.VERSION.SDK_INT >= 22) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ IMPL = new AccessibilityNodeInfoApi24Impl();
+ } else if (Build.VERSION.SDK_INT >= 22) {
IMPL = new AccessibilityNodeInfoApi22Impl();
} else if (Build.VERSION.SDK_INT >= 21) {
IMPL = new AccessibilityNodeInfoApi21Impl();
@@ -3003,6 +3073,33 @@
}
/**
+ * Returns whether the node originates from a view considered important for accessibility.
+ *
+ * @return {@code true} if the node originates from a view considered important for
+ * accessibility, {@code false} otherwise
+ *
+ * @see View#isImportantForAccessibility()
+ */
+ public boolean isImportantForAccessibility() {
+ return IMPL.isImportantForAccessibility(mInfo);
+ }
+
+ /**
+ * Sets whether the node is considered important for accessibility.
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param important {@code true} if the node is considered important for accessibility,
+ * {@code false} otherwise
+ */
+ public void setImportantForAccessibility(boolean important) {
+ IMPL.setImportantForAccessibility(mInfo, important);
+ }
+
+ /**
* Gets the package this node comes from.
*
* @return The package name.
@@ -3179,6 +3276,36 @@
}
/**
+ * Get the drawing order of the view corresponding it this node.
+ * <p>
+ * Drawing order is determined only within the node's parent, so this index is only relative
+ * to its siblings.
+ * <p>
+ * In some cases, the drawing order is essentially simultaneous, so it is possible for two
+ * siblings to return the same value. It is also possible that values will be skipped.
+ *
+ * @return The drawing position of the view corresponding to this node relative to its siblings.
+ */
+ public int getDrawingOrder() {
+ return IMPL.getDrawingOrder(mInfo);
+ }
+
+ /**
+ * Set the drawing order of the view corresponding it this node.
+ *
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ * @param drawingOrderInParent
+ * @throws IllegalStateException If called from an AccessibilityService.
+ */
+ public void setDrawingOrder(int drawingOrderInParent) {
+ IMPL.setDrawingOrder(mInfo, drawingOrderInParent);
+ }
+
+ /**
* Gets the collection info if the node is a collection. A collection
* child is always a collection item.
*
@@ -3768,6 +3895,42 @@
return IMPL.refresh(mInfo);
}
+ /**
+ * Gets the custom role description.
+ * @return The role description.
+ */
+ public @Nullable CharSequence getRoleDescription() {
+ return IMPL.getRoleDescription(mInfo);
+ }
+
+ /**
+ * Sets the custom role description.
+ *
+ * <p>
+ * The role description allows you to customize the name for the view's semantic
+ * role. For example, if you create a custom subclass of {@link android.view.View}
+ * to display a menu bar, you could assign it the role description of "menu bar".
+ * </p>
+ * <p>
+ * <strong>Warning:</strong> For consistency with other applications, you should
+ * not use the role description to force accessibility services to describe
+ * standard views (such as buttons or checkboxes) using specific wording. For
+ * example, you should not set a role description of "check box" or "tick box" for
+ * a standard {@link android.widget.CheckBox}. Instead let accessibility services
+ * decide what feedback to provide.
+ * </p>
+ * <p>
+ * <strong>Note:</strong> Cannot be called from an
+ * {@link android.accessibilityservice.AccessibilityService}.
+ * This class is made immutable before being delivered to an AccessibilityService.
+ * </p>
+ *
+ * @param roleDescription The role description.
+ */
+ public void setRoleDescription(@Nullable CharSequence roleDescription) {
+ IMPL.setRoleDescription(mInfo, roleDescription);
+ }
+
@Override
public int hashCode() {
return (mInfo == null) ? 0 : mInfo.hashCode();
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityRecordCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
index f5655cc..dee67c9 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityRecordCompat.java
@@ -525,6 +525,7 @@
* {@link AccessibilityEventCompat#asRecord(AccessibilityEvent)}. This method will be removed
* in a subsequent release of the support library.
*/
+ @Deprecated
public AccessibilityRecordCompat(Object record) {
mRecord = record;
}
@@ -535,6 +536,7 @@
* @deprecated This method will be removed in a subsequent release of
* the support library.
*/
+ @Deprecated
public Object getImpl() {
return mRecord;
}
diff --git a/v4/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java b/v4/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
index 3197bb1..738924c 100644
--- a/v4/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
+++ b/v4/java/android/support/v4/view/accessibility/AccessibilityWindowInfoCompat.java
@@ -39,10 +39,12 @@
public boolean isAccessibilityFocused(Object info);
public int getChildCount(Object info);
public Object getChild(Object info, int index);
+ public CharSequence getTitle(Object info);
+ public Object getAnchor(Object info);
public void recycle(Object info);
}
- private static class AccessibilityWindowInfoStubImpl implements AccessibilityWindowInfoImpl {
+ private static class AccessibilityWindowInfoStubImpl implements AccessibilityWindowInfoImpl {
@Override
public Object obtain() {
@@ -111,6 +113,16 @@
@Override
public void recycle(Object info) {
}
+
+ @Override
+ public CharSequence getTitle(Object info) {
+ return null;
+ }
+
+ @Override
+ public Object getAnchor(Object info) {
+ return null;
+ }
}
private static class AccessibilityWindowInfoApi21Impl extends AccessibilityWindowInfoStubImpl {
@@ -185,8 +197,22 @@
}
}
+ private static class AccessibilityWindowInfoApi24Impl extends AccessibilityWindowInfoApi21Impl {
+ @Override
+ public CharSequence getTitle(Object info) {
+ return AccessibilityWindowInfoCompatApi24.getTitle(info);
+ }
+
+ @Override
+ public Object getAnchor(Object info) {
+ return AccessibilityWindowInfoCompatApi24.getAnchor(info);
+ }
+ }
+
static {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ IMPL = new AccessibilityWindowInfoApi24Impl();
+ } else if (Build.VERSION.SDK_INT >= 21) {
IMPL = new AccessibilityWindowInfoApi21Impl();
} else {
IMPL = new AccessibilityWindowInfoStubImpl();
@@ -355,6 +381,25 @@
}
/**
+ * Gets the title of the window.
+ *
+ * @return The title of the window, or the application label for the window if no title was
+ * explicitly set, or {@code null} if neither is available.
+ */
+ public CharSequence getTitle() {
+ return IMPL.getTitle(mInfo);
+ }
+
+ /**
+ * Gets the node that anchors this window to another.
+ *
+ * @return The anchor node, or {@code null} if none exists.
+ */
+ public AccessibilityNodeInfoCompat getAnchor() {
+ return AccessibilityNodeInfoCompat.wrapNonNullInstance(IMPL.getAnchor(mInfo));
+ }
+
+ /**
* Returns a cached instance if such is available or a new one is
* created.
*
diff --git a/v4/java/android/support/v4/view/animation/PathInterpolatorCompat.java b/v4/java/android/support/v4/view/animation/PathInterpolatorCompat.java
index 2e06132..3451fe0 100644
--- a/v4/java/android/support/v4/view/animation/PathInterpolatorCompat.java
+++ b/v4/java/android/support/v4/view/animation/PathInterpolatorCompat.java
@@ -25,7 +25,7 @@
* platform implementation will be used and on older platforms a compatible alternative
* implementation will be used.
*/
-public class PathInterpolatorCompat {
+public final class PathInterpolatorCompat {
private PathInterpolatorCompat() {
// prevent instantiation
diff --git a/v4/java/android/support/v4/widget/CompoundButtonCompat.java b/v4/java/android/support/v4/widget/CompoundButtonCompat.java
index f4348ce..13ab974 100644
--- a/v4/java/android/support/v4/widget/CompoundButtonCompat.java
+++ b/v4/java/android/support/v4/widget/CompoundButtonCompat.java
@@ -156,7 +156,7 @@
/**
* @return the blending mode used to apply the tint to the button drawable
- * @attr ref android.R.styleable#CompoundButton_buttonTintMode
+ * @attr name android:buttonTintMode
* @see #setButtonTintMode(PorterDuff.Mode)
*/
@Nullable
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index 549b8ea..a48ec4e9 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -32,9 +32,13 @@
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.KeyEventCompat;
@@ -207,7 +211,8 @@
private boolean mDisallowInterceptRequested;
private boolean mChildrenCanceledTouch;
- private DrawerListener mListener;
+ private @Nullable DrawerListener mListener;
+ private List<DrawerListener> mListeners;
private float mInitialMotionX;
private float mInitialMotionY;
@@ -508,16 +513,66 @@
}
/**
- * Set a listener to be notified of drawer events.
+ * Set a listener to be notified of drawer events. Note that this method is deprecated
+ * and you should use {@link #addDrawerListener(DrawerListener)} to add a listener and
+ * {@link #removeDrawerListener(DrawerListener)} to remove a registered listener.
*
* @param listener Listener to notify when drawer events occur
+ * @deprecated Use {@link #addDrawerListener(DrawerListener)}
* @see DrawerListener
+ * @see #addDrawerListener(DrawerListener)
+ * @see #removeDrawerListener(DrawerListener)
*/
+ @Deprecated
public void setDrawerListener(DrawerListener listener) {
+ // The logic in this method emulates what we had before support for multiple
+ // registered listeners.
+ if (mListener != null) {
+ removeDrawerListener(mListener);
+ }
+ if (listener != null) {
+ addDrawerListener(listener);
+ }
+ // Update the deprecated field so that we can remove the passed listener the next
+ // time we're called
mListener = listener;
}
/**
+ * Adds the specified listener to the list of listeners that will be notified of drawer events.
+ *
+ * @param listener Listener to notify when drawer events occur.
+ * @see #removeDrawerListener(DrawerListener)
+ */
+ public void addDrawerListener(@NonNull DrawerListener listener) {
+ if (listener == null) {
+ return;
+ }
+ if (mListeners == null) {
+ mListeners = new ArrayList<DrawerListener>();
+ }
+ mListeners.add(listener);
+ }
+
+ /**
+ * Removes the specified listener from the list of listeners that will be notified of drawer
+ * events.
+ *
+ * @param listener Listener to remove from being notified of drawer events
+ * @see #addDrawerListener(DrawerListener)
+ */
+ public void removeDrawerListener(@NonNull DrawerListener listener) {
+ if (listener == null) {
+ return;
+ }
+ if (mListeners == null) {
+ // This can happen if this method is called before the first call to addDrawerListener
+ return;
+ }
+ mListeners.remove(listener);
+ }
+
+ /**
* Enable or disable interaction with all drawers.
*
* <p>This allows the application to restrict the user's ability to open or close
@@ -688,6 +743,9 @@
*/
@LockMode
public int getDrawerLockMode(View drawerView) {
+ if (!isDrawerView(drawerView)) {
+ throw new IllegalArgumentException("View " + drawerView + " is not a drawer");
+ }
final int drawerGravity = ((LayoutParams) drawerView.getLayoutParams()).gravity;
return getDrawerLockMode(drawerGravity);
}
@@ -761,8 +819,13 @@
if (state != mDrawerState) {
mDrawerState = state;
- if (mListener != null) {
- mListener.onDrawerStateChanged(state);
+ if (mListeners != null) {
+ // Notify the listeners. Do that from the end of the list so that if a listener
+ // removes itself as the result of being called, it won't mess up with our iteration
+ int listenerCount = mListeners.size();
+ for (int i = listenerCount - 1; i >= 0; i--) {
+ mListeners.get(i).onDrawerStateChanged(state);
+ }
}
}
}
@@ -771,8 +834,14 @@
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 1) {
lp.openState = 0;
- if (mListener != null) {
- mListener.onDrawerClosed(drawerView);
+
+ if (mListeners != null) {
+ // Notify the listeners. Do that from the end of the list so that if a listener
+ // removes itself as the result of being called, it won't mess up with our iteration
+ int listenerCount = mListeners.size();
+ for (int i = listenerCount - 1; i >= 0; i--) {
+ mListeners.get(i).onDrawerClosed(drawerView);
+ }
}
updateChildrenImportantForAccessibility(drawerView, false);
@@ -793,8 +862,13 @@
final LayoutParams lp = (LayoutParams) drawerView.getLayoutParams();
if ((lp.openState & LayoutParams.FLAG_IS_OPENED) == 0) {
lp.openState = LayoutParams.FLAG_IS_OPENED;
- if (mListener != null) {
- mListener.onDrawerOpened(drawerView);
+ if (mListeners != null) {
+ // Notify the listeners. Do that from the end of the list so that if a listener
+ // removes itself as the result of being called, it won't mess up with our iteration
+ int listenerCount = mListeners.size();
+ for (int i = listenerCount - 1; i >= 0; i--) {
+ mListeners.get(i).onDrawerOpened(drawerView);
+ }
}
updateChildrenImportantForAccessibility(drawerView, true);
@@ -826,8 +900,13 @@
}
void dispatchOnDrawerSlide(View drawerView, float slideOffset) {
- if (mListener != null) {
- mListener.onDrawerSlide(drawerView, slideOffset);
+ if (mListeners != null) {
+ // Notify the listeners. Do that from the end of the list so that if a listener
+ // removes itself as the result of being called, it won't mess up with our iteration
+ int listenerCount = mListeners.size();
+ for (int i = listenerCount - 1; i >= 0; i--) {
+ mListeners.get(i).onDrawerSlide(drawerView, slideOffset);
+ }
}
}
@@ -968,8 +1047,10 @@
final boolean applyInsets = mLastInsets != null && ViewCompat.getFitsSystemWindows(this);
final int layoutDirection = ViewCompat.getLayoutDirection(this);
- // Gravity value for each drawer we've seen. Only one of each permitted.
- int foundDrawers = 0;
+ // Only one drawer is permitted along each vertical edge (left / right). These two booleans
+ // are tracking the presence of the edge drawers.
+ boolean hasDrawerOnLeftEdge = false;
+ boolean hasDrawerOnRightEdge = false;
final int childCount = getChildCount();
for (int i = 0; i < childCount; i++) {
final View child = getChildAt(i);
@@ -1002,14 +1083,22 @@
ViewCompat.setElevation(child, mDrawerElevation);
}
}
- final int childGravity =
+ final @EdgeGravity int childGravity =
getDrawerViewAbsoluteGravity(child) & Gravity.HORIZONTAL_GRAVITY_MASK;
- if ((foundDrawers & childGravity) != 0) {
+ // Note that the isDrawerView check guarantees that childGravity here is either
+ // LEFT or RIGHT
+ boolean isLeftEdgeDrawer = (childGravity == Gravity.LEFT);
+ if ((isLeftEdgeDrawer && hasDrawerOnLeftEdge) ||
+ (!isLeftEdgeDrawer && hasDrawerOnRightEdge)) {
throw new IllegalStateException("Child drawer has absolute gravity " +
gravityToString(childGravity) + " but this " + TAG + " already has a " +
"drawer view along that edge");
}
- foundDrawers = foundDrawers | childGravity;
+ if (isLeftEdgeDrawer) {
+ hasDrawerOnLeftEdge = true;
+ } else {
+ hasDrawerOnRightEdge = true;
+ }
final int drawerWidthSpec = getChildMeasureSpec(widthMeasureSpec,
mMinDrawerMargin + lp.leftMargin + lp.rightMargin,
lp.width);
@@ -1330,7 +1419,15 @@
final int gravity = ((LayoutParams) child.getLayoutParams()).gravity;
final int absGravity = GravityCompat.getAbsoluteGravity(gravity,
ViewCompat.getLayoutDirection(child));
- return (absGravity & (Gravity.LEFT | Gravity.RIGHT)) != 0;
+ if ((absGravity & Gravity.LEFT) != 0) {
+ // This child is a left-edge drawer
+ return true;
+ }
+ if ((absGravity & Gravity.RIGHT) != 0) {
+ // This child is a right-edge drawer
+ return true;
+ }
+ return false;
}
@Override
@@ -1490,6 +1587,16 @@
* @param drawerView Drawer view to open
*/
public void openDrawer(View drawerView) {
+ openDrawer(drawerView, true);
+ }
+
+ /**
+ * Open the specified drawer view.
+ *
+ * @param drawerView Drawer view to open
+ * @param animate Whether opening of the drawer should be animated.
+ */
+ public void openDrawer(View drawerView, boolean animate) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
@@ -1500,7 +1607,7 @@
lp.openState = LayoutParams.FLAG_IS_OPENED;
updateChildrenImportantForAccessibility(drawerView, true);
- } else {
+ } else if (animate) {
lp.openState |= LayoutParams.FLAG_IS_OPENING;
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
@@ -1509,6 +1616,10 @@
mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
drawerView.getTop());
}
+ } else {
+ moveDrawerToOffset(drawerView, 1.f);
+ updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+ drawerView.setVisibility(VISIBLE);
}
invalidate();
}
@@ -1520,12 +1631,23 @@
* GravityCompat.START or GravityCompat.END may also be used.
*/
public void openDrawer(@EdgeGravity int gravity) {
+ openDrawer(gravity, true);
+ }
+
+ /**
+ * Open the specified drawer.
+ *
+ * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+ * GravityCompat.START or GravityCompat.END may also be used.
+ * @param animate Whether opening of the drawer should be animated.
+ */
+ public void openDrawer(@EdgeGravity int gravity, boolean animate) {
final View drawerView = findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
gravityToString(gravity));
}
- openDrawer(drawerView);
+ openDrawer(drawerView, animate);
}
/**
@@ -1534,6 +1656,16 @@
* @param drawerView Drawer view to close
*/
public void closeDrawer(View drawerView) {
+ closeDrawer(drawerView, true);
+ }
+
+ /**
+ * Close the specified drawer view.
+ *
+ * @param drawerView Drawer view to close
+ * @param animate Whether closing of the drawer should be animated.
+ */
+ public void closeDrawer(View drawerView, boolean animate) {
if (!isDrawerView(drawerView)) {
throw new IllegalArgumentException("View " + drawerView + " is not a sliding drawer");
}
@@ -1542,7 +1674,7 @@
if (mFirstLayout) {
lp.onScreen = 0.f;
lp.openState = 0;
- } else {
+ } else if (animate) {
lp.openState |= LayoutParams.FLAG_IS_CLOSING;
if (checkDrawerViewAbsoluteGravity(drawerView, Gravity.LEFT)) {
@@ -1551,6 +1683,10 @@
} else {
mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
}
+ } else {
+ moveDrawerToOffset(drawerView, 0.f);
+ updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+ drawerView.setVisibility(INVISIBLE);
}
invalidate();
}
@@ -1562,12 +1698,23 @@
* GravityCompat.START or GravityCompat.END may also be used.
*/
public void closeDrawer(@EdgeGravity int gravity) {
+ closeDrawer(gravity, true);
+ }
+
+ /**
+ * Close the specified drawer.
+ *
+ * @param gravity Gravity.LEFT to move the left drawer or Gravity.RIGHT for the right.
+ * GravityCompat.START or GravityCompat.END may also be used.
+ * @param animate Whether closing of the drawer should be animated.
+ */
+ public void closeDrawer(@EdgeGravity int gravity, boolean animate) {
final View drawerView = findDrawerWithGravity(gravity);
if (drawerView == null) {
throw new IllegalArgumentException("No drawer view found with gravity " +
gravityToString(gravity));
}
- closeDrawer(drawerView);
+ closeDrawer(drawerView, animate);
}
/**
@@ -1759,6 +1906,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
@@ -1851,15 +2003,15 @@
/**
* State persisted across instances
*/
- protected static class SavedState extends BaseSavedState {
+ protected static class SavedState extends AbsSavedState {
int openDrawerGravity = Gravity.NO_GRAVITY;
@LockMode int lockModeLeft;
@LockMode int lockModeRight;
@LockMode int lockModeStart;
@LockMode int lockModeEnd;
- public SavedState(Parcel in) {
- super(in);
+ public SavedState(Parcel in, ClassLoader loader) {
+ super(in, loader);
openDrawerGravity = in.readInt();
lockModeLeft = in.readInt();
lockModeRight = in.readInt();
@@ -1881,18 +2033,18 @@
dest.writeInt(lockModeEnd);
}
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel source) {
- return new SavedState(source);
- }
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
}
private class ViewDragCallback extends ViewDragHelper.Callback {
diff --git a/v4/java/android/support/v4/widget/EdgeEffectCompat.java b/v4/java/android/support/v4/widget/EdgeEffectCompat.java
index 55b1319..df69ca3 100644
--- a/v4/java/android/support/v4/widget/EdgeEffectCompat.java
+++ b/v4/java/android/support/v4/widget/EdgeEffectCompat.java
@@ -28,7 +28,7 @@
* be used by views that wish to use the standard Android visual effects at the edges
* of scrolling containers.
*/
-public class EdgeEffectCompat {
+public final class EdgeEffectCompat {
private Object mEdgeEffect;
private static final EdgeEffectImpl IMPL;
@@ -191,6 +191,7 @@
* @return true if the host view should call invalidate, false if it should not.
* @deprecated use {@link #onPull(float, float)}
*/
+ @Deprecated
public boolean onPull(float deltaDistance) {
return IMPL.onPull(mEdgeEffect, deltaDistance);
}
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index 64f6634..3e44962 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -19,22 +19,29 @@
import android.content.Context;
import android.graphics.Rect;
import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.util.SparseArrayCompat;
import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.KeyEventCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
+import android.support.v4.view.ViewCompat.FocusDirection;
+import android.support.v4.view.ViewCompat.FocusRealDirection;
import android.support.v4.view.ViewParentCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityManagerCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.support.v4.view.accessibility.AccessibilityNodeProviderCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
+import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
-import java.util.LinkedList;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -47,8 +54,36 @@
* <p>
* Clients should override abstract methods on this class and attach it to the
* host view using {@link ViewCompat#setAccessibilityDelegate}:
- *
+ * <p>
* <pre>
+ * class MyCustomView extends View {
+ * private MyVirtualViewHelper mVirtualViewHelper;
+ *
+ * public MyCustomView(Context context, ...) {
+ * ...
+ * mVirtualViewHelper = new MyVirtualViewHelper(this);
+ * ViewCompat.setAccessibilityDelegate(this, mVirtualViewHelper);
+ * }
+ *
+ * @Override
+ * public boolean dispatchHoverEvent(MotionEvent event) {
+ * return mHelper.dispatchHoverEvent(this, event)
+ * || super.dispatchHoverEvent(event);
+ * }
+ *
+ * @Override
+ * public boolean dispatchKeyEvent(KeyEvent event) {
+ * return mHelper.dispatchKeyEvent(event)
+ * || super.dispatchKeyEvent(event);
+ * }
+ *
+ * @Override
+ * public boolean onFocusChanged(boolean gainFocus, int direction,
+ * Rect previouslyFocusedRect) {
+ * super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * }
+ * }
* mAccessHelper = new MyExploreByTouchHelper(someView);
* ViewCompat.setAccessibilityDelegate(someView, mAccessHelper);
* </pre>
@@ -61,7 +96,11 @@
public static final int HOST_ID = View.NO_ID;
/** Default class name used for virtual views. */
- private static final String DEFAULT_CLASS_NAME = View.class.getName();
+ private static final String DEFAULT_CLASS_NAME = "android.view.View";
+
+ /** Default bounds used to determine if the client didn't set any. */
+ private static final Rect INVALID_PARENT_BOUNDS = new Rect(
+ Integer.MAX_VALUE, Integer.MAX_VALUE, Integer.MIN_VALUE, Integer.MIN_VALUE);
// Temporary, reusable data structures.
private final Rect mTempScreenRect = new Rect();
@@ -73,66 +112,73 @@
private final AccessibilityManager mManager;
/** View whose internal structure is exposed through this helper. */
- private final View mView;
+ private final View mHost;
- /** Node provider that handles creating nodes and performing actions. */
- private ExploreByTouchNodeProvider mNodeProvider;
+ /** Virtual node provider used to expose logical structure to services. */
+ private MyNodeProvider mNodeProvider;
- /** Virtual view id for the currently focused logical item. */
- private int mFocusedVirtualViewId = INVALID_ID;
+ /** Identifier for the virtual view that holds accessibility focus. */
+ private int mAccessibilityFocusedVirtualViewId = INVALID_ID;
- /** Virtual view id for the currently hovered logical item. */
+ /** Identifier for the virtual view that holds keyboard focus. */
+ private int mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+ /** Identifier for the virtual view that is currently hovered. */
private int mHoveredVirtualViewId = INVALID_ID;
/**
- * Factory method to create a new {@link ExploreByTouchHelper}.
+ * Constructs a new helper that can expose a virtual view hierarchy for the
+ * specified host view.
*
- * @param forView View whose logical children are exposed by this helper.
+ * @param host view whose virtual view hierarchy is exposed by this helper
*/
- public ExploreByTouchHelper(View forView) {
- if (forView == null) {
+ public ExploreByTouchHelper(View host) {
+ if (host == null) {
throw new IllegalArgumentException("View may not be null");
}
- mView = forView;
- final Context context = forView.getContext();
+ mHost = host;
+
+ final Context context = host.getContext();
mManager = (AccessibilityManager) context.getSystemService(Context.ACCESSIBILITY_SERVICE);
+
+ // Host view must be focusable so that we can delegate to virtual
+ // views.
+ host.setFocusable(true);
+ if (ViewCompat.getImportantForAccessibility(host)
+ == ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
+ ViewCompat.setImportantForAccessibility(
+ host, ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
+ }
}
- /**
- * Returns the {@link AccessibilityNodeProviderCompat} for this helper.
- *
- * @param host View whose logical children are exposed by this helper.
- * @return The accessibility node provider for this helper.
- */
@Override
public AccessibilityNodeProviderCompat getAccessibilityNodeProvider(View host) {
if (mNodeProvider == null) {
- mNodeProvider = new ExploreByTouchNodeProvider();
+ mNodeProvider = new MyNodeProvider();
}
return mNodeProvider;
}
/**
+ * Delegates hover events from the host view.
+ * <p>
* Dispatches hover {@link MotionEvent}s to the virtual view hierarchy when
* the Explore by Touch feature is enabled.
* <p>
- * This method should be called by overriding
- * {@link View#dispatchHoverEvent}:
- *
+ * This method should be called by overriding the host view's
+ * {@link View#dispatchHoverEvent(MotionEvent)} method:
* <pre>@Override
* public boolean dispatchHoverEvent(MotionEvent event) {
- * if (mHelper.dispatchHoverEvent(this, event) {
- * return true;
- * }
- * return super.dispatchHoverEvent(event);
+ * return mHelper.dispatchHoverEvent(this, event)
+ * || super.dispatchHoverEvent(event);
* }
* </pre>
*
* @param event The hover event to dispatch to the virtual view hierarchy.
* @return Whether the hover event was handled.
*/
- public boolean dispatchHoverEvent(MotionEvent event) {
+ public final boolean dispatchHoverEvent(@NonNull MotionEvent event) {
if (!mManager.isEnabled()
|| !AccessibilityManagerCompat.isTouchExplorationEnabled(mManager)) {
return false;
@@ -145,7 +191,7 @@
updateHoveredVirtualView(virtualViewId);
return (virtualViewId != INVALID_ID);
case MotionEventCompat.ACTION_HOVER_EXIT:
- if (mFocusedVirtualViewId != INVALID_ID) {
+ if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
updateHoveredVirtualView(INVALID_ID);
return true;
}
@@ -156,78 +202,412 @@
}
/**
+ * Delegates key events from the host view.
+ * <p>
+ * This method should be called by overriding the host view's
+ * {@link View#dispatchKeyEvent(KeyEvent)} method:
+ * <pre>@Override
+ * public boolean dispatchKeyEvent(KeyEvent event) {
+ * return mHelper.dispatchKeyEvent(event)
+ * || super.dispatchKeyEvent(event);
+ * }
+ * </pre>
+ */
+ public final boolean dispatchKeyEvent(@NonNull KeyEvent event) {
+ boolean handled = false;
+
+ final int action = event.getAction();
+ if (action != KeyEvent.ACTION_UP) {
+ final int keyCode = event.getKeyCode();
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ case KeyEvent.KEYCODE_DPAD_UP:
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ case KeyEvent.KEYCODE_DPAD_DOWN:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ final int direction = keyToDirection(keyCode);
+ final int count = 1 + event.getRepeatCount();
+ for (int i = 0; i < count; i++) {
+ if (moveFocus(direction, null)) {
+ handled = true;
+ } else {
+ break;
+ }
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_DPAD_CENTER:
+ case KeyEvent.KEYCODE_ENTER:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ if (event.getRepeatCount() == 0) {
+ clickKeyboardFocusedVirtualView();
+ handled = true;
+ }
+ }
+ break;
+ case KeyEvent.KEYCODE_TAB:
+ if (KeyEventCompat.hasNoModifiers(event)) {
+ handled = moveFocus(View.FOCUS_FORWARD, null);
+ } else if (KeyEventCompat.hasModifiers(event, KeyEvent.META_SHIFT_ON)) {
+ handled = moveFocus(View.FOCUS_BACKWARD, null);
+ }
+ break;
+ }
+ }
+
+ return handled;
+ }
+
+ /**
+ * Delegates focus changes from the host view.
+ * <p>
+ * This method should be called by overriding the host view's
+ * {@link View#onFocusChanged(boolean, int, Rect)} method:
+ * <pre>@Override
+ * public boolean onFocusChanged(boolean gainFocus, int direction,
+ * Rect previouslyFocusedRect) {
+ * super.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * mHelper.onFocusChanged(gainFocus, direction, previouslyFocusedRect);
+ * }
+ * </pre>
+ */
+ public final void onFocusChanged(boolean gainFocus, int direction,
+ @Nullable Rect previouslyFocusedRect) {
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+ }
+
+ if (gainFocus) {
+ moveFocus(direction, previouslyFocusedRect);
+ }
+ }
+
+ /**
+ * @return the identifier of the virtual view that has accessibility focus
+ * or {@link #INVALID_ID} if no virtual view has accessibility
+ * focus
+ */
+ public final int getAccessibilityFocusedVirtualViewId() {
+ return mAccessibilityFocusedVirtualViewId;
+ }
+
+ /**
+ * @return the identifier of the virtual view that has keyboard focus
+ * or {@link #INVALID_ID} if no virtual view has keyboard focus
+ */
+ public final int getKeyboardFocusedVirtualViewId() {
+ return mKeyboardFocusedVirtualViewId;
+ }
+
+ /**
+ * Maps key event codes to focus directions.
+ *
+ * @param keyCode the key event code
+ * @return the corresponding focus direction
+ */
+ @FocusRealDirection
+ private static int keyToDirection(int keyCode) {
+ switch (keyCode) {
+ case KeyEvent.KEYCODE_DPAD_LEFT:
+ return View.FOCUS_LEFT;
+ case KeyEvent.KEYCODE_DPAD_UP:
+ return View.FOCUS_UP;
+ case KeyEvent.KEYCODE_DPAD_RIGHT:
+ return View.FOCUS_RIGHT;
+ default:
+ return View.FOCUS_DOWN;
+ }
+ }
+
+ /**
+ * Obtains the bounds for the specified virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view
+ * @param outBounds the rect to populate with virtual view bounds
+ */
+ private void getBoundsInParent(int virtualViewId, Rect outBounds) {
+ final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+ node.getBoundsInParent(outBounds);
+ }
+
+ /**
+ * Adapts AccessibilityNodeInfoCompat for obtaining bounds.
+ */
+ private static final FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat> NODE_ADAPTER =
+ new FocusStrategy.BoundsAdapter<AccessibilityNodeInfoCompat>() {
+ @Override
+ public void obtainBounds(AccessibilityNodeInfoCompat node, Rect outBounds) {
+ node.getBoundsInParent(outBounds);
+ }
+ };
+
+ /**
+ * Adapts SparseArrayCompat for iterating through values.
+ */
+ private static final FocusStrategy.CollectionAdapter<SparseArrayCompat<
+ AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat> SPARSE_VALUES_ADAPTER =
+ new FocusStrategy.CollectionAdapter<SparseArrayCompat<
+ AccessibilityNodeInfoCompat>, AccessibilityNodeInfoCompat>() {
+ @Override
+ public AccessibilityNodeInfoCompat get(
+ SparseArrayCompat<AccessibilityNodeInfoCompat> collection, int index) {
+ return collection.valueAt(index);
+ }
+
+ @Override
+ public int size(SparseArrayCompat<AccessibilityNodeInfoCompat> collection) {
+ return collection.size();
+ }
+ };
+
+ /**
+ * Attempts to move keyboard focus in the specified direction.
+ *
+ * @param direction the direction in which to move keyboard focus
+ * @param previouslyFocusedRect the bounds of the previously focused item,
+ * or {@code null} if not available
+ * @return {@code true} if keyboard focus moved to a virtual view managed
+ * by this helper, or {@code false} otherwise
+ */
+ private boolean moveFocus(@FocusDirection int direction, @Nullable Rect previouslyFocusedRect) {
+ final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = getAllNodes();
+
+ final int focusedNodeId = mKeyboardFocusedVirtualViewId;
+ final AccessibilityNodeInfoCompat focusedNode =
+ focusedNodeId == INVALID_ID ? null : allNodes.get(focusedNodeId);
+
+ final AccessibilityNodeInfoCompat nextFocusedNode;
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ case View.FOCUS_BACKWARD:
+ final boolean isLayoutRtl =
+ ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
+ nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(allNodes,
+ SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
+ false);
+ break;
+ case View.FOCUS_LEFT:
+ case View.FOCUS_UP:
+ case View.FOCUS_RIGHT:
+ case View.FOCUS_DOWN:
+ final Rect selectedRect = new Rect();
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ // Focus is moving from a virtual view within the host.
+ getBoundsInParent(mKeyboardFocusedVirtualViewId, selectedRect);
+ } else if (previouslyFocusedRect != null) {
+ // Focus is moving from a real view outside the host.
+ selectedRect.set(previouslyFocusedRect);
+ } else {
+ // Focus is moving from... somewhere? Make a guess.
+ // Usually this happens when another view was too lazy
+ // to pass the previously focused rect (ex. ScrollView
+ // when moving UP or DOWN).
+ guessPreviouslyFocusedRect(mHost, direction, selectedRect);
+ }
+ nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(allNodes,
+ SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_FORWARD, FOCUS_BACKWARD, FOCUS_UP, FOCUS_DOWN, "
+ + "FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ final int nextFocusedNodeId;
+ if (nextFocusedNode == null) {
+ nextFocusedNodeId = INVALID_ID;
+ } else {
+ final int index = allNodes.indexOfValue(nextFocusedNode);
+ nextFocusedNodeId = allNodes.keyAt(index);
+ }
+
+ return requestKeyboardFocusForVirtualView(nextFocusedNodeId);
+ }
+
+ private SparseArrayCompat<AccessibilityNodeInfoCompat> getAllNodes() {
+ final List<Integer> virtualViewIds = new ArrayList<>();
+ getVisibleVirtualViews(virtualViewIds);
+
+ final SparseArrayCompat<AccessibilityNodeInfoCompat> allNodes = new SparseArrayCompat<>();
+ for (int virtualViewId = 0; virtualViewId < virtualViewIds.size(); virtualViewId++) {
+ final AccessibilityNodeInfoCompat virtualView = createNodeForChild(virtualViewId);
+ allNodes.put(virtualViewId, virtualView);
+ }
+
+ return allNodes;
+ }
+
+ /**
+ * Obtains a best guess for the previously focused rect for keyboard focus
+ * moving in the specified direction.
+ *
+ * @param host the view into which focus is moving
+ * @param direction the absolute direction in which focus is moving
+ * @param outBounds the rect to populate with the best-guess bounds for the
+ * previous focus rect
+ */
+ private static Rect guessPreviouslyFocusedRect(@NonNull View host,
+ @FocusRealDirection int direction, @NonNull Rect outBounds) {
+ final int w = host.getWidth();
+ final int h = host.getHeight();
+
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ outBounds.set(w, 0, w, h);
+ break;
+ case View.FOCUS_UP:
+ outBounds.set(0, h, w, h);
+ break;
+ case View.FOCUS_RIGHT:
+ outBounds.set(-1, 0, -1, h);
+ break;
+ case View.FOCUS_DOWN:
+ outBounds.set(0, -1, w, -1);
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ return outBounds;
+ }
+
+ /**
+ * Performs a click action on the keyboard focused virtual view, if any.
+ *
+ * @return {@code true} if the click action was performed successfully or
+ * {@code false} otherwise
+ */
+ private boolean clickKeyboardFocusedVirtualView() {
+ return mKeyboardFocusedVirtualViewId != INVALID_ID && onPerformActionForVirtualView(
+ mKeyboardFocusedVirtualViewId, AccessibilityNodeInfoCompat.ACTION_CLICK, null);
+ }
+
+ /**
* Populates an event of the specified type with information about an item
* and attempts to send it up through the view hierarchy.
* <p>
* You should call this method after performing a user action that normally
* fires an accessibility event, such as clicking on an item.
- *
+ * <p>
* <pre>public void performItemClick(T item) {
* ...
* sendEventForVirtualViewId(item.id, AccessibilityEvent.TYPE_VIEW_CLICKED);
* }
* </pre>
*
- * @param virtualViewId The virtual view id for which to send an event.
- * @param eventType The type of event to send.
- * @return true if the event was sent successfully.
+ * @param virtualViewId the identifier of the virtual view for which to
+ * send an event
+ * @param eventType the type of event to send
+ * @return {@code true} if the event was sent successfully, {@code false}
+ * otherwise
*/
- public boolean sendEventForVirtualView(int virtualViewId, int eventType) {
+ public final boolean sendEventForVirtualView(int virtualViewId, int eventType) {
if ((virtualViewId == INVALID_ID) || !mManager.isEnabled()) {
return false;
}
- final ViewParent parent = mView.getParent();
+ final ViewParent parent = mHost.getParent();
if (parent == null) {
return false;
}
final AccessibilityEvent event = createEvent(virtualViewId, eventType);
- return ViewParentCompat.requestSendAccessibilityEvent(parent, mView, event);
+ return ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
}
/**
* Notifies the accessibility framework that the properties of the parent
* view have changed.
* <p>
- * You <b>must</b> call this method after adding or removing items from the
- * parent view.
+ * You <strong>must</strong> call this method after adding or removing
+ * items from the parent view.
*/
- public void invalidateRoot() {
- invalidateVirtualView(HOST_ID);
+ public final void invalidateRoot() {
+ invalidateVirtualView(HOST_ID, AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE);
}
/**
* Notifies the accessibility framework that the properties of a particular
* item have changed.
* <p>
- * You <b>must</b> call this method after changing any of the properties set
- * in {@link #onPopulateNodeForVirtualView}.
+ * You <strong>must</strong> call this method after changing any of the
+ * properties set in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
*
- * @param virtualViewId The virtual view id to invalidate.
+ * @param virtualViewId the virtual view id to invalidate, or
+ * {@link #HOST_ID} to invalidate the root view
+ * @see #invalidateVirtualView(int, int)
*/
- public void invalidateVirtualView(int virtualViewId) {
- sendEventForVirtualView(
- virtualViewId, AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+ public final void invalidateVirtualView(int virtualViewId) {
+ invalidateVirtualView(virtualViewId,
+ AccessibilityEventCompat.CONTENT_CHANGE_TYPE_UNDEFINED);
}
/**
- * Returns the virtual view id for the currently focused item,
+ * Notifies the accessibility framework that the properties of a particular
+ * item have changed.
+ * <p>
+ * You <strong>must</strong> call this method after changing any of the
+ * properties set in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
*
- * @return A virtual view id, or {@link #INVALID_ID} if no item is
- * currently focused.
+ * @param virtualViewId the virtual view id to invalidate, or
+ * {@link #HOST_ID} to invalidate the root view
+ * @param changeTypes the bit mask of change types. May be {@code 0} for the
+ * default (undefined) change type or one or more of:
+ * <ul>
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_CONTENT_DESCRIPTION}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_SUBTREE}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_TEXT}
+ * <li>{@link AccessibilityEventCompat#CONTENT_CHANGE_TYPE_UNDEFINED}
+ * </ul>
*/
+ public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
+ if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
+ final ViewParent parent = mHost.getParent();
+ if (parent != null) {
+ // Send events up the hierarchy so they can be coalesced.
+ final AccessibilityEvent event = createEvent(virtualViewId,
+ AccessibilityEventCompat.TYPE_WINDOW_CONTENT_CHANGED);
+ AccessibilityEventCompat.setContentChangeTypes(event, changeTypes);
+ ViewParentCompat.requestSendAccessibilityEvent(parent, mHost, event);
+ }
+ }
+ }
+
+ /**
+ * Returns the virtual view ID for the currently accessibility focused
+ * item.
+ *
+ * @return the identifier of the virtual view that has accessibility focus
+ * or {@link #INVALID_ID} if no virtual view has accessibility
+ * focus
+ * @deprecated Use {@link #getAccessibilityFocusedVirtualViewId()}.
+ */
+ @Deprecated
public int getFocusedVirtualView() {
- return mFocusedVirtualViewId;
+ return getAccessibilityFocusedVirtualViewId();
+ }
+
+ /**
+ * Called when the focus state of a virtual view changes.
+ *
+ * @param virtualViewId the virtual view identifier
+ * @param hasFocus {@code true} if the view has focus, {@code false}
+ * otherwise
+ */
+ protected void onVirtualViewKeyboardFocusChanged(int virtualViewId, boolean hasFocus) {
+ // Stub method.
}
/**
* Sets the currently hovered item, sending hover accessibility events as
* necessary to maintain the correct state.
*
- * @param virtualViewId The virtual view id for the item currently being
- * hovered, or {@link #INVALID_ID} if no item is hovered within
- * the parent view.
+ * @param virtualViewId the virtual view id for the item currently being
+ * hovered, or {@link #INVALID_ID} if no item is
+ * hovered within the parent view
*/
private void updateHoveredVirtualView(int virtualViewId) {
if (mHoveredVirtualViewId == virtualViewId) {
@@ -248,11 +628,11 @@
* Constructs and returns an {@link AccessibilityEvent} for the specified
* virtual view id, which includes the host view ({@link #HOST_ID}).
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct an event.
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param virtualViewId the virtual view id for the item for which to
+ * construct an event
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEvent(int virtualViewId, int eventType) {
switch (virtualViewId) {
@@ -266,30 +646,46 @@
/**
* Constructs and returns an {@link AccessibilityEvent} for the host node.
*
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEventForHost(int eventType) {
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- ViewCompat.onInitializeAccessibilityEvent(mView, event);
+ ViewCompat.onInitializeAccessibilityEvent(mHost, event);
return event;
}
+ @Override
+ public void onInitializeAccessibilityEvent(View host, AccessibilityEvent event) {
+ super.onInitializeAccessibilityEvent(host, event);
+
+ // Allow the client to populate the event.
+ onPopulateEventForHost(event);
+ }
+
/**
* Constructs and returns an {@link AccessibilityEvent} populated with
* information about the specified item.
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct an event.
- * @param eventType The type of event to construct.
- * @return An {@link AccessibilityEvent} populated with information about
- * the specified item.
+ * @param virtualViewId the virtual view id for the item for which to
+ * construct an event
+ * @param eventType the type of event to construct
+ * @return an {@link AccessibilityEvent} populated with information about
+ * the specified item
*/
private AccessibilityEvent createEventForChild(int virtualViewId, int eventType) {
final AccessibilityEvent event = AccessibilityEvent.obtain(eventType);
- event.setEnabled(true);
- event.setClassName(DEFAULT_CLASS_NAME);
+ final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
+ final AccessibilityNodeInfoCompat node = obtainAccessibilityNodeInfo(virtualViewId);
+
+ // Allow the client to override these properties,
+ record.getText().add(node.getText());
+ record.setContentDescription(node.getContentDescription());
+ record.setScrollable(node.isScrollable());
+ record.setPassword(node.isPassword());
+ record.setEnabled(node.isEnabled());
+ record.setChecked(node.isChecked());
// Allow the client to populate the event.
onPopulateEventForVirtualView(virtualViewId, event);
@@ -301,55 +697,67 @@
}
// Don't allow the client to override these properties.
- event.setPackageName(mView.getContext().getPackageName());
-
- final AccessibilityRecordCompat record = AccessibilityEventCompat.asRecord(event);
- record.setSource(mView, virtualViewId);
+ record.setClassName(node.getClassName());
+ record.setSource(mHost, virtualViewId);
+ event.setPackageName(mHost.getContext().getPackageName());
return event;
}
/**
- * Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
- * specified virtual view id, which includes the host view
- * ({@link #HOST_ID}).
+ * Obtains a populated {@link AccessibilityNodeInfoCompat} for the
+ * virtual view with the specified identifier.
+ * <p>
+ * This method may be called with identifier {@link #HOST_ID} to obtain a
+ * node for the host view.
*
- * @param virtualViewId The virtual view id for the item for which to
- * construct a node.
- * @return An {@link AccessibilityNodeInfoCompat} populated with information
- * about the specified item.
+ * @param virtualViewId the identifier of the virtual view for which to
+ * construct a node
+ * @return an {@link AccessibilityNodeInfoCompat} populated with information
+ * about the specified item
*/
- private AccessibilityNodeInfoCompat createNode(int virtualViewId) {
- switch (virtualViewId) {
- case HOST_ID:
- return createNodeForHost();
- default:
- return createNodeForChild(virtualViewId);
+ @NonNull
+ private AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
+ if (virtualViewId == HOST_ID) {
+ return createNodeForHost();
}
+
+ return createNodeForChild(virtualViewId);
}
/**
* Constructs and returns an {@link AccessibilityNodeInfoCompat} for the
* host view populated with its virtual descendants.
*
- * @return An {@link AccessibilityNodeInfoCompat} for the parent node.
+ * @return an {@link AccessibilityNodeInfoCompat} for the parent node
*/
+ @NonNull
private AccessibilityNodeInfoCompat createNodeForHost() {
- final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain(mView);
- ViewCompat.onInitializeAccessibilityNodeInfo(mView, node);
-
- // Allow the client to populate the host node.
- onPopulateNodeForHost(node);
+ final AccessibilityNodeInfoCompat info = AccessibilityNodeInfoCompat.obtain(mHost);
+ ViewCompat.onInitializeAccessibilityNodeInfo(mHost, info);
// Add the virtual descendants.
- final LinkedList<Integer> virtualViewIds = new LinkedList<Integer>();
+ final ArrayList<Integer> virtualViewIds = new ArrayList<>();
getVisibleVirtualViews(virtualViewIds);
- for (Integer childVirtualViewId : virtualViewIds) {
- node.addChild(mView, childVirtualViewId);
+ final int realNodeCount = info.getChildCount();
+ if (realNodeCount > 0 && virtualViewIds.size() > 0) {
+ throw new RuntimeException("Views cannot have both real and virtual children");
}
- return node;
+ for (int i = 0, count = virtualViewIds.size(); i < count; i++) {
+ info.addChild(mHost, virtualViewIds.get(i));
+ }
+
+ return info;
+ }
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(host, info);
+
+ // Allow the client to populate the host node.
+ onPopulateNodeForHost(info);
}
/**
@@ -376,16 +784,20 @@
* <li>{@link AccessibilityNodeInfoCompat#setBoundsInParent}
* </ul>
*
- * @param virtualViewId The virtual view id for item for which to construct
- * a node.
- * @return An {@link AccessibilityNodeInfoCompat} for the specified item.
+ * @param virtualViewId the virtual view id for item for which to construct
+ * a node
+ * @return an {@link AccessibilityNodeInfoCompat} for the specified item
*/
+ @NonNull
private AccessibilityNodeInfoCompat createNodeForChild(int virtualViewId) {
final AccessibilityNodeInfoCompat node = AccessibilityNodeInfoCompat.obtain();
// Ensure the client has good defaults.
node.setEnabled(true);
+ node.setFocusable(true);
node.setClassName(DEFAULT_CLASS_NAME);
+ node.setBoundsInParent(INVALID_PARENT_BOUNDS);
+ node.setBoundsInScreen(INVALID_PARENT_BOUNDS);
// Allow the client to populate the node.
onPopulateNodeForVirtualView(virtualViewId, node);
@@ -397,7 +809,7 @@
}
node.getBoundsInParent(mTempParentRect);
- if (mTempParentRect.isEmpty()) {
+ if (mTempParentRect.equals(INVALID_PARENT_BOUNDS)) {
throw new RuntimeException("Callbacks must set parent bounds in "
+ "populateNodeForVirtualViewId()");
}
@@ -413,12 +825,12 @@
}
// Don't allow the client to override these properties.
- node.setPackageName(mView.getContext().getPackageName());
- node.setSource(mView, virtualViewId);
- node.setParent(mView);
+ node.setPackageName(mHost.getContext().getPackageName());
+ node.setSource(mHost, virtualViewId);
+ node.setParent(mHost);
// Manage internal accessibility focus state.
- if (mFocusedVirtualViewId == virtualViewId) {
+ if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
node.setAccessibilityFocused(true);
node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS);
} else {
@@ -426,19 +838,31 @@
node.addAction(AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS);
}
+ // Manage internal keyboard focus state.
+ final boolean isFocused = mKeyboardFocusedVirtualViewId == virtualViewId;
+ if (isFocused) {
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS);
+ } else if (node.isFocusable()) {
+ node.addAction(AccessibilityNodeInfoCompat.ACTION_FOCUS);
+ }
+ node.setFocused(isFocused);
+
// Set the visibility based on the parent bound.
if (intersectVisibleToUser(mTempParentRect)) {
node.setVisibleToUser(true);
node.setBoundsInParent(mTempParentRect);
}
- // Calculate screen-relative bound.
- mView.getLocationOnScreen(mTempGlobalRect);
- final int offsetX = mTempGlobalRect[0];
- final int offsetY = mTempGlobalRect[1];
- mTempScreenRect.set(mTempParentRect);
- mTempScreenRect.offset(offsetX, offsetY);
- node.setBoundsInScreen(mTempScreenRect);
+ // If not explicitly specified, calculate screen-relative bounds and
+ // offset for scroll position based on bounds in parent.
+ node.getBoundsInScreen(mTempScreenRect);
+ if (mTempScreenRect.equals(INVALID_PARENT_BOUNDS)) {
+ mHost.getLocationOnScreen(mTempGlobalRect);
+ node.getBoundsInParent(mTempScreenRect);
+ mTempScreenRect.offset(mTempGlobalRect[0] - mHost.getScrollX(),
+ mTempGlobalRect[1] - mHost.getScrollY());
+ node.setBoundsInScreen(mTempScreenRect);
+ }
return node;
}
@@ -453,27 +877,21 @@
}
private boolean performActionForHost(int action, Bundle arguments) {
- return ViewCompat.performAccessibilityAction(mView, action, arguments);
+ return ViewCompat.performAccessibilityAction(mHost, action, arguments);
}
private boolean performActionForChild(int virtualViewId, int action, Bundle arguments) {
switch (action) {
case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
- case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
- return manageFocusForChild(virtualViewId, action, arguments);
- default:
- return onPerformActionForVirtualView(virtualViewId, action, arguments);
- }
- }
-
- private boolean manageFocusForChild(int virtualViewId, int action, Bundle arguments) {
- switch (action) {
- case AccessibilityNodeInfoCompat.ACTION_ACCESSIBILITY_FOCUS:
return requestAccessibilityFocus(virtualViewId);
case AccessibilityNodeInfoCompat.ACTION_CLEAR_ACCESSIBILITY_FOCUS:
return clearAccessibilityFocus(virtualViewId);
+ case AccessibilityNodeInfoCompat.ACTION_FOCUS:
+ return requestKeyboardFocusForVirtualView(virtualViewId);
+ case AccessibilityNodeInfoCompat.ACTION_CLEAR_FOCUS:
+ return clearKeyboardFocusForVirtualView(virtualViewId);
default:
- return false;
+ return onPerformActionForVirtualView(virtualViewId, action, arguments);
}
}
@@ -482,8 +900,8 @@
* portion of its parent {@link View}. Modifies {@code localRect} to contain
* only the visible portion.
*
- * @param localRect A rectangle in local (parent) coordinates.
- * @return Whether the specified {@link Rect} is visible on the screen.
+ * @param localRect a rectangle in local (parent) coordinates
+ * @return whether the specified {@link Rect} is visible on the screen
*/
private boolean intersectVisibleToUser(Rect localRect) {
// Missing or empty bounds mean this view is not visible.
@@ -492,12 +910,12 @@
}
// Attached to invisible window means this view is not visible.
- if (mView.getWindowVisibility() != View.VISIBLE) {
+ if (mHost.getWindowVisibility() != View.VISIBLE) {
return false;
}
// An invisible predecessor means that this view is not visible.
- ViewParent viewParent = mView.getParent();
+ ViewParent viewParent = mHost.getParent();
while (viewParent instanceof View) {
final View view = (View) viewParent;
if ((ViewCompat.getAlpha(view) <= 0) || (view.getVisibility() != View.VISIBLE)) {
@@ -512,7 +930,7 @@
}
// If no portion of the parent is visible, this view is not visible.
- if (!mView.getLocalVisibleRect(mTempVisibleRect)) {
+ if (!mHost.getLocalVisibleRect(mTempVisibleRect)) {
return false;
}
@@ -521,15 +939,6 @@
}
/**
- * Returns whether this virtual view is accessibility focused.
- *
- * @return True if the view is accessibility focused.
- */
- private boolean isAccessibilityFocused(int virtualViewId) {
- return (mFocusedVirtualViewId == virtualViewId);
- }
-
- /**
* Attempts to give accessibility focus to a virtual view.
* <p>
* A virtual view will not actually take focus if
@@ -537,9 +946,9 @@
* {@link AccessibilityManager#isTouchExplorationEnabled()} returns false,
* or the view already has accessibility focus.
*
- * @param virtualViewId The id of the virtual view on which to place
- * accessibility focus.
- * @return Whether this virtual view actually took accessibility focus.
+ * @param virtualViewId the identifier of the virtual view on which to
+ * place accessibility focus
+ * @return whether this virtual view actually took accessibility focus
*/
private boolean requestAccessibilityFocus(int virtualViewId) {
if (!mManager.isEnabled()
@@ -547,18 +956,17 @@
return false;
}
// TODO: Check virtual view visibility.
- if (!isAccessibilityFocused(virtualViewId)) {
+ if (mAccessibilityFocusedVirtualViewId != virtualViewId) {
// Clear focus from the previously focused view, if applicable.
- if (mFocusedVirtualViewId != INVALID_ID) {
- sendEventForVirtualView(mFocusedVirtualViewId,
- AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
+ if (mAccessibilityFocusedVirtualViewId != INVALID_ID) {
+ clearAccessibilityFocus(mAccessibilityFocusedVirtualViewId);
}
// Set focus on the new view.
- mFocusedVirtualViewId = virtualViewId;
+ mAccessibilityFocusedVirtualViewId = virtualViewId;
// TODO: Only invalidate virtual view bounds.
- mView.invalidate();
+ mHost.invalidate();
sendEventForVirtualView(virtualViewId,
AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUSED);
return true;
@@ -569,14 +977,14 @@
/**
* Attempts to clear accessibility focus from a virtual view.
*
- * @param virtualViewId The id of the virtual view from which to clear
- * accessibility focus.
- * @return Whether this virtual view actually cleared accessibility focus.
+ * @param virtualViewId the identifier of the virtual view from which to
+ * clear accessibility focus
+ * @return whether this virtual view actually cleared accessibility focus
*/
private boolean clearAccessibilityFocus(int virtualViewId) {
- if (isAccessibilityFocused(virtualViewId)) {
- mFocusedVirtualViewId = INVALID_ID;
- mView.invalidate();
+ if (mAccessibilityFocusedVirtualViewId == virtualViewId) {
+ mAccessibilityFocusedVirtualViewId = INVALID_ID;
+ mHost.invalidate();
sendEventForVirtualView(virtualViewId,
AccessibilityEventCompat.TYPE_VIEW_ACCESSIBILITY_FOCUS_CLEARED);
return true;
@@ -585,6 +993,57 @@
}
/**
+ * Attempts to give keyboard focus to a virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view on which to
+ * place keyboard focus
+ * @return whether this virtual view actually took keyboard focus
+ */
+ public final boolean requestKeyboardFocusForVirtualView(int virtualViewId) {
+ if (!mHost.isFocused() && !mHost.requestFocus()) {
+ // Host must have real keyboard focus.
+ return false;
+ }
+
+ if (mKeyboardFocusedVirtualViewId == virtualViewId) {
+ // The virtual view already has focus.
+ return false;
+ }
+
+ if (mKeyboardFocusedVirtualViewId != INVALID_ID) {
+ clearKeyboardFocusForVirtualView(mKeyboardFocusedVirtualViewId);
+ }
+
+ mKeyboardFocusedVirtualViewId = virtualViewId;
+
+ onVirtualViewKeyboardFocusChanged(virtualViewId, true);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ return true;
+ }
+
+ /**
+ * Attempts to clear keyboard focus from a virtual view.
+ *
+ * @param virtualViewId the identifier of the virtual view from which to
+ * clear keyboard focus
+ * @return whether this virtual view actually cleared keyboard focus
+ */
+ public final boolean clearKeyboardFocusForVirtualView(int virtualViewId) {
+ if (mKeyboardFocusedVirtualViewId != virtualViewId) {
+ // The virtual view is not focused.
+ return false;
+ }
+
+ mKeyboardFocusedVirtualViewId = INVALID_ID;
+
+ onVirtualViewKeyboardFocusChanged(virtualViewId, false);
+ sendEventForVirtualView(virtualViewId, AccessibilityEvent.TYPE_VIEW_FOCUSED);
+
+ return true;
+ }
+
+ /**
* Provides a mapping between view-relative coordinates and logical
* items.
*
@@ -609,22 +1068,25 @@
* Populates an {@link AccessibilityEvent} with information about the
* specified item.
* <p>
- * Implementations <b>must</b> populate the following required fields:
+ * The helper class automatically populates the following fields based on
+ * the values set by
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)},
+ * but implementations may optionally override them:
* <ul>
- * <li>event text, see {@link AccessibilityEvent#getText} or
- * {@link AccessibilityEvent#setContentDescription}
- * </ul>
- * <p>
- * The helper class automatically populates the following fields with
- * default values, but implementations may optionally override them:
- * <ul>
- * <li>item class name, set to android.view.View, see
- * {@link AccessibilityEvent#setClassName}
+ * <li>event text, see {@link AccessibilityEvent#getText()}
+ * <li>content description, see
+ * {@link AccessibilityEvent#setContentDescription(CharSequence)}
+ * <li>scrollability, see {@link AccessibilityEvent#setScrollable(boolean)}
+ * <li>password state, see {@link AccessibilityEvent#setPassword(boolean)}
+ * <li>enabled state, see {@link AccessibilityEvent#setEnabled(boolean)}
+ * <li>checked state, see {@link AccessibilityEvent#setChecked(boolean)}
* </ul>
* <p>
* The following required fields are automatically populated by the
* helper class and may not be overridden:
* <ul>
+ * <li>item class name, set to the value used in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}
* <li>package name, set to the package of the host view's
* {@link Context}, see {@link AccessibilityEvent#setPackageName}
* <li>event source, set to the host view and virtual view identifier,
@@ -635,55 +1097,76 @@
* populate the event
* @param event The event to populate
*/
- protected abstract void onPopulateEventForVirtualView(
- int virtualViewId, AccessibilityEvent event);
+ protected void onPopulateEventForVirtualView(int virtualViewId, AccessibilityEvent event) {
+ // Default implementation is no-op.
+ }
+
+ /**
+ * Populates an {@link AccessibilityEvent} with information about the host
+ * view.
+ * <p>
+ * The default implementation is a no-op.
+ *
+ * @param event the event to populate with information about the host view
+ */
+ protected void onPopulateEventForHost(AccessibilityEvent event) {
+ // Default implementation is no-op.
+ }
/**
* Populates an {@link AccessibilityNodeInfoCompat} with information
* about the specified item.
* <p>
- * Implementations <b>must</b> populate the following required fields:
+ * Implementations <strong>must</strong> populate the following required
+ * fields:
* <ul>
- * <li>event text, see {@link AccessibilityNodeInfoCompat#setText} or
- * {@link AccessibilityNodeInfoCompat#setContentDescription}
+ * <li>event text, see
+ * {@link AccessibilityNodeInfoCompat#setText(CharSequence)} or
+ * {@link AccessibilityNodeInfoCompat#setContentDescription(CharSequence)}
* <li>bounds in parent coordinates, see
- * {@link AccessibilityNodeInfoCompat#setBoundsInParent}
+ * {@link AccessibilityNodeInfoCompat#setBoundsInParent(Rect)}
* </ul>
* <p>
* The helper class automatically populates the following fields with
* default values, but implementations may optionally override them:
* <ul>
- * <li>enabled state, set to true, see
- * {@link AccessibilityNodeInfoCompat#setEnabled}
- * <li>item class name, identical to the class name set by
- * {@link #onPopulateEventForVirtualView}, see
- * {@link AccessibilityNodeInfoCompat#setClassName}
+ * <li>enabled state, set to {@code true}, see
+ * {@link AccessibilityNodeInfoCompat#setEnabled(boolean)}
+ * <li>keyboard focusability, set to {@code true}, see
+ * {@link AccessibilityNodeInfoCompat#setFocusable(boolean)}
+ * <li>item class name, set to {@code android.view.View}, see
+ * {@link AccessibilityNodeInfoCompat#setClassName(CharSequence)}
* </ul>
* <p>
* The following required fields are automatically populated by the
* helper class and may not be overridden:
* <ul>
* <li>package name, identical to the package name set by
- * {@link #onPopulateEventForVirtualView}, see
+ * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
* {@link AccessibilityNodeInfoCompat#setPackageName}
* <li>node source, identical to the event source set in
- * {@link #onPopulateEventForVirtualView}, see
+ * {@link #onPopulateEventForVirtualView(int, AccessibilityEvent)}, see
* {@link AccessibilityNodeInfoCompat#setSource(View, int)}
* <li>parent view, set to the host view, see
* {@link AccessibilityNodeInfoCompat#setParent(View)}
* <li>visibility, computed based on parent-relative bounds, see
- * {@link AccessibilityNodeInfoCompat#setVisibleToUser}
+ * {@link AccessibilityNodeInfoCompat#setVisibleToUser(boolean)}
* <li>accessibility focus, computed based on internal helper state, see
- * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused}
+ * {@link AccessibilityNodeInfoCompat#setAccessibilityFocused(boolean)}
+ * <li>keyboard focus, computed based on internal helper state, see
+ * {@link AccessibilityNodeInfoCompat#setFocused(boolean)}
* <li>bounds in screen coordinates, computed based on host view bounds,
- * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen}
+ * see {@link AccessibilityNodeInfoCompat#setBoundsInScreen(Rect)}
* </ul>
* <p>
- * Additionally, the helper class automatically handles accessibility
- * focus management by adding the appropriate
- * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS} or
+ * Additionally, the helper class automatically handles keyboard focus and
+ * accessibility focus management by adding the appropriate
+ * {@link AccessibilityNodeInfoCompat#ACTION_FOCUS},
+ * {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_FOCUS},
+ * {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}, or
* {@link AccessibilityNodeInfoCompat#ACTION_CLEAR_ACCESSIBILITY_FOCUS}
- * action. Implementations must <b>never</b> manually add these actions.
+ * actions. Implementations must <strong>never</strong> manually add these
+ * actions.
* <p>
* The helper class also automatically modifies parent- and
* screen-relative bounds to reflect the portion of the item visible
@@ -700,10 +1183,11 @@
* Populates an {@link AccessibilityNodeInfoCompat} with information
* about the host view.
* <p>
- * The following required fields are automatically populated by the
- * helper class and may not be overridden:
+ * The default implementation is a no-op.
+ *
+ * @param node the node to populate with information about the host view
*/
- public void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
+ protected void onPopulateNodeForHost(AccessibilityNodeInfoCompat node) {
// Default implementation is no-op.
}
@@ -713,8 +1197,9 @@
* {@link AccessibilityNodeInfoCompat#performAction(int, Bundle)} for
* more information.
* <p>
- * Implementations <b>must</b> handle any actions added manually in
- * {@link #onPopulateNodeForVirtualView}.
+ * Implementations <strong>must</strong> handle any actions added manually
+ * in
+ * {@link #onPopulateNodeForVirtualView(int, AccessibilityNodeInfoCompat)}.
* <p>
* The helper class automatically handles focus management resulting
* from {@link AccessibilityNodeInfoCompat#ACTION_ACCESSIBILITY_FOCUS}
@@ -733,13 +1218,16 @@
int virtualViewId, int action, Bundle arguments);
/**
- * Exposes a virtual view hierarchy to the accessibility framework. Only
- * used in API 16+.
+ * Exposes a virtual view hierarchy to the accessibility framework.
*/
- private class ExploreByTouchNodeProvider extends AccessibilityNodeProviderCompat {
+ private class MyNodeProvider extends AccessibilityNodeProviderCompat {
@Override
public AccessibilityNodeInfoCompat createAccessibilityNodeInfo(int virtualViewId) {
- return ExploreByTouchHelper.this.createNode(virtualViewId);
+ // The caller takes ownership of the node and is expected to
+ // recycle it when done, so always return a copy.
+ final AccessibilityNodeInfoCompat node =
+ ExploreByTouchHelper.this.obtainAccessibilityNodeInfo(virtualViewId);
+ return AccessibilityNodeInfoCompat.obtain(node);
}
@Override
diff --git a/v4/java/android/support/v4/widget/FocusStrategy.java b/v4/java/android/support/v4/widget/FocusStrategy.java
new file mode 100644
index 0000000..8be9f1a
--- /dev/null
+++ b/v4/java/android/support/v4/widget/FocusStrategy.java
@@ -0,0 +1,454 @@
+/*
+ * 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.v4.widget;
+
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.View;
+
+import android.support.v4.view.ViewCompat.FocusRealDirection;
+import android.support.v4.view.ViewCompat.FocusRelativeDirection;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+
+/**
+ * Implements absolute and relative focus movement strategies. Adapted from
+ * android.view.FocusFinder to work with generic collections of bounded items.
+ */
+class FocusStrategy {
+ public static <L,T> T findNextFocusInRelativeDirection(@NonNull L focusables,
+ @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+ @Nullable T focused, @FocusRelativeDirection int direction, boolean isLayoutRtl,
+ boolean wrap) {
+ final int count = collectionAdapter.size(focusables);
+ final ArrayList<T> sortedFocusables = new ArrayList<>(count);
+ for (int i = 0; i < count; i++) {
+ sortedFocusables.add(collectionAdapter.get(focusables, i));
+ }
+
+ final SequentialComparator<T> comparator = new SequentialComparator<>(isLayoutRtl, adapter);
+ Collections.sort(sortedFocusables, comparator);
+
+ switch (direction) {
+ case View.FOCUS_FORWARD:
+ return getNextFocusable(focused, sortedFocusables, wrap);
+ case View.FOCUS_BACKWARD:
+ return getPreviousFocusable(focused, sortedFocusables, wrap);
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_FORWARD, FOCUS_BACKWARD}.");
+ }
+ }
+
+ private static <T> T getNextFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+ final int count = focusables.size();
+
+ // The position of the next focusable item, which is the first item if
+ // no item is currently focused.
+ final int position = (focused == null ? -1 : focusables.lastIndexOf(focused)) + 1;
+ if (position < count) {
+ return focusables.get(position);
+ } else if (wrap && count > 0) {
+ return focusables.get(0);
+ } else {
+ return null;
+ }
+ }
+
+ private static <T> T getPreviousFocusable(T focused, ArrayList<T> focusables, boolean wrap) {
+ final int count = focusables.size();
+
+ // The position of the previous focusable item, which is the last item
+ // if no item is currently focused.
+ final int position = (focused == null ? count : focusables.indexOf(focused)) - 1;
+ if (position >= 0) {
+ return focusables.get(position);
+ } else if (wrap && count > 0) {
+ return focusables.get(count - 1);
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Sorts views according to their visual layout and geometry for default tab order.
+ * This is used for sequential focus traversal.
+ */
+ private static class SequentialComparator<T> implements Comparator<T> {
+ private final Rect mTemp1 = new Rect();
+ private final Rect mTemp2 = new Rect();
+
+ private final boolean mIsLayoutRtl;
+ private final BoundsAdapter<T> mAdapter;
+
+ public SequentialComparator(boolean isLayoutRtl, BoundsAdapter<T> adapter) {
+ mIsLayoutRtl = isLayoutRtl;
+ mAdapter = adapter;
+ }
+
+ public int compare(T first, T second) {
+ final Rect firstRect = mTemp1;
+ final Rect secondRect = mTemp2;
+
+ mAdapter.obtainBounds(first, firstRect);
+ mAdapter.obtainBounds(second, secondRect);
+
+ if (firstRect.top < secondRect.top) {
+ return -1;
+ } else if (firstRect.top > secondRect.top) {
+ return 1;
+ } else if (firstRect.left < secondRect.left) {
+ return mIsLayoutRtl ? 1 : -1;
+ } else if (firstRect.left > secondRect.left) {
+ return mIsLayoutRtl ? -1 : 1;
+ } else if (firstRect.bottom < secondRect.bottom) {
+ return -1;
+ } else if (firstRect.bottom > secondRect.bottom) {
+ return 1;
+ } else if (firstRect.right < secondRect.right) {
+ return mIsLayoutRtl ? 1 : -1;
+ } else if (firstRect.right > secondRect.right) {
+ return mIsLayoutRtl ? -1 : 1;
+ } else {
+ // The view are distinct but completely coincident so we
+ // consider them equal for our purposes. Since the sort is
+ // stable, this means that the views will retain their
+ // layout order relative to one another.
+ return 0;
+ }
+ }
+ }
+
+ public static <L,T> T findNextFocusInAbsoluteDirection(@NonNull L focusables,
+ @NonNull CollectionAdapter<L,T> collectionAdapter, @NonNull BoundsAdapter<T> adapter,
+ @Nullable T focused, @NonNull Rect focusedRect, int direction) {
+ // Initialize the best candidate to something impossible so that
+ // the first plausible view will become the best choice.
+ final Rect bestCandidateRect = new Rect(focusedRect);
+
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ bestCandidateRect.offset(focusedRect.width() + 1, 0);
+ break;
+ case View.FOCUS_RIGHT:
+ bestCandidateRect.offset(-(focusedRect.width() + 1), 0);
+ break;
+ case View.FOCUS_UP:
+ bestCandidateRect.offset(0, focusedRect.height() + 1);
+ break;
+ case View.FOCUS_DOWN:
+ bestCandidateRect.offset(0, -(focusedRect.height() + 1));
+ break;
+ default:
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ T closest = null;
+
+ final int count = collectionAdapter.size(focusables);
+ final Rect focusableRect = new Rect();
+ for (int i = 0; i < count; i++) {
+ final T focusable = collectionAdapter.get(focusables, i);
+ if (focusable == focused) {
+ continue;
+ }
+
+ // get focus bounds of other view
+ adapter.obtainBounds(focusable, focusableRect);
+ if (isBetterCandidate(direction, focusedRect, focusableRect, bestCandidateRect)) {
+ bestCandidateRect.set(focusableRect);
+ closest = focusable;
+ }
+ }
+
+ return closest;
+ }
+
+ /**
+ * Is candidate a better candidate than currentBest for a focus search
+ * in a particular direction from a source rect? This is the core
+ * routine that determines the order of focus searching.
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param source the source from which we are searching
+ * @param candidate the candidate rectangle
+ * @param currentBest the current best rectangle
+ * @return {@code true} if the candidate rectangle is a better than the
+ * current best rectangle, {@code false} otherwise
+ */
+ private static boolean isBetterCandidate(
+ @FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect candidate, @NonNull Rect currentBest) {
+ // To be a better candidate, need to at least be a candidate in the
+ // first place. :)
+ if (!isCandidate(source, candidate, direction)) {
+ return false;
+ }
+
+ // We know that candidateRect is a candidate. If currentBest is not
+ // a candidate, candidateRect is better.
+ if (!isCandidate(source, currentBest, direction)) {
+ return true;
+ }
+
+ // If candidateRect is better by beam, it wins.
+ if (beamBeats(direction, source, candidate, currentBest)) {
+ return true;
+ }
+
+ // If currentBest is better, then candidateRect cant' be. :)
+ if (beamBeats(direction, source, currentBest, candidate)) {
+ return false;
+ }
+
+ // Otherwise, do fudge-tastic comparison of the major and minor
+ // axis.
+ final int candidateDist = getWeightedDistanceFor(
+ majorAxisDistance(direction, source, candidate),
+ minorAxisDistance(direction, source, candidate));
+ final int currentBestDist = getWeightedDistanceFor(
+ majorAxisDistance(direction, source, currentBest),
+ minorAxisDistance(direction, source, currentBest));
+ return candidateDist < currentBestDist;
+ }
+
+ /**
+ * One rectangle may be another candidate than another by virtue of
+ * being exclusively in the beam of the source rect.
+ *
+ * @return whether rect1 is a better candidate than rect2 by virtue of
+ * it being in source's beam
+ */
+ private static boolean beamBeats(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect rect1, @NonNull Rect rect2) {
+ final boolean rect1InSrcBeam = beamsOverlap(direction, source, rect1);
+ final boolean rect2InSrcBeam = beamsOverlap(direction, source, rect2);
+
+ // If rect1 isn't exclusively in the src beam, it doesn't win.
+ if (rect2InSrcBeam || !rect1InSrcBeam) {
+ return false;
+ }
+
+ // We know rect1 is in the beam, and rect2 is not.
+
+ // If rect1 is to the direction of, and rect2 is not, rect1 wins.
+ // For example, for direction left, if rect1 is to the left of the
+ // source and rect2 is below, then we always prefer the in beam
+ // rect1, since rect2 could be reached by going down.
+ if (!isToDirectionOf(direction, source, rect2)) {
+ return true;
+ }
+
+ // For horizontal directions, being exclusively in beam always
+ // wins.
+ if (direction == View.FOCUS_LEFT || direction == View.FOCUS_RIGHT) {
+ return true;
+ }
+
+ // For vertical directions, beams only beat up to a point: now, as
+ // long as rect2 isn't completely closer, rect1 wins, e.g. for
+ // direction down, completely closer means for rect2's top edge to
+ // be closer to the source's top edge than rect1's bottom edge.
+ return majorAxisDistance(direction, source, rect1)
+ < majorAxisDistanceToFarEdge(direction, source, rect2);
+ }
+
+ /**
+ * Fudge-factor opportunity: how to calculate distance given major and
+ * minor axis distances.
+ * <p/>
+ * Warning: this fudge factor is finely tuned, be sure to run all focus
+ * tests if you dare tweak it.
+ */
+ private static int getWeightedDistanceFor(int majorAxisDistance, int minorAxisDistance) {
+ return 13 * majorAxisDistance * majorAxisDistance
+ + minorAxisDistance * minorAxisDistance;
+ }
+
+ /**
+ * Is destRect a candidate for the next focus given the direction? This
+ * checks whether the dest is at least partially to the direction of
+ * (e.g. left of) from source.
+ * <p/>
+ * Includes an edge case for an empty rect,which is used in some cases
+ * when searching from a point on the screen.
+ */
+ private static boolean isCandidate(@NonNull Rect srcRect, @NonNull Rect destRect,
+ @FocusRealDirection int direction) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (srcRect.right > destRect.right || srcRect.left >= destRect.right)
+ && srcRect.left > destRect.left;
+ case View.FOCUS_RIGHT:
+ return (srcRect.left < destRect.left || srcRect.right <= destRect.left)
+ && srcRect.right < destRect.right;
+ case View.FOCUS_UP:
+ return (srcRect.bottom > destRect.bottom || srcRect.top >= destRect.bottom)
+ && srcRect.top > destRect.top;
+ case View.FOCUS_DOWN:
+ return (srcRect.top < destRect.top || srcRect.bottom <= destRect.top)
+ && srcRect.bottom < destRect.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+
+ /**
+ * Do the "beams" w.r.t the given direction's axis of rect1 and rect2 overlap?
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param rect1 the first rectangle
+ * @param rect2 the second rectangle
+ * @return whether the beams overlap
+ */
+ private static boolean beamsOverlap(@FocusRealDirection int direction,
+ @NonNull Rect rect1, @NonNull Rect rect2) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ return (rect2.bottom >= rect1.top) && (rect2.top <= rect1.bottom);
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ return (rect2.right >= rect1.left) && (rect2.left <= rect1.right);
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * e.g for left, is 'to left of'
+ */
+ private static boolean isToDirectionOf(@FocusRealDirection int direction,
+ @NonNull Rect src, @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return src.left >= dest.right;
+ case View.FOCUS_RIGHT:
+ return src.right <= dest.left;
+ case View.FOCUS_UP:
+ return src.top >= dest.bottom;
+ case View.FOCUS_DOWN:
+ return src.bottom <= dest.top;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return the distance from the edge furthest in the given direction
+ * of source to the edge nearest in the given direction of
+ * dest. If the dest is not in the direction from source,
+ * returns 0.
+ */
+ private static int majorAxisDistance(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ return Math.max(0, majorAxisDistanceRaw(direction, source, dest));
+ }
+
+ private static int majorAxisDistanceRaw(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.right;
+ case View.FOCUS_RIGHT:
+ return dest.left - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.bottom;
+ case View.FOCUS_DOWN:
+ return dest.top - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * @return the distance along the major axis w.r.t the direction from
+ * the edge of source to the far edge of dest. If the dest is
+ * not in the direction from source, returns 1 to break ties
+ * with {@link #majorAxisDistance}.
+ */
+ private static int majorAxisDistanceToFarEdge(@FocusRealDirection int direction,
+ @NonNull Rect source, @NonNull Rect dest) {
+ return Math.max(1, majorAxisDistanceToFarEdgeRaw(direction, source, dest));
+ }
+
+ private static int majorAxisDistanceToFarEdgeRaw(
+ @FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return source.left - dest.left;
+ case View.FOCUS_RIGHT:
+ return dest.right - source.right;
+ case View.FOCUS_UP:
+ return source.top - dest.top;
+ case View.FOCUS_DOWN:
+ return dest.bottom - source.bottom;
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Finds the distance on the minor axis w.r.t the direction to the
+ * nearest edge of the destination rectangle.
+ *
+ * @param direction the direction (up, down, left, right)
+ * @param source the source rect
+ * @param dest the destination rect
+ * @return the distance
+ */
+ private static int minorAxisDistance(@FocusRealDirection int direction, @NonNull Rect source,
+ @NonNull Rect dest) {
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ case View.FOCUS_RIGHT:
+ // the distance between the center verticals
+ return Math.abs(
+ ((source.top + source.height() / 2) -
+ ((dest.top + dest.height() / 2))));
+ case View.FOCUS_UP:
+ case View.FOCUS_DOWN:
+ // the distance between the center horizontals
+ return Math.abs(
+ ((source.left + source.width() / 2) -
+ ((dest.left + dest.width() / 2))));
+ }
+ throw new IllegalArgumentException("direction must be one of "
+ + "{FOCUS_UP, FOCUS_DOWN, FOCUS_LEFT, FOCUS_RIGHT}.");
+ }
+
+ /**
+ * Adapter used to obtain bounds from a generic data type.
+ */
+ public interface BoundsAdapter<T> {
+ void obtainBounds(T data, Rect outBounds);
+ }
+
+ /**
+ * Adapter used to obtain items from a generic collection type.
+ */
+ public interface CollectionAdapter<T, V> {
+ V get(T collection, int index);
+ int size(T collection);
+ }
+}
diff --git a/v4/java/android/support/v4/widget/ListPopupWindowCompat.java b/v4/java/android/support/v4/widget/ListPopupWindowCompat.java
index 44222bc..8d34312 100644
--- a/v4/java/android/support/v4/widget/ListPopupWindowCompat.java
+++ b/v4/java/android/support/v4/widget/ListPopupWindowCompat.java
@@ -23,7 +23,7 @@
* Helper for accessing features in ListPopupWindow introduced after API level 4
* in a backwards compatible fashion.
*/
-public class ListPopupWindowCompat {
+public final class ListPopupWindowCompat {
/**
* Interface for the full API.
*/
diff --git a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
index b23ca23..73d18ce 100644
--- a/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
+++ b/v4/java/android/support/v4/widget/ListViewAutoScrollHelper.java
@@ -34,19 +34,7 @@
@Override
public void scrollTargetBy(int deltaX, int deltaY) {
- final ListView target = mTarget;
- final int firstPosition = target.getFirstVisiblePosition();
- if (firstPosition == ListView.INVALID_POSITION) {
- return;
- }
-
- final View firstView = target.getChildAt(0);
- if (firstView == null) {
- return;
- }
-
- final int newTop = firstView.getTop() - deltaY;
- target.setSelectionFromTop(firstPosition, newTop);
+ ListViewCompat.scrollListBy(mTarget, deltaY);
}
@Override
diff --git a/v4/java/android/support/v4/widget/ListViewCompat.java b/v4/java/android/support/v4/widget/ListViewCompat.java
new file mode 100644
index 0000000..811315c
--- /dev/null
+++ b/v4/java/android/support/v4/widget/ListViewCompat.java
@@ -0,0 +1,44 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.widget.ListView;
+
+/**
+ * Helper for accessing features in {@link ListView} introduced after API level
+ * 4 in a backwards compatible fashion.
+ */
+public final class ListViewCompat {
+
+ /**
+ * Scrolls the list items within the view by a specified number of pixels.
+ *
+ * @param listView the list to scroll
+ * @param y the amount of pixels to scroll by vertically
+ */
+ public static void scrollListBy(@NonNull ListView listView, int y) {
+ if (Build.VERSION.SDK_INT >= 19) {
+ ListViewCompatKitKat.scrollListBy(listView, y);
+ } else {
+ ListViewCompatDonut.scrollListBy(listView, y);
+ }
+ }
+
+ private ListViewCompat() {}
+}
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
index 27e4307..6f5dbbc 100644
--- a/v4/java/android/support/v4/widget/NestedScrollView.java
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -46,7 +46,6 @@
import android.view.VelocityTracker;
import android.view.View;
import android.view.ViewConfiguration;
-import android.view.ViewDebug;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.accessibility.AccessibilityEvent;
@@ -286,7 +285,7 @@
@Override
public void onNestedPreScroll(View target, int dx, int dy, int[] consumed) {
- // Do nothing
+ dispatchNestedPreScroll(dx, dy, consumed, null);
}
@Override
@@ -300,8 +299,7 @@
@Override
public boolean onNestedPreFling(View target, float velocityX, float velocityY) {
- // Do nothing
- return false;
+ return dispatchNestedPreFling(velocityX, velocityY);
}
@Override
@@ -355,7 +353,7 @@
}
private void initScrollView() {
- mScroller = new ScrollerCompat(getContext(), null);
+ mScroller = ScrollerCompat.create(getContext(), null);
setFocusable(true);
setDescendantFocusability(FOCUS_AFTER_DESCENDANTS);
setWillNotDraw(false);
@@ -431,7 +429,7 @@
*
* @return True if the content fills the viewport, false otherwise.
*
- * @attr ref android.R.styleable#ScrollView_fillViewport
+ * @attr name android:fillViewport
*/
public boolean isFillViewport() {
return mFillViewport;
@@ -444,7 +442,7 @@
* @param fillViewport True to stretch the content's height to the viewport's
* boundaries, false otherwise.
*
- * @attr ref android.R.styleable#ScrollView_fillViewport
+ * @attr name android:fillViewport
*/
public void setFillViewport(boolean fillViewport) {
if (fillViewport != mFillViewport) {
@@ -683,10 +681,12 @@
initOrResetVelocityTracker();
mVelocityTracker.addMovement(ev);
/*
- * If being flinged and user touches the screen, initiate drag;
- * otherwise don't. mScroller.isFinished should be false when
- * being flinged.
+ * If being flinged and user touches the screen, initiate drag;
+ * otherwise don't. mScroller.isFinished should be false when
+ * being flinged. We need to call computeScrollOffset() first so that
+ * isFinished() is correct.
*/
+ mScroller.computeScrollOffset();
mIsBeingDragged = !mScroller.isFinished();
startNestedScroll(ViewCompat.SCROLL_AXIS_VERTICAL);
break;
@@ -1647,6 +1647,8 @@
@Override
public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
mIsLaidOut = false;
}
@@ -1820,6 +1822,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
mSavedState = ss;
diff --git a/v4/java/android/support/v4/widget/PopupMenuCompat.java b/v4/java/android/support/v4/widget/PopupMenuCompat.java
index db0e2c9..1202cbf 100644
--- a/v4/java/android/support/v4/widget/PopupMenuCompat.java
+++ b/v4/java/android/support/v4/widget/PopupMenuCompat.java
@@ -22,7 +22,7 @@
* Helper for accessing features in PopupMenu introduced after API level 4 in a
* backwards compatible fashion.
*/
-public class PopupMenuCompat {
+public final class PopupMenuCompat {
/**
* Interface for the full API.
*/
diff --git a/v4/java/android/support/v4/widget/PopupWindowCompat.java b/v4/java/android/support/v4/widget/PopupWindowCompat.java
index 7f4c828..68dbb02 100644
--- a/v4/java/android/support/v4/widget/PopupWindowCompat.java
+++ b/v4/java/android/support/v4/widget/PopupWindowCompat.java
@@ -16,6 +16,9 @@
package android.support.v4.widget;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.view.Gravity;
import android.view.View;
import android.view.WindowManager;
import android.widget.PopupWindow;
@@ -24,7 +27,7 @@
* Helper for accessing features in PopupWindow introduced after API level 4
* in a backwards compatible fashion.
*/
-public class PopupWindowCompat {
+public final class PopupWindowCompat {
/**
* Interface for the full API.
*/
@@ -43,6 +46,13 @@
@Override
public void showAsDropDown(PopupWindow popup, View anchor, int xoff, int yoff,
int gravity) {
+ final int hgrav = GravityCompat.getAbsoluteGravity(gravity,
+ ViewCompat.getLayoutDirection(anchor)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ // Flip the location to align the right sides of the popup and
+ // anchor instead of left.
+ xoff -= (popup.getWidth() - anchor.getWidth());
+ }
popup.showAsDropDown(anchor, xoff, yoff);
}
diff --git a/v4/java/android/support/v4/widget/ResourceCursorAdapter.java b/v4/java/android/support/v4/widget/ResourceCursorAdapter.java
index c854cf5..75573bc 100644
--- a/v4/java/android/support/v4/widget/ResourceCursorAdapter.java
+++ b/v4/java/android/support/v4/widget/ResourceCursorAdapter.java
@@ -63,6 +63,11 @@
* When using this constructor, {@link #FLAG_REGISTER_CONTENT_OBSERVER}
* will always be set.
*
+ * @deprecated This option is discouraged, as it results in Cursor queries
+ * being performed on the application's UI thread and thus can cause poor
+ * responsiveness or even Application Not Responding errors. As an alternative,
+ * use {@link android.app.LoaderManager} with a {@link android.content.CursorLoader}.
+ *
* @param context The context where the ListView associated with this adapter is running
* @param layout resource identifier of a layout file that defines the views
* for this list item. Unless you override them later, this will
@@ -72,6 +77,7 @@
* cursor whenever it changes so the most recent
* data is always displayed. Using true here is discouraged.
*/
+ @Deprecated
public ResourceCursorAdapter(Context context, int layout, Cursor c, boolean autoRequery) {
super(context, c, autoRequery);
mLayout = mDropDownLayout = layout;
diff --git a/v4/java/android/support/v4/widget/ScrollerCompat.java b/v4/java/android/support/v4/widget/ScrollerCompat.java
index 3115a41..693346c 100644
--- a/v4/java/android/support/v4/widget/ScrollerCompat.java
+++ b/v4/java/android/support/v4/widget/ScrollerCompat.java
@@ -29,7 +29,7 @@
* current device's preferred scroll physics and fling behavior. It offers a subset of
* the APIs from Scroller or OverScroller.</p>
*/
-public class ScrollerCompat {
+public final class ScrollerCompat {
private static final String TAG = "ScrollerCompat";
Object mScroller;
@@ -262,12 +262,7 @@
}
public static ScrollerCompat create(Context context, Interpolator interpolator) {
- return new ScrollerCompat(context, interpolator);
- }
-
- ScrollerCompat(Context context, Interpolator interpolator) {
- this(Build.VERSION.SDK_INT, context, interpolator);
-
+ return new ScrollerCompat(Build.VERSION.SDK_INT, context, interpolator);
}
/**
diff --git a/v4/java/android/support/v4/widget/SearchViewCompat.java b/v4/java/android/support/v4/widget/SearchViewCompat.java
index c51ecf2..297ee84 100644
--- a/v4/java/android/support/v4/widget/SearchViewCompat.java
+++ b/v4/java/android/support/v4/widget/SearchViewCompat.java
@@ -27,7 +27,7 @@
* Helper for accessing features in {@link android.widget.SearchView}
* introduced after API level 4 in a backwards compatible fashion.
*/
-public class SearchViewCompat {
+public final class SearchViewCompat {
interface SearchViewCompatImpl {
View newSearchView(Context context);
diff --git a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
index ca71e9e..12a85bf 100644
--- a/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
+++ b/v4/java/android/support/v4/widget/SimpleCursorAdapter.java
@@ -63,7 +63,7 @@
super(context, layout, c);
mTo = to;
mOriginalFrom = from;
- findColumns(from);
+ findColumns(c, from);
}
/**
@@ -89,7 +89,7 @@
super(context, layout, c, flags);
mTo = to;
mOriginalFrom = from;
- findColumns(from);
+ findColumns(c, from);
}
/**
@@ -109,10 +109,9 @@
*
* @throws IllegalStateException if binding cannot occur
*
- * @see android.widget.CursorAdapter#bindView(android.view.View,
- * android.content.Context, android.database.Cursor)
+ * @see android.widget.CursorAdapter#bindView(View, Context, Cursor)
* @see #getViewBinder()
- * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+ * @see #setViewBinder(ViewBinder)
* @see #setViewImage(ImageView, String)
* @see #setViewText(TextView, String)
*/
@@ -156,7 +155,7 @@
* @return a ViewBinder or null if the binder does not exist
*
* @see #bindView(android.view.View, android.content.Context, android.database.Cursor)
- * @see #setViewBinder(android.widget.SimpleCursorAdapter.ViewBinder)
+ * @see #setViewBinder(ViewBinder)
*/
public ViewBinder getViewBinder() {
return mViewBinder;
@@ -201,7 +200,7 @@
/**
* Called by bindView() to set the text for a TextView but only if
* there is no existing ViewBinder or if the existing ViewBinder cannot
- * handle binding to an TextView.
+ * handle binding to a TextView.
*
* Intended to be overridden by Adapters that need to filter strings
* retrieved from the database.
@@ -221,7 +220,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #setStringConversionColumn(int)
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public int getStringConversionColumn() {
@@ -239,7 +238,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
* @see #getStringConversionColumn()
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getCursorToStringConverter()
*/
public void setStringConversionColumn(int stringConversionColumn) {
@@ -253,7 +252,7 @@
* @return null if the converter does not exist or an instance of
* {@link android.widget.SimpleCursorAdapter.CursorToStringConverter}
*
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -269,7 +268,7 @@
* @param cursorToStringConverter the Cursor to String converter, or
* null to remove the converter
*
- * @see #setCursorToStringConverter(android.widget.SimpleCursorAdapter.CursorToStringConverter)
+ * @see #setCursorToStringConverter(CursorToStringConverter)
* @see #getStringConversionColumn()
* @see #setStringConversionColumn(int)
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
@@ -301,20 +300,21 @@
}
/**
- * Create a map from an array of strings to an array of column-id integers in mCursor.
- * If mCursor is null, the array will be discarded.
- *
+ * Create a map from an array of strings to an array of column-id integers in cursor c.
+ * If c is null, the array will be discarded.
+ *
+ * @param c the cursor to find the columns from
* @param from the Strings naming the columns of interest
*/
- private void findColumns(String[] from) {
- if (mCursor != null) {
+ private void findColumns(Cursor c, String[] from) {
+ if (c != null) {
int i;
int count = from.length;
if (mFrom == null || mFrom.length != count) {
mFrom = new int[count];
}
for (i = 0; i < count; i++) {
- mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]);
+ mFrom[i] = c.getColumnIndexOrThrow(from[i]);
}
} else {
mFrom = null;
@@ -323,15 +323,16 @@
@Override
public Cursor swapCursor(Cursor c) {
- Cursor res = super.swapCursor(c);
- // rescan columns in case cursor layout is different
- findColumns(mOriginalFrom);
- return res;
+ // super.swapCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ findColumns(c, mOriginalFrom);
+ return super.swapCursor(c);
}
-
+
/**
* Change the cursor and change the column-to-view mappings at the same time.
- *
+ *
* @param c The database cursor. Can be null if the cursor is not available yet.
* @param from A list of column names representing the data to bind to the UI. Can be null
* if the cursor is not available yet.
@@ -343,8 +344,11 @@
public void changeCursorAndColumns(Cursor c, String[] from, int[] to) {
mOriginalFrom = from;
mTo = to;
- super.changeCursor(c);
- findColumns(mOriginalFrom);
+ // super.changeCursor() will notify observers before we have
+ // a valid mapping, make sure we have a mapping before this
+ // happens
+ findColumns(c, mOriginalFrom);
+ super.changeCursor(c);
}
/**
@@ -356,11 +360,11 @@
* change the way binding occurs for views supported by
* SimpleCursorAdapter.
*
- * @see SimpleCursorAdapter#bindView(android.view.View, android.content.Context, android.database.Cursor)
+ * @see SimpleCursorAdapter#bindView(View, Context, Cursor)
* @see SimpleCursorAdapter#setViewImage(ImageView, String)
* @see SimpleCursorAdapter#setViewText(TextView, String)
*/
- public static interface ViewBinder {
+ public interface ViewBinder {
/**
* Binds the Cursor column defined by the specified index to the specified view.
*
@@ -383,7 +387,7 @@
*
* @see android.widget.CursorAdapter#convertToString(android.database.Cursor)
*/
- public static interface CursorToStringConverter {
+ public interface CursorToStringConverter {
/**
* Returns a CharSequence representing the specified Cursor.
*
diff --git a/v4/java/android/support/v4/widget/SlidingPaneLayout.java b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
index aff4546..7881691 100644
--- a/v4/java/android/support/v4/widget/SlidingPaneLayout.java
+++ b/v4/java/android/support/v4/widget/SlidingPaneLayout.java
@@ -31,6 +31,9 @@
import android.os.Parcelable;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
@@ -1125,6 +1128,9 @@
* during opening/closing.
*
* @param resId Resource ID of a drawable to use
+ * @deprecated Renamed to {@link #setShadowResourceLeft(int)} to support LTR (left to
+ * right language) and {@link #setShadowResourceRight(int)} to support RTL (right to left
+ * language) during opening/closing.
*/
@Deprecated
public void setShadowResource(@DrawableRes int resId) {
@@ -1287,6 +1293,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
@@ -1448,15 +1459,15 @@
}
- static class SavedState extends BaseSavedState {
+ static class SavedState extends AbsSavedState {
boolean isOpen;
SavedState(Parcelable superState) {
super(superState);
}
- private SavedState(Parcel in) {
- super(in);
+ private SavedState(Parcel in, ClassLoader loader) {
+ super(in, loader);
isOpen = in.readInt() != 0;
}
@@ -1466,16 +1477,18 @@
out.writeInt(isOpen ? 1 : 0);
}
- public static final Parcelable.Creator<SavedState> CREATOR =
- new Parcelable.Creator<SavedState>() {
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
}
interface SlidingPanelLayoutImpl {
diff --git a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
index 06db6f2..2059557 100644
--- a/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
+++ b/v4/java/android/support/v4/widget/SwipeRefreshLayout.java
@@ -21,6 +21,7 @@
import android.content.res.TypedArray;
import android.support.annotation.ColorInt;
import android.support.annotation.ColorRes;
+import android.support.annotation.VisibleForTesting;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
import android.support.v4.view.NestedScrollingChildHelper;
@@ -68,14 +69,16 @@
// Maps to ProgressBar default style
public static final int DEFAULT = MaterialProgressDrawable.DEFAULT;
+ @VisibleForTesting
+ static final int CIRCLE_DIAMETER = 40;
+ @VisibleForTesting
+ static final int CIRCLE_DIAMETER_LARGE = 56;
+
private static final String LOG_TAG = SwipeRefreshLayout.class.getSimpleName();
private static final int MAX_ALPHA = 255;
private static final int STARTING_PROGRESS_ALPHA = (int) (.3f * MAX_ALPHA);
- private static final int CIRCLE_DIAMETER = 40;
- private static final int CIRCLE_DIAMETER_LARGE = 56;
-
private static final float DECELERATE_INTERPOLATION_FACTOR = 2f;
private static final int INVALID_POINTER = -1;
private static final float DRAG_RATE = .5f;
@@ -538,8 +541,7 @@
*
* @param colors
*/
- @ColorInt
- public void setColorSchemeColors(int... colors) {
+ public void setColorSchemeColors(@ColorInt int... colors) {
ensureTarget();
mProgress.setColorSchemeColors(colors);
}
@@ -744,7 +746,7 @@
@Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
- return isEnabled() && canChildScrollUp() && !mReturningToStart && !mRefreshing
+ return isEnabled() && !mReturningToStart && !mRefreshing
&& (nestedScrollAxes & ViewCompat.SCROLL_AXIS_VERTICAL) != 0;
}
@@ -769,7 +771,6 @@
} else {
mTotalUnconsumed -= dy;
consumed[1] = dy;
-
}
moveSpinner(mTotalUnconsumed);
}
@@ -823,7 +824,7 @@
// 'offset in window 'functionality to see if we have been moved from the event.
// This is a decent indication of whether we should take over the event stream or not.
final int dy = dyUnconsumed + mParentOffsetInWindow[1];
- if (dy < 0) {
+ if (dy < 0 && !canChildScrollUp()) {
mTotalUnconsumed += Math.abs(dy);
moveSpinner(mTotalUnconsumed);
}
@@ -918,24 +919,26 @@
ViewCompat.setScaleX(mCircleView, 1f);
ViewCompat.setScaleY(mCircleView, 1f);
}
+
+ if (mScale) {
+ setAnimationProgress(Math.min(1f, overscrollTop / mTotalDragDistance));
+ }
if (overscrollTop < mTotalDragDistance) {
- if (mScale) {
- setAnimationProgress(overscrollTop / mTotalDragDistance);
- }
if (mProgress.getAlpha() > STARTING_PROGRESS_ALPHA
&& !isAnimationRunning(mAlphaStartAnimation)) {
// Animate the alpha
startProgressAlphaStartAnimation();
}
- float strokeStart = adjustedPercent * .8f;
- mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
- mProgress.setArrowScale(Math.min(1f, adjustedPercent));
} else {
if (mProgress.getAlpha() < MAX_ALPHA && !isAnimationRunning(mAlphaMaxAnimation)) {
// Animate the alpha
startProgressAlphaMaxAnimation();
}
}
+ float strokeStart = adjustedPercent * .8f;
+ mProgress.setStartEndTrim(0f, Math.min(MAX_PROGRESS_ANGLE, strokeStart));
+ mProgress.setArrowScale(Math.min(1f, adjustedPercent));
+
float rotation = (-0.25f + .4f * adjustedPercent + tensionPercent * 2) * .5f;
mProgress.setProgressRotation(rotation);
setTargetOffsetTopAndBottom(targetY - mCurrentTargetOffsetTop, true /* requires update */);
diff --git a/v4/java/android/support/v4/widget/TextViewCompat.java b/v4/java/android/support/v4/widget/TextViewCompat.java
index 621ee66..510dde1 100644
--- a/v4/java/android/support/v4/widget/TextViewCompat.java
+++ b/v4/java/android/support/v4/widget/TextViewCompat.java
@@ -18,16 +18,18 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.support.annotation.DrawableRes;
import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
import android.widget.TextView;
/**
* Helper for accessing features in {@link TextView} introduced after API level
* 4 in a backwards compatible fashion.
*/
-public class TextViewCompat {
+public final class TextViewCompat {
// Hide constructor
private TextViewCompat() {}
@@ -40,10 +42,11 @@
@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
@Nullable Drawable bottom);
void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom);
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom);
int getMaxLines(TextView textView);
int getMinLines(TextView textView);
- void setTextAppearance(@NonNull TextView textView, @IdRes int resId);
+ void setTextAppearance(@NonNull TextView textView, @StyleRes int resId);
}
static class BaseTextViewCompatImpl implements TextViewCompatImpl {
@@ -63,7 +66,8 @@
@Override
public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom) {
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom) {
textView.setCompoundDrawablesWithIntrinsicBounds(start, top, end, bottom);
}
@@ -78,7 +82,7 @@
}
@Override
- public void setTextAppearance(TextView textView, int resId) {
+ public void setTextAppearance(TextView textView, @StyleRes int resId) {
TextViewCompatDonut.setTextAppearance(textView, resId);
}
}
@@ -113,7 +117,8 @@
@Override
public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom) {
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom) {
TextViewCompatJbMr1.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
start, top, end, bottom);
}
@@ -138,7 +143,8 @@
@Override
public void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom) {
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom) {
TextViewCompatJbMr2.setCompoundDrawablesRelativeWithIntrinsicBounds(textView,
start, top, end, bottom);
}
@@ -146,7 +152,7 @@
static class Api23TextViewCompatImpl extends JbMr2TextViewCompatImpl {
@Override
- public void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+ public void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
TextViewCompatApi23.setTextAppearance(textView, resId);
}
}
@@ -178,10 +184,10 @@
* {@link TextView#setCompoundDrawables} or related methods.
*
* @param textView The TextView against which to invoke the method.
- * @attr ref android.R.styleable#TextView_drawableStart
- * @attr ref android.R.styleable#TextView_drawableTop
- * @attr ref android.R.styleable#TextView_drawableEnd
- * @attr ref android.R.styleable#TextView_drawableBottom
+ * @attr name android:drawableStart
+ * @attr name android:drawableTop
+ * @attr name android:drawableEnd
+ * @attr name android:drawableBottom
*/
public static void setCompoundDrawablesRelative(@NonNull TextView textView,
@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
@@ -198,10 +204,10 @@
* {@link TextView#setCompoundDrawables} or related methods.
*
* @param textView The TextView against which to invoke the method.
- * @attr ref android.R.styleable#TextView_drawableStart
- * @attr ref android.R.styleable#TextView_drawableTop
- * @attr ref android.R.styleable#TextView_drawableEnd
- * @attr ref android.R.styleable#TextView_drawableBottom
+ * @attr name android:drawableStart
+ * @attr name android:drawableTop
+ * @attr name android:drawableEnd
+ * @attr name android:drawableBottom
*/
public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
@Nullable Drawable start, @Nullable Drawable top, @Nullable Drawable end,
@@ -222,13 +228,14 @@
* @param top Resource identifier of the top Drawable.
* @param end Resource identifier of the end Drawable.
* @param bottom Resource identifier of the bottom Drawable.
- * @attr ref android.R.styleable#TextView_drawableStart
- * @attr ref android.R.styleable#TextView_drawableTop
- * @attr ref android.R.styleable#TextView_drawableEnd
- * @attr ref android.R.styleable#TextView_drawableBottom
+ * @attr name android:drawableStart
+ * @attr name android:drawableTop
+ * @attr name android:drawableEnd
+ * @attr name android:drawableBottom
*/
public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom) {
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom) {
IMPL.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom);
}
@@ -259,7 +266,7 @@
* @param textView The TextView against which to invoke the method.
* @param resId The resource identifier of the style to apply.
*/
- public static void setTextAppearance(@NonNull TextView textView, @IdRes int resId) {
+ public static void setTextAppearance(@NonNull TextView textView, @StyleRes int resId) {
IMPL.setTextAppearance(textView, resId);
}
}
diff --git a/v4/java/android/support/v4/widget/ViewDragHelper.java b/v4/java/android/support/v4/widget/ViewDragHelper.java
index 3f4e571..d500e42 100644
--- a/v4/java/android/support/v4/widget/ViewDragHelper.java
+++ b/v4/java/android/support/v4/widget/ViewDragHelper.java
@@ -21,6 +21,7 @@
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
+import android.util.Log;
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
@@ -727,10 +728,10 @@
final int dy = y - mCapturedView.getTop();
if (dx != 0) {
- mCapturedView.offsetLeftAndRight(dx);
+ ViewCompat.offsetLeftAndRight(mCapturedView, dx);
}
if (dy != 0) {
- mCapturedView.offsetTopAndBottom(dy);
+ ViewCompat.offsetTopAndBottom(mCapturedView, dy);
}
if (dx != 0 || dy != 0) {
@@ -1009,6 +1010,10 @@
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
+
+ // If pointer is invalid then skip the ACTION_MOVE.
+ if (!isValidPointerForActionMove(pointerId)) continue;
+
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
@@ -1141,6 +1146,9 @@
case MotionEvent.ACTION_MOVE: {
if (mDragState == STATE_DRAGGING) {
+ // If pointer is invalid then skip the ACTION_MOVE.
+ if (!isValidPointerForActionMove(mActivePointerId)) break;
+
final int index = MotionEventCompat.findPointerIndex(ev, mActivePointerId);
final float x = MotionEventCompat.getX(ev, index);
final float y = MotionEventCompat.getY(ev, index);
@@ -1155,6 +1163,10 @@
final int pointerCount = MotionEventCompat.getPointerCount(ev);
for (int i = 0; i < pointerCount; i++) {
final int pointerId = MotionEventCompat.getPointerId(ev, i);
+
+ // If pointer is invalid then skip the ACTION_MOVE.
+ if (!isValidPointerForActionMove(pointerId)) continue;
+
final float x = MotionEventCompat.getX(ev, i);
final float y = MotionEventCompat.getY(ev, i);
final float dx = x - mInitialMotionX[pointerId];
@@ -1402,11 +1414,11 @@
final int oldTop = mCapturedView.getTop();
if (dx != 0) {
clampedX = mCallback.clampViewPositionHorizontal(mCapturedView, left, dx);
- mCapturedView.offsetLeftAndRight(clampedX - oldLeft);
+ ViewCompat.offsetLeftAndRight(mCapturedView, clampedX - oldLeft);
}
if (dy != 0) {
clampedY = mCallback.clampViewPositionVertical(mCapturedView, top, dy);
- mCapturedView.offsetTopAndBottom(clampedY - oldTop);
+ ViewCompat.offsetTopAndBottom(mCapturedView, clampedY - oldTop);
}
if (dx != 0 || dy != 0) {
@@ -1479,4 +1491,14 @@
return result;
}
+
+ private boolean isValidPointerForActionMove(int pointerId) {
+ if (!isPointerDown(pointerId)) {
+ Log.e(TAG, "Ignoring pointerId=" + pointerId + " because ACTION_DOWN was not received "
+ + "for this pointer before ACTION_MOVE. It likely happened because "
+ + " ViewDragHelper did not receive all the events in the event stream.");
+ return false;
+ }
+ return true;
+ }
}
\ No newline at end of file
diff --git a/v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java
new file mode 100644
index 0000000..b16db18
--- /dev/null
+++ b/v4/jellybean-mr1/android/support/v4/content/res/ConfigurationHelperJellybeanMr1.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.content.res;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.support.annotation.NonNull;
+
+class ConfigurationHelperJellybeanMr1 {
+ static int getDensityDpi(@NonNull Resources resources) {
+ return resources.getConfiguration().densityDpi;
+ }
+}
diff --git a/v4/jellybean-mr1/android/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.java
index 015723f..09891c9 100644
--- a/v4/jellybean-mr1/android/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.java
+++ b/v4/jellybean-mr1/android/support/v4/graphics/drawable/DrawableCompatJellybeanMr1.java
@@ -37,7 +37,7 @@
private static Method sGetLayoutDirectionMethod;
private static boolean sGetLayoutDirectionMethodFetched;
- public static void setLayoutDirection(Drawable drawable, int layoutDirection) {
+ public static boolean setLayoutDirection(Drawable drawable, int layoutDirection) {
if (!sSetLayoutDirectionMethodFetched) {
try {
sSetLayoutDirectionMethod =
@@ -52,11 +52,13 @@
if (sSetLayoutDirectionMethod != null) {
try {
sSetLayoutDirectionMethod.invoke(drawable, layoutDirection);
+ return true;
} catch (Exception e) {
Log.i(TAG, "Failed to invoke setLayoutDirection(int) via reflection", e);
sSetLayoutDirectionMethod = null;
}
}
+ return false;
}
public static int getLayoutDirection(Drawable drawable) {
diff --git a/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java b/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
deleted file mode 100644
index 6e5cfd5..0000000
--- a/v4/jellybean-mr1/android/support/v4/media/routing/MediaRouterJellybeanMr1.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-import android.content.Context;
-import android.hardware.display.DisplayManager;
-import android.os.Build;
-import android.os.Handler;
-import android.util.Log;
-import android.view.Display;
-
-import java.lang.reflect.Field;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-class MediaRouterJellybeanMr1 extends MediaRouterJellybean {
- private static final String TAG = "MediaRouterJellybeanMr1";
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static final class RouteInfo {
- public static boolean isEnabled(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).isEnabled();
- }
-
- public static Display getPresentationDisplay(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
- }
- }
-
- public static interface Callback extends MediaRouterJellybean.Callback {
- public void onRoutePresentationDisplayChanged(Object routeObj);
- }
-
- /**
- * Workaround the fact that the version of MediaRouter.addCallback() that accepts a
- * flag to perform an active scan does not exist in JB MR1 so we need to force
- * wifi display scans directly through the DisplayManager.
- * Do not use on JB MR2 and above.
- */
- public static final class ActiveScanWorkaround implements Runnable {
- // Time between wifi display scans when actively scanning in milliseconds.
- private static final int WIFI_DISPLAY_SCAN_INTERVAL = 15000;
-
- private final DisplayManager mDisplayManager;
- private final Handler mHandler;
- private Method mScanWifiDisplaysMethod;
-
- private boolean mActivelyScanningWifiDisplays;
-
- public ActiveScanWorkaround(Context context, Handler handler) {
- if (Build.VERSION.SDK_INT != 17) {
- throw new UnsupportedOperationException();
- }
-
- mDisplayManager = (DisplayManager) context.getSystemService(Context.DISPLAY_SERVICE);
- mHandler = handler;
- try {
- mScanWifiDisplaysMethod = DisplayManager.class.getMethod("scanWifiDisplays");
- } catch (NoSuchMethodException ex) {
- }
- }
-
- public void setActiveScanRouteTypes(int routeTypes) {
- // On JB MR1, there is no API to scan wifi display routes.
- // Instead we must make a direct call into the DisplayManager to scan
- // wifi displays on this version but only when live video routes are requested.
- // See also the JellybeanMr2Impl implementation of this method.
- // This was fixed in JB MR2 by adding a new overload of addCallback() to
- // enable active scanning on request.
- if ((routeTypes & MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO) != 0) {
- if (!mActivelyScanningWifiDisplays) {
- if (mScanWifiDisplaysMethod != null) {
- mActivelyScanningWifiDisplays = true;
- mHandler.post(this);
- } else {
- Log.w(TAG, "Cannot scan for wifi displays because the "
- + "DisplayManager.scanWifiDisplays() method is "
- + "not available on this device.");
- }
- }
- } else {
- if (mActivelyScanningWifiDisplays) {
- mActivelyScanningWifiDisplays = false;
- mHandler.removeCallbacks(this);
- }
- }
- }
-
- @Override
- public void run() {
- if (mActivelyScanningWifiDisplays) {
- try {
- mScanWifiDisplaysMethod.invoke(mDisplayManager);
- } catch (IllegalAccessException ex) {
- Log.w(TAG, "Cannot scan for wifi displays.", ex);
- } catch (InvocationTargetException ex) {
- Log.w(TAG, "Cannot scan for wifi displays.", ex);
- }
- mHandler.postDelayed(this, WIFI_DISPLAY_SCAN_INTERVAL);
- }
- }
- }
-
- /**
- * Workaround the fact that the isConnecting() method does not exist in JB MR1.
- * Do not use on JB MR2 and above.
- */
- public static final class IsConnectingWorkaround {
- private Method mGetStatusCodeMethod;
- private int mStatusConnecting;
-
- public IsConnectingWorkaround() {
- if (Build.VERSION.SDK_INT != 17) {
- throw new UnsupportedOperationException();
- }
-
- try {
- Field statusConnectingField =
- android.media.MediaRouter.RouteInfo.class.getField("STATUS_CONNECTING");
- mStatusConnecting = statusConnectingField.getInt(null);
- mGetStatusCodeMethod =
- android.media.MediaRouter.RouteInfo.class.getMethod("getStatusCode");
- } catch (NoSuchFieldException ex) {
- } catch (NoSuchMethodException ex) {
- } catch (IllegalAccessException ex) {
- }
- }
-
- public boolean isConnecting(Object routeObj) {
- android.media.MediaRouter.RouteInfo route =
- (android.media.MediaRouter.RouteInfo)routeObj;
-
- if (mGetStatusCodeMethod != null) {
- try {
- int statusCode = (Integer)mGetStatusCodeMethod.invoke(route);
- return statusCode == mStatusConnecting;
- } catch (IllegalAccessException ex) {
- } catch (InvocationTargetException ex) {
- }
- }
-
- // Assume not connecting.
- return false;
- }
- }
-
- static class CallbackProxy<T extends Callback>
- extends MediaRouterJellybean.CallbackProxy<T> {
- public CallbackProxy(T callback) {
- super(callback);
- }
-
- @Override
- public void onRoutePresentationDisplayChanged(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route) {
- mCallback.onRoutePresentationDisplayChanged(route);
- }
- }
-}
diff --git a/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java b/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
deleted file mode 100644
index 92a1607..0000000
--- a/v4/jellybean-mr2/android/support/v4/media/routing/MediaRouterJellybeanMr2.java
+++ /dev/null
@@ -1,44 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-class MediaRouterJellybeanMr2 extends MediaRouterJellybeanMr1 {
- public static Object getDefaultRoute(Object routerObj) {
- return ((android.media.MediaRouter)routerObj).getDefaultRoute();
- }
-
- public static void addCallback(Object routerObj, int types, Object callbackObj, int flags) {
- ((android.media.MediaRouter)routerObj).addCallback(types,
- (android.media.MediaRouter.Callback)callbackObj, flags);
- }
-
- public static final class RouteInfo {
- public static CharSequence getDescription(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getDescription();
- }
-
- public static boolean isConnecting(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).isConnecting();
- }
- }
-
- public static final class UserRouteInfo {
- public static void setDescription(Object routeObj, CharSequence description) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setDescription(description);
- }
- }
-}
diff --git a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
index 8ceb38c..693eb39 100644
--- a/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
+++ b/v4/jellybean-mr2/android/support/v4/media/session/MediaSessionCompatApi18.java
@@ -31,9 +31,8 @@
private static boolean sIsMbrPendingIntentSupported = true;
- public static Object createPlaybackPositionUpdateListener(
- MediaSessionCompatApi14.Callback callback) {
- return new OnPlaybackPositionUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+ public static Object createPlaybackPositionUpdateListener(Callback callback) {
+ return new OnPlaybackPositionUpdateListener<Callback>(callback);
}
public static void registerMediaButtonEventReceiver(Context context, PendingIntent pi,
@@ -105,7 +104,7 @@
return transportControlFlags;
}
- static class OnPlaybackPositionUpdateListener<T extends MediaSessionCompatApi14.Callback>
+ static class OnPlaybackPositionUpdateListener<T extends Callback>
implements RemoteControlClient.OnPlaybackPositionUpdateListener {
protected final T mCallback;
@@ -118,4 +117,8 @@
mCallback.onSeekTo(newPositionMs);
}
}
+
+ interface Callback {
+ public void onSeekTo(long pos);
+ }
}
diff --git a/v4/jellybean-mr2/android/support/v4/view/ViewCompatJellybeanMr2.java b/v4/jellybean-mr2/android/support/v4/view/ViewCompatJellybeanMr2.java
index 7000927..b8fcd9d 100644
--- a/v4/jellybean-mr2/android/support/v4/view/ViewCompatJellybeanMr2.java
+++ b/v4/jellybean-mr2/android/support/v4/view/ViewCompatJellybeanMr2.java
@@ -32,4 +32,7 @@
view.setClipBounds(clipBounds);
}
+ public static boolean isInLayout(View view) {
+ return view.isInLayout();
+ }
}
diff --git a/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
index ec9fe61..73f9666 100644
--- a/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
+++ b/v4/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java
@@ -17,6 +17,7 @@
package android.support.v4.widget;
import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.widget.TextView;
@@ -36,7 +37,8 @@
}
public static void setCompoundDrawablesRelativeWithIntrinsicBounds(@NonNull TextView textView,
- int start, int top, int end, int bottom) {
+ @DrawableRes int start, @DrawableRes int top, @DrawableRes int end,
+ @DrawableRes int bottom) {
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom);
}
diff --git a/v4/jellybean/android/support/v4/app/ActivityCompatJB.java b/v4/jellybean/android/support/v4/app/ActivityCompatJB.java
index 0c7318f..77fe7fb 100644
--- a/v4/jellybean/android/support/v4/app/ActivityCompatJB.java
+++ b/v4/jellybean/android/support/v4/app/ActivityCompatJB.java
@@ -19,6 +19,7 @@
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentSender;
import android.os.Bundle;
class ActivityCompatJB {
@@ -30,6 +31,13 @@
activity.startActivityForResult(intent, requestCode, options);
}
+ public static void startIntentSenderForResult(Activity activity, IntentSender intent,
+ int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ activity.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask,
+ flagsValues, extraFlags, options);
+ }
+
public static void finishAffinity(Activity activity) {
activity.finishAffinity();
}
diff --git a/v4/jellybean/android/support/v4/app/BaseFragmentActivityJB.java b/v4/jellybean/android/support/v4/app/BaseFragmentActivityJB.java
new file mode 100644
index 0000000..147251c
--- /dev/null
+++ b/v4/jellybean/android/support/v4/app/BaseFragmentActivityJB.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v4.app;
+
+import android.content.Intent;
+import android.content.IntentSender;
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+
+/**
+ * Base class for {@code FragmentActivity} to be able to use v16 APIs.
+ *
+ * @hide
+ */
+abstract class BaseFragmentActivityJB extends BaseFragmentActivityHoneycomb {
+
+ // We need to keep track of whether startActivityForResult originated from a Fragment, so we
+ // can conditionally check whether the requestCode collides with our reserved ID space for the
+ // request index (see above). Unfortunately we can't just call
+ // super.startActivityForResult(...) to bypass the check when the call didn't come from a
+ // fragment, since we need to use the ActivityCompat version for backward compatibility.
+ boolean mStartedActivityFromFragment;
+
+ @Override
+ public void startActivityForResult(Intent intent, int requestCode,
+ @Nullable Bundle options) {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedActivityFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startActivityForResult(intent, requestCode, options);
+ }
+
+ @Override
+ public void startIntentSenderForResult(IntentSender intent, int requestCode,
+ @Nullable Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags,
+ Bundle options) throws IntentSender.SendIntentException {
+ // If this was started from a Fragment we've already checked the upper 16 bits were not in
+ // use, and then repurposed them for the Fragment's index.
+ if (!mStartedIntentSenderFromFragment) {
+ if (requestCode != -1) {
+ checkForValidRequestCode(requestCode);
+ }
+ }
+ super.startIntentSenderForResult(intent, requestCode, fillInIntent, flagsMask, flagsValues,
+ extraFlags, options);
+ }
+}
diff --git a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
index 6f54c4b..261d8c4 100644
--- a/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
+++ b/v4/jellybean/android/support/v4/app/NotificationCompatJellybean.java
@@ -41,6 +41,7 @@
static final String EXTRA_GROUP_SUMMARY = "android.support.isGroupSummary";
static final String EXTRA_SORT_KEY = "android.support.sortKey";
static final String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
+ static final String EXTRA_ALLOW_GENERATED_REPLIES = "android.support.allowGeneratedReplies";
// Bundle keys for storing action fields in a bundle
private static final String KEY_ICON = "icon";
@@ -48,6 +49,7 @@
private static final String KEY_ACTION_INTENT = "actionIntent";
private static final String KEY_EXTRAS = "extras";
private static final String KEY_REMOTE_INPUTS = "remoteInputs";
+ private static final String KEY_ALLOW_GENERATED_REPLIES = "allowGeneratedReplies";
private static final Object sExtrasLock = new Object();
private static Field sExtrasField;
@@ -66,6 +68,8 @@
private Notification.Builder b;
private final Bundle mExtras;
private List<Bundle> mActionExtrasList = new ArrayList<Bundle>();
+ private RemoteViews mContentView;
+ private RemoteViews mBigContentView;
public Builder(Context context, Notification n,
CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
@@ -73,7 +77,8 @@
PendingIntent contentIntent, PendingIntent fullScreenIntent, Bitmap largeIcon,
int progressMax, int progress, boolean progressIndeterminate,
boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
- Bundle extras, String groupKey, boolean groupSummary, String sortKey) {
+ Bundle extras, String groupKey, boolean groupSummary, String sortKey,
+ RemoteViews contentView, RemoteViews bigContentView) {
b = new Notification.Builder(context)
.setWhen(n.when)
.setSmallIcon(n.icon, n.iconLevel)
@@ -117,6 +122,8 @@
if (sortKey != null) {
mExtras.putString(EXTRA_SORT_KEY, sortKey);
}
+ mContentView = contentView;
+ mBigContentView = bigContentView;
}
@Override
@@ -146,6 +153,12 @@
// Add the action extras sparse array if any action was added with extras.
getExtras(notif).putSparseParcelableArray(EXTRA_ACTION_EXTRAS, actionExtrasMap);
}
+ if (mContentView != null) {
+ notif.contentView = mContentView;
+ }
+ if (mBigContentView != null) {
+ notif.bigContentView = mBigContentView;
+ }
return notif;
}
}
@@ -245,12 +258,15 @@
RemoteInputCompatBase.RemoteInput.Factory remoteInputFactory, int icon,
CharSequence title, PendingIntent actionIntent, Bundle extras) {
RemoteInputCompatBase.RemoteInput[] remoteInputs = null;
+ boolean allowGeneratedReplies = false;
if (extras != null) {
remoteInputs = RemoteInputCompatJellybean.fromBundleArray(
BundleUtil.getBundleArrayFromBundle(extras, EXTRA_REMOTE_INPUTS),
remoteInputFactory);
+ allowGeneratedReplies = extras.getBoolean(EXTRA_ALLOW_GENERATED_REPLIES);
}
- return factory.build(icon, title, actionIntent, extras, remoteInputs);
+ return factory.build(icon, title, actionIntent, extras, remoteInputs,
+ allowGeneratedReplies);
}
public static Bundle writeActionAndGetExtras(
@@ -261,6 +277,8 @@
actionExtras.putParcelableArray(EXTRA_REMOTE_INPUTS,
RemoteInputCompatJellybean.toBundleArray(action.getRemoteInputs()));
}
+ actionExtras.putBoolean(EXTRA_ALLOW_GENERATED_REPLIES,
+ action.getAllowGeneratedReplies());
return actionExtras;
}
@@ -362,7 +380,7 @@
bundle.getBundle(KEY_EXTRAS),
RemoteInputCompatJellybean.fromBundleArray(
BundleUtil.getBundleArrayFromBundle(bundle, KEY_REMOTE_INPUTS),
- remoteInputFactory));
+ remoteInputFactory), bundle.getBoolean(KEY_ALLOW_GENERATED_REPLIES));
}
public static ArrayList<Parcelable> getParcelableArrayListForActions(
diff --git a/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java b/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
deleted file mode 100644
index 3cf6727..0000000
--- a/v4/jellybean/android/support/v4/media/routing/MediaRouterJellybean.java
+++ /dev/null
@@ -1,442 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media.routing;
-
-import android.content.Context;
-import android.graphics.drawable.Drawable;
-import android.os.Build;
-import android.util.Log;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.ArrayList;
-import java.util.List;
-
-class MediaRouterJellybean {
- private static final String TAG = "MediaRouterJellybean";
-
- public static final int ROUTE_TYPE_LIVE_AUDIO = 0x1;
- public static final int ROUTE_TYPE_LIVE_VIDEO = 0x2;
- public static final int ROUTE_TYPE_USER = 0x00800000;
-
- public static final int ALL_ROUTE_TYPES =
- MediaRouterJellybean.ROUTE_TYPE_LIVE_AUDIO
- | MediaRouterJellybean.ROUTE_TYPE_LIVE_VIDEO
- | MediaRouterJellybean.ROUTE_TYPE_USER;
-
- public static Object getMediaRouter(Context context) {
- return context.getSystemService(Context.MEDIA_ROUTER_SERVICE);
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static List getRoutes(Object routerObj) {
- final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
- final int count = router.getRouteCount();
- List out = new ArrayList(count);
- for (int i = 0; i < count; i++) {
- out.add(router.getRouteAt(i));
- }
- return out;
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static List getCategories(Object routerObj) {
- final android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
- final int count = router.getCategoryCount();
- List out = new ArrayList(count);
- for (int i = 0; i < count; i++) {
- out.add(router.getCategoryAt(i));
- }
- return out;
- }
-
- public static Object getSelectedRoute(Object routerObj, int type) {
- return ((android.media.MediaRouter)routerObj).getSelectedRoute(type);
- }
-
- public static void selectRoute(Object routerObj, int types, Object routeObj) {
- ((android.media.MediaRouter)routerObj).selectRoute(types,
- (android.media.MediaRouter.RouteInfo)routeObj);
- }
-
- public static void addCallback(Object routerObj, int types, Object callbackObj) {
- ((android.media.MediaRouter)routerObj).addCallback(types,
- (android.media.MediaRouter.Callback)callbackObj);
- }
-
- public static void removeCallback(Object routerObj, Object callbackObj) {
- ((android.media.MediaRouter)routerObj).removeCallback(
- (android.media.MediaRouter.Callback)callbackObj);
- }
-
- public static Object createRouteCategory(Object routerObj,
- String name, boolean isGroupable) {
- return ((android.media.MediaRouter)routerObj).createRouteCategory(name, isGroupable);
- }
-
- public static Object createUserRoute(Object routerObj, Object categoryObj) {
- return ((android.media.MediaRouter)routerObj).createUserRoute(
- (android.media.MediaRouter.RouteCategory)categoryObj);
- }
-
- public static void addUserRoute(Object routerObj, Object routeObj) {
- ((android.media.MediaRouter)routerObj).addUserRoute(
- (android.media.MediaRouter.UserRouteInfo)routeObj);
- }
-
- public static void removeUserRoute(Object routerObj, Object routeObj) {
- ((android.media.MediaRouter)routerObj).removeUserRoute(
- (android.media.MediaRouter.UserRouteInfo)routeObj);
- }
-
- public static Object createCallback(Callback callback) {
- return new CallbackProxy<Callback>(callback);
- }
-
- public static Object createVolumeCallback(VolumeCallback callback) {
- return new VolumeCallbackProxy<VolumeCallback>(callback);
- }
-
- public static final class RouteInfo {
- public static CharSequence getName(Object routeObj, Context context) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getName(context);
- }
-
- public static CharSequence getStatus(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getStatus();
- }
-
- public static int getSupportedTypes(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getSupportedTypes();
- }
-
- public static Object getCategory(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getCategory();
- }
-
- public static Drawable getIconDrawable(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getIconDrawable();
- }
-
- public static int getPlaybackType(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackType();
- }
-
- public static int getPlaybackStream(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getPlaybackStream();
- }
-
- public static int getVolume(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getVolume();
- }
-
- public static int getVolumeMax(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeMax();
- }
-
- public static int getVolumeHandling(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getVolumeHandling();
- }
-
- public static Object getTag(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getTag();
- }
-
- public static void setTag(Object routeObj, Object tag) {
- ((android.media.MediaRouter.RouteInfo)routeObj).setTag(tag);
- }
-
- public static void requestSetVolume(Object routeObj, int volume) {
- ((android.media.MediaRouter.RouteInfo)routeObj).requestSetVolume(volume);
- }
-
- public static void requestUpdateVolume(Object routeObj, int direction) {
- ((android.media.MediaRouter.RouteInfo)routeObj).requestUpdateVolume(direction);
- }
-
- public static Object getGroup(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getGroup();
- }
-
- public static boolean isGroup(Object routeObj) {
- return routeObj instanceof android.media.MediaRouter.RouteGroup;
- }
- }
-
- public static final class RouteGroup {
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static List getGroupedRoutes(Object groupObj) {
- final android.media.MediaRouter.RouteGroup group =
- (android.media.MediaRouter.RouteGroup)groupObj;
- final int count = group.getRouteCount();
- List out = new ArrayList(count);
- for (int i = 0; i < count; i++) {
- out.add(group.getRouteAt(i));
- }
- return out;
- }
- }
-
- public static final class UserRouteInfo {
- public static void setName(Object routeObj, CharSequence name) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setName(name);
- }
-
- public static void setStatus(Object routeObj, CharSequence status) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setStatus(status);
- }
-
- public static void setIconDrawable(Object routeObj, Drawable icon) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setIconDrawable(icon);
- }
-
- public static void setPlaybackType(Object routeObj, int type) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackType(type);
- }
-
- public static void setPlaybackStream(Object routeObj, int stream) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setPlaybackStream(stream);
- }
-
- public static void setVolume(Object routeObj, int volume) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolume(volume);
- }
-
- public static void setVolumeMax(Object routeObj, int volumeMax) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeMax(volumeMax);
- }
-
- public static void setVolumeHandling(Object routeObj, int volumeHandling) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeHandling(volumeHandling);
- }
-
- public static void setVolumeCallback(Object routeObj, Object volumeCallbackObj) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setVolumeCallback(
- (android.media.MediaRouter.VolumeCallback)volumeCallbackObj);
- }
-
- public static void setRemoteControlClient(Object routeObj, Object rccObj) {
- ((android.media.MediaRouter.UserRouteInfo)routeObj).setRemoteControlClient(
- (android.media.RemoteControlClient)rccObj);
- }
- }
-
- public static final class RouteCategory {
- public static CharSequence getName(Object categoryObj, Context context) {
- return ((android.media.MediaRouter.RouteCategory)categoryObj).getName(context);
- }
-
- @SuppressWarnings({ "rawtypes", "unchecked" })
- public static List getRoutes(Object categoryObj) {
- ArrayList out = new ArrayList();
- ((android.media.MediaRouter.RouteCategory)categoryObj).getRoutes(out);
- return out;
- }
-
- public static int getSupportedTypes(Object categoryObj) {
- return ((android.media.MediaRouter.RouteCategory)categoryObj).getSupportedTypes();
- }
-
- public static boolean isGroupable(Object categoryObj) {
- return ((android.media.MediaRouter.RouteCategory)categoryObj).isGroupable();
- }
- }
-
- public static interface Callback {
- public void onRouteSelected(int type, Object routeObj);
- public void onRouteUnselected(int type, Object routeObj);
- public void onRouteAdded(Object routeObj);
- public void onRouteRemoved(Object routeObj);
- public void onRouteChanged(Object routeObj);
- public void onRouteGrouped(Object routeObj, Object groupObj, int index);
- public void onRouteUngrouped(Object routeObj, Object groupObj);
- public void onRouteVolumeChanged(Object routeObj);
- }
-
- public static interface VolumeCallback {
- public void onVolumeSetRequest(Object routeObj, int volume);
- public void onVolumeUpdateRequest(Object routeObj, int direction);
- }
-
- /**
- * Workaround for limitations of selectRoute() on JB and JB MR1.
- * Do not use on JB MR2 and above.
- */
- public static final class SelectRouteWorkaround {
- private Method mSelectRouteIntMethod;
-
- public SelectRouteWorkaround() {
- if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
- throw new UnsupportedOperationException();
- }
- try {
- mSelectRouteIntMethod = android.media.MediaRouter.class.getMethod(
- "selectRouteInt", int.class, android.media.MediaRouter.RouteInfo.class);
- } catch (NoSuchMethodException ex) {
- }
- }
-
- public void selectRoute(Object routerObj, int types, Object routeObj) {
- android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
- android.media.MediaRouter.RouteInfo route =
- (android.media.MediaRouter.RouteInfo)routeObj;
-
- int routeTypes = route.getSupportedTypes();
- if ((routeTypes & ROUTE_TYPE_USER) == 0) {
- // Handle non-user routes.
- // On JB and JB MR1, the selectRoute() API only supports programmatically
- // selecting user routes. So instead we rely on the hidden selectRouteInt()
- // method on these versions of the platform.
- // This limitation was removed in JB MR2.
- if (mSelectRouteIntMethod != null) {
- try {
- mSelectRouteIntMethod.invoke(router, types, route);
- return; // success!
- } catch (IllegalAccessException ex) {
- Log.w(TAG, "Cannot programmatically select non-user route. "
- + "Media routing may not work.", ex);
- } catch (InvocationTargetException ex) {
- Log.w(TAG, "Cannot programmatically select non-user route. "
- + "Media routing may not work.", ex);
- }
- } else {
- Log.w(TAG, "Cannot programmatically select non-user route "
- + "because the platform is missing the selectRouteInt() "
- + "method. Media routing may not work.");
- }
- }
-
- // Default handling.
- router.selectRoute(types, route);
- }
- }
-
- /**
- * Workaround the fact that the getDefaultRoute() method does not exist in JB and JB MR1.
- * Do not use on JB MR2 and above.
- */
- public static final class GetDefaultRouteWorkaround {
- private Method mGetSystemAudioRouteMethod;
-
- public GetDefaultRouteWorkaround() {
- if (Build.VERSION.SDK_INT < 16 || Build.VERSION.SDK_INT > 17) {
- throw new UnsupportedOperationException();
- }
- try {
- mGetSystemAudioRouteMethod =
- android.media.MediaRouter.class.getMethod("getSystemAudioRoute");
- } catch (NoSuchMethodException ex) {
- }
- }
-
- public Object getDefaultRoute(Object routerObj) {
- android.media.MediaRouter router = (android.media.MediaRouter)routerObj;
-
- if (mGetSystemAudioRouteMethod != null) {
- try {
- return mGetSystemAudioRouteMethod.invoke(router);
- } catch (IllegalAccessException ex) {
- } catch (InvocationTargetException ex) {
- }
- }
-
- // Could not find the method or it does not work.
- // Return the first route and hope for the best.
- return router.getRouteAt(0);
- }
- }
-
- static class CallbackProxy<T extends Callback>
- extends android.media.MediaRouter.Callback {
- protected final T mCallback;
-
- public CallbackProxy(T callback) {
- mCallback = callback;
- }
-
- @Override
- public void onRouteSelected(android.media.MediaRouter router,
- int type, android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteSelected(type, route);
- }
-
- @Override
- public void onRouteUnselected(android.media.MediaRouter router,
- int type, android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteUnselected(type, route);
- }
-
- @Override
- public void onRouteAdded(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteAdded(route);
- }
-
- @Override
- public void onRouteRemoved(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteRemoved(route);
- }
-
- @Override
- public void onRouteChanged(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteChanged(route);
- }
-
- @Override
- public void onRouteGrouped(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route,
- android.media.MediaRouter.RouteGroup group, int index) {
- mCallback.onRouteGrouped(route, group, index);
- }
-
- @Override
- public void onRouteUngrouped(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route,
- android.media.MediaRouter.RouteGroup group) {
- mCallback.onRouteUngrouped(route, group);
- }
-
- @Override
- public void onRouteVolumeChanged(android.media.MediaRouter router,
- android.media.MediaRouter.RouteInfo route) {
- mCallback.onRouteVolumeChanged(route);
- }
- }
-
- static class VolumeCallbackProxy<T extends VolumeCallback>
- extends android.media.MediaRouter.VolumeCallback {
- protected final T mCallback;
-
- public VolumeCallbackProxy(T callback) {
- mCallback = callback;
- }
-
- @Override
- public void onVolumeSetRequest(android.media.MediaRouter.RouteInfo route,
- int volume) {
- mCallback.onVolumeSetRequest(route, volume);
- }
-
- @Override
- public void onVolumeUpdateRequest(android.media.MediaRouter.RouteInfo route,
- int direction) {
- mCallback.onVolumeUpdateRequest(route, direction);
- }
- }
-}
diff --git a/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
index e2fd3b7..3039458 100644
--- a/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/app/NotificationCompatKitKat.java
@@ -33,6 +33,8 @@
private Notification.Builder b;
private Bundle mExtras;
private List<Bundle> mActionExtrasList = new ArrayList<Bundle>();
+ private RemoteViews mContentView;
+ private RemoteViews mBigContentView;
public Builder(Context context, Notification n,
CharSequence contentTitle, CharSequence contentText, CharSequence contentInfo,
@@ -41,7 +43,7 @@
int progressMax, int progress, boolean progressIndeterminate, boolean showWhen,
boolean useChronometer, int priority, CharSequence subText, boolean localOnly,
ArrayList<String> people, Bundle extras, String groupKey, boolean groupSummary,
- String sortKey) {
+ String sortKey, RemoteViews contentView, RemoteViews bigContentView) {
b = new Notification.Builder(context)
.setWhen(n.when)
.setShowWhen(showWhen)
@@ -90,6 +92,8 @@
if (sortKey != null) {
mExtras.putString(NotificationCompatJellybean.EXTRA_SORT_KEY, sortKey);
}
+ mContentView = contentView;
+ mBigContentView = bigContentView;
}
@Override
@@ -112,7 +116,14 @@
NotificationCompatJellybean.EXTRA_ACTION_EXTRAS, actionExtrasMap);
}
b.setExtras(mExtras);
- return b.build();
+ Notification notification = b.build();
+ if (mContentView != null) {
+ notification.contentView = mContentView;
+ }
+ if (mBigContentView != null) {
+ notification.bigContentView = mBigContentView;
+ }
+ return notification;
}
}
diff --git a/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java b/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java
new file mode 100644
index 0000000..baf58a9
--- /dev/null
+++ b/v4/kitkat/android/support/v4/app/NotificationManagerCompatKitKat.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.app.AppOpsManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+class NotificationManagerCompatKitKat {
+ private static final String CHECK_OP_NO_THROW = "checkOpNoThrow";
+ private static final String OP_POST_NOTIFICATION = "OP_POST_NOTIFICATION";
+
+ public static boolean areNotificationsEnabled(Context context) {
+ AppOpsManager appOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
+ ApplicationInfo appInfo = context.getApplicationInfo();
+ String pkg = context.getApplicationContext().getPackageName();
+ int uid = appInfo.uid;
+ try {
+ Class<?> appOpsClass = Class.forName(AppOpsManager.class.getName());
+ Method checkOpNoThrowMethod = appOpsClass.getMethod(CHECK_OP_NO_THROW, Integer.TYPE,
+ Integer.TYPE, String.class);
+ Field opPostNotificationValue = appOpsClass.getDeclaredField(OP_POST_NOTIFICATION);
+ int value = (int) opPostNotificationValue.get(Integer.class);
+ return ((int) checkOpNoThrowMethod.invoke(appOps, value, uid, pkg)
+ == AppOpsManager.MODE_ALLOWED);
+ } catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException |
+ InvocationTargetException | IllegalAccessException | RuntimeException e) {
+ return true;
+ }
+ }
+}
diff --git a/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java b/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
index b42c86b..d9cf352 100644
--- a/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/graphics/drawable/DrawableCompatKitKat.java
@@ -33,9 +33,13 @@
}
public static Drawable wrapForTinting(Drawable drawable) {
- if (!(drawable instanceof DrawableWrapperKitKat)) {
+ if (!(drawable instanceof TintAwareDrawable)) {
return new DrawableWrapperKitKat(drawable);
}
return drawable;
}
+
+ public static int getAlpha(Drawable drawable) {
+ return drawable.getAlpha();
+ }
}
diff --git a/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
index dc70c6e..3cfb9e1 100644
--- a/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
+++ b/v4/kitkat/android/support/v4/graphics/drawable/DrawableWrapperKitKat.java
@@ -16,7 +16,10 @@
package android.support.v4.graphics.drawable;
+import android.content.res.Resources;
import android.graphics.drawable.Drawable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
class DrawableWrapperKitKat extends DrawableWrapperHoneycomb {
@@ -24,6 +27,10 @@
super(drawable);
}
+ DrawableWrapperKitKat(DrawableWrapperState state, Resources resources) {
+ super(state, resources);
+ }
+
@Override
public void setAutoMirrored(boolean mirrored) {
mDrawable.setAutoMirrored(mirrored);
@@ -33,4 +40,22 @@
public boolean isAutoMirrored() {
return mDrawable.isAutoMirrored();
}
+
+ @NonNull
+ @Override
+ DrawableWrapperState mutateConstantState() {
+ return new DrawableWrapperStateKitKat(mState, null);
+ }
+
+ private static class DrawableWrapperStateKitKat extends DrawableWrapperState {
+ DrawableWrapperStateKitKat(@Nullable DrawableWrapperState orig,
+ @Nullable Resources res) {
+ super(orig, res);
+ }
+
+ @Override
+ public Drawable newDrawable(@Nullable Resources res) {
+ return new DrawableWrapperKitKat(this, res);
+ }
+ }
}
diff --git a/v4/kitkat/android/support/v4/media/RatingCompatKitkat.java b/v4/kitkat/android/support/v4/media/RatingCompatKitkat.java
new file mode 100644
index 0000000..b15c15e
--- /dev/null
+++ b/v4/kitkat/android/support/v4/media/RatingCompatKitkat.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.media;
+
+import android.media.Rating;
+
+class RatingCompatKitkat {
+ public static Object newUnratedRating(int ratingStyle) {
+ return Rating.newUnratedRating(ratingStyle);
+ }
+
+ public static Object newHeartRating(boolean hasHeart) {
+ return Rating.newHeartRating(hasHeart);
+ }
+
+ public static Object newThumbRating(boolean thumbIsUp) {
+ return Rating.newThumbRating(thumbIsUp);
+ }
+
+ public static Object newStarRating(int starRatingStyle, float starRating) {
+ return Rating.newStarRating(starRatingStyle, starRating);
+ }
+
+ public static Object newPercentageRating(float percent) {
+ return Rating.newPercentageRating(percent);
+ }
+
+ public static boolean isRated(Object ratingObj) {
+ return ((Rating)ratingObj).isRated();
+ }
+
+ public static int getRatingStyle(Object ratingObj) {
+ return ((Rating)ratingObj).getRatingStyle();
+ }
+
+ public static boolean hasHeart(Object ratingObj) {
+ return ((Rating)ratingObj).hasHeart();
+ }
+
+ public static boolean isThumbUp(Object ratingObj) {
+ return ((Rating)ratingObj).isThumbUp();
+ }
+
+ public static float getStarRating(Object ratingObj) {
+ return ((Rating)ratingObj).getStarRating();
+ }
+
+ public static float getPercentRating(Object ratingObj) {
+ return ((Rating)ratingObj).getPercentRating();
+ }
+}
diff --git a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
index f3c19e2..43c22af 100644
--- a/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
+++ b/v4/kitkat/android/support/v4/media/session/MediaSessionCompatApi19.java
@@ -35,8 +35,8 @@
getRccTransportControlFlagsFromActions(actions));
}
- public static Object createMetadataUpdateListener(MediaSessionCompatApi14.Callback callback) {
- return new OnMetadataUpdateListener<MediaSessionCompatApi14.Callback>(callback);
+ public static Object createMetadataUpdateListener(Callback callback) {
+ return new OnMetadataUpdateListener<Callback>(callback);
}
public static void setMetadata(Object rccObj, Bundle metadata, long actions) {
@@ -82,7 +82,7 @@
}
}
- static class OnMetadataUpdateListener<T extends MediaSessionCompatApi14.Callback> implements
+ static class OnMetadataUpdateListener<T extends Callback> implements
RemoteControlClient.OnMetadataUpdateListener {
protected final T mCallback;
@@ -97,4 +97,8 @@
}
}
}
+
+ interface Callback extends MediaSessionCompatApi18.Callback {
+ public void onSetRating(Object ratingObj);
+ }
}
diff --git a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 091d5a4..3a6273e 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -37,6 +37,7 @@
import android.print.PrintDocumentAdapter;
import android.print.PrintDocumentInfo;
import android.print.PrintManager;
+import android.print.PrintAttributes.MediaSize;
import android.print.pdf.PrintedPdfDocument;
import android.util.Log;
@@ -87,13 +88,27 @@
public void onFinish();
}
+ /**
+ * Whether the PrintActivity respects the suggested orientation
+ */
+ protected boolean mPrintActivityRespectsOrientation;
+
+ /**
+ * Whether the print subsystem handles min margins correctly. If not the print helper needs to
+ * fake this.
+ */
+ protected boolean mIsMinMarginsHandlingCorrect;
+
int mScaleMode = SCALE_MODE_FILL;
int mColorMode = COLOR_MODE_COLOR;
- int mOrientation = ORIENTATION_LANDSCAPE;
+ int mOrientation;
PrintHelperKitkat(Context context) {
+ mPrintActivityRespectsOrientation = true;
+ mIsMinMarginsHandlingCorrect = true;
+
mContext = context;
}
@@ -150,6 +165,10 @@
* {@link #ORIENTATION_LANDSCAPE} or {@link #ORIENTATION_PORTRAIT}
*/
public int getOrientation() {
+ /// Unset defaults to landscape but might turn image
+ if (mOrientation == 0) {
+ return ORIENTATION_LANDSCAPE;
+ }
return mOrientation;
}
@@ -164,6 +183,40 @@
}
/**
+ * Check if the supplied bitmap should best be printed on a portrait orientation paper.
+ *
+ * @param bitmap The bitmap to be printed.
+ * @return true iff the picture should best be printed on a portrait orientation paper.
+ */
+ private static boolean isPortrait(Bitmap bitmap) {
+ if (bitmap.getWidth() <= bitmap.getHeight()) {
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /**
+ * Create a build with a copy from the other print attributes.
+ *
+ * @param other The other print attributes
+ *
+ * @return A builder that will build print attributes that match the other attributes
+ */
+ protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
+ PrintAttributes.Builder b = (new PrintAttributes.Builder())
+ .setMediaSize(other.getMediaSize())
+ .setResolution(other.getResolution())
+ .setMinMargins(other.getMinMargins());
+
+ if (other.getColorMode() != 0) {
+ b.setColorMode(other.getColorMode());
+ }
+
+ return b;
+ }
+
+ /**
* Prints a bitmap.
*
* @param jobName The print job name.
@@ -177,8 +230,10 @@
}
final int fittingMode = mScaleMode; // grab the fitting mode at time of call
PrintManager printManager = (PrintManager) mContext.getSystemService(Context.PRINT_SERVICE);
- PrintAttributes.MediaSize mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
- if (bitmap.getWidth() > bitmap.getHeight()) {
+ PrintAttributes.MediaSize mediaSize;
+ if (isPortrait(bitmap)) {
+ mediaSize = PrintAttributes.MediaSize.UNKNOWN_PORTRAIT;
+ } else {
mediaSize = PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE;
}
PrintAttributes attr = new PrintAttributes.Builder()
@@ -211,54 +266,8 @@
public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
CancellationSignal cancellationSignal,
WriteResultCallback writeResultCallback) {
- PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
- mAttributes);
-
- Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap,
- mAttributes.getColorMode());
- try {
- Page page = pdfDocument.startPage(1);
-
- RectF content = new RectF(page.getInfo().getContentRect());
-
- Matrix matrix = getMatrix(
- maybeGrayscale.getWidth(), maybeGrayscale.getHeight(),
- content, fittingMode);
-
- // Draw the bitmap.
- page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
-
- // Finish the page.
- pdfDocument.finishPage(page);
-
- try {
- // Write the document.
- pdfDocument.writeTo(new FileOutputStream(
- fileDescriptor.getFileDescriptor()));
- // Done.
- writeResultCallback.onWriteFinished(
- new PageRange[]{PageRange.ALL_PAGES});
- } catch (IOException ioe) {
- // Failed.
- Log.e(LOG_TAG, "Error writing printed content", ioe);
- writeResultCallback.onWriteFailed(null);
- }
- } finally {
- if (pdfDocument != null) {
- pdfDocument.close();
- }
- if (fileDescriptor != null) {
- try {
- fileDescriptor.close();
- } catch (IOException ioe) {
- /* ignore */
- }
- }
- // If we created a new instance for grayscaling, then recycle it here.
- if (maybeGrayscale != bitmap) {
- maybeGrayscale.recycle();
- }
- }
+ writeBitmap(mAttributes, fittingMode, bitmap, fileDescriptor,
+ writeResultCallback);
}
@Override
@@ -301,6 +310,98 @@
}
/**
+ * Write a bitmap for a PDF document.
+ *
+ * @param attributes The print attributes
+ * @param fittingMode How to fit the bitmap
+ * @param bitmap The bitmap to write
+ * @param fileDescriptor The file to write to
+ * @param writeResultCallback Callback to call once written
+ */
+ private void writeBitmap(PrintAttributes attributes, int fittingMode, Bitmap bitmap,
+ ParcelFileDescriptor fileDescriptor,
+ PrintDocumentAdapter.WriteResultCallback writeResultCallback) {
+ PrintAttributes pdfAttributes;
+ if (mIsMinMarginsHandlingCorrect) {
+ pdfAttributes = attributes;
+ } else {
+ // If the handling of any margin != 0 is broken, strip the margins and add them to the
+ // bitmap later
+ pdfAttributes = copyAttributes(attributes)
+ .setMinMargins(new PrintAttributes.Margins(0,0,0,0)).build();
+ }
+
+ PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
+ pdfAttributes);
+
+ Bitmap maybeGrayscale = convertBitmapForColorMode(bitmap,
+ pdfAttributes.getColorMode());
+ try {
+ Page page = pdfDocument.startPage(1);
+
+ RectF contentRect;
+ if (mIsMinMarginsHandlingCorrect) {
+ contentRect = new RectF(page.getInfo().getContentRect());
+ } else {
+ // Create dummy doc that has the margins to compute correctly sized content
+ // rectangle
+ PrintedPdfDocument dummyDocument = new PrintedPdfDocument(mContext,
+ attributes);
+ Page dummyPage = dummyDocument.startPage(1);
+ contentRect = new RectF(dummyPage.getInfo().getContentRect());
+ dummyDocument.finishPage(dummyPage);
+ dummyDocument.close();
+ }
+
+ // Resize bitmap
+ Matrix matrix = getMatrix(
+ maybeGrayscale.getWidth(), maybeGrayscale.getHeight(),
+ contentRect, fittingMode);
+
+ if (mIsMinMarginsHandlingCorrect) {
+ // The pdfDocument takes care of the positioning and margins
+ } else {
+ // Move it to the correct position.
+ matrix.postTranslate(contentRect.left, contentRect.top);
+
+ // Cut off margins
+ page.getCanvas().clipRect(contentRect);
+ }
+
+ // Draw the bitmap.
+ page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
+
+ // Finish the page.
+ pdfDocument.finishPage(page);
+
+ try {
+ // Write the document.
+ pdfDocument.writeTo(new FileOutputStream(fileDescriptor.getFileDescriptor()));
+ // Done.
+ writeResultCallback.onWriteFinished(new PageRange[]{PageRange.ALL_PAGES});
+ } catch (IOException ioe) {
+ // Failed.
+ Log.e(LOG_TAG, "Error writing printed content", ioe);
+ writeResultCallback.onWriteFailed(null);
+ }
+ } finally {
+ pdfDocument.close();
+
+ if (fileDescriptor != null) {
+ try {
+ fileDescriptor.close();
+ } catch (IOException ioe) {
+ // ignore
+ }
+ }
+ // If we created a new instance for grayscaling, then recycle it here.
+ if (maybeGrayscale != bitmap) {
+ maybeGrayscale.recycle();
+ }
+ }
+ }
+
+ /**
* Prints an image located at the Uri. Image types supported are those of
* <code>BitmapFactory.decodeStream</code> (JPEG, GIF, PNG, BMP, WEBP)
*
@@ -325,7 +426,9 @@
final LayoutResultCallback layoutResultCallback,
Bundle bundle) {
- mAttributes = newPrintAttributes;
+ synchronized (this) {
+ mAttributes = newPrintAttributes;
+ }
if (cancellationSignal.isCanceled()) {
layoutResultCallback.onLayoutCancelled();
@@ -343,7 +446,6 @@
}
mLoadBitmap = new AsyncTask<Uri, Boolean, Bitmap>() {
-
@Override
protected void onPreExecute() {
// First register for cancellation requests.
@@ -370,12 +472,35 @@
@Override
protected void onPostExecute(Bitmap bitmap) {
super.onPostExecute(bitmap);
+
+ // If orientation was not set by the caller, try to fit the bitmap on
+ // the current paper by potentially rotating the bitmap by 90 degrees.
+ if (bitmap != null
+ && (!mPrintActivityRespectsOrientation || mOrientation == 0)) {
+ MediaSize mediaSize;
+
+ synchronized (this) {
+ mediaSize = mAttributes.getMediaSize();
+ }
+
+ if (mediaSize != null) {
+ if (mediaSize.isPortrait() != isPortrait(bitmap)) {
+ Matrix rotation = new Matrix();
+
+ rotation.postRotate(90);
+ bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
+ bitmap.getHeight(), rotation, true);
+ }
+ }
+ }
+
mBitmap = bitmap;
if (bitmap != null) {
PrintDocumentInfo info = new PrintDocumentInfo.Builder(jobName)
.setContentType(PrintDocumentInfo.CONTENT_TYPE_PHOTO)
.setPageCount(1)
.build();
+
boolean changed = !newPrintAttributes.equals(oldPrintAttributes);
layoutResultCallback.onLayoutFinished(info, changed);
@@ -424,53 +549,7 @@
public void onWrite(PageRange[] pageRanges, ParcelFileDescriptor fileDescriptor,
CancellationSignal cancellationSignal,
WriteResultCallback writeResultCallback) {
- PrintedPdfDocument pdfDocument = new PrintedPdfDocument(mContext,
- mAttributes);
- Bitmap maybeGrayscale = convertBitmapForColorMode(mBitmap,
- mAttributes.getColorMode());
- try {
-
- Page page = pdfDocument.startPage(1);
- RectF content = new RectF(page.getInfo().getContentRect());
-
- // Compute and apply scale to fill the page.
- Matrix matrix = getMatrix(mBitmap.getWidth(), mBitmap.getHeight(),
- content, fittingMode);
-
- // Draw the bitmap.
- page.getCanvas().drawBitmap(maybeGrayscale, matrix, null);
-
- // Finish the page.
- pdfDocument.finishPage(page);
-
- try {
- // Write the document.
- pdfDocument.writeTo(new FileOutputStream(
- fileDescriptor.getFileDescriptor()));
- // Done.
- writeResultCallback.onWriteFinished(
- new PageRange[]{PageRange.ALL_PAGES});
- } catch (IOException ioe) {
- // Failed.
- Log.e(LOG_TAG, "Error writing printed content", ioe);
- writeResultCallback.onWriteFailed(null);
- }
- } finally {
- if (pdfDocument != null) {
- pdfDocument.close();
- }
- if (fileDescriptor != null) {
- try {
- fileDescriptor.close();
- } catch (IOException ioe) {
- /* ignore */
- }
- }
- // If we created a new instance for grayscaling, then recycle it here.
- if (maybeGrayscale != mBitmap) {
- maybeGrayscale.recycle();
- }
- }
+ writeBitmap(mAttributes, fittingMode, mBitmap, fileDescriptor, writeResultCallback);
}
};
@@ -478,7 +557,7 @@
PrintAttributes.Builder builder = new PrintAttributes.Builder();
builder.setColorMode(mColorMode);
- if (mOrientation == ORIENTATION_LANDSCAPE) {
+ if (mOrientation == ORIENTATION_LANDSCAPE || mOrientation == 0) {
builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_LANDSCAPE);
} else if (mOrientation == ORIENTATION_PORTRAIT) {
builder.setMediaSize(PrintAttributes.MediaSize.UNKNOWN_PORTRAIT);
@@ -583,4 +662,4 @@
return grayscale;
}
-}
\ No newline at end of file
+}
diff --git a/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java b/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
index cf126d9..fd1b327db 100644
--- a/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/ViewCompatKitKat.java
@@ -37,4 +37,8 @@
public static boolean isAttachedToWindow(View view) {
return view.isAttachedToWindow();
}
+
+ public static boolean isLayoutDirectionResolved(View view) {
+ return view.isLayoutDirectionResolved();
+ }
}
diff --git a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
index 3805d02c..0629d24 100644
--- a/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
+++ b/v4/kitkat/android/support/v4/view/accessibility/AccessibilityNodeInfoCompatKitKat.java
@@ -23,6 +23,9 @@
* KitKat-specific AccessibilityNodeInfo API implementation.
*/
class AccessibilityNodeInfoCompatKitKat {
+ private static final String ROLE_DESCRIPTION_KEY =
+ "AccessibilityNodeInfo.roleDescription";
+
static int getLiveRegion(Object info) {
return ((AccessibilityNodeInfo) info).getLiveRegion();
}
@@ -112,6 +115,16 @@
((AccessibilityNodeInfo) info).setMultiLine(multiLine);
}
+ public static CharSequence getRoleDescription(Object info) {
+ Bundle extras = getExtras(info);
+ return extras.getCharSequence(ROLE_DESCRIPTION_KEY);
+ }
+
+ public static void setRoleDescription(Object info, CharSequence roleDescription) {
+ Bundle extras = getExtras(info);
+ extras.putCharSequence(ROLE_DESCRIPTION_KEY, roleDescription);
+ }
+
static class CollectionInfo {
static int getColumnCount(Object info) {
return ((AccessibilityNodeInfo.CollectionInfo) info).getColumnCount();
diff --git a/v4/kitkat/android/support/v4/widget/ListViewCompatKitKat.java b/v4/kitkat/android/support/v4/widget/ListViewCompatKitKat.java
new file mode 100644
index 0000000..f1a66dc
--- /dev/null
+++ b/v4/kitkat/android/support/v4/widget/ListViewCompatKitKat.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.widget.AbsListView;
+import android.widget.ListView;
+
+class ListViewCompatKitKat {
+ static void scrollListBy(final ListView listView, int y) {
+ listView.scrollListBy(y);
+ }
+}
diff --git a/v4/proguard-rules.pro b/v4/proguard-rules.pro
new file mode 100644
index 0000000..2ec1c65
--- /dev/null
+++ b/v4/proguard-rules.pro
@@ -0,0 +1,16 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# Make sure we keep annotations for ViewPager's DecorView
+-keepattributes *Annotation*
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index 466bfb8..4ad6ffa 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -17,26 +17,41 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="android.support.v4.test">
- <uses-sdk android:minSdkVersion="4" tools:overrideLibrary="android.support.test,
- android.support.test.espresso, android.support.test.espresso.idling"/>
+ <uses-sdk
+ android:minSdkVersion="4"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling"/>
- <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+ <uses-permission android:name="android.permission.VIBRATE"/>
+ <uses-permission android:name="android.permission.WAKE_LOCK"/>
+ <uses-permission android:name="android.permission.READ_CONTACTS"/>
+ <uses-permission android:name="android.permission.WRITE_CONTACTS"/>
- <application>
+ <application
+ android:supportsRtl="true"
+ android:theme="@style/TestActivityTheme">
<uses-library android:name="android.test.runner" />
- <activity android:name="android.support.v4.widget.test.TextViewTestActivity"/>
- <activity android:name="android.support.v4.widget.TestActivity"/>
+ <activity android:name="android.support.v4.widget.TextViewTestActivity"/>
- <provider
- android:name="android.support.v4.content.FileProvider"
- android:authorities="moocow"
- android:exported="false"
- android:grantUriPermissions="true">
- <meta-data
- android:name="android.support.FILE_PROVIDER_PATHS"
- android:resource="@xml/paths" />
- </provider>
+ <activity android:name="android.support.v4.widget.SwipeRefreshLayoutActivity"/>
+
+ <activity android:name="android.support.v4.widget.ExploreByTouchHelperTestActivity"/>
+
+ <activity android:name="android.support.v4.view.ViewPagerWithTitleStripActivity"/>
+
+ <activity android:name="android.support.v4.view.ViewPagerWithTabStripActivity"/>
+
+ <activity android:name="android.support.v4.view.VpaActivity"/>
+
+ <activity
+ android:name="android.support.v4.ThemedYellowActivity"
+ android:theme="@style/YellowTheme" />
+ <activity android:name="android.support.v4.app.test.FragmentTestActivity"/>
+
+ <activity android:name="android.support.v4.app.test.EmptyFragmentTestActivity" />
+
+ <activity android:name="android.support.v4.app.test.FragmentResultActivity" />
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
diff --git a/v4/tests/NO_DOCS b/v4/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v4/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/tests/java/android/support/v4/BaseInstrumentationTestCase.java b/v4/tests/java/android/support/v4/BaseInstrumentationTestCase.java
new file mode 100644
index 0000000..5f9ce85
--- /dev/null
+++ b/v4/tests/java/android/support/v4/BaseInstrumentationTestCase.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4;
+
+import android.app.Activity;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import org.junit.Rule;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+ @Rule
+ public final ActivityTestRule<A> mActivityTestRule;
+
+ protected BaseInstrumentationTestCase(Class<A> activityClass) {
+ mActivityTestRule = new ActivityTestRule<A>(activityClass);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/BaseTestActivity.java b/v4/tests/java/android/support/v4/BaseTestActivity.java
new file mode 100755
index 0000000..e0682ce
--- /dev/null
+++ b/v4/tests/java/android/support/v4/BaseTestActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+
+public abstract class BaseTestActivity extends Activity {
+ @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() {}
+}
diff --git a/v4/tests/java/android/support/v4/ThemedYellowActivity.java b/v4/tests/java/android/support/v4/ThemedYellowActivity.java
new file mode 100644
index 0000000..e256251
--- /dev/null
+++ b/v4/tests/java/android/support/v4/ThemedYellowActivity.java
@@ -0,0 +1,35 @@
+/*
+ * 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.v4;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.view.WindowManager;
+import android.widget.FrameLayout;
+
+public class ThemedYellowActivity extends Activity {
+ FrameLayout mContainer;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ mContainer = new FrameLayout(this);
+
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(mContainer);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/ChildFragmentStateTest.java b/v4/tests/java/android/support/v4/app/ChildFragmentStateTest.java
new file mode 100644
index 0000000..78c3e28
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/ChildFragmentStateTest.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+
+public class ChildFragmentStateTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
+ public ChildFragmentStateTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ }
+
+ @UiThreadTest
+ public void testChildFragmentOrdering() throws Throwable {
+ FragmentTestActivity.ParentFragment parent = new ParentFragment();
+ FragmentManager fm = getActivity().getSupportFragmentManager();
+ fm.beginTransaction().add(parent, "parent").commit();
+ fm.executePendingTransactions();
+ assertTrue(parent.wasAttachedInTime);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
new file mode 100644
index 0000000..764a6ec
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -0,0 +1,706 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.Bundle;
+import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.EmptyFragmentTestActivity;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+
+import android.widget.TextView;
+import org.junit.Assert;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.FileDescriptor;
+import java.io.PrintWriter;
+
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertNotNull;
+import static junit.framework.Assert.assertNotSame;
+import static junit.framework.Assert.assertNull;
+import static junit.framework.Assert.assertSame;
+import static junit.framework.Assert.assertTrue;
+import static org.junit.Assert.assertNotEquals;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public class FragmentLifecycleTest {
+
+ @Rule
+ public ActivityTestRule<EmptyFragmentTestActivity> mActivityRule =
+ new ActivityTestRule<EmptyFragmentTestActivity>(EmptyFragmentTestActivity.class);
+
+ @Test
+ public void basicLifecycle() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictFragment strictFragment = new StrictFragment();
+
+ // Add fragment; StrictFragment will throw if it detects any violation
+ // in standard lifecycle method ordering or expected preconditions.
+ fm.beginTransaction().add(strictFragment, "EmptyHeadless").commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment is not added", strictFragment.isAdded());
+ assertFalse("fragment is detached", strictFragment.isDetached());
+ assertTrue("fragment is not resumed", strictFragment.isResumed());
+
+ // Test removal as well; StrictFragment will throw here too.
+ fm.beginTransaction().remove(strictFragment).commit();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment is added", strictFragment.isAdded());
+ assertFalse("fragment is resumed", strictFragment.isResumed());
+
+ // This one is perhaps counterintuitive; "detached" means specifically detached
+ // but still managed by a FragmentManager. The .remove call above
+ // should not enter this state.
+ assertFalse("fragment is detached", strictFragment.isDetached());
+ }
+
+ @Test
+ public void detachment() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictFragment f1 = new StrictFragment();
+ final StrictFragment f2 = new StrictFragment();
+
+ fm.beginTransaction().add(f1, "1").add(f2, "2").commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+ assertTrue("fragment 2 is not added", f2.isAdded());
+
+ // Test detaching fragments using StrictFragment to throw on errors.
+ fm.beginTransaction().detach(f1).detach(f2).commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not detached", f1.isDetached());
+ assertTrue("fragment 2 is not detached", f2.isDetached());
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertFalse("fragment 2 is added", f2.isAdded());
+
+ // Only reattach f1; leave v2 detached.
+ fm.beginTransaction().attach(f1).commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+ assertFalse("fragment 1 is detached", f1.isDetached());
+ assertTrue("fragment 2 is not detached", f2.isDetached());
+
+ // Remove both from the FragmentManager.
+ fm.beginTransaction().remove(f1).remove(f2).commit();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertFalse("fragment 2 is added", f2.isAdded());
+ assertFalse("fragment 1 is detached", f1.isDetached());
+ assertFalse("fragment 2 is detached", f2.isDetached());
+ }
+
+ @Test
+ public void basicBackStack() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictFragment f1 = new StrictFragment();
+ final StrictFragment f2 = new StrictFragment();
+
+ // Add a fragment normally to set up
+ fm.beginTransaction().add(f1, "1").commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+
+ // Remove the first one and add a second. We're not using replace() here since
+ // these fragments are headless and as of this test writing, replace() only works
+ // for fragments with views and a container view id.
+ // Add it to the back stack so we can pop it afterwards.
+ fm.beginTransaction().remove(f1).add(f2, "2").addToBackStack("stack1").commit();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertTrue("fragment 2 is not added", f2.isAdded());
+
+ // Test popping the stack
+ fm.popBackStack();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment 2 is added", f2.isAdded());
+ assertTrue("fragment 1 is not added", f1.isAdded());
+ }
+
+ @Test
+ public void attachBackStack() throws Throwable {
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictFragment f1 = new StrictFragment();
+ final StrictFragment f2 = new StrictFragment();
+
+ // Add a fragment normally to set up
+ fm.beginTransaction().add(f1, "1").commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+
+ fm.beginTransaction().detach(f1).add(f2, "2").addToBackStack("stack1").commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not detached", f1.isDetached());
+ assertFalse("fragment 2 is detached", f2.isDetached());
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertTrue("fragment 2 is not added", f2.isAdded());
+ }
+
+ @Test
+ public void viewLifecycle() throws Throwable {
+ // Test basic lifecycle when the fragment creates a view
+
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictViewFragment f1 = new StrictViewFragment();
+
+ fm.beginTransaction().add(android.R.id.content, f1).commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+ final View view = f1.getView();
+ assertNotNull("fragment 1 returned null from getView", view);
+ assertTrue("fragment 1's view is not attached to a window", view.isAttachedToWindow());
+
+ fm.beginTransaction().remove(f1).commit();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertNull("fragment 1 returned non-null from getView after removal", f1.getView());
+ assertFalse("fragment 1's previous view is still attached to a window",
+ view.isAttachedToWindow());
+ }
+
+ @Test
+ public void viewReplace() throws Throwable {
+ // Replace one view with another, then reverse it with the back stack
+
+ final FragmentManager fm = mActivityRule.getActivity().getSupportFragmentManager();
+ final StrictViewFragment f1 = new StrictViewFragment();
+ final StrictViewFragment f2 = new StrictViewFragment();
+
+ fm.beginTransaction().add(android.R.id.content, f1).commit();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+
+ View origView1 = f1.getView();
+ assertNotNull("fragment 1 returned null view", origView1);
+ assertTrue("fragment 1's view not attached", origView1.isAttachedToWindow());
+
+ fm.beginTransaction().replace(android.R.id.content, f2).addToBackStack("stack1").commit();
+ executePendingTransactions(fm);
+
+ assertFalse("fragment 1 is added", f1.isAdded());
+ assertTrue("fragment 2 is added", f2.isAdded());
+ assertNull("fragment 1 returned non-null view", f1.getView());
+ assertFalse("fragment 1's old view still attached", origView1.isAttachedToWindow());
+ View origView2 = f2.getView();
+ assertNotNull("fragment 2 returned null view", origView2);
+ assertTrue("fragment 2's view not attached", origView2.isAttachedToWindow());
+
+ fm.popBackStack();
+ executePendingTransactions(fm);
+
+ assertTrue("fragment 1 is not added", f1.isAdded());
+ assertFalse("fragment 2 is added", f2.isAdded());
+ assertNull("fragment 2 returned non-null view", f2.getView());
+ assertFalse("fragment 2's view still attached", origView2.isAttachedToWindow());
+ View newView1 = f1.getView();
+ assertNotSame("fragment 1 had same view from last attachment", origView1, newView1);
+ assertTrue("fragment 1's view not attached", newView1.isAttachedToWindow());
+ }
+
+ @Test
+ @UiThreadTest
+ public void restoreRetainedInstanceFragments() throws Throwable {
+ // Create a new FragmentManager in isolation, nest some assorted fragments
+ // and then restore them to a second new FragmentManager.
+
+ final FragmentController fc1 = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm1 = fc1.getSupportFragmentManager();
+
+ fc1.attachHost(null);
+ fc1.dispatchCreate();
+
+ // Configure fragments.
+
+ // Grandparent fragment will not retain instance
+ final StateSaveFragment grandparentFragment = new StateSaveFragment("Grandparent",
+ "UnsavedGrandparent");
+ assertNotNull("grandparent fragment saved state not initialized",
+ grandparentFragment.getSavedState());
+ assertNotNull("grandparent fragment unsaved state not initialized",
+ grandparentFragment.getUnsavedState());
+ fm1.beginTransaction().add(grandparentFragment, "tag:grandparent").commitNow();
+
+ // Parent fragment will retain instance
+ final StateSaveFragment parentFragment = new StateSaveFragment("Parent", "UnsavedParent");
+ assertNotNull("parent fragment saved state not initialized",
+ parentFragment.getSavedState());
+ assertNotNull("parent fragment unsaved state not initialized",
+ parentFragment.getUnsavedState());
+ parentFragment.setRetainInstance(true);
+ grandparentFragment.getChildFragmentManager().beginTransaction()
+ .add(parentFragment, "tag:parent").commitNow();
+ assertSame("parent fragment is not a child of grandparent",
+ grandparentFragment, parentFragment.getParentFragment());
+
+ // Child fragment will not retain instance
+ final StateSaveFragment childFragment = new StateSaveFragment("Child", "UnsavedChild");
+ assertNotNull("child fragment saved state not initialized",
+ childFragment.getSavedState());
+ assertNotNull("child fragment unsaved state not initialized",
+ childFragment.getUnsavedState());
+ parentFragment.getChildFragmentManager().beginTransaction()
+ .add(childFragment, "tag:child").commitNow();
+ assertSame("child fragment is not a child of grandpanret",
+ parentFragment, childFragment.getParentFragment());
+
+ // Saved for comparison later
+ final FragmentManager parentChildFragmentManager = parentFragment.getChildFragmentManager();
+
+ fc1.dispatchActivityCreated();
+ fc1.noteStateNotSaved();
+ fc1.execPendingActions();
+ fc1.doLoaderStart();
+ fc1.dispatchStart();
+ fc1.reportLoaderStart();
+ fc1.dispatchResume();
+ fc1.execPendingActions();
+
+ // Bring the state back down to destroyed, simulating an activity restart
+ fc1.dispatchPause();
+ final Parcelable savedState = fc1.saveAllState();
+ final FragmentManagerNonConfig nonconf = fc1.retainNestedNonConfig();
+ fc1.dispatchStop();
+ fc1.dispatchReallyStop();
+ fc1.dispatchDestroy();
+
+ // Create the new controller and restore state
+ final FragmentController fc2 = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+
+ final FragmentManager fm2 = fc2.getSupportFragmentManager();
+
+ fc2.attachHost(null);
+ fc2.restoreAllState(savedState, nonconf);
+ fc2.dispatchCreate();
+
+ // Confirm that the restored fragments are available and in the expected states
+ final StateSaveFragment restoredGrandparent = (StateSaveFragment) fm2.findFragmentByTag(
+ "tag:grandparent");
+ assertNotNull("grandparent fragment not restored", restoredGrandparent);
+
+ assertNotSame("grandparent fragment instance was saved",
+ grandparentFragment, restoredGrandparent);
+ assertEquals("grandparent fragment saved state was not equal",
+ grandparentFragment.getSavedState(), restoredGrandparent.getSavedState());
+ assertNotEquals("grandparent fragment unsaved state was unexpectedly preserved",
+ grandparentFragment.getUnsavedState(), restoredGrandparent.getUnsavedState());
+
+ final StateSaveFragment restoredParent = (StateSaveFragment) restoredGrandparent
+ .getChildFragmentManager().findFragmentByTag("tag:parent");
+ assertNotNull("parent fragment not restored", restoredParent);
+
+ assertSame("parent fragment instance was not saved", parentFragment, restoredParent);
+ assertEquals("parent fragment saved state was not equal",
+ parentFragment.getSavedState(), restoredParent.getSavedState());
+ assertEquals("parent fragment unsaved state was not equal",
+ parentFragment.getUnsavedState(), restoredParent.getUnsavedState());
+ assertNotSame("parent fragment has the same child FragmentManager",
+ parentChildFragmentManager, restoredParent.getChildFragmentManager());
+
+ final StateSaveFragment restoredChild = (StateSaveFragment) restoredParent
+ .getChildFragmentManager().findFragmentByTag("tag:child");
+ assertNotNull("child fragment not restored", restoredChild);
+
+ assertNotSame("child fragment instance state was saved", childFragment, restoredChild);
+ assertEquals("child fragment saved state was not equal",
+ childFragment.getSavedState(), restoredChild.getSavedState());
+ assertNotEquals("child fragment saved state was unexpectedly equal",
+ childFragment.getUnsavedState(), restoredChild.getUnsavedState());
+
+ fc2.dispatchActivityCreated();
+ fc2.noteStateNotSaved();
+ fc2.execPendingActions();
+ fc2.doLoaderStart();
+ fc2.dispatchStart();
+ fc2.reportLoaderStart();
+ fc2.dispatchResume();
+ fc2.execPendingActions();
+
+ // Test that the fragments are in the configuration we expect
+
+ // Bring the state back down to destroyed before we finish the test
+ fc2.dispatchPause();
+ fc2.saveAllState();
+ fc2.dispatchStop();
+ fc2.dispatchReallyStop();
+ fc2.dispatchDestroy();
+
+ assertTrue("grandparent not destroyed", restoredGrandparent.mCalledOnDestroy);
+ assertTrue("parent not destroyed", restoredParent.mCalledOnDestroy);
+ assertTrue("child not destroyed", restoredChild.mCalledOnDestroy);
+ }
+
+ @Test
+ @UiThreadTest
+ public void saveAnimationState() throws Throwable {
+ FragmentController fc = startupFragmentController(null);
+ FragmentManager fm = fc.getSupportFragmentManager();
+
+ fm.beginTransaction()
+ .setCustomAnimations(0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out)
+ .add(android.R.id.content, SimpleFragment.create(R.layout.fragment_a))
+ .addToBackStack(null)
+ .commit();
+ fm.executePendingTransactions();
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+ // Causes save and restore of fragments and back stack
+ fc = restartFragmentController(fc);
+ fm = fc.getSupportFragmentManager();
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+ fm.beginTransaction()
+ .setCustomAnimations(R.anim.fade_in, R.anim.fade_out, 0, 0)
+ .replace(android.R.id.content, SimpleFragment.create(R.layout.fragment_b))
+ .addToBackStack(null)
+ .commit();
+ fm.executePendingTransactions();
+
+ assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
+
+ // Causes save and restore of fragments and back stack
+ fc = restartFragmentController(fc);
+ fm = fc.getSupportFragmentManager();
+
+ assertAnimationsMatch(fm, R.anim.fade_in, R.anim.fade_out, 0, 0);
+
+ fm.popBackStackImmediate();
+
+ assertAnimationsMatch(fm, 0, R.anim.fade_out, R.anim.fade_in, R.anim.fade_out);
+
+ shutdownFragmentController(fc);
+ }
+
+ /**
+ * This test confirms that as long as a parent fragment has called super.onCreate,
+ * any child fragments added, committed and with transactions executed will be brought
+ * to at least the CREATED state by the time the parent fragment receives onCreateView.
+ * This means the child fragment will have received onAttach/onCreate.
+ */
+ @Test
+ @UiThreadTest
+ public void childFragmentManagerAttach() throws Throwable {
+ FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+ fc.attachHost(null);
+ fc.dispatchCreate();
+
+ FragmentManager fm = fc.getSupportFragmentManager();
+
+ ChildFragmentManagerFragment fragment = new ChildFragmentManagerFragment();
+ fm.beginTransaction()
+ .add(android.R.id.content, fragment)
+ .commitNow();
+
+ fc.dispatchActivityCreated();
+
+ fc.dispatchStart();
+ fc.dispatchResume();
+
+ // Confirm that the parent fragment received onAttachFragment
+ assertTrue("parent fragment did not receive onAttachFragment",
+ fragment.mCalledOnAttachFragment);
+
+ fc.dispatchStop();
+ fc.dispatchReallyStop();
+ fc.dispatchDestroy();
+ }
+
+ private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
+ int popExit) {
+ FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
+ BackStackRecord record = fmImpl.mBackStack.get(fmImpl.mBackStack.size() - 1);
+
+ Assert.assertEquals(enter, record.mEnterAnim);
+ Assert.assertEquals(exit, record.mExitAnim);
+ Assert.assertEquals(popEnter, record.mPopEnterAnim);
+ Assert.assertEquals(popExit, record.mPopExitAnim);
+ }
+
+ private FragmentController restartFragmentController(FragmentController fc) {
+ Parcelable savedState = shutdownFragmentController(fc);
+ return startupFragmentController(savedState);
+ }
+
+ private FragmentController startupFragmentController(Parcelable savedState) {
+ final FragmentController fc = FragmentController.createController(
+ new HostCallbacks(mActivityRule.getActivity()));
+ fc.attachHost(null);
+ fc.restoreAllState(savedState, (FragmentManagerNonConfig) null);
+ fc.dispatchCreate();
+ fc.dispatchActivityCreated();
+ fc.noteStateNotSaved();
+ fc.execPendingActions();
+ fc.doLoaderStart();
+ fc.dispatchStart();
+ fc.reportLoaderStart();
+ fc.dispatchResume();
+ fc.execPendingActions();
+ return fc;
+ }
+
+ private Parcelable shutdownFragmentController(FragmentController fc) {
+ fc.dispatchPause();
+ final Parcelable savedState = fc.saveAllState();
+ fc.dispatchStop();
+ fc.dispatchReallyStop();
+ fc.dispatchDestroy();
+ return savedState;
+ }
+
+ private void executePendingTransactions(final FragmentManager fm) throws Throwable {
+ mActivityRule.runOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ fm.executePendingTransactions();
+ }
+ });
+ }
+
+ public static class StateSaveFragment extends StrictFragment {
+ private static final String STATE_KEY = "state";
+
+ private String mSavedState;
+ private String mUnsavedState;
+
+ public StateSaveFragment() {
+ }
+
+ public StateSaveFragment(String savedState, String unsavedState) {
+ mSavedState = savedState;
+ mUnsavedState = unsavedState;
+ }
+
+ public String getSavedState() {
+ return mSavedState;
+ }
+
+ public String getUnsavedState() {
+ return mUnsavedState;
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mSavedState = savedInstanceState.getString(STATE_KEY);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putString(STATE_KEY, mSavedState);
+ }
+ }
+
+ public static class ChildFragmentManagerFragment extends StrictFragment {
+ private FragmentManager mSavedChildFragmentManager;
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mSavedChildFragmentManager = getChildFragmentManager();
+ }
+
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ assertSame("child FragmentManagers not the same instance", mSavedChildFragmentManager,
+ getChildFragmentManager());
+ ChildFragmentManagerChildFragment child = new ChildFragmentManagerChildFragment("foo");
+ mSavedChildFragmentManager.beginTransaction()
+ .add(child, "tag")
+ .commitNow();
+ assertEquals("argument strings don't match", "foo", child.getString());
+ return new TextView(container.getContext());
+ }
+ }
+
+ public static class ChildFragmentManagerChildFragment extends StrictFragment {
+ private String mString;
+
+ public ChildFragmentManagerChildFragment() {
+ }
+
+ public ChildFragmentManagerChildFragment(String arg) {
+ final Bundle b = new Bundle();
+ b.putString("string", arg);
+ setArguments(b);
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mString = getArguments().getString("string", "NO VALUE");
+ }
+
+ public String getString() {
+ return mString;
+ }
+ }
+
+ static class HostCallbacks extends FragmentHostCallback<FragmentActivity> {
+ private final FragmentActivity mActivity;
+
+ public HostCallbacks(FragmentActivity activity) {
+ super(activity);
+ mActivity = activity;
+ }
+
+ @Override
+ public void onDump(String prefix, FileDescriptor fd, PrintWriter writer, String[] args) {
+ }
+
+ @Override
+ public boolean onShouldSaveFragmentState(Fragment fragment) {
+ return !mActivity.isFinishing();
+ }
+
+ @Override
+ public LayoutInflater onGetLayoutInflater() {
+ return mActivity.getLayoutInflater().cloneInContext(mActivity);
+ }
+
+ @Override
+ public FragmentActivity onGetHost() {
+ return mActivity;
+ }
+
+ @Override
+ public void onSupportInvalidateOptionsMenu() {
+ mActivity.supportInvalidateOptionsMenu();
+ }
+
+ @Override
+ public void onStartActivityFromFragment(Fragment fragment, Intent intent, int requestCode) {
+ mActivity.startActivityFromFragment(fragment, intent, requestCode);
+ }
+
+ @Override
+ public void onStartActivityFromFragment(
+ Fragment fragment, Intent intent, int requestCode, @Nullable Bundle options) {
+ mActivity.startActivityFromFragment(fragment, intent, requestCode, options);
+ }
+
+ @Override
+ public void onRequestPermissionsFromFragment(@NonNull Fragment fragment,
+ @NonNull String[] permissions, int requestCode) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean onShouldShowRequestPermissionRationale(@NonNull String permission) {
+ return ActivityCompat.shouldShowRequestPermissionRationale(
+ mActivity, permission);
+ }
+
+ @Override
+ public boolean onHasWindowAnimations() {
+ return mActivity.getWindow() != null;
+ }
+
+ @Override
+ public int onGetWindowAnimations() {
+ final Window w = mActivity.getWindow();
+ return (w == null) ? 0 : w.getAttributes().windowAnimations;
+ }
+
+ @Override
+ public void onAttachFragment(Fragment fragment) {
+ mActivity.onAttachFragment(fragment);
+ }
+
+ @Nullable
+ @Override
+ public View onFindViewById(int id) {
+ return mActivity.findViewById(id);
+ }
+
+ @Override
+ public boolean onHasView() {
+ final Window w = mActivity.getWindow();
+ return (w != null && w.peekDecorView() != null);
+ }
+ }
+
+ public static class SimpleFragment extends Fragment {
+ private int mLayoutId;
+ private static final String LAYOUT_ID = "layoutId";
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(LAYOUT_ID, mLayoutId);
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(mLayoutId, container, false);
+ }
+
+ public static SimpleFragment create(int layoutId) {
+ SimpleFragment fragment = new SimpleFragment();
+ fragment.mLayoutId = layoutId;
+ return fragment;
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentReceiveResultTest.java b/v4/tests/java/android/support/v4/app/FragmentReceiveResultTest.java
new file mode 100644
index 0000000..2799b16
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentReceiveResultTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.app.PendingIntent;
+import android.content.Intent;
+import android.content.IntentSender;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.app.test.FragmentResultActivity;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static junit.framework.TestCase.assertEquals;
+import static junit.framework.TestCase.assertTrue;
+import static junit.framework.TestCase.fail;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests for Fragment startActivityForResult and startIntentSenderForResult.
+ */
+@RunWith(AndroidJUnit4.class)
+public class FragmentReceiveResultTest extends BaseInstrumentationTestCase<FragmentTestActivity> {
+ private FragmentTestActivity mActivity;
+ private TestFragment mFragment;
+
+ public FragmentReceiveResultTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ mActivity = mActivityTestRule.getActivity();
+ mFragment = attachTestFragment();
+ }
+
+ @Test
+ @SmallTest
+ public void testStartActivityForResultOk() {
+ startActivityForResult(10, Activity.RESULT_OK, "content 10");
+
+ assertTrue("Fragment should receive result", mFragment.mHasResult);
+ assertEquals(10, mFragment.mRequestCode);
+ assertEquals(Activity.RESULT_OK, mFragment.mResultCode);
+ assertEquals("content 10", mFragment.mResultContent);
+ }
+
+ @Test
+ @SmallTest
+ public void testStartActivityForResultCanceled() {
+ startActivityForResult(20, Activity.RESULT_CANCELED, "content 20");
+
+ assertTrue("Fragment should receive result", mFragment.mHasResult);
+ assertEquals(20, mFragment.mRequestCode);
+ assertEquals(Activity.RESULT_CANCELED, mFragment.mResultCode);
+ assertEquals("content 20", mFragment.mResultContent);
+ }
+
+ @Test
+ @SmallTest
+ public void testStartIntentSenderForResultOk() {
+ startIntentSenderForResult(30, Activity.RESULT_OK, "content 30");
+
+ assertTrue("Fragment should receive result", mFragment.mHasResult);
+ assertEquals(30, mFragment.mRequestCode);
+ assertEquals(Activity.RESULT_OK, mFragment.mResultCode);
+ assertEquals("content 30", mFragment.mResultContent);
+ }
+
+ @Test
+ @SmallTest
+ public void testStartIntentSenderForResultCanceled() {
+ startIntentSenderForResult(40, Activity.RESULT_CANCELED, "content 40");
+
+ assertTrue("Fragment should receive result", mFragment.mHasResult);
+ assertEquals(40, mFragment.mRequestCode);
+ assertEquals(Activity.RESULT_CANCELED, mFragment.mResultCode);
+ assertEquals("content 40", mFragment.mResultContent);
+ }
+
+ private TestFragment attachTestFragment() {
+ final TestFragment fragment = new TestFragment();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .add(R.id.content, fragment)
+ .addToBackStack(null)
+ .commitAllowingStateLoss();
+ mActivity.getFragmentManager().executePendingTransactions();
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ return fragment;
+ }
+
+ private void startActivityForResult(final int requestCode, final int resultCode,
+ final String content) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent(mActivity, FragmentResultActivity.class);
+ intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CODE, resultCode);
+ intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT, content);
+
+ mFragment.startActivityForResult(intent, requestCode);
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void startIntentSenderForResult(final int requestCode, final int resultCode,
+ final String content) {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ Intent intent = new Intent(mActivity, FragmentResultActivity.class);
+ intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CODE, resultCode);
+ intent.putExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT, content);
+
+ PendingIntent pendingIntent = PendingIntent.getActivity(mActivity,
+ requestCode, intent, 0);
+
+ try {
+ mFragment.startIntentSenderForResult(pendingIntent.getIntentSender(),
+ requestCode, null, 0, 0, 0, null);
+ } catch (IntentSender.SendIntentException e) {
+ fail("IntentSender failed");
+ }
+ }
+ });
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private static class TestFragment extends Fragment {
+ boolean mHasResult = false;
+ int mRequestCode = -1;
+ int mResultCode = 100;
+ String mResultContent;
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ mHasResult = true;
+ mRequestCode = requestCode;
+ mResultCode = resultCode;
+ mResultContent = data.getStringExtra(FragmentResultActivity.EXTRA_RESULT_CONTENT);
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentReplaceTest.java b/v4/tests/java/android/support/v4/app/FragmentReplaceTest.java
new file mode 100644
index 0000000..6a71e5c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentReplaceTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.app.Fragment;
+import android.support.test.filters.SdkSuppress;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.TestFragment;
+import android.support.v4.test.R;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.view.KeyEvent;
+
+/**
+ * Test to prevent regressions in SupportFragmentManager fragment replace method. See b/24693644
+ */
+public class FragmentReplaceTest extends
+ ActivityInstrumentationTestCase2<FragmentTestActivity> {
+ private FragmentTestActivity mActivity;
+
+
+ public FragmentReplaceTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ @UiThreadTest
+ public void testReplaceFragment() throws Throwable {
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .add(R.id.content, TestFragment.create(R.layout.fragment_a))
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ assertNotNull(mActivity.findViewById(R.id.textA));
+ assertNull(mActivity.findViewById(R.id.textB));
+ assertNull(mActivity.findViewById(R.id.textC));
+
+
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .add(R.id.content, TestFragment.create(R.layout.fragment_b))
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ assertNotNull(mActivity.findViewById(R.id.textA));
+ assertNotNull(mActivity.findViewById(R.id.textB));
+ assertNull(mActivity.findViewById(R.id.textC));
+
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, TestFragment.create(R.layout.fragment_c))
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ assertNull(mActivity.findViewById(R.id.textA));
+ assertNull(mActivity.findViewById(R.id.textB));
+ assertNotNull(mActivity.findViewById(R.id.textC));
+ }
+
+ @SdkSuppress(minSdkVersion = 11)
+ @UiThreadTest
+ public void testBackPressWithFrameworkFragment() throws Throwable {
+ mActivity.getFragmentManager().beginTransaction()
+ .add(R.id.content, new Fragment())
+ .addToBackStack(null)
+ .commit();
+ mActivity.getFragmentManager().executePendingTransactions();
+ assertEquals(1, mActivity.getFragmentManager().getBackStackEntryCount());
+
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
+
+ assertEquals(0, mActivity.getFragmentManager().getBackStackEntryCount());
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentTest.java b/v4/tests/java/android/support/v4/app/FragmentTest.java
new file mode 100644
index 0000000..50ec5a7
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentTest.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app;
+
+import android.os.Bundle;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.test.R;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * Miscellaneous tests for fragments that aren't big enough to belong to their own classes.
+ */
+public class FragmentTest extends
+ ActivityInstrumentationTestCase2<FragmentTestActivity> {
+ private FragmentTestActivity mActivity;
+
+ public FragmentTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mActivity = getActivity();
+ }
+
+ @SmallTest
+ @UiThreadTest
+ public void testOnCreateOrder() throws Throwable {
+ OrderFragment fragment1 = new OrderFragment();
+ OrderFragment fragment2 = new OrderFragment();
+ mActivity.getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.content, fragment1)
+ .add(R.id.content, fragment2)
+ .commitNow();
+ assertEquals(0, fragment1.createOrder);
+ assertEquals(1, fragment2.createOrder);
+ }
+
+ @SmallTest
+ public void testChildFragmentManagerGone() throws Throwable {
+ final FragmentA fragmentA = new FragmentA();
+ final FragmentB fragmentB = new FragmentB();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .add(R.id.content, fragmentA)
+ .commitNow();
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
+ R.anim.long_fade_in, R.anim.long_fade_out)
+ .replace(R.id.content, fragmentB)
+ .addToBackStack(null)
+ .commit();
+ }
+ });
+ // Wait for the middle of the animation
+ Thread.sleep(150);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .setCustomAnimations(R.anim.long_fade_in, R.anim.long_fade_out,
+ R.anim.long_fade_in, R.anim.long_fade_out)
+ .replace(R.id.content, fragmentA)
+ .addToBackStack(null)
+ .commit();
+ }
+ });
+ // Wait for the middle of the animation
+ Thread.sleep(150);
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ }
+ });
+ // Wait for the middle of the animation
+ Thread.sleep(150);
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ }
+ });
+ }
+
+ @MediumTest
+ @UiThreadTest
+ public void testViewOrder() throws Throwable {
+ FragmentA fragmentA = new FragmentA();
+ FragmentB fragmentB = new FragmentB();
+ FragmentC fragmentC = new FragmentC();
+ mActivity.getSupportFragmentManager()
+ .beginTransaction()
+ .add(R.id.content, fragmentA)
+ .add(R.id.content, fragmentB)
+ .add(R.id.content, fragmentC)
+ .commitNow();
+ ViewGroup content = (ViewGroup) mActivity.findViewById(R.id.content);
+ assertEquals(3, content.getChildCount());
+ assertNotNull(content.getChildAt(0).findViewById(R.id.textA));
+ assertNotNull(content.getChildAt(1).findViewById(R.id.textB));
+ assertNotNull(content.getChildAt(2).findViewById(R.id.textC));
+ }
+
+ public static class OrderFragment extends Fragment {
+ private static AtomicInteger sOrder = new AtomicInteger();
+ public int createOrder = -1;
+
+ public OrderFragment() {}
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ createOrder = sOrder.getAndIncrement();
+ super.onCreate(savedInstanceState);
+ }
+ }
+
+ public static class FragmentA extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_a, container, false);
+ }
+ }
+
+ public static class FragmentB extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_b, container, false);
+ }
+ }
+
+ public static class FragmentC extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(R.layout.fragment_c, container, false);
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
new file mode 100644
index 0000000..f49d8e2
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/FragmentTransitionTest.java
@@ -0,0 +1,380 @@
+/*
+ * 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.v4.app;
+
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.TestFragment;
+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;
+ private TestFragment mMidFragment;
+ private TestFragment mEndFragment;
+ private FragmentTestActivity mActivity;
+
+ public FragmentTransitionTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+ mStartFragment = null;
+ mMidFragment = null;
+ mEndFragment = null;
+ mActivity = getActivity();
+ }
+
+ public void testFragmentTransition() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ launchStartFragment();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final View sharedElement = mActivity.findViewById(R.id.hello);
+ assertEquals("source", ViewCompat.getTransitionName(sharedElement));
+
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mEndFragment)
+ .addSharedElement(sharedElement, "destination")
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mEndFragment, TestFragment.ENTER);
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.ENTER));
+ assertTrue(mStartFragment.wasEndCalled(TestFragment.EXIT));
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.SHARED_ELEMENT_ENTER));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final View textView = mActivity.findViewById(R.id.hello);
+ assertEquals("destination", ViewCompat.getTransitionName(textView));
+ mActivity.getSupportFragmentManager().popBackStack();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mStartFragment, TestFragment.REENTER);
+ assertTrue(mStartFragment.wasEndCalled(TestFragment.REENTER));
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ }
+
+ public void testFirstOutLastInTransition() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ launchStartFragment();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mMidFragment = TestFragment.create(R.layout.fragment_middle);
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mMidFragment)
+ .replace(R.id.content, mEndFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mEndFragment, TestFragment.ENTER);
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertTrue(mStartFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ mStartFragment.clearNotifications();
+ mEndFragment.clearNotifications();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mEndFragment, TestFragment.RETURN);
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ assertTrue(mStartFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.RETURN));
+ }
+
+ public void testPopTwo() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ launchStartFragment();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mMidFragment = TestFragment.create(R.layout.fragment_middle);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mMidFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mMidFragment, TestFragment.ENTER);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mEndFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForEnd(mEndFragment, TestFragment.ENTER);
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertTrue(mStartFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertTrue(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertTrue(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ mStartFragment.clearNotifications();
+ mMidFragment.clearNotifications();
+ mEndFragment.clearNotifications();
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ FragmentManager fm = mActivity.getSupportFragmentManager();
+ int id = fm.getBackStackEntryAt(0).getId();
+ fm.popBackStack(id, FragmentManager.POP_BACK_STACK_INCLUSIVE);
+ fm.executePendingTransactions();
+ }
+ });
+ waitForEnd(mEndFragment, TestFragment.RETURN);
+ assertTrue(mEndFragment.wasEndCalled(TestFragment.RETURN));
+
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ assertTrue(mStartFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.RETURN));
+ }
+
+ public void testNullTransition() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mStartFragment = TestFragment.create(R.layout.fragment_start);
+ mStartFragment.clearTransitions();
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mStartFragment)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForStart(mStartFragment, TestFragment.ENTER);
+ // No transitions
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.ENTER));
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mMidFragment = TestFragment.create(R.layout.fragment_middle);
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mEndFragment.clearTransitions();
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mMidFragment)
+ .replace(R.id.content, mEndFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForStart(mEndFragment, TestFragment.ENTER);
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mStartFragment.wasEndCalled(TestFragment.REENTER));
+
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForStart(mEndFragment, TestFragment.RETURN);
+ assertFalse(mEndFragment.wasEndCalled(TestFragment.RETURN));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mMidFragment.wasStartCalled(TestFragment.RETURN));
+
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.RETURN));
+ }
+
+ public void testRemoveAdded() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ launchStartFragment();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mEndFragment)
+ .replace(R.id.content, mStartFragment)
+ .replace(R.id.content, mEndFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ assertTrue(waitForEnd(mEndFragment, TestFragment.ENTER));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ assertTrue(waitForEnd(mStartFragment, TestFragment.REENTER));
+ }
+
+ public void testAddRemoved() throws Throwable {
+ if (VERSION.SDK_INT < VERSION_CODES.LOLLIPOP) {
+ return;
+ }
+ launchStartFragment();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mEndFragment = TestFragment.create(R.layout.fragment_end);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mEndFragment)
+ .replace(R.id.content, mStartFragment)
+ .addToBackStack(null)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForStart(mStartFragment, TestFragment.ENTER);
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.EXIT));
+ assertFalse(mEndFragment.wasStartCalled(TestFragment.ENTER));
+ assertFalse(mEndFragment.wasStartCalled(TestFragment.EXIT));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.getSupportFragmentManager().popBackStack();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ waitForStart(mStartFragment, TestFragment.REENTER);
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mStartFragment.wasStartCalled(TestFragment.RETURN));
+ assertFalse(mEndFragment.wasStartCalled(TestFragment.REENTER));
+ assertFalse(mEndFragment.wasStartCalled(TestFragment.RETURN));
+ }
+
+ private void launchStartFragment() throws Throwable {
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mStartFragment = TestFragment.create(R.layout.fragment_start);
+ mActivity.getSupportFragmentManager().beginTransaction()
+ .replace(R.id.content, mStartFragment)
+ .commit();
+ mActivity.getSupportFragmentManager().executePendingTransactions();
+ }
+ });
+ assertTrue(waitForEnd(mStartFragment, TestFragment.ENTER));
+ mStartFragment.clearNotifications();
+ }
+
+ private boolean waitForStart(TestFragment fragment, int key) throws InterruptedException {
+ boolean started = fragment.waitForStart(key);
+ getInstrumentation().waitForIdleSync();
+ return started;
+ }
+
+ private boolean waitForEnd(TestFragment fragment, int key) throws InterruptedException {
+ if (!waitForStart(fragment, key)) {
+ return false;
+ }
+ final boolean ended = fragment.waitForEnd(key);
+ getInstrumentation().waitForIdleSync();
+ return ended;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
new file mode 100644
index 0000000..2ec1a6c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentRestoreTest.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment.OnAttachListener;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import static junit.framework.TestCase.assertNotNull;
+import static junit.framework.TestCase.assertNotSame;
+import static junit.framework.TestCase.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+public class NestedFragmentRestoreTest {
+
+ @Rule
+ public ActivityTestRule<FragmentTestActivity> mActivityRule = new ActivityTestRule<>(
+ FragmentTestActivity.class);
+
+ public NestedFragmentRestoreTest() {
+ }
+
+ @Test
+ @SmallTest
+ public void recreateActivity() throws Throwable {
+ final FragmentTestActivity activity = mActivityRule.getActivity();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ FragmentTestActivity.ParentFragment parent = new ParentFragment();
+ parent.setRetainChildInstance(true);
+
+ activity.getSupportFragmentManager().beginTransaction()
+ .add(parent, "parent")
+ .commitNow();
+ }
+ });
+
+ FragmentManager fm = activity.getSupportFragmentManager();
+ ParentFragment parent = (ParentFragment) fm.findFragmentByTag("parent");
+ ChildFragment child = parent.getChildFragment();
+
+ final Context[] attachedTo = new Context[1];
+ final CountDownLatch latch = new CountDownLatch(1);
+ child.setOnAttachListener(new OnAttachListener() {
+ @Override
+ public void onAttach(Context activity, ChildFragment fragment) {
+ attachedTo[0] = activity;
+ latch.countDown();
+ }
+ });
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ activity.recreate();
+ }
+ });
+
+ assertTrue("timeout waiting for recreate", latch.await(10, TimeUnit.SECONDS));
+
+ assertNotNull("attached as part of recreate", attachedTo[0]);
+ assertNotSame("attached to new context", activity, attachedTo[0]);
+ assertNotSame("attached to new parent fragment", parent, child);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
new file mode 100644
index 0000000..cc884f99
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ChildFragment;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class NestedFragmentTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
+
+ ParentFragment mParentFragment;
+
+ public NestedFragmentTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ mParentFragment = new ParentFragment();
+ fragmentManager.beginTransaction().add(mParentFragment, "parent").commit();
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ fragmentManager.executePendingTransactions();
+ latch.countDown();
+ }
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ }
+
+ @UiThreadTest
+ public void testThrowsWhenUsingReservedRequestCode() {
+ try {
+ mParentFragment.getChildFragment().startActivityForResult(
+ new Intent(Intent.ACTION_CALL), 16777216 /* requestCode */);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {}
+ }
+
+ public void testNestedFragmentStartActivityForResult() throws Exception {
+ Instrumentation.ActivityResult activityResult = new Instrumentation.ActivityResult(
+ Activity.RESULT_OK, new Intent());
+
+ Instrumentation.ActivityMonitor activityMonitor =
+ getInstrumentation().addMonitor(
+ new IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */);
+
+ // Sanity check that onActivityResult hasn't been called yet.
+ assertFalse(mParentFragment.getChildFragment().onActivityResultCalled);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ mParentFragment.getChildFragment().startActivityForResult(
+ new Intent(Intent.ACTION_CALL),
+ 5 /* requestCode */);
+ latch.countDown();
+ }
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ assertTrue(getInstrumentation().checkMonitorHit(activityMonitor, 1));
+
+ final ChildFragment childFragment = mParentFragment.getChildFragment();
+ assertTrue(childFragment.onActivityResultCalled);
+ assertEquals(5, childFragment.onActivityResultRequestCode);
+ assertEquals(Activity.RESULT_OK, childFragment.onActivityResultResultCode);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/StrictFragment.java b/v4/tests/java/android/support/v4/app/StrictFragment.java
new file mode 100644
index 0000000..dfda814
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/StrictFragment.java
@@ -0,0 +1,180 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.content.Context;
+import android.os.Bundle;
+
+/**
+ * This fragment watches its primary lifecycle events and throws IllegalStateException
+ * if any of them are called out of order or from a bad/unexpected state.
+ */
+public class StrictFragment extends Fragment {
+ public static final int DETACHED = 0;
+ public static final int ATTACHED = 1;
+ public static final int CREATED = 2;
+ public static final int ACTIVITY_CREATED = 3;
+ public static final int STARTED = 4;
+ public static final int RESUMED = 5;
+
+ int mState;
+
+ boolean mCalledOnAttach, mCalledOnCreate, mCalledOnActivityCreated,
+ mCalledOnStart, mCalledOnResume, mCalledOnSaveInstanceState,
+ mCalledOnPause, mCalledOnStop, mCalledOnDestroy, mCalledOnDetach,
+ mCalledOnAttachFragment;
+
+ static String stateToString(int state) {
+ switch (state) {
+ case DETACHED: return "DETACHED";
+ case ATTACHED: return "ATTACHED";
+ case CREATED: return "CREATED";
+ case ACTIVITY_CREATED: return "ACTIVITY_CREATED";
+ case STARTED: return "STARTED";
+ case RESUMED: return "RESUMED";
+ }
+ return "(unknown " + state + ")";
+ }
+
+ public void checkGetActivity() {
+ if (getActivity() == null) {
+ throw new IllegalStateException("getActivity() returned null at unexpected time");
+ }
+ }
+
+ public void checkState(String caller, int... expected) {
+ if (expected == null || expected.length == 0) {
+ throw new IllegalArgumentException("must supply at least one expected state");
+ }
+ for (int expect : expected) {
+ if (mState == expect) {
+ return;
+ }
+ }
+ final StringBuilder expectString = new StringBuilder(stateToString(expected[0]));
+ for (int i = 1; i < expected.length; i++) {
+ expectString.append(" or ").append(stateToString(expected[i]));
+ }
+ throw new IllegalStateException(caller + " called while fragment was "
+ + stateToString(mState) + "; expected " + expectString.toString());
+ }
+
+ public void checkStateAtLeast(String caller, int minState) {
+ if (mState < minState) {
+ throw new IllegalStateException(caller + " called while fragment was "
+ + stateToString(mState) + "; expected at least " + stateToString(minState));
+ }
+ }
+
+ @Override
+ public void onAttachFragment(Fragment childFragment) {
+ mCalledOnAttachFragment = true;
+ }
+
+ @Override
+ public void onAttach(Context context) {
+ super.onAttach(context);
+ mCalledOnAttach = true;
+ checkState("onAttach", DETACHED);
+ mState = ATTACHED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (mCalledOnCreate) {
+ throw new IllegalStateException("onCreate called more than once");
+ }
+ mCalledOnCreate = true;
+ checkState("onCreate", ATTACHED);
+ mState = CREATED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
+ mCalledOnActivityCreated = true;
+ checkState("onActivityCreated", ATTACHED, CREATED);
+ mState = ACTIVITY_CREATED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onStart() {
+ super.onStart();
+ mCalledOnStart = true;
+ checkState("onStart", ACTIVITY_CREATED);
+ mState = STARTED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mCalledOnResume = true;
+ checkState("onResume", STARTED);
+ mState = RESUMED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ mCalledOnSaveInstanceState = true;
+ checkGetActivity();
+ checkStateAtLeast("onSaveInstanceState", STARTED);
+ }
+
+ @Override
+ public void onPause() {
+ super.onPause();
+ mCalledOnPause = true;
+ checkState("onPause", RESUMED);
+ mState = STARTED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onStop() {
+ super.onStop();
+ mCalledOnStop = true;
+ checkState("onStop", STARTED);
+ mState = CREATED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+ mCalledOnDestroy = true;
+ checkState("onDestroy", CREATED);
+ mState = ATTACHED;
+ checkGetActivity();
+ }
+
+ @Override
+ public void onDetach() {
+ super.onDetach();
+ mCalledOnDetach = true;
+ checkState("onDestroy", CREATED, ATTACHED);
+ mState = DETACHED;
+ checkGetActivity();
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/StrictViewFragment.java b/v4/tests/java/android/support/v4/app/StrictViewFragment.java
new file mode 100644
index 0000000..01251d2
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/StrictViewFragment.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app;
+
+import android.support.v4.test.R;
+import android.os.Bundle;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+public class StrictViewFragment extends StrictFragment {
+ boolean mOnCreateViewCalled, mOnViewCreatedCalled, mOnDestroyViewCalled;
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ checkGetActivity();
+ checkState("onCreateView", CREATED);
+ final View result = inflater.inflate(R.layout.strict_view_fragment, container, false);
+ mOnCreateViewCalled = true;
+ return result;
+ }
+
+ @Override
+ public void onViewCreated(View view, Bundle savedInstanceState) {
+ if (view == null) {
+ throw new IllegalArgumentException("onViewCreated view argument should not be null");
+ }
+ checkGetActivity();
+ checkState("onViewCreated", CREATED);
+ mOnViewCreatedCalled = true;
+ }
+
+ @Override
+ public void onDestroyView() {
+ super.onDestroyView();
+ if (getView() == null) {
+ throw new IllegalStateException("getView returned null in onDestroyView");
+ }
+ checkGetActivity();
+ checkState("onDestroyView", CREATED);
+ mOnDestroyViewCalled = true;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java b/v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java
new file mode 100644
index 0000000..460d7d7
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/test/EmptyFragmentTestActivity.java
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+
+package android.support.v4.app.test;
+
+import android.support.v4.app.FragmentActivity;
+
+public class EmptyFragmentTestActivity extends FragmentActivity {
+}
diff --git a/v4/tests/java/android/support/v4/app/test/FragmentResultActivity.java b/v4/tests/java/android/support/v4/app/test/FragmentResultActivity.java
new file mode 100644
index 0000000..65abaf9
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/test/FragmentResultActivity.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v4.app.test;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+
+/**
+ * A simple Activity used to return a result.
+ */
+public class FragmentResultActivity extends Activity {
+ public static final String EXTRA_RESULT_CODE = "result";
+ public static final String EXTRA_RESULT_CONTENT = "result_content";
+
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ int resultCode = getIntent().getIntExtra(EXTRA_RESULT_CODE, Activity.RESULT_OK);
+ String result = getIntent().getStringExtra(EXTRA_RESULT_CONTENT);
+ Intent intent = new Intent();
+ intent.putExtra(EXTRA_RESULT_CONTENT, result);
+ setResult(resultCode, intent);
+ finish();
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
new file mode 100644
index 0000000..06cf753
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
@@ -0,0 +1,261 @@
+/*
+ * 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.v4.app.test;
+
+import android.app.Activity;
+import android.content.Context;
+import android.content.Intent;
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import android.os.Bundle;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentActivity;
+import android.support.v4.test.R;
+import android.transition.Transition;
+import android.transition.Transition.TransitionListener;
+import android.transition.TransitionInflater;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * A simple activity used for Fragment Transitions and lifecycle event ordering
+ */
+public class FragmentTestActivity extends FragmentActivity {
+ @Override
+ public void onCreate(Bundle icicle) {
+ super.onCreate(icicle);
+ setContentView(R.layout.activity_content);
+ }
+
+ public static class TestFragment extends Fragment {
+ public static final int ENTER = 0;
+ public static final int RETURN = 1;
+ public static final int EXIT = 2;
+ public static final int REENTER = 3;
+ public static final int SHARED_ELEMENT_ENTER = 4;
+ public static final int SHARED_ELEMENT_RETURN = 5;
+ private static final int TRANSITION_COUNT = 6;
+
+ private static final String LAYOUT_ID = "layoutId";
+ private static final String TRANSITION_KEY = "transition_";
+ private int mLayoutId = R.layout.fragment_start;
+ private final int[] mTransitionIds = new int[] {
+ R.transition.fade,
+ R.transition.fade,
+ R.transition.fade,
+ R.transition.fade,
+ R.transition.change_bounds,
+ R.transition.change_bounds,
+ };
+ private final Object[] mListeners = new Object[TRANSITION_COUNT];
+
+ public TestFragment() {
+ if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) {
+ for (int i = 0; i < TRANSITION_COUNT; i++) {
+ mListeners[i] = new TransitionCalledListener();
+ }
+ }
+ }
+
+ public static TestFragment create(int layoutId) {
+ TestFragment testFragment = new TestFragment();
+ testFragment.mLayoutId = layoutId;
+ return testFragment;
+ }
+
+ public void clearTransitions() {
+ for (int i = 0; i < TRANSITION_COUNT; i++) {
+ mTransitionIds[i] = 0;
+ }
+ }
+
+ public void clearNotifications() {
+ for (int i = 0; i < TRANSITION_COUNT; i++) {
+ ((TransitionCalledListener)mListeners[i]).startLatch = new CountDownLatch(1);
+ ((TransitionCalledListener)mListeners[i]).endLatch = new CountDownLatch(1);
+ }
+ }
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState != null) {
+ mLayoutId = savedInstanceState.getInt(LAYOUT_ID, mLayoutId);
+ for (int i = 0; i < TRANSITION_COUNT; i++) {
+ String key = TRANSITION_KEY + i;
+ mTransitionIds[i] = savedInstanceState.getInt(key, mTransitionIds[i]);
+ }
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(LAYOUT_ID, mLayoutId);
+ for (int i = 0; i < TRANSITION_COUNT; i++) {
+ String key = TRANSITION_KEY + i;
+ outState.putInt(key, mTransitionIds[i]);
+ }
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container,
+ Bundle savedInstanceState) {
+ return inflater.inflate(mLayoutId, container, false);
+ }
+
+ @SuppressWarnings("deprecation")
+ @Override
+ public void onAttach(Activity activity) {
+ super.onAttach(activity);
+ if (VERSION.SDK_INT > VERSION_CODES.KITKAT) {
+ setEnterTransition(loadTransition(ENTER));
+ setReenterTransition(loadTransition(REENTER));
+ setExitTransition(loadTransition(EXIT));
+ setReturnTransition(loadTransition(RETURN));
+ setSharedElementEnterTransition(loadTransition(SHARED_ELEMENT_ENTER));
+ setSharedElementReturnTransition(loadTransition(SHARED_ELEMENT_RETURN));
+ }
+ }
+
+ public boolean wasStartCalled(int transitionKey) {
+ return ((TransitionCalledListener)mListeners[transitionKey]).startLatch.getCount() == 0;
+ }
+
+ public boolean wasEndCalled(int transitionKey) {
+ return ((TransitionCalledListener)mListeners[transitionKey]).endLatch.getCount() == 0;
+ }
+
+ public boolean waitForStart(int transitionKey)
+ throws InterruptedException {
+ TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
+ return l.startLatch.await(500,TimeUnit.MILLISECONDS);
+ }
+
+ public boolean waitForEnd(int transitionKey)
+ throws InterruptedException {
+ TransitionCalledListener l = ((TransitionCalledListener)mListeners[transitionKey]);
+ return l.endLatch.await(500,TimeUnit.MILLISECONDS);
+ }
+
+ private Transition loadTransition(int key) {
+ final int id = mTransitionIds[key];
+ if (id == 0) {
+ return null;
+ }
+ Transition transition = TransitionInflater.from(getActivity()).inflateTransition(id);
+ transition.addListener(((TransitionCalledListener)mListeners[key]));
+ return transition;
+ }
+
+ private class TransitionCalledListener implements TransitionListener {
+ public CountDownLatch startLatch = new CountDownLatch(1);
+ public CountDownLatch endLatch = new CountDownLatch(1);
+
+ public TransitionCalledListener() {
+ }
+
+ @Override
+ public void onTransitionStart(Transition transition) {
+ startLatch.countDown();
+ }
+
+ @Override
+ public void onTransitionEnd(Transition transition) {
+ endLatch.countDown();
+ }
+
+ @Override
+ public void onTransitionCancel(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionPause(Transition transition) {
+ }
+
+ @Override
+ public void onTransitionResume(Transition transition) {
+ }
+ }
+ }
+
+ public static class ParentFragment extends Fragment {
+ static final String CHILD_FRAGMENT_TAG = "childFragment";
+ public boolean wasAttachedInTime;
+
+ private boolean mRetainChild;
+
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ ChildFragment f = getChildFragment();
+ if (f == null) {
+ f = new ChildFragment();
+ if (mRetainChild) {
+ f.setRetainInstance(true);
+ }
+ getChildFragmentManager().beginTransaction().add(f, CHILD_FRAGMENT_TAG).commitNow();
+ }
+ wasAttachedInTime = f.attached;
+ }
+
+ public ChildFragment getChildFragment() {
+ return (ChildFragment) getChildFragmentManager().findFragmentByTag(CHILD_FRAGMENT_TAG);
+ }
+
+ public void setRetainChildInstance(boolean retainChild) {
+ mRetainChild = retainChild;
+ }
+ }
+
+ public static class ChildFragment extends Fragment {
+ private OnAttachListener mOnAttachListener;
+
+ public boolean attached;
+ public boolean onActivityResultCalled;
+ public int onActivityResultRequestCode;
+ public int onActivityResultResultCode;
+
+ @Override
+ public void onAttach(Context activity) {
+ super.onAttach(activity);
+ attached = true;
+ if (mOnAttachListener != null) {
+ mOnAttachListener.onAttach(activity, this);
+ }
+ }
+
+ public void setOnAttachListener(OnAttachListener listener) {
+ mOnAttachListener = listener;
+ }
+
+ public interface OnAttachListener {
+ void onAttach(Context activity, ChildFragment fragment);
+ }
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ onActivityResultCalled = true;
+ onActivityResultRequestCode = requestCode;
+ onActivityResultResultCode = resultCode;
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/content/ContextCompatTest.java b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
new file mode 100644
index 0000000..56e89a5
--- /dev/null
+++ b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.v4.content;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.ThemedYellowActivity;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.DisplayMetrics;
+
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class ContextCompatTest extends BaseInstrumentationTestCase<ThemedYellowActivity> {
+ private Context mContext;
+
+ public ContextCompatTest() {
+ super(ThemedYellowActivity.class);
+ }
+
+ @Before
+ public void setup() {
+ mContext = mActivityTestRule.getActivity();
+ }
+
+ @Test
+ public void testGetColor() throws Throwable {
+ assertEquals("Unthemed color load", 0xFFFF8090,
+ ContextCompat.getColor(mContext, R.color.text_color));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following test is only expected to pass on v23+ devices. The result of
+ // calling theme-aware getColor() in pre-v23 is undefined.
+ assertEquals("Themed yellow color load",
+ ContextCompat.getColor(mContext, R.color.simple_themed_selector),
+ 0xFFF0B000);
+ }
+ }
+
+ @Test
+ public void testGetColorStateList() throws Throwable {
+ ColorStateList unthemedColorStateList =
+ ContextCompat.getColorStateList(mContext, R.color.complex_unthemed_selector);
+ assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
+ unthemedColorStateList.getDefaultColor());
+ assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
+ unthemedColorStateList.getColorForState(
+ new int[]{android.R.attr.state_focused}, 0));
+ assertEquals("Unthemed color state list load: pressed", 0xFF6080B0,
+ unthemedColorStateList.getColorForState(
+ new int[]{android.R.attr.state_pressed}, 0));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following tests are only expected to pass on v23+ devices. The result of
+ // calling theme-aware getColorStateList() in pre-v23 is undefined.
+ ColorStateList themedYellowColorStateList =
+ ContextCompat.getColorStateList(mContext, R.color.complex_themed_selector);
+ assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
+ themedYellowColorStateList.getDefaultColor());
+ assertEquals("Themed yellow color state list load: focused", 0xFFF0A020,
+ themedYellowColorStateList.getColorForState(
+ new int[]{android.R.attr.state_focused}, 0));
+ assertEquals("Themed yellow color state list load: pressed", 0xFFE0A040,
+ themedYellowColorStateList.getColorForState(
+ new int[]{android.R.attr.state_pressed}, 0));
+ }
+ }
+
+ @Test
+ public void testGetDrawable() throws Throwable {
+ Drawable unthemedDrawable =
+ ContextCompat.getDrawable(mContext, R.drawable.test_drawable_red);
+ TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
+ unthemedDrawable, mContext.getResources().getColor(R.color.test_red));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following test is only expected to pass on v23+ devices. The result of
+ // calling theme-aware getDrawable() in pre-v23 is undefined.
+ Drawable themedYellowDrawable =
+ ContextCompat.getDrawable(mContext, R.drawable.themed_drawable);
+ TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
+ themedYellowDrawable, 0xFFF0B000);
+ }
+ }
+
+ @Test
+ public void testDrawableConfigurationWorkaround() throws Throwable {
+ final int expectedWidth = scaleFromDensity(7, DisplayMetrics.DENSITY_LOW,
+ mContext.getResources().getDisplayMetrics().densityDpi);
+
+ // Ensure we retrieve the correct drawable configuration. Specifically,
+ // this tests a workaround for a bug in drawable configuration that
+ // exists on API < 16 for references to drawables.
+ Drawable referencedDrawable = ContextCompat.getDrawable(mContext,
+ R.drawable.aliased_drawable);
+ assertEquals("Drawable configuration does not match DisplayMetrics",
+ expectedWidth, referencedDrawable.getIntrinsicWidth());
+ }
+
+ private static int scaleFromDensity(int size, int sdensity, int tdensity) {
+ if (sdensity == tdensity) {
+ return size;
+ }
+
+ // Scale by tdensity / sdensity, rounding up.
+ return ((size * tdensity) + (sdensity >> 1)) / sdensity;
+ }
+
+ @Test(expected = IllegalArgumentException.class)
+ public void testCheckSelfPermissionNull() {
+ ContextCompat.checkSelfPermission(mContext, null);
+ }
+
+ @Test
+ public void testCheckSelfPermission() {
+ assertEquals("Vibrate permission granted", PackageManager.PERMISSION_GRANTED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.VIBRATE));
+ assertEquals("Wake lock permission granted", PackageManager.PERMISSION_GRANTED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.WAKE_LOCK));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // As documented in http://developer.android.com/training/permissions/requesting.html
+ // starting in Android M (v23) dangerous permissions are not granted automactically
+ // to apps targeting SDK 23 even if those are defined in the manifest.
+ // This is why the following permissions are expected to be denied.
+ assertEquals("Read contacts permission granted", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.READ_CONTACTS));
+ assertEquals("Write contacts permission granted", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.WRITE_CONTACTS));
+ } else {
+ // And on older devices declared dangerous permissions are expected to be granted.
+ assertEquals("Read contacts permission denied", PackageManager.PERMISSION_GRANTED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.READ_CONTACTS));
+ assertEquals("Write contacts permission denied", PackageManager.PERMISSION_GRANTED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.WRITE_CONTACTS));
+ }
+
+ // The following permissions (normal and dangerous) are expected to be denied as they are
+ // not declared in our manifest.
+ assertEquals("Access network state permission denied", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.ACCESS_NETWORK_STATE));
+ assertEquals("Bluetooth permission denied", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.BLUETOOTH));
+ assertEquals("Call phone permission denied", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.CALL_PHONE));
+ assertEquals("Delete packages permission denied", PackageManager.PERMISSION_DENIED,
+ ContextCompat.checkSelfPermission(mContext,
+ android.Manifest.permission.DELETE_PACKAGES));
+ }
+}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/content/FileProviderTest.java b/v4/tests/java/android/support/v4/content/FileProviderTest.java
deleted file mode 100644
index c446d85..0000000
--- a/v4/tests/java/android/support/v4/content/FileProviderTest.java
+++ /dev/null
@@ -1,382 +0,0 @@
-/*
- * Copyright (C) 2013 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.content;
-
-import static android.provider.OpenableColumns.DISPLAY_NAME;
-import static android.provider.OpenableColumns.SIZE;
-
-import android.content.ContentResolver;
-import android.database.Cursor;
-import android.net.Uri;
-import android.os.Environment;
-import android.support.v4.content.FileProvider.SimplePathStrategy;
-import android.test.AndroidTestCase;
-import android.test.MoreAsserts;
-
-import java.io.ByteArrayOutputStream;
-import java.io.Closeable;
-import java.io.File;
-import java.io.FileNotFoundException;
-import java.io.FileOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.OutputStream;
-
-/**
- * Tests for {@link FileProvider}
- */
-public class FileProviderTest extends AndroidTestCase {
- private static final String TEST_AUTHORITY = "moocow";
-
- private static final String TEST_FILE = "file.test";
- private static final byte[] TEST_DATA = new byte[] { (byte) 0xf0, 0x00, 0x0d };
- private static final byte[] TEST_DATA_ALT = new byte[] { (byte) 0x33, 0x66 };
-
- private ContentResolver mResolver;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mResolver = getContext().getContentResolver();
- }
-
- public void testStrategyUriSimple() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- File file = buildPath(mContext.getFilesDir(), "file.test");
- assertEquals("content://authority/tag/file.test",
- strat.getUriForFile(file).toString());
-
- file = buildPath(mContext.getFilesDir(), "subdir", "file.test");
- assertEquals("content://authority/tag/subdir/file.test",
- strat.getUriForFile(file).toString());
-
- file = buildPath(Environment.getExternalStorageDirectory(), "file.test");
- try {
- strat.getUriForFile(file);
- fail("somehow got uri for file outside roots?");
- } catch (IllegalArgumentException e) {
- }
- }
-
- public void testStrategyUriJumpOutside() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- File file = buildPath(mContext.getFilesDir(), "..", "file.test");
- try {
- strat.getUriForFile(file);
- fail("file escaped!");
- } catch (IllegalArgumentException e) {
- }
- }
-
- public void testStrategyUriShortestRoot() throws Exception {
- SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag1", mContext.getFilesDir());
- strat.addRoot("tag2", new File("/"));
-
- File file = buildPath(mContext.getFilesDir(), "file.test");
- assertEquals("content://authority/tag1/file.test",
- strat.getUriForFile(file).toString());
-
- strat = new SimplePathStrategy("authority");
- strat.addRoot("tag1", new File("/"));
- strat.addRoot("tag2", mContext.getFilesDir());
-
- file = buildPath(mContext.getFilesDir(), "file.test");
- assertEquals("content://authority/tag2/file.test",
- strat.getUriForFile(file).toString());
- }
-
- public void testStrategyFileSimple() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- File expectedRoot = mContext.getFilesDir().getCanonicalFile();
- File file = buildPath(expectedRoot, "file.test");
- assertEquals(file.getPath(),
- strat.getFileForUri(Uri.parse("content://authority/tag/file.test")).getPath());
-
- file = buildPath(expectedRoot, "subdir", "file.test");
- assertEquals(file.getPath(), strat.getFileForUri(
- Uri.parse("content://authority/tag/subdir/file.test")).getPath());
- }
-
- public void testStrategyFileJumpOutside() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- try {
- strat.getFileForUri(Uri.parse("content://authority/tag/../file.test"));
- fail("file escaped!");
- } catch (SecurityException e) {
- }
- }
-
- public void testStrategyEscaping() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("t/g", mContext.getFilesDir());
-
- File expectedRoot = mContext.getFilesDir().getCanonicalFile();
- File file = buildPath(expectedRoot, "lol\"wat?foo&bar", "wat.txt");
- final String expected = "content://authority/t%2Fg/lol%22wat%3Ffoo%26bar/wat.txt";
-
- assertEquals(expected,
- strat.getUriForFile(file).toString());
- assertEquals(file.getPath(),
- strat.getFileForUri(Uri.parse(expected)).getPath());
- }
-
- public void testStrategyExtraParams() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- File expectedRoot = mContext.getFilesDir().getCanonicalFile();
- File file = buildPath(expectedRoot, "file.txt");
- assertEquals(file.getPath(), strat.getFileForUri(
- Uri.parse("content://authority/tag/file.txt?extra=foo")).getPath());
- }
-
- public void testStrategyExtraSeparators() throws Exception {
- final SimplePathStrategy strat = new SimplePathStrategy("authority");
- strat.addRoot("tag", mContext.getFilesDir());
-
- // When canonicalized, the path separators are trimmed
- File inFile = new File(mContext.getFilesDir(), "//foo//bar//");
- File expectedRoot = mContext.getFilesDir().getCanonicalFile();
- File outFile = new File(expectedRoot, "/foo/bar");
- final String expected = "content://authority/tag/foo/bar";
-
- assertEquals(expected,
- strat.getUriForFile(inFile).toString());
- assertEquals(outFile.getPath(),
- strat.getFileForUri(Uri.parse(expected)).getPath());
- }
-
- public void testQueryProjectionNull() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- // Verify that null brings out default columns
- Cursor cursor = mResolver.query(uri, null, null, null, null);
- try {
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(TEST_FILE, cursor.getString(cursor.getColumnIndex(DISPLAY_NAME)));
- assertEquals(TEST_DATA.length, cursor.getLong(cursor.getColumnIndex(SIZE)));
- } finally {
- cursor.close();
- }
- }
-
- public void testQueryProjectionOrder() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- // Verify that swapped order works
- Cursor cursor = mResolver.query(uri, new String[] {
- SIZE, DISPLAY_NAME }, null, null, null);
- try {
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(TEST_DATA.length, cursor.getLong(0));
- assertEquals(TEST_FILE, cursor.getString(1));
- } finally {
- cursor.close();
- }
-
- cursor = mResolver.query(uri, new String[] {
- DISPLAY_NAME, SIZE }, null, null, null);
- try {
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(TEST_FILE, cursor.getString(0));
- assertEquals(TEST_DATA.length, cursor.getLong(1));
- } finally {
- cursor.close();
- }
- }
-
- public void testQueryExtraColumn() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- // Verify that extra column doesn't gook things up
- Cursor cursor = mResolver.query(uri, new String[] {
- SIZE, "foobar", DISPLAY_NAME }, null, null, null);
- try {
- assertEquals(1, cursor.getCount());
- cursor.moveToFirst();
- assertEquals(TEST_DATA.length, cursor.getLong(0));
- assertEquals(TEST_FILE, cursor.getString(1));
- } finally {
- cursor.close();
- }
- }
-
- public void testReadFile() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- assertContentsEquals(TEST_DATA, uri);
- }
-
- public void testWriteFile() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- assertContentsEquals(TEST_DATA, uri);
-
- final OutputStream out = mResolver.openOutputStream(uri);
- try {
- out.write(TEST_DATA_ALT);
- } finally {
- closeQuietly(out);
- }
-
- assertContentsEquals(TEST_DATA_ALT, uri);
- }
-
- public void testWriteMissingFile() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, null);
-
- try {
- assertContentsEquals(new byte[0], uri);
- fail("Somehow read missing file?");
- } catch(FileNotFoundException e) {
- }
-
- final OutputStream out = mResolver.openOutputStream(uri);
- try {
- out.write(TEST_DATA_ALT);
- } finally {
- closeQuietly(out);
- }
-
- assertContentsEquals(TEST_DATA_ALT, uri);
- }
-
- public void testDelete() throws Exception {
- final File file = new File(mContext.getFilesDir(), TEST_FILE);
- final Uri uri = stageFileAndGetUri(file, TEST_DATA);
-
- assertContentsEquals(TEST_DATA, uri);
-
- assertEquals(1, mResolver.delete(uri, null, null));
- assertEquals(0, mResolver.delete(uri, null, null));
-
- try {
- assertContentsEquals(new byte[0], uri);
- fail("Somehow read missing file?");
- } catch(FileNotFoundException e) {
- }
- }
-
- public void testMetaDataTargets() {
- Uri actual;
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- new File("/proc/version"));
- assertEquals("content://moocow/test_root/proc/version", actual.toString());
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- new File("/proc/1/mountinfo"));
- assertEquals("content://moocow/test_init/mountinfo", actual.toString());
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(mContext.getFilesDir(), "meow"));
- assertEquals("content://moocow/test_files/meow", actual.toString());
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(mContext.getFilesDir(), "thumbs", "rawr"));
- assertEquals("content://moocow/test_thumbs/rawr", actual.toString());
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(mContext.getCacheDir(), "up", "down"));
- assertEquals("content://moocow/test_cache/up/down", actual.toString());
-
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(Environment.getExternalStorageDirectory(), "Android", "obb", "foobar"));
- assertEquals("content://moocow/test_external/Android/obb/foobar", actual.toString());
-
- File[] externalFilesDirs = ContextCompat.getExternalFilesDirs(mContext, null);
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(externalFilesDirs[0], "foo", "bar"));
- assertEquals("content://moocow/test_external_files/foo/bar", actual.toString());
-
- File[] externalCacheDirs = ContextCompat.getExternalCacheDirs(mContext);
- actual = FileProvider.getUriForFile(mContext, TEST_AUTHORITY,
- buildPath(externalCacheDirs[0], "foo", "bar"));
- assertEquals("content://moocow/test_external_cache/foo/bar", actual.toString());
- }
-
- private void assertContentsEquals(byte[] expected, Uri actual) throws Exception {
- final InputStream in = mResolver.openInputStream(actual);
- try {
- MoreAsserts.assertEquals(expected, readFully(in));
- } finally {
- closeQuietly(in);
- }
- }
-
- private Uri stageFileAndGetUri(File file, byte[] data) throws Exception {
- if (data != null) {
- final FileOutputStream out = new FileOutputStream(file);
- try {
- out.write(data);
- } finally {
- out.close();
- }
- } else {
- file.delete();
- }
- return FileProvider.getUriForFile(mContext, TEST_AUTHORITY, file);
- }
-
- private static File buildPath(File base, String... segments) {
- File cur = base;
- for (String segment : segments) {
- if (cur == null) {
- cur = new File(segment);
- } else {
- cur = new File(cur, segment);
- }
- }
- return cur;
- }
-
- private static void closeQuietly(Closeable c) {
- try {
- c.close();
- } catch (IOException ignored) {
- }
- }
-
- private static byte[] readFully(InputStream is) throws IOException {
- ByteArrayOutputStream out = new ByteArrayOutputStream();
- byte[] buffer = new byte[4096];
- int read;
- while ((read = is.read(buffer)) != -1) {
- out.write(buffer, 0, read);
- }
- return out.toByteArray();
- }
-}
diff --git a/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
new file mode 100644
index 0000000..19d5609
--- /dev/null
+++ b/v4/tests/java/android/support/v4/content/res/ResourcesCompatTest.java
@@ -0,0 +1,280 @@
+/*
+ * 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.v4.content.res;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtils;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.DisplayMetrics;
+import org.junit.Before;
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+
+@SmallTest
+public class ResourcesCompatTest {
+ private Resources mResources;
+
+ @Before
+ public void setup() {
+ mResources = InstrumentationRegistry.getContext().getResources();
+ }
+
+ @Test
+ public void testGetColor() throws Throwable {
+ assertEquals("Unthemed color load",
+ ResourcesCompat.getColor(mResources, R.color.text_color, null),
+ 0xFFFF8090);
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following tests are only expected to pass on v23+ devices. The result of
+ // calling theme-aware getColor() in pre-v23 is undefined.
+ final Resources.Theme yellowTheme = mResources.newTheme();
+ yellowTheme.applyStyle(R.style.YellowTheme, true);
+ assertEquals("Themed yellow color load", 0xFFF0B000,
+ ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+ yellowTheme));
+
+ final Resources.Theme lilacTheme = mResources.newTheme();
+ lilacTheme.applyStyle(R.style.LilacTheme, true);
+ assertEquals("Themed lilac color load", 0xFFF080F0,
+ ResourcesCompat.getColor(mResources, R.color.simple_themed_selector,
+ lilacTheme));
+ }
+ }
+
+ @Test
+ public void testGetColorStateList() throws Throwable {
+ final ColorStateList unthemedColorStateList =
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_unthemed_selector,
+ null);
+ assertEquals("Unthemed color state list load: default", 0xFF70A0C0,
+ unthemedColorStateList.getDefaultColor());
+ assertEquals("Unthemed color state list load: focused", 0xFF70B0F0,
+ unthemedColorStateList.getColorForState(
+ new int[]{android.R.attr.state_focused}, 0));
+ assertEquals("Unthemed color state list load: pressed", 0xFF6080B0,
+ unthemedColorStateList.getColorForState(
+ new int[]{android.R.attr.state_pressed}, 0));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following tests are only expected to pass on v23+ devices. The result of
+ // calling theme-aware getColorStateList() in pre-v23 is undefined.
+ final Resources.Theme yellowTheme = mResources.newTheme();
+ yellowTheme.applyStyle(R.style.YellowTheme, true);
+ final ColorStateList themedYellowColorStateList =
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
+ yellowTheme);
+ assertEquals("Themed yellow color state list load: default", 0xFFF0B000,
+ themedYellowColorStateList.getDefaultColor());
+ assertEquals("Themed yellow color state list load: focused", 0xFFF0A020,
+ themedYellowColorStateList.getColorForState(
+ new int[]{android.R.attr.state_focused}, 0));
+ assertEquals("Themed yellow color state list load: pressed", 0xFFE0A040,
+ themedYellowColorStateList.getColorForState(
+ new int[]{android.R.attr.state_pressed}, 0));
+
+ final Resources.Theme lilacTheme = mResources.newTheme();
+ lilacTheme.applyStyle(R.style.LilacTheme, true);
+ final ColorStateList themedLilacColorStateList =
+ ResourcesCompat.getColorStateList(mResources, R.color.complex_themed_selector,
+ lilacTheme);
+ assertEquals("Themed lilac color state list load: default", 0xFFF080F0,
+ themedLilacColorStateList.getDefaultColor());
+ assertEquals("Themed lilac color state list load: focused", 0xFFF070D0,
+ themedLilacColorStateList.getColorForState(
+ new int[]{android.R.attr.state_focused}, 0));
+ assertEquals("Themed lilac color state list load: pressed", 0xFFE070A0,
+ themedLilacColorStateList.getColorForState(
+ new int[]{android.R.attr.state_pressed}, 0));
+ }
+ }
+
+ @Test
+ public void testGetDrawable() throws Throwable {
+ final Drawable unthemedDrawable =
+ ResourcesCompat.getDrawable(mResources, R.drawable.test_drawable_red, null);
+ TestUtils.assertAllPixelsOfColor("Unthemed drawable load",
+ unthemedDrawable, mResources.getColor(R.color.test_red));
+
+ if (Build.VERSION.SDK_INT >= 23) {
+ // The following tests are only expected to pass on v23+ devices. The result of
+ // calling theme-aware getDrawable() in pre-v23 is undefined.
+ final Resources.Theme yellowTheme = mResources.newTheme();
+ yellowTheme.applyStyle(R.style.YellowTheme, true);
+ final Drawable themedYellowDrawable =
+ ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable,
+ yellowTheme);
+ TestUtils.assertAllPixelsOfColor("Themed yellow drawable load",
+ themedYellowDrawable, 0xFFF0B000);
+
+ final Resources.Theme lilacTheme = mResources.newTheme();
+ lilacTheme.applyStyle(R.style.LilacTheme, true);
+ final Drawable themedLilacDrawable =
+ ResourcesCompat.getDrawable(mResources, R.drawable.themed_drawable, lilacTheme);
+ TestUtils.assertAllPixelsOfColor("Themed lilac drawable load",
+ themedLilacDrawable, 0xFFF080F0);
+ }
+ }
+
+ @Test
+ public void testGetDrawableForDensityUnthemed() throws Throwable {
+ // Density-aware drawable loading for now only works on raw bitmap drawables.
+
+ // Different variants of density_aware_drawable are set up in the following way:
+ // mdpi - 12x12 px which is 12x12 dip
+ // hdpi - 21x21 px which is 14x14 dip
+ // xhdpi - 32x32 px which is 16x16 dip
+ // xxhdpi - 54x54 px which is 18x18 dip
+ // The tests below (on v15+ devices) are checking that an unthemed density-aware
+ // loading of raw bitmap drawables returns a drawable with matching intrinsic
+ // dimensions.
+
+ final Drawable unthemedDrawableForMediumDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
+ DisplayMetrics.DENSITY_MEDIUM, null);
+ // For pre-v15 devices we should get a drawable that corresponds to the density of the
+ // current device. For v15+ devices we should get a drawable that corresponds to the
+ // density requested in the API call.
+ final int expectedSizeForMediumDensity = (Build.VERSION.SDK_INT < 15) ?
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 12;
+ assertEquals("Unthemed density-aware drawable load: medium width",
+ expectedSizeForMediumDensity, unthemedDrawableForMediumDensity.getIntrinsicWidth());
+ assertEquals("Unthemed density-aware drawable load: medium height",
+ expectedSizeForMediumDensity,
+ unthemedDrawableForMediumDensity.getIntrinsicHeight());
+
+ final Drawable unthemedDrawableForHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
+ DisplayMetrics.DENSITY_HIGH, null);
+ // For pre-v15 devices we should get a drawable that corresponds to the density of the
+ // current device. For v15+ devices we should get a drawable that corresponds to the
+ // density requested in the API call.
+ final int expectedSizeForHighDensity = (Build.VERSION.SDK_INT < 15) ?
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 21;
+ assertEquals("Unthemed density-aware drawable load: high width",
+ expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicWidth());
+ assertEquals("Unthemed density-aware drawable load: high height",
+ expectedSizeForHighDensity, unthemedDrawableForHighDensity.getIntrinsicHeight());
+
+ final Drawable unthemedDrawableForXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
+ DisplayMetrics.DENSITY_XHIGH, null);
+ // For pre-v15 devices we should get a drawable that corresponds to the density of the
+ // current device. For v15+ devices we should get a drawable that corresponds to the
+ // density requested in the API call.
+ final int expectedSizeForXHighDensity = (Build.VERSION.SDK_INT < 15) ?
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 32;
+ assertEquals("Unthemed density-aware drawable load: xhigh width",
+ expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicWidth());
+ assertEquals("Unthemed density-aware drawable load: xhigh height",
+ expectedSizeForXHighDensity, unthemedDrawableForXHighDensity.getIntrinsicHeight());
+
+ final Drawable unthemedDrawableForXXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.density_aware_drawable,
+ DisplayMetrics.DENSITY_XXHIGH, null);
+ // For pre-v15 devices we should get a drawable that corresponds to the density of the
+ // current device. For v15+ devices we should get a drawable that corresponds to the
+ // density requested in the API call.
+ final int expectedSizeForXXHighDensity = (Build.VERSION.SDK_INT < 15) ?
+ mResources.getDimensionPixelSize(R.dimen.density_aware_size) : 54;
+ assertEquals("Unthemed density-aware drawable load: xxhigh width",
+ expectedSizeForXXHighDensity, unthemedDrawableForXXHighDensity.getIntrinsicWidth());
+ assertEquals("Unthemed density-aware drawable load: xxhigh height",
+ expectedSizeForXXHighDensity,
+ unthemedDrawableForXXHighDensity.getIntrinsicHeight());
+ }
+
+ @Test
+ public void testGetDrawableForDensityThemed() throws Throwable {
+ if (Build.VERSION.SDK_INT < 21) {
+ // The following tests are only expected to pass on v21+ devices. The result of
+ // calling theme-aware getDrawableForDensity() in pre-v21 is undefined.
+ return;
+ }
+
+ // Density- and theme-aware drawable loading for now only works partially. This test
+ // checks only for theming of a tinted bitmap XML drawable, but not correct scaling.
+
+ // Set up the two test themes, yellow and lilac.
+ final Resources.Theme yellowTheme = mResources.newTheme();
+ yellowTheme.applyStyle(R.style.YellowTheme, true);
+
+ final Resources.Theme lilacTheme = mResources.newTheme();
+ lilacTheme.applyStyle(R.style.LilacTheme, true);
+
+ Drawable themedYellowDrawableForMediumDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_MEDIUM, yellowTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : medium color",
+ themedYellowDrawableForMediumDensity, 0xFFF0B000);
+
+ Drawable themedLilacDrawableForMediumDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_MEDIUM, lilacTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : medium color",
+ themedLilacDrawableForMediumDensity, 0xFFF080F0);
+
+ Drawable themedYellowDrawableForHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_HIGH, yellowTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : high color",
+ themedYellowDrawableForHighDensity, 0xFFF0B000);
+
+ Drawable themedLilacDrawableForHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_HIGH, lilacTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : high color",
+ themedLilacDrawableForHighDensity, 0xFFF080F0);
+
+ Drawable themedYellowDrawableForXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_XHIGH, yellowTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xhigh color",
+ themedYellowDrawableForXHighDensity, 0xFFF0B000);
+
+ Drawable themedLilacDrawableForXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_XHIGH, lilacTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xhigh color",
+ themedLilacDrawableForXHighDensity, 0xFFF080F0);
+
+ Drawable themedYellowDrawableForXXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_XXHIGH, yellowTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed yellow density-aware drawable load : xxhigh color",
+ themedYellowDrawableForXXHighDensity, 0xFFF0B000);
+
+ Drawable themedLilacDrawableForXXHighDensity =
+ ResourcesCompat.getDrawableForDensity(mResources, R.drawable.themed_bitmap,
+ DisplayMetrics.DENSITY_XXHIGH, lilacTheme);
+ // We should get a drawable that corresponds to the theme requested in the API call.
+ TestUtils.assertAllPixelsOfColor("Themed lilac density-aware drawable load : xxhigh color",
+ themedLilacDrawableForXXHighDensity, 0xFFF080F0);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
index 56cb6fb..998980b 100644
--- a/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
+++ b/v4/tests/java/android/support/v4/graphics/ColorUtilsTest.java
@@ -17,21 +17,28 @@
package android.support.v4.graphics;
import android.graphics.Color;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.lang.Integer;
import java.util.ArrayList;
-/**
- * @hide
- */
-public class ColorUtilsTest extends AndroidTestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ColorUtilsTest {
// 0.5% of the max value
private static final float ALLOWED_OFFSET_HUE = 360 * 0.005f;
private static final float ALLOWED_OFFSET_SATURATION = 0.005f;
private static final float ALLOWED_OFFSET_LIGHTNESS = 0.005f;
private static final float ALLOWED_OFFSET_MIN_ALPHA = 0.01f;
+ private static final double ALLOWED_OFFSET_LAB = 0.01;
+ private static final double ALLOWED_OFFSET_XYZ = 0.01;
private static final int ALLOWED_OFFSET_RGB_COMPONENT = 2;
@@ -39,38 +46,57 @@
static {
sEntryList.add(new TestEntry(Color.BLACK).setHsl(0f, 0f, 0f)
+ .setLab(0, 0, 0).setXyz(0, 0, 0)
.setWhiteMinAlpha30(0.35f).setWhiteMinAlpha45(0.46f));
+
sEntryList.add(new TestEntry(Color.WHITE).setHsl(0f, 0f, 1f)
+ .setLab(100, 0.005, -0.01).setXyz(95.05, 100, 108.9)
.setBlackMinAlpha30(0.42f).setBlackMinAlpha45(0.54f));
+
sEntryList.add(new TestEntry(Color.BLUE).setHsl(240f, 1f, 0.5f)
+ .setLab(32.303, 79.197, -107.864).setXyz(18.05, 7.22, 95.05)
.setWhiteMinAlpha30(0.55f).setWhiteMinAlpha45(0.71f));
+
sEntryList.add(new TestEntry(Color.GREEN).setHsl(120f, 1f, 0.5f)
+ .setLab(87.737, -86.185, 83.181).setXyz(35.76, 71.520, 11.920)
.setBlackMinAlpha30(0.43f).setBlackMinAlpha45(0.55f));
+
sEntryList.add(new TestEntry(Color.RED).setHsl(0f, 1f, 0.5f)
+ .setLab(53.233, 80.109, 67.22).setXyz(41.24, 21.26, 1.93)
.setWhiteMinAlpha30(0.84f).setBlackMinAlpha30(0.55f).setBlackMinAlpha45(0.78f));
+
sEntryList.add(new TestEntry(Color.CYAN).setHsl(180f, 1f, 0.5f)
+ .setLab(91.117, -48.08, -14.138).setXyz(53.81, 78.74, 106.97)
.setBlackMinAlpha30(0.43f).setBlackMinAlpha45(0.55f));
+
sEntryList.add(new TestEntry(0xFF2196F3).setHsl(207f, 0.9f, 0.54f)
+ .setLab(60.433, 2.091, -55.116).setXyz(27.711, 28.607, 88.855)
.setBlackMinAlpha30(0.52f).setWhiteMinAlpha30(0.97f).setBlackMinAlpha45(0.7f));
+
sEntryList.add(new TestEntry(0xFFD1C4E9).setHsl(261f, 0.46f, 0.84f)
+ .setLab(81.247, 11.513, -16.677).setXyz(60.742, 58.918, 85.262)
.setBlackMinAlpha30(0.45f).setBlackMinAlpha45(0.58f));
+
sEntryList.add(new TestEntry(0xFF311B92).setHsl(251.09f, 0.687f, 0.339f)
+ .setLab(21.988, 44.301, -60.942).setXyz(6.847, 3.512, 27.511)
.setWhiteMinAlpha30(0.39f).setWhiteMinAlpha45(0.54f));
}
- public void testToHSL() {
+ public void testColorToHSL() {
for (TestEntry entry : sEntryList) {
- testColorToHSL(entry.rgb, entry.hsl);
+ verifyColorToHSL(entry.rgb, entry.hsl);
}
}
- public void testFromHSL() {
+ @Test
+ public void testHSLToColor() {
for (TestEntry entry : sEntryList) {
- testHSLToColor(entry.hsl, entry.rgb);
+ verifyHSLToColor(entry.hsl, entry.rgb);
}
}
- public void testToHslLimits() {
+ @Test
+ public void testColorToHslLimits() {
final float[] hsl = new float[3];
for (TestEntry entry : sEntryList) {
@@ -82,70 +108,159 @@
}
}
+ @Test
+ public void testColorToXYZ() {
+ for (TestEntry entry : sEntryList) {
+ verifyColorToXYZ(entry.rgb, entry.xyz);
+ }
+ }
+
+ @Test
+ public void testColorToLAB() {
+ for (TestEntry entry : sEntryList) {
+ verifyColorToLAB(entry.rgb, entry.lab);
+ }
+ }
+
+ @Test
+ public void testLABToXYZ() {
+ for (TestEntry entry : sEntryList) {
+ verifyLABToXYZ(entry.lab, entry.xyz);
+ }
+ }
+
+ @Test
+ public void testXYZToColor() {
+ for (TestEntry entry : sEntryList) {
+ verifyXYZToColor(entry.xyz, entry.rgb);
+ }
+ }
+
+ @Test
+ public void testLABToColor() {
+ for (TestEntry entry : sEntryList) {
+ verifyLABToColor(entry.lab, entry.rgb);
+ }
+ }
+
+ @Test
public void testMinAlphas() {
for (TestEntry entry : sEntryList) {
- testMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
+ verifyMinAlpha("Black title", entry.rgb, entry.blackMinAlpha30,
ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 3.0f));
- testMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
+ verifyMinAlpha("Black body", entry.rgb, entry.blackMinAlpha45,
ColorUtils.calculateMinimumAlpha(Color.BLACK, entry.rgb, 4.5f));
- testMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
+ verifyMinAlpha("White title", entry.rgb, entry.whiteMinAlpha30,
ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 3.0f));
- testMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
+ verifyMinAlpha("White body", entry.rgb, entry.whiteMinAlpha45,
ColorUtils.calculateMinimumAlpha(Color.WHITE, entry.rgb, 4.5f));
}
}
- private static void testMinAlpha(String title, int color, float expected, int actual) {
+ @Test
+ public void testCircularInterpolationForwards() {
+ assertEquals(0f, ColorUtils.circularInterpolate(0, 180, 0f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(0, 180, 0.5f), 0f);
+ assertEquals(180f, ColorUtils.circularInterpolate(0, 180, 1f), 0f);
+ }
+
+ @Test
+ public void testCircularInterpolationBackwards() {
+ assertEquals(180f, ColorUtils.circularInterpolate(180, 0, 0f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(180, 0, 0.5f), 0f);
+ assertEquals(0f, ColorUtils.circularInterpolate(180, 0, 1f), 0f);
+ }
+
+ @Test
+ public void testCircularInterpolationCrossZero() {
+ assertEquals(270f, ColorUtils.circularInterpolate(270, 90, 0f), 0f);
+ assertEquals(180f, ColorUtils.circularInterpolate(270, 90, 0.5f), 0f);
+ assertEquals(90f, ColorUtils.circularInterpolate(270, 90, 1f), 0f);
+ }
+
+ private static void verifyMinAlpha(String title, int color, float expected, int actual) {
final String message = title + " text within error for #" + Integer.toHexString(color);
if (expected < 0) {
assertEquals(message, actual, -1);
} else {
- assertClose(message, expected, actual / 255f, ALLOWED_OFFSET_MIN_ALPHA);
+ assertEquals(message, expected, actual / 255f, ALLOWED_OFFSET_MIN_ALPHA);
}
}
- private static void assertClose(String message, float expected, float actual,
- float allowedOffset) {
- StringBuilder sb = new StringBuilder(message);
- sb.append(". Expected: ").append(expected).append(". Actual: ").append(actual);
-
- assertTrue(sb.toString(), Math.abs(expected - actual) <= allowedOffset);
- }
-
- private static void assertClose(String message, int expected, int actual,
- int allowedOffset) {
- StringBuilder sb = new StringBuilder(message);
- sb.append(". Expected: ").append(expected).append(". Actual: ").append(actual);
-
- assertTrue(sb.toString(), Math.abs(expected - actual) <= allowedOffset);
- }
-
- private static void testColorToHSL(int color, float[] expectedHsl) {
+ private static void verifyColorToHSL(int color, float[] expected) {
float[] actualHSL = new float[3];
ColorUtils.colorToHSL(color, actualHSL);
- assertClose("Hue not within offset", expectedHsl[0], actualHSL[0],
+ assertEquals("Hue not within offset", expected[0], actualHSL[0],
ALLOWED_OFFSET_HUE);
- assertClose("Saturation not within offset", expectedHsl[1], actualHSL[1],
+ assertEquals("Saturation not within offset", expected[1], actualHSL[1],
ALLOWED_OFFSET_SATURATION);
- assertClose("Lightness not within offset", expectedHsl[2], actualHSL[2],
+ assertEquals("Lightness not within offset", expected[2], actualHSL[2],
ALLOWED_OFFSET_LIGHTNESS);
}
- private static void testHSLToColor(float[] hsl, int expectedRgb) {
+ private static void verifyHSLToColor(float[] hsl, int expected) {
final int actualRgb = ColorUtils.HSLToColor(hsl);
- assertClose("Red not within offset",
- Color.red(expectedRgb), Color.red(actualRgb), ALLOWED_OFFSET_RGB_COMPONENT);
- assertClose("Green not within offset",
- Color.green(expectedRgb), Color.green(actualRgb), ALLOWED_OFFSET_RGB_COMPONENT);
- assertClose("Blue not within offset",
- Color.blue(expectedRgb), Color.blue(actualRgb), ALLOWED_OFFSET_RGB_COMPONENT);
+ assertEquals("Red not within offset", Color.red(expected), Color.red(actualRgb),
+ ALLOWED_OFFSET_RGB_COMPONENT);
+ assertEquals("Green not within offset", Color.green(expected), Color.green(actualRgb),
+ ALLOWED_OFFSET_RGB_COMPONENT);
+ assertEquals("Blue not within offset", Color.blue(expected), Color.blue(actualRgb),
+ ALLOWED_OFFSET_RGB_COMPONENT);
+ }
+
+ private static void verifyColorToLAB(int color, double[] expected) {
+ double[] result = new double[3];
+ ColorUtils.colorToLAB(color, result);
+
+ assertEquals("L not within offset", expected[0], result[0], ALLOWED_OFFSET_LAB);
+ assertEquals("A not within offset", expected[1], result[1], ALLOWED_OFFSET_LAB);
+ assertEquals("B not within offset", expected[2], result[2], ALLOWED_OFFSET_LAB);
+ }
+
+ private static void verifyColorToXYZ(int color, double[] expected) {
+ double[] result = new double[3];
+ ColorUtils.colorToXYZ(color, result);
+
+ assertEquals("X not within offset", expected[0], result[0], ALLOWED_OFFSET_XYZ);
+ assertEquals("Y not within offset", expected[1], result[1], ALLOWED_OFFSET_XYZ);
+ assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
+ }
+
+ private static void verifyLABToXYZ(double[] lab, double[] expected) {
+ double[] result = new double[3];
+ ColorUtils.LABToXYZ(lab[0], lab[1], lab[2], result);
+
+ assertEquals("X not within offset", expected[0], result[0], ALLOWED_OFFSET_XYZ);
+ assertEquals("Y not within offset", expected[1], result[1], ALLOWED_OFFSET_XYZ);
+ assertEquals("Z not within offset", expected[2], result[2], ALLOWED_OFFSET_XYZ);
+ }
+
+ private static void verifyXYZToColor(double[] xyz, int expected) {
+ final int result = ColorUtils.XYZToColor(xyz[0], xyz[1], xyz[2]);
+ verifyRGBComponentsClose(expected, result);
+ }
+
+ private static void verifyLABToColor(double[] lab, int expected) {
+ final int result = ColorUtils.LABToColor(lab[0], lab[1], lab[2]);
+ verifyRGBComponentsClose(expected, result);
+ }
+
+ private static void verifyRGBComponentsClose(int expected, int actual) {
+ final String message = "Expected: #" + Integer.toHexString(expected)
+ + ", Actual: #" + Integer.toHexString(actual);
+ assertEquals("R not equal: " + message, Color.red(expected), Color.red(actual), 1);
+ assertEquals("G not equal: " + message, Color.green(expected), Color.green(actual), 1);
+ assertEquals("B not equal: " + message, Color.blue(expected), Color.blue(actual), 1);
}
private static class TestEntry {
final int rgb;
final float[] hsl = new float[3];
+ final double[] xyz = new double[3];
+ final double[] lab = new double[3];
+
float blackMinAlpha45 = -1;
float blackMinAlpha30 = -1;
float whiteMinAlpha45 = -1;
@@ -162,6 +277,20 @@
return this;
}
+ TestEntry setXyz(double x, double y, double z) {
+ xyz[0] = x;
+ xyz[1] = y;
+ xyz[2] = z;
+ return this;
+ }
+
+ TestEntry setLab(double l, double a, double b) {
+ lab[0] = l;
+ lab[1] = a;
+ lab[2] = b;
+ return this;
+ }
+
TestEntry setBlackMinAlpha30(float minAlpha) {
blackMinAlpha30 = minAlpha;
return this;
diff --git a/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
new file mode 100644
index 0000000..6716a7c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/graphics/DrawableCompatTest.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics;
+
+import android.content.res.ColorStateList;
+import android.graphics.Color;
+import android.graphics.PorterDuff;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.GradientDrawable;
+import android.support.test.runner.AndroidJUnit4;
+import android.os.Build;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertSame;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class DrawableCompatTest {
+ @Test
+ public void testDrawableWrap() {
+ final Drawable original = new GradientDrawable();
+ final Drawable wrappedDrawable = DrawableCompat.wrap(original);
+
+ if (Build.VERSION.SDK_INT < 23) {
+ assertNotSame(original, wrappedDrawable);
+ } else {
+ assertSame(original, wrappedDrawable);
+ }
+ }
+
+ @Test
+ public void testDrawableUnwrap() {
+ final Drawable original = new GradientDrawable();
+ final Drawable wrappedDrawable = DrawableCompat.wrap(original);
+ assertSame(original, DrawableCompat.unwrap(wrappedDrawable));
+ }
+
+ @Test
+ public void testDrawableChangeBoundsCopy() {
+ final Rect bounds = new Rect(0, 0, 10, 10);
+
+ final Drawable drawable = new GradientDrawable();
+
+ final Drawable wrapper = DrawableCompat.wrap(drawable);
+ wrapper.setBounds(bounds);
+
+ // Assert that the bounds were given to the original drawable
+ assertEquals(bounds, drawable.getBounds());
+ }
+
+ @Test
+ public void testDrawableWrapOnlyWrapsOnce() {
+ final Drawable wrappedDrawable = DrawableCompat.wrap(new GradientDrawable());
+ assertSame(wrappedDrawable, DrawableCompat.wrap(wrappedDrawable));
+ }
+
+ @Test
+ public void testWrapMutatedDrawableHasConstantState() {
+ // First create a Drawable, and mutated it so that it has a constant state
+ Drawable drawable = new GradientDrawable();
+ drawable = drawable.mutate();
+ assertNotNull(drawable.getConstantState());
+
+ // Now wrap and assert that the wrapper also returns a constant state
+ final Drawable wrapper = DrawableCompat.wrap(drawable);
+ assertNotNull(wrapper.getConstantState());
+ }
+
+ @Test
+ public void testWrappedDrawableHasCallbackSet() {
+ // First create a Drawable
+ final Drawable drawable = new GradientDrawable();
+
+ // Now wrap it and set a mock as the wrapper's callback
+ final Drawable wrapper = DrawableCompat.wrap(drawable);
+ final Drawable.Callback mockCallback = mock(Drawable.Callback.class);
+ wrapper.setCallback(mockCallback);
+
+ // Now make the wrapped drawable invalidate itself
+ drawable.invalidateSelf();
+
+ // ...and verify that the wrapper calls to be invalidated
+ verify(mockCallback, times(1)).invalidateDrawable(wrapper);
+ }
+
+ @Test
+ public void testDoesNotWrapTintAwareDrawable() {
+ final TestTintAwareDrawable tintAwareDrawable = new TestTintAwareDrawable();
+ final Drawable wrapped = DrawableCompat.wrap(tintAwareDrawable);
+ // Assert that the tint aware drawable was not wrapped
+ assertSame(tintAwareDrawable, wrapped);
+ }
+
+ @Test
+ public void testTintAwareDrawableGetsTintCallsDirectly() {
+ final TestTintAwareDrawable d = mock(TestTintAwareDrawable.class);
+
+ final ColorStateList tint = ColorStateList.valueOf(Color.BLACK);
+ final PorterDuff.Mode tintMode = PorterDuff.Mode.DST;
+
+ // Now set the tint list and mode using DrawableCompat
+ DrawableCompat.setTintList(d, tint);
+ DrawableCompat.setTintMode(d, tintMode);
+
+ // Verify that the calls were directly on to the TintAwareDrawable
+ verify(d).setTintList(tint);
+ verify(d).setTintMode(tintMode);
+ }
+
+}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/graphics/TestTintAwareDrawable.java b/v4/tests/java/android/support/v4/graphics/TestTintAwareDrawable.java
new file mode 100644
index 0000000..e1cdfa1
--- /dev/null
+++ b/v4/tests/java/android/support/v4/graphics/TestTintAwareDrawable.java
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.graphics;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.BitmapDrawable;
+import android.support.v4.graphics.drawable.TintAwareDrawable;
+
+/**
+ * @hide
+ */
+public class TestTintAwareDrawable extends BitmapDrawable implements TintAwareDrawable {
+
+ public TestTintAwareDrawable() {
+ super();
+ }
+
+ @Override
+ public void setTint(int tintColor) {
+ // no-op so that the method isn't abstract on API 20 and below
+ }
+
+ @Override
+ public void setTintList(ColorStateList tint) {
+ // no-op so that the method isn't abstract on API 20 and below
+ }
+
+ @Override
+ public void setTintMode(PorterDuff.Mode tintMode) {
+ // no-op so that the method isn't abstract on API 20 and below
+ }
+}
diff --git a/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.java b/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.java
new file mode 100755
index 0000000..25ae971
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/LayoutDirectionActions.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.testutils;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class LayoutDirectionActions {
+ /**
+ * Sets layout direction on the view.
+ */
+ public static ViewAction setLayoutDirection(final int layoutDirection) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set layout direction";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setLayoutDirection(view, layoutDirection);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/v4/tests/java/android/support/v4/testutils/TestUtils.java b/v4/tests/java/android/support/v4/testutils/TestUtils.java
new file mode 100644
index 0000000..9b1acee
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/TestUtils.java
@@ -0,0 +1,141 @@
+/*
+ * 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.v4.testutils;
+
+import java.lang.IllegalArgumentException;
+import java.lang.RuntimeException;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import junit.framework.Assert;
+
+public class TestUtils {
+ /**
+ * Converts the specified value from dips to pixels for use as view size.
+ */
+ public static int convertSizeDipsToPixels(DisplayMetrics displayMetrics, int dipValue) {
+ // Round to the nearest int value. This follows the logic in
+ // TypedValue.complexToDimensionPixelSize
+ final int res = (int) (dipValue * displayMetrics.density + 0.5f);
+ if (res != 0) {
+ return res;
+ }
+ if (dipValue == 0) {
+ return 0;
+ }
+ if (dipValue > 0) {
+ return 1;
+ }
+ return -1;
+ }
+
+ /**
+ * Converts the specified value from dips to pixels for use as view offset.
+ */
+ public static int convertOffsetDipsToPixels(DisplayMetrics displayMetrics, int dipValue) {
+ // Round to the nearest int value.
+ return (int) (dipValue * displayMetrics.density);
+ }
+
+
+ /**
+ * Checks whether all the pixels in the specified drawable are of the same specified color.
+ * If the passed <code>Drawable</code> does not have positive intrinsic dimensions set, this
+ * method will throw an <code>IllegalArgumentException</code>. If there is a color mismatch,
+ * this method will call <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ @ColorInt int color) {
+ int drawableWidth = drawable.getIntrinsicWidth();
+ int drawableHeight = drawable.getIntrinsicHeight();
+
+ if ((drawableWidth <= 0) || (drawableHeight <= 0)) {
+ throw new IllegalArgumentException("Drawable must be configured to have non-zero size");
+ }
+
+ assertAllPixelsOfColor(failMessagePrefix, drawable, drawableWidth, drawableHeight, color,
+ false);
+ }
+
+ /**
+ * Checks whether all the pixels in the specified drawable are of the same specified color.
+ *
+ * In case there is a color mismatch, the behavior of this method depends on the
+ * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+ * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+ * <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ int drawableWidth, int drawableHeight, @ColorInt int color,
+ boolean throwExceptionIfFails) {
+ // Create a bitmap
+ Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888);
+ // Create a canvas that wraps the bitmap
+ Canvas canvas = new Canvas(bitmap);
+ // Configure the drawable to have bounds that match its intrinsic size
+ drawable.setBounds(0, 0, drawableWidth, drawableHeight);
+ // And ask the drawable to draw itself to the canvas / bitmap
+ drawable.draw(canvas);
+
+ try {
+ int[] rowPixels = new int[drawableWidth];
+ for (int row = 0; row < drawableHeight; row++) {
+ bitmap.getPixels(rowPixels, 0, drawableWidth, 0, row, drawableWidth, 1);
+ for (int column = 0; column < drawableWidth; column++) {
+ if (rowPixels[column] != color) {
+ String mismatchDescription = failMessagePrefix
+ + ": expected all drawable colors to be ["
+ + Color.red(color) + "," + Color.green(color) + ","
+ + Color.blue(color)
+ + "] but at position (" + row + "," + column + ") found ["
+ + Color.red(rowPixels[column]) + ","
+ + Color.green(rowPixels[column]) + ","
+ + Color.blue(rowPixels[column]) + "]";
+ if (throwExceptionIfFails) {
+ throw new RuntimeException(mismatchDescription);
+ } else {
+ Assert.fail(mismatchDescription);
+ }
+ }
+ }
+ }
+ } finally {
+ bitmap.recycle();
+ }
+ }
+
+ /**
+ * Checks whether the specified rectangle matches the specified left / top / right /
+ * bottom bounds.
+ */
+ public static void assertRectangleBounds(String failMessagePrefix, @NonNull Rect rectangle,
+ int left, int top, int right, int bottom) {
+ Assert.assertEquals(failMessagePrefix + " left", rectangle.left, left);
+ Assert.assertEquals(failMessagePrefix + " top", rectangle.top, top);
+ Assert.assertEquals(failMessagePrefix + " right", rectangle.right, right);
+ Assert.assertEquals(failMessagePrefix + " bottom", rectangle.bottom, bottom);
+ }
+}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/testutils/TestUtilsAssertions.java b/v4/tests/java/android/support/v4/testutils/TestUtilsAssertions.java
new file mode 100644
index 0000000..161c0c7
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/TestUtilsAssertions.java
@@ -0,0 +1,64 @@
+/*
+ * 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.v4.testutils;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import android.support.test.espresso.NoMatchingViewException;
+import android.support.test.espresso.ViewAssertion;
+
+import junit.framework.AssertionFailedError;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+
+public class TestUtilsAssertions {
+
+ /**
+ * Returns an assertion that asserts that there is specified number of fully displayed
+ * children.
+ */
+ public static ViewAssertion hasDisplayedChildren(final int expectedCount) {
+ return new ViewAssertion() {
+ @Override
+ public void check(final View foundView, NoMatchingViewException noViewException) {
+ if (noViewException != null) {
+ throw noViewException;
+ } else {
+ if (!(foundView instanceof ViewGroup)) {
+ throw new AssertionFailedError("View " +
+ foundView.getClass().getSimpleName() + " is not a ViewGroup");
+ }
+ final ViewGroup foundGroup = (ViewGroup) foundView;
+
+ final int childrenCount = foundGroup.getChildCount();
+ int childrenDisplayedCount = 0;
+ for (int i = 0; i < childrenCount; i++) {
+ if (isDisplayed().matches(foundGroup.getChildAt(i))) {
+ childrenDisplayedCount++;
+ }
+ }
+
+ if (childrenDisplayedCount != expectedCount) {
+ throw new AssertionFailedError("Expected " + expectedCount +
+ " displayed children, but found " + childrenDisplayedCount);
+ }
+ }
+ }
+ };
+ }
+}
\ No newline at end of file
diff --git a/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java b/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java
new file mode 100644
index 0000000..c6f07e5
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java
@@ -0,0 +1,272 @@
+/*
+ * 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.v4.testutils;
+
+import java.lang.String;
+import java.util.List;
+
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.v4.testutils.TestUtils;
+import android.support.v4.view.ViewCompat;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import junit.framework.Assert;
+
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+public class TestUtilsMatchers {
+ /**
+ * Returns a matcher that matches views which have specific background color.
+ */
+ public static Matcher backgroundColor(@ColorInt final int backgroundColor) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedComparisonDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("with background color: ");
+
+ description.appendText(failedComparisonDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ Drawable actualBackgroundDrawable = view.getBackground();
+ if (actualBackgroundDrawable == null) {
+ return false;
+ }
+
+ // One option is to check if we have a ColorDrawable and then call getColor
+ // but that API is v11+. Instead, we call our helper method that checks whether
+ // all pixels in a Drawable are of the same specified color. Here we pass
+ // hard-coded dimensions of 40x40 since we can't rely on the intrinsic dimensions
+ // being set on our drawable.
+ try {
+ TestUtils.assertAllPixelsOfColor("", actualBackgroundDrawable,
+ 40, 40, backgroundColor, true);
+ // If we are here, the color comparison has passed.
+ failedComparisonDescription = null;
+ return true;
+ } catch (Throwable t) {
+ // If we are here, the color comparison has failed.
+ failedComparisonDescription = t.getMessage();
+ return false;
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views which are an instance of the provided class.
+ */
+ public static Matcher<View> isOfClass(final Class<? extends View> clazz) {
+ if (clazz == null) {
+ Assert.fail("Passed null Class instance");
+ }
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is identical to class: " + clazz);
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ return clazz.equals(view.getClass());
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views that are aligned to the left / start edge of
+ * their parent.
+ */
+ public static Matcher<View> startAlignedToParent() {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ final ViewParent parent = view.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return false;
+ }
+ final ViewGroup parentGroup = (ViewGroup) parent;
+
+ final int parentLayoutDirection = ViewCompat.getLayoutDirection(parentGroup);
+ if (parentLayoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ if (view.getLeft() == 0) {
+ return true;
+ } else {
+ failedCheckDescription =
+ "not aligned to start (left) edge of parent : left=" +
+ view.getLeft();
+ return false;
+ }
+ } else {
+ if (view.getRight() == parentGroup.getWidth()) {
+ return true;
+ } else {
+ failedCheckDescription =
+ "not aligned to start (right) edge of parent : right=" +
+ view.getRight() + ", parent width=" +
+ parentGroup.getWidth();
+ return false;
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views that are aligned to the right / end edge of
+ * their parent.
+ */
+ public static Matcher<View> endAlignedToParent() {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ final ViewParent parent = view.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return false;
+ }
+ final ViewGroup parentGroup = (ViewGroup) parent;
+
+ final int parentLayoutDirection = ViewCompat.getLayoutDirection(parentGroup);
+ if (parentLayoutDirection == ViewCompat.LAYOUT_DIRECTION_LTR) {
+ if (view.getRight() == parentGroup.getWidth()) {
+ return true;
+ } else {
+ failedCheckDescription =
+ "not aligned to end (right) edge of parent : right=" +
+ view.getRight() + ", parent width=" +
+ parentGroup.getWidth();
+ return false;
+ }
+ } else {
+ if (view.getLeft() == 0) {
+ return true;
+ } else {
+ failedCheckDescription =
+ "not aligned to end (left) edge of parent : left=" +
+ view.getLeft();
+ return false;
+ }
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views that are centered horizontally in their parent.
+ */
+ public static Matcher<View> centerAlignedInParent() {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedCheckDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText(failedCheckDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ final ViewParent parent = view.getParent();
+ if (!(parent instanceof ViewGroup)) {
+ return false;
+ }
+ final ViewGroup parentGroup = (ViewGroup) parent;
+
+ final int viewLeft = view.getLeft();
+ final int viewRight = view.getRight();
+ final int parentWidth = parentGroup.getWidth();
+
+ final int viewMiddle = (viewLeft + viewRight) / 2;
+ final int parentMiddle = parentWidth / 2;
+
+ // Check that the view is centered in its parent, accounting for off-by-one
+ // pixel difference in case one is even and the other is odd.
+ if (Math.abs(viewMiddle - parentMiddle) > 1) {
+ failedCheckDescription =
+ "not aligned to center of parent : own span=[" +
+ viewLeft + "-" + viewRight + "], parent width=" + parentWidth;
+ return false;
+ }
+
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches lists of integer values that match the specified sequence
+ * of values.
+ */
+ public static Matcher<List<Integer>> matches(final int ... expectedValues) {
+ return new TypeSafeMatcher<List<Integer>>() {
+ private String mFailedDescription;
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(mFailedDescription);
+ }
+
+ @Override
+ protected boolean matchesSafely(List<Integer> item) {
+ int actualCount = item.size();
+ int expectedCount = expectedValues.length;
+
+ if (actualCount != expectedCount) {
+ mFailedDescription = "Expected " + expectedCount + " values, but got " +
+ actualCount;
+ return false;
+ }
+
+ for (int i = 0; i < expectedCount; i++) {
+ int curr = item.get(i);
+
+ if (curr != expectedValues[i]) {
+ mFailedDescription = "At #" + i + " got " + curr + " but should be " +
+ expectedValues[i];
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+
+}
diff --git a/v4/tests/java/android/support/v4/testutils/TextViewActions.java b/v4/tests/java/android/support/v4/testutils/TextViewActions.java
new file mode 100644
index 0000000..f67e8c0
--- /dev/null
+++ b/v4/tests/java/android/support/v4/testutils/TextViewActions.java
@@ -0,0 +1,230 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.testutils;
+
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.TextViewCompat;
+import android.view.View;
+import android.widget.TextView;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+public class TextViewActions {
+ /**
+ * Sets max lines count on <code>TextView</code>.
+ */
+ public static ViewAction setMaxLines(final int maxLines) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set max lines";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ textView.setMaxLines(maxLines);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets min lines count on <code>TextView</code>.
+ */
+ public static ViewAction setMinLines(final int minLines) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set min lines";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ textView.setMinLines(minLines);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets text content on <code>TextView</code>.
+ */
+ public static ViewAction setText(final @StringRes int stringResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set text";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ textView.setText(stringResId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets text appearance on <code>TextView</code>.
+ */
+ public static ViewAction setTextAppearance(final @StyleRes int styleResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set text appearance";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ TextViewCompat.setTextAppearance(textView, styleResId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets compound drawables on <code>TextView</code>.
+ */
+ public static ViewAction setCompoundDrawablesRelative(final @Nullable Drawable start,
+ final @Nullable Drawable top, final @Nullable Drawable end,
+ final @Nullable Drawable bottom) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set compound drawables";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ TextViewCompat.setCompoundDrawablesRelative(textView, start, top, end, bottom);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets compound drawables on <code>TextView</code>.
+ */
+ public static ViewAction setCompoundDrawablesRelativeWithIntrinsicBounds(
+ final @Nullable Drawable start, final @Nullable Drawable top,
+ final @Nullable Drawable end, final @Nullable Drawable bottom) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set compound drawables";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ textView, start, top, end, bottom);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets compound drawables on <code>TextView</code>.
+ */
+ public static ViewAction setCompoundDrawablesRelativeWithIntrinsicBounds(
+ final @DrawableRes int start, final @DrawableRes int top, final @DrawableRes int end,
+ final @DrawableRes int bottom) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(TextView.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set compound drawables";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ TextViewCompat.setCompoundDrawablesRelativeWithIntrinsicBounds(
+ textView, start, top, end, bottom);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
index ab32dc5..6dc2042 100644
--- a/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
+++ b/v4/tests/java/android/support/v4/text/BidiFormatterTest.java
@@ -16,12 +16,19 @@
package android.support.v4.text;
-import android.test.AndroidTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.Locale;
-/** @hide */
-public class BidiFormatterTest extends AndroidTestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class BidiFormatterTest {
private static final BidiFormatter LTR_FMT = BidiFormatter.getInstance(false /* LTR context */);
private static final BidiFormatter RTL_FMT = BidiFormatter.getInstance(true /* RTL context */);
@@ -40,6 +47,7 @@
private static final String RLE = "\u202B";
private static final String PDF = "\u202C";
+ @Test
public void testIsRtlContext() {
assertEquals(false, LTR_FMT.isRtlContext());
assertEquals(true, RTL_FMT.isRtlContext());
@@ -48,11 +56,13 @@
assertEquals(true, BidiFormatter.getInstance(true).isRtlContext());
}
+ @Test
public void testBuilderIsRtlContext() {
assertEquals(false, new BidiFormatter.Builder(false).build().isRtlContext());
assertEquals(true, new BidiFormatter.Builder(true).build().isRtlContext());
}
+ @Test
public void testIsRtl() {
assertEquals(true, BidiFormatter.getInstance(true).isRtl(HE));
assertEquals(true, BidiFormatter.getInstance(false).isRtl(HE));
@@ -61,6 +71,7 @@
assertEquals(false, BidiFormatter.getInstance(false).isRtl(EN));
}
+ @Test
public void testUnicodeWrap() {
// Make sure an input of null doesn't crash anything.
assertNull(LTR_FMT.unicodeWrap(null));
@@ -146,14 +157,16 @@
LTR_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
assertEquals("entry and exit dir opposite to LTR context, no isolation",
HE + EN + HE,
- LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+ LTR_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+ false));
assertEquals("entry and exit dir opposite to RTL context",
EN + HE + EN + RLM,
RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
assertEquals("entry and exit dir opposite to RTL context, no isolation",
EN + HE + EN,
- RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+ RTL_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+ false));
// Entry and exit directionality matching context, but with opposite overall directionality.
assertEquals("overall dir (but not entry or exit dir) opposite to LTR context",
@@ -164,7 +177,8 @@
LTR_FMT.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL));
assertEquals("overall dir (but not entry or exit dir) opposite to LTR context, no isolation",
RLE + EN + HE + EN + PDF,
- LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL, false));
+ LTR_FMT_EXIT_RESET.unicodeWrap(EN + HE + EN, TextDirectionHeuristicsCompat.RTL,
+ false));
assertEquals("overall dir (but not entry or exit dir) opposite to RTL context",
LRE + HE + EN + HE + PDF + RLM,
@@ -174,6 +188,7 @@
RTL_FMT.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR));
assertEquals("overall dir (but not entry or exit dir) opposite to RTL context, no isolation",
LRE + HE + EN + HE + PDF,
- RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR, false));
+ RTL_FMT_EXIT_RESET.unicodeWrap(HE + EN + HE, TextDirectionHeuristicsCompat.LTR,
+ false));
}
}
diff --git a/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
new file mode 100644
index 0000000..6fc7234
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
@@ -0,0 +1,979 @@
+/*
+ * 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.v4.view;
+
+import android.app.Activity;
+import android.graphics.Color;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtilsMatchers;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.util.Pair;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.swipeLeft;
+import static android.support.test.espresso.action.ViewActions.swipeRight;
+import static android.support.test.espresso.assertion.PositionAssertions.*;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static android.support.v4.testutils.TestUtilsAssertions.hasDisplayedChildren;
+import static android.support.v4.testutils.TestUtilsMatchers.*;
+import static android.support.v4.view.ViewPagerActions.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
+
+/**
+ * Base class for testing <code>ViewPager</code>. Most of the testing logic should be in this
+ * class as it is independent on the specific pager title implementation (interactive or non
+ * interactive).
+ *
+ * Testing logic that does depend on the specific pager title implementation is pushed into the
+ * extending classes in <code>assertStripInteraction()</code> method.
+ */
+public abstract class BaseViewPagerTest<T extends Activity> extends BaseInstrumentationTestCase<T> {
+ protected ViewPager mViewPager;
+
+ 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 BaseViewPagerTest(Class<T> activityClass) {
+ super(activityClass);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final T activity = mActivityTestRule.getActivity();
+ mViewPager = (ViewPager) activity.findViewById(R.id.pager);
+
+ ColorPagerAdapter adapter = new ColorPagerAdapter();
+ adapter.add("Red", Color.RED);
+ adapter.add("Green", Color.GREEN);
+ adapter.add("Blue", Color.BLUE);
+ onView(withId(R.id.pager)).perform(setAdapter(adapter), scrollToPage(0, false));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ onView(withId(R.id.pager)).perform(setAdapter(null));
+ }
+
+ private void verifyPageSelections(boolean smoothScroll) {
+ assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right", 1, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(1);
+
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right", 2, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
+
+ // Try "scrolling" beyond the last page and test that we're still on the last page.
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right beyond last page", 2, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
+
+ onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
+ assertEquals("Scroll left", 1, mViewPager.getCurrentItem());
+ // Verify that this is the second time we're called on index 1
+ verify(mockPageChangeListener, times(2)).onPageSelected(1);
+
+ onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
+ assertEquals("Scroll left", 0, mViewPager.getCurrentItem());
+ // Verify that this is the first time we're called on index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Try "scrolling" beyond the first page and test that we're still on the first page.
+ onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
+ assertEquals("Scroll left beyond first page", 0, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Unregister our listener
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+
+ // Go from index 0 to index 2
+ onView(withId(R.id.pager)).perform(scrollToPage(2, smoothScroll));
+ assertEquals("Scroll to last page", 2, mViewPager.getCurrentItem());
+ // Our listener is not registered anymore, so we shouldn't have been called with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
+
+ // And back to 0
+ onView(withId(R.id.pager)).perform(scrollToPage(0, smoothScroll));
+ assertEquals("Scroll to first page", 0, mViewPager.getCurrentItem());
+ // Our listener is not registered anymore, so we shouldn't have been called with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Verify the overall sequence of calls to onPageSelected of our listener
+ ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture());
+ assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0));
+ }
+
+ @Test
+ @SmallTest
+ public void testPageSelectionsImmediate() {
+ verifyPageSelections(false);
+ }
+
+ @Test
+ @SmallTest
+ public void testPageSelectionsSmooth() {
+ verifyPageSelections(true);
+ }
+
+ @Test
+ @SmallTest
+ public void testPageSwipes() {
+ assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
+ onView(withId(R.id.pager)).perform(wrap(swipeLeft()));
+ assertEquals("Swipe left", 1, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(1);
+
+ onView(withId(R.id.pager)).perform(wrap(swipeLeft()));
+ assertEquals("Swipe left", 2, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
+
+ // Try swiping beyond the last page and test that we're still on the last page.
+ onView(withId(R.id.pager)).perform(wrap(swipeLeft()));
+ assertEquals("Swipe left beyond last page", 2, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
+
+ onView(withId(R.id.pager)).perform(wrap(swipeRight()));
+ assertEquals("Swipe right", 1, mViewPager.getCurrentItem());
+ // Verify that this is the second time we're called on index 1
+ verify(mockPageChangeListener, times(2)).onPageSelected(1);
+
+ onView(withId(R.id.pager)).perform(wrap(swipeRight()));
+ assertEquals("Swipe right", 0, mViewPager.getCurrentItem());
+ // Verify that this is the first time we're called on index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Try swiping beyond the first page and test that we're still on the first page.
+ onView(withId(R.id.pager)).perform(wrap(swipeRight()));
+ assertEquals("Swipe right beyond first page", 0, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+
+ // Verify the overall sequence of calls to onPageSelected of our listener
+ ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture());
+ assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0));
+ }
+
+ @Test
+ @SmallTest
+ public void testPageSwipesComposite() {
+ assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+
+ onView(withId(R.id.pager)).perform(wrap(swipeLeft()), wrap(swipeLeft()));
+ assertEquals("Swipe twice left", 2, mViewPager.getCurrentItem());
+
+ onView(withId(R.id.pager)).perform(wrap(swipeLeft()), wrap(swipeRight()));
+ assertEquals("Swipe left beyond last page and then right", 1, mViewPager.getCurrentItem());
+
+ onView(withId(R.id.pager)).perform(wrap(swipeRight()), wrap(swipeRight()));
+ assertEquals("Swipe right and then right beyond first page", 0,
+ mViewPager.getCurrentItem());
+
+ onView(withId(R.id.pager)).perform(wrap(swipeRight()), wrap(swipeLeft()));
+ assertEquals("Swipe right beyond first page and then left", 1, mViewPager.getCurrentItem());
+ }
+
+ private void verifyPageContent(boolean smoothScroll) {
+ assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+
+ // Verify the displayed content to match the initial adapter - with 3 pages and each
+ // one rendered as a View.
+
+ // Page #0 should be displayed, page #1 should not be displayed and page #2 should not exist
+ // yet as it's outside of the offscreen window limit.
+ onView(withId(R.id.page_0)).check(matches(allOf(
+ isOfClass(View.class),
+ isDisplayed(),
+ backgroundColor(Color.RED))));
+ onView(withId(R.id.page_1)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_2)).check(doesNotExist());
+
+ // Scroll one page to select page #1
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right", 1, mViewPager.getCurrentItem());
+ // Pages #0 / #2 should not be displayed, page #1 should be displayed.
+ onView(withId(R.id.page_0)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_1)).check(matches(allOf(
+ isOfClass(View.class),
+ isDisplayed(),
+ backgroundColor(Color.GREEN))));
+ onView(withId(R.id.page_2)).check(matches(not(isDisplayed())));
+
+ // Scroll one more page to select page #2
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right again", 2, mViewPager.getCurrentItem());
+ // Page #0 should not exist as it's bumped to the outside of the offscreen window limit,
+ // page #1 should not be displayed, page #2 should be displayed.
+ onView(withId(R.id.page_0)).check(doesNotExist());
+ onView(withId(R.id.page_1)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_2)).check(matches(allOf(
+ isOfClass(View.class),
+ isDisplayed(),
+ backgroundColor(Color.BLUE))));
+ }
+
+ @Test
+ @SmallTest
+ public void testPageContentImmediate() {
+ verifyPageContent(false);
+ }
+
+ @Test
+ @SmallTest
+ public void testPageContentSmooth() {
+ verifyPageContent(true);
+ }
+
+ private void verifyAdapterChange(boolean smoothScroll) {
+ // 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();
+ newAdapter.add("Title 0", "Body 0");
+ newAdapter.add("Title 1", "Body 1");
+ newAdapter.add("Title 2", "Body 2");
+ newAdapter.add("Title 3", "Body 3");
+ onView(withId(R.id.pager)).perform(setAdapter(newAdapter), scrollToPage(0, smoothScroll));
+
+ // Verify the displayed content to match the newly set adapter - with 4 pages and each
+ // one rendered as a TextView.
+
+ // Page #0 should be displayed, page #1 should not be displayed and pages #2 / #3 should not
+ // exist yet as they're outside of the offscreen window limit.
+ onView(withId(R.id.page_0)).check(matches(allOf(
+ isOfClass(TextView.class),
+ isDisplayed(),
+ withText("Body 0"))));
+ onView(withId(R.id.page_1)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_2)).check(doesNotExist());
+ onView(withId(R.id.page_3)).check(doesNotExist());
+
+ // Scroll one page to select page #1
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right", 1, mViewPager.getCurrentItem());
+ // Pages #0 / #2 should not be displayed, page #1 should be displayed, page #3 is still
+ // outside the offscreen limit.
+ onView(withId(R.id.page_0)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_1)).check(matches(allOf(
+ isOfClass(TextView.class),
+ isDisplayed(),
+ withText("Body 1"))));
+ onView(withId(R.id.page_2)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_3)).check(doesNotExist());
+
+ // Scroll one more page to select page #2
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right again", 2, mViewPager.getCurrentItem());
+ // Page #0 should not exist as it's bumped to the outside of the offscreen window limit,
+ // pages #1 / #3 should not be displayed, page #2 should be displayed.
+ onView(withId(R.id.page_0)).check(doesNotExist());
+ onView(withId(R.id.page_1)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_2)).check(matches(allOf(
+ isOfClass(TextView.class),
+ isDisplayed(),
+ withText("Body 2"))));
+ onView(withId(R.id.page_3)).check(matches(not(isDisplayed())));
+
+ // Scroll one more page to select page #2
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ assertEquals("Scroll right one more time", 3, mViewPager.getCurrentItem());
+ // Pages #0 / #1 should not exist as they're bumped to the outside of the offscreen window
+ // limit, page #2 should not be displayed, page #3 should be displayed.
+ onView(withId(R.id.page_0)).check(doesNotExist());
+ onView(withId(R.id.page_1)).check(doesNotExist());
+ onView(withId(R.id.page_2)).check(matches(not(isDisplayed())));
+ onView(withId(R.id.page_3)).check(matches(allOf(
+ isOfClass(TextView.class),
+ isDisplayed(),
+ withText("Body 3"))));
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterChangeImmediate() {
+ verifyAdapterChange(false);
+ }
+
+ @Test
+ @SmallTest
+ public void testAdapterChangeSmooth() {
+ verifyAdapterChange(true);
+ }
+
+ private void verifyTitleStripLayout(String expectedStartTitle, String expectedSelectedTitle,
+ String expectedEndTitle, int selectedPageId) {
+ // Check that the title strip spans the whole width of the pager and is aligned to
+ // its top
+ onView(withId(R.id.titles)).check(isLeftAlignedWith(withId(R.id.pager)));
+ onView(withId(R.id.titles)).check(isRightAlignedWith(withId(R.id.pager)));
+ onView(withId(R.id.titles)).check(isTopAlignedWith(withId(R.id.pager)));
+
+ // Check that the currently selected page spans the whole width of the pager and is below
+ // the title strip
+ onView(withId(selectedPageId)).check(isLeftAlignedWith(withId(R.id.pager)));
+ onView(withId(selectedPageId)).check(isRightAlignedWith(withId(R.id.pager)));
+ onView(withId(selectedPageId)).check(isBelow(withId(R.id.titles)));
+ onView(withId(selectedPageId)).check(isBottomAlignedWith(withId(R.id.pager)));
+
+ boolean hasStartTitle = !TextUtils.isEmpty(expectedStartTitle);
+ boolean hasEndTitle = !TextUtils.isEmpty(expectedEndTitle);
+
+ // Check that the title strip shows the expected number of children (tab titles)
+ int nonNullTitles = (hasStartTitle ? 1 : 0) + 1 + (hasEndTitle ? 1 : 0);
+ onView(withId(R.id.titles)).check(hasDisplayedChildren(nonNullTitles));
+
+ if (hasStartTitle) {
+ // Check that the title for the start page is displayed at the start edge of its parent
+ // (title strip)
+ onView(withId(R.id.titles)).check(matches(hasDescendant(
+ allOf(withText(expectedStartTitle), isDisplayed(), startAlignedToParent()))));
+ }
+ // Check that the title for the selected page is displayed centered in its parent
+ // (title strip)
+ onView(withId(R.id.titles)).check(matches(hasDescendant(
+ allOf(withText(expectedSelectedTitle), isDisplayed(), centerAlignedInParent()))));
+ if (hasEndTitle) {
+ // Check that the title for the end page is displayed at the end edge of its parent
+ // (title strip)
+ onView(withId(R.id.titles)).check(matches(hasDescendant(
+ allOf(withText(expectedEndTitle), isDisplayed(), endAlignedToParent()))));
+ }
+ }
+
+ private void verifyPagerStrip(boolean smoothScroll) {
+ // Set an adapter with 5 pages
+ final ColorPagerAdapter adapter = new ColorPagerAdapter();
+ adapter.add("Red", Color.RED);
+ adapter.add("Green", Color.GREEN);
+ adapter.add("Blue", Color.BLUE);
+ adapter.add("Yellow", Color.YELLOW);
+ adapter.add("Magenta", Color.MAGENTA);
+ onView(withId(R.id.pager)).perform(setAdapter(adapter),
+ scrollToPage(0, smoothScroll));
+
+ // Check that the pager has a title strip
+ onView(withId(R.id.pager)).check(matches(hasDescendant(withId(R.id.titles))));
+ // Check that the title strip is displayed and is of the expected class
+ onView(withId(R.id.titles)).check(matches(allOf(
+ isDisplayed(), isOfClass(getStripClass()))));
+
+ // The following block tests the overall layout of tab strip and main pager content
+ // (vertical stacking), the content of the tab strip (showing texts for the selected
+ // tab and the ones on its left / right) as well as the alignment of the content in the
+ // tab strip (selected in center, others on left and right).
+
+ // Check the content and alignment of title strip for selected page #0
+ verifyTitleStripLayout(null, "Red", "Green", R.id.page_0);
+
+ // Scroll one page to select page #1 and check layout / content of title strip
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ verifyTitleStripLayout("Red", "Green", "Blue", R.id.page_1);
+
+ // Scroll one page to select page #2 and check layout / content of title strip
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ verifyTitleStripLayout("Green", "Blue", "Yellow", R.id.page_2);
+
+ // Scroll one page to select page #3 and check layout / content of title strip
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ verifyTitleStripLayout("Blue", "Yellow", "Magenta", R.id.page_3);
+
+ // Scroll one page to select page #4 and check layout / content of title strip
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+ verifyTitleStripLayout("Yellow", "Magenta", null, R.id.page_4);
+
+ // Scroll back to page #0
+ onView(withId(R.id.pager)).perform(scrollToPage(0, smoothScroll));
+
+ assertStripInteraction(smoothScroll);
+ }
+
+ @Test
+ @SmallTest
+ public void testPagerStripImmediate() {
+ verifyPagerStrip(false);
+ }
+
+ @Test
+ @SmallTest
+ public void testPagerStripSmooth() {
+ verifyPagerStrip(true);
+ }
+
+ /**
+ * Returns the class of the pager strip.
+ */
+ protected abstract Class getStripClass();
+
+ /**
+ * Checks assertions that are specific to the pager strip implementation (interactive or
+ * non interactive).
+ */
+ protected abstract void assertStripInteraction(boolean smoothScroll);
+
+ /**
+ * Helper method that performs the specified action on the <code>ViewPager</code> and then
+ * checks the sequence of calls to the page change listener based on the specified expected
+ * scroll state changes.
+ *
+ * If that expected list is empty, this method verifies that there were no calls to
+ * onPageScrollStateChanged when the action was performed. Otherwise it verifies that the actual
+ * sequence of calls to onPageScrollStateChanged matches the expected (specified) one.
+ */
+ private void verifyScrollStateChange(ViewAction viewAction, int... expectedScrollStateChanges) {
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
+ // Perform our action
+ onView(withId(R.id.pager)).perform(viewAction);
+
+ int expectedScrollStateChangeCount = (expectedScrollStateChanges != null) ?
+ expectedScrollStateChanges.length : 0;
+
+ if (expectedScrollStateChangeCount == 0) {
+ verify(mockPageChangeListener, never()).onPageScrollStateChanged(anyInt());
+ } else {
+ ArgumentCaptor<Integer> pageScrollStateCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, times(expectedScrollStateChangeCount)).
+ onPageScrollStateChanged(pageScrollStateCaptor.capture());
+ assertThat(pageScrollStateCaptor.getAllValues(),
+ TestUtilsMatchers.matches(expectedScrollStateChanges));
+ }
+
+ // Remove our mock listener to get back to clean state for the next test
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testPageScrollStateChangedImmediate() {
+ // Note that all the actions tested in this method are immediate (no scrolling) and
+ // as such we test that we do not get any calls to onPageScrollStateChanged in any of them
+
+ // Select one page to the right
+ verifyScrollStateChange(scrollRight(false));
+ // Select one more page to the right
+ verifyScrollStateChange(scrollRight(false));
+ // Select one page to the left
+ verifyScrollStateChange(scrollLeft(false));
+ // Select one more page to the left
+ verifyScrollStateChange(scrollLeft(false));
+ // Select last page
+ verifyScrollStateChange(scrollToLast(false));
+ // Select first page
+ verifyScrollStateChange(scrollToFirst(false));
+ }
+
+ @Test
+ @MediumTest
+ public void testPageScrollStateChangedSmooth() {
+ // Note that all the actions tested in this method use smooth scrolling and as such we test
+ // that we get the matching calls to onPageScrollStateChanged
+ final int[] expectedScrollStateChanges = new int[] {
+ ViewPager.SCROLL_STATE_SETTLING, ViewPager.SCROLL_STATE_IDLE
+ };
+
+ // Select one page to the right
+ verifyScrollStateChange(scrollRight(true), expectedScrollStateChanges);
+ // Select one more page to the right
+ verifyScrollStateChange(scrollRight(true), expectedScrollStateChanges);
+ // Select one page to the left
+ verifyScrollStateChange(scrollLeft(true), expectedScrollStateChanges);
+ // Select one more page to the left
+ verifyScrollStateChange(scrollLeft(true), expectedScrollStateChanges);
+ // Select last page
+ verifyScrollStateChange(scrollToLast(true), expectedScrollStateChanges);
+ // Select first page
+ verifyScrollStateChange(scrollToFirst(true), expectedScrollStateChanges);
+ }
+
+ @Test
+ @MediumTest
+ public void testPageScrollStateChangedSwipe() {
+ // Note that all the actions tested in this method use swiping and as such we test
+ // that we get the matching calls to onPageScrollStateChanged
+ final int[] expectedScrollStateChanges = new int[] { ViewPager.SCROLL_STATE_DRAGGING,
+ ViewPager.SCROLL_STATE_SETTLING, ViewPager.SCROLL_STATE_IDLE };
+
+ // Swipe one page to the left
+ verifyScrollStateChange(wrap(swipeLeft()), expectedScrollStateChanges);
+ assertEquals("Swipe left", 1, mViewPager.getCurrentItem());
+
+ // Swipe one more page to the left
+ verifyScrollStateChange(wrap(swipeLeft()), expectedScrollStateChanges);
+ assertEquals("Swipe left", 2, mViewPager.getCurrentItem());
+
+ // Swipe one page to the right
+ verifyScrollStateChange(wrap(swipeRight()), expectedScrollStateChanges);
+ assertEquals("Swipe right", 1, mViewPager.getCurrentItem());
+
+ // Swipe one more page to the right
+ verifyScrollStateChange(wrap(swipeRight()), expectedScrollStateChanges);
+ assertEquals("Swipe right", 0, mViewPager.getCurrentItem());
+ }
+
+ /**
+ * Helper method to verify the internal consistency of values passed to
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} callback when we go from a page with
+ * lower index to a page with higher index.
+ *
+ * @param startPageIndex Index of the starting page.
+ * @param endPageIndex Index of the ending page.
+ * @param pageWidth Page width in pixels.
+ * @param positions List of "position" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ * @param positionOffsets List of "positionOffset" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ * @param positionOffsetPixels List of "positionOffsetPixel" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ */
+ private void verifyScrollCallbacksToHigherPage(int startPageIndex, int endPageIndex,
+ int pageWidth, List<Integer> positions, List<Float> positionOffsets,
+ List<Integer> positionOffsetPixels) {
+ int callbackCount = positions.size();
+
+ // The last entry in all three lists must match the index of the end page
+ Assert.assertEquals("Position at last index",
+ endPageIndex, (int) positions.get(callbackCount - 1));
+ Assert.assertEquals("Position offset at last index",
+ 0.0f, positionOffsets.get(callbackCount - 1), 0.0f);
+ Assert.assertEquals("Position offset pixel at last index",
+ 0, (int) positionOffsetPixels.get(callbackCount - 1));
+
+ // If this was our only callback, return. This can happen on immediate page change
+ // or on very slow devices.
+ if (callbackCount == 1) {
+ return;
+ }
+
+ // If we have additional callbacks, verify that the values provided to our callback reflect
+ // a valid sequence of events going from startPageIndex to endPageIndex.
+ for (int i = 0; i < callbackCount - 1; i++) {
+ // Page position must be between start page and end page
+ int pagePositionCurr = positions.get(i);
+ if ((pagePositionCurr < startPageIndex) || (pagePositionCurr > endPageIndex)) {
+ Assert.fail("Position at #" + i + " is " + pagePositionCurr +
+ ", but should be between " + startPageIndex + " and " + endPageIndex);
+ }
+
+ // Page position sequence cannot be decreasing
+ int pagePositionNext = positions.get(i + 1);
+ if (pagePositionCurr > pagePositionNext) {
+ Assert.fail("Position at #" + i + " is " + pagePositionCurr +
+ " and then decreases to " + pagePositionNext + " at #" + (i + 1));
+ }
+
+ // Position offset must be in [0..1) range (inclusive / exclusive)
+ float positionOffsetCurr = positionOffsets.get(i);
+ if ((positionOffsetCurr < 0.0f) || (positionOffsetCurr >= 1.0f)) {
+ Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr +
+ ", but should be in [0..1) range");
+ }
+
+ // Position pixel offset must be in [0..pageWidth) range (inclusive / exclusive)
+ int positionOffsetPixelCurr = positionOffsetPixels.get(i);
+ if ((positionOffsetPixelCurr < 0.0f) || (positionOffsetPixelCurr >= pageWidth)) {
+ Assert.fail("Position pixel offset at #" + i + " is " + positionOffsetCurr +
+ ", but should be in [0.." + pageWidth + ") range");
+ }
+
+ // Position pixel offset must match the position offset and page width within
+ // a one-pixel tolerance range
+ Assert.assertEquals("Position pixel offset at #" + i + " is " +
+ positionOffsetPixelCurr + ", but doesn't match position offset which is" +
+ positionOffsetCurr + " and page width which is " + pageWidth,
+ positionOffsetPixelCurr, positionOffsetCurr * pageWidth, 1.0f);
+
+ // If we stay on the same page between this index and the next one, both position
+ // offset and position pixel offset must increase
+ if (pagePositionNext == pagePositionCurr) {
+ float positionOffsetNext = positionOffsets.get(i + 1);
+ // Note that since position offset sequence is float, we are checking for strict
+ // increasing
+ if (positionOffsetNext <= positionOffsetCurr) {
+ Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr +
+ " and at #" + (i + 1) + " is " + positionOffsetNext +
+ ". Since both are for page " + pagePositionCurr +
+ ", they cannot decrease");
+ }
+
+ int positionOffsetPixelNext = positionOffsetPixels.get(i + 1);
+ // Note that since position offset pixel sequence is the mapping of position offset
+ // into screen pixels, we can get two (or more) callbacks with strictly increasing
+ // position offsets that are converted into the same pixel value. This is why here
+ // we are checking for non-strict increasing
+ if (positionOffsetPixelNext < positionOffsetPixelCurr) {
+ Assert.fail("Position offset pixel at #" + i + " is " +
+ positionOffsetPixelCurr + " and at #" + (i + 1) + " is " +
+ positionOffsetPixelNext + ". Since both are for page " +
+ pagePositionCurr + ", they cannot decrease");
+ }
+ }
+ }
+ }
+
+ /**
+ * Helper method to verify the internal consistency of values passed to
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} callback when we go from a page with
+ * higher index to a page with lower index.
+ *
+ * @param startPageIndex Index of the starting page.
+ * @param endPageIndex Index of the ending page.
+ * @param pageWidth Page width in pixels.
+ * @param positions List of "position" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ * @param positionOffsets List of "positionOffset" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ * @param positionOffsetPixels List of "positionOffsetPixel" values passed to all
+ * {@link ViewPager.OnPageChangeListener#onPageScrolled} calls.
+ */
+ private void verifyScrollCallbacksToLowerPage(int startPageIndex, int endPageIndex,
+ int pageWidth, List<Integer> positions, List<Float> positionOffsets,
+ List<Integer> positionOffsetPixels) {
+ int callbackCount = positions.size();
+
+ // The last entry in all three lists must match the index of the end page
+ Assert.assertEquals("Position at last index",
+ endPageIndex, (int) positions.get(callbackCount - 1));
+ Assert.assertEquals("Position offset at last index",
+ 0.0f, positionOffsets.get(callbackCount - 1), 0.0f);
+ Assert.assertEquals("Position offset pixel at last index",
+ 0, (int) positionOffsetPixels.get(callbackCount - 1));
+
+ // If this was our only callback, return. This can happen on immediate page change
+ // or on very slow devices.
+ if (callbackCount == 1) {
+ return;
+ }
+
+ // If we have additional callbacks, verify that the values provided to our callback reflect
+ // a valid sequence of events going from startPageIndex to endPageIndex.
+ for (int i = 0; i < callbackCount - 1; i++) {
+ // Page position must be between start page and end page
+ int pagePositionCurr = positions.get(i);
+ if ((pagePositionCurr > startPageIndex) || (pagePositionCurr < endPageIndex)) {
+ Assert.fail("Position at #" + i + " is " + pagePositionCurr +
+ ", but should be between " + endPageIndex + " and " + startPageIndex);
+ }
+
+ // Page position sequence cannot be increasing
+ int pagePositionNext = positions.get(i + 1);
+ if (pagePositionCurr < pagePositionNext) {
+ Assert.fail("Position at #" + i + " is " + pagePositionCurr +
+ " and then increases to " + pagePositionNext + " at #" + (i + 1));
+ }
+
+ // Position offset must be in [0..1) range (inclusive / exclusive)
+ float positionOffsetCurr = positionOffsets.get(i);
+ if ((positionOffsetCurr < 0.0f) || (positionOffsetCurr >= 1.0f)) {
+ Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr +
+ ", but should be in [0..1) range");
+ }
+
+ // Position pixel offset must be in [0..pageWidth) range (inclusive / exclusive)
+ int positionOffsetPixelCurr = positionOffsetPixels.get(i);
+ if ((positionOffsetPixelCurr < 0.0f) || (positionOffsetPixelCurr >= pageWidth)) {
+ Assert.fail("Position pixel offset at #" + i + " is " + positionOffsetCurr +
+ ", but should be in [0.." + pageWidth + ") range");
+ }
+
+ // Position pixel offset must match the position offset and page width within
+ // a one-pixel tolerance range
+ Assert.assertEquals("Position pixel offset at #" + i + " is " +
+ positionOffsetPixelCurr + ", but doesn't match position offset which is" +
+ positionOffsetCurr + " and page width which is " + pageWidth,
+ positionOffsetPixelCurr, positionOffsetCurr * pageWidth, 1.0f);
+
+ // If we stay on the same page between this index and the next one, both position
+ // offset and position pixel offset must decrease
+ if (pagePositionNext == pagePositionCurr) {
+ float positionOffsetNext = positionOffsets.get(i + 1);
+ // Note that since position offset sequence is float, we are checking for strict
+ // decreasing
+ if (positionOffsetNext >= positionOffsetCurr) {
+ Assert.fail("Position offset at #" + i + " is " + positionOffsetCurr +
+ " and at #" + (i + 1) + " is " + positionOffsetNext +
+ ". Since both are for page " + pagePositionCurr +
+ ", they cannot increase");
+ }
+
+ int positionOffsetPixelNext = positionOffsetPixels.get(i + 1);
+ // Note that since position offset pixel sequence is the mapping of position offset
+ // into screen pixels, we can get two (or more) callbacks with strictly decreasing
+ // position offsets that are converted into the same pixel value. This is why here
+ // we are checking for non-strict decreasing
+ if (positionOffsetPixelNext > positionOffsetPixelCurr) {
+ Assert.fail("Position offset pixel at #" + i + " is " +
+ positionOffsetPixelCurr + " and at #" + (i + 1) + " is " +
+ positionOffsetPixelNext + ". Since both are for page " +
+ pagePositionCurr + ", they cannot increase");
+ }
+ }
+ }
+ }
+
+ private void verifyScrollCallbacksToHigherPage(ViewAction viewAction,
+ int expectedEndPageIndex) {
+ final int startPageIndex = mViewPager.getCurrentItem();
+
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
+ // Perform our action
+ onView(withId(R.id.pager)).perform(viewAction);
+
+ final int endPageIndex = mViewPager.getCurrentItem();
+ Assert.assertEquals("Current item after action", expectedEndPageIndex, endPageIndex);
+
+ ArgumentCaptor<Integer> positionCaptor = ArgumentCaptor.forClass(int.class);
+ ArgumentCaptor<Float> positionOffsetCaptor = ArgumentCaptor.forClass(float.class);
+ ArgumentCaptor<Integer> positionOffsetPixelsCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, atLeastOnce()).onPageScrolled(positionCaptor.capture(),
+ positionOffsetCaptor.capture(), positionOffsetPixelsCaptor.capture());
+
+ verifyScrollCallbacksToHigherPage(startPageIndex, endPageIndex, mViewPager.getWidth(),
+ positionCaptor.getAllValues(), positionOffsetCaptor.getAllValues(),
+ positionOffsetPixelsCaptor.getAllValues());
+
+ // Remove our mock listener to get back to clean state for the next test
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+ }
+
+ private void verifyScrollCallbacksToLowerPage(ViewAction viewAction,
+ int expectedEndPageIndex) {
+ final int startPageIndex = mViewPager.getCurrentItem();
+
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
+ // Perform our action
+ onView(withId(R.id.pager)).perform(viewAction);
+
+ final int endPageIndex = mViewPager.getCurrentItem();
+ Assert.assertEquals("Current item after action", expectedEndPageIndex, endPageIndex);
+
+ ArgumentCaptor<Integer> positionCaptor = ArgumentCaptor.forClass(int.class);
+ ArgumentCaptor<Float> positionOffsetCaptor = ArgumentCaptor.forClass(float.class);
+ ArgumentCaptor<Integer> positionOffsetPixelsCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, atLeastOnce()).onPageScrolled(positionCaptor.capture(),
+ positionOffsetCaptor.capture(), positionOffsetPixelsCaptor.capture());
+
+ verifyScrollCallbacksToLowerPage(startPageIndex, endPageIndex, mViewPager.getWidth(),
+ positionCaptor.getAllValues(), positionOffsetCaptor.getAllValues(),
+ positionOffsetPixelsCaptor.getAllValues());
+
+ // Remove our mock listener to get back to clean state for the next test
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testPageScrollPositionChangesImmediate() {
+ // Scroll one page to the right
+ verifyScrollCallbacksToHigherPage(scrollRight(false), 1);
+ // Scroll one more page to the right
+ verifyScrollCallbacksToHigherPage(scrollRight(false), 2);
+ // Scroll one page to the left
+ verifyScrollCallbacksToLowerPage(scrollLeft(false), 1);
+ // Scroll one more page to the left
+ verifyScrollCallbacksToLowerPage(scrollLeft(false), 0);
+
+ // Scroll to the last page
+ verifyScrollCallbacksToHigherPage(scrollToLast(false), 2);
+ // Scroll to the first page
+ verifyScrollCallbacksToLowerPage(scrollToFirst(false), 0);
+ }
+
+ @Test
+ @MediumTest
+ public void testPageScrollPositionChangesSmooth() {
+ // Scroll one page to the right
+ verifyScrollCallbacksToHigherPage(scrollRight(true), 1);
+ // Scroll one more page to the right
+ verifyScrollCallbacksToHigherPage(scrollRight(true), 2);
+ // Scroll one page to the left
+ verifyScrollCallbacksToLowerPage(scrollLeft(true), 1);
+ // Scroll one more page to the left
+ verifyScrollCallbacksToLowerPage(scrollLeft(true), 0);
+
+ // Scroll to the last page
+ verifyScrollCallbacksToHigherPage(scrollToLast(true), 2);
+ // Scroll to the first page
+ verifyScrollCallbacksToLowerPage(scrollToFirst(true), 0);
+ }
+
+ @Test
+ @MediumTest
+ public void testPageScrollPositionChangesSwipe() {
+ // Swipe one page to the left
+ verifyScrollCallbacksToHigherPage(wrap(swipeLeft()), 1);
+ // Swipe one more page to the left
+ verifyScrollCallbacksToHigherPage(wrap(swipeLeft()), 2);
+ // Swipe one page to the right
+ verifyScrollCallbacksToLowerPage(wrap(swipeRight()), 1);
+ // Swipe one more page to the right
+ verifyScrollCallbacksToLowerPage(wrap(swipeRight()), 0);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/GravityCompatTest.java b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
new file mode 100644
index 0000000..36e557a
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/GravityCompatTest.java
@@ -0,0 +1,327 @@
+/*
+ * 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.v4.view;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v4.testutils.TestUtils;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Gravity;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GravityCompatTest {
+ @Test
+ public void testConstants() {
+ // Compat constants must match core constants since they can be OR'd with
+ // other core constants.
+ assertEquals("Start constants", Gravity.START, GravityCompat.START);
+ assertEquals("End constants", Gravity.END, GravityCompat.END);
+ }
+
+ @Test
+ public void testGetAbsoluteGravity() {
+ assertEquals("Left under LTR",
+ GravityCompat.getAbsoluteGravity(Gravity.LEFT, ViewCompat.LAYOUT_DIRECTION_LTR),
+ Gravity.LEFT);
+ assertEquals("Right under LTR",
+ GravityCompat.getAbsoluteGravity(Gravity.RIGHT, ViewCompat.LAYOUT_DIRECTION_LTR),
+ Gravity.RIGHT);
+ assertEquals("Left under RTL",
+ GravityCompat.getAbsoluteGravity(Gravity.LEFT, ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.LEFT);
+ assertEquals("Right under RTL",
+ GravityCompat.getAbsoluteGravity(Gravity.RIGHT, ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.RIGHT);
+
+ assertEquals("Start under LTR",
+ GravityCompat.getAbsoluteGravity(GravityCompat.START,
+ ViewCompat.LAYOUT_DIRECTION_LTR),
+ Gravity.LEFT);
+ assertEquals("End under LTR",
+ GravityCompat.getAbsoluteGravity(GravityCompat.END,
+ ViewCompat.LAYOUT_DIRECTION_LTR),
+ Gravity.RIGHT);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ // The following tests are only expected to pass on v17+ devices
+ assertEquals("Start under RTL",
+ GravityCompat.getAbsoluteGravity(GravityCompat.START,
+ ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.RIGHT);
+ assertEquals("End under RTL",
+ GravityCompat.getAbsoluteGravity(GravityCompat.END,
+ ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.LEFT);
+ } else {
+ // And on older devices START is always LEFT, END is always RIGHT
+ assertEquals("Start under RTL",
+ GravityCompat.getAbsoluteGravity(GravityCompat.START,
+ ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.LEFT);
+ assertEquals("End under RTL",
+ GravityCompat.getAbsoluteGravity(GravityCompat.END,
+ ViewCompat.LAYOUT_DIRECTION_RTL),
+ Gravity.RIGHT);
+ }
+ }
+
+ @Test
+ public void testApplyNoOffsetsLtr() {
+ Rect outRect = new Rect();
+
+ // Left / top aligned under LTR direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Left / top aligned under LTR: ",
+ outRect, 0, 0, 100, 50);
+
+ // Center / top aligned under LTR direction
+ GravityCompat.apply(Gravity.CENTER_HORIZONTAL | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Center / top aligned under LTR: ",
+ outRect, 50, 0, 150, 50);
+
+ // Right / top aligned under LTR direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Right / top aligned under LTR: ",
+ outRect, 100, 0, 200, 50);
+
+ // Left / center aligned under LTR direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Left / center aligned under LTR: ",
+ outRect, 0, 25, 100, 75);
+
+ // Center / center aligned under LTR direction
+ GravityCompat.apply(Gravity.CENTER, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Center / center aligned under LTR: ",
+ outRect, 50, 25, 150, 75);
+
+ // Right / center aligned under LTR direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Right / center aligned under LTR: ",
+ outRect, 100, 25, 200, 75);
+
+ // Left / bottom aligned under LTR direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Left / bottom aligned under LTR: ",
+ outRect, 0, 50, 100, 100);
+
+ // Center / bottom aligned under LTR direction
+ GravityCompat.apply(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Center / bottom aligned under LTR: ",
+ outRect, 50, 50, 150, 100);
+
+ // Right / bottom aligned under LTR direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Right / bottom aligned under LTR: ",
+ outRect, 100, 50, 200, 100);
+
+ // The following tests are expected to pass on all devices since START under LTR is LEFT
+ // and END under LTR is RIGHT on pre-v17 and v17+ versions of the platform.
+
+ // Start / top aligned under LTR direction
+ GravityCompat.apply(GravityCompat.START | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Start / top aligned under LTR: ",
+ outRect, 0, 0, 100, 50);
+
+ // End / top aligned under LTR direction
+ GravityCompat.apply(GravityCompat.END | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("End / top aligned under LTR: ",
+ outRect, 100, 0, 200, 50);
+
+ // Start / center aligned under LTR direction
+ GravityCompat.apply(GravityCompat.START | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Start / center aligned under LTR: ",
+ outRect, 0, 25, 100, 75);
+
+ // End / center aligned under LTR direction
+ GravityCompat.apply(GravityCompat.END | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("End / center aligned under LTR: ",
+ outRect, 100, 25, 200, 75);
+
+ // Start / bottom aligned under LTR direction
+ GravityCompat.apply(GravityCompat.START | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("Start / bottom aligned under LTR: ",
+ outRect, 0, 50, 100, 100);
+
+ // End / bottom aligned under LTR direction
+ GravityCompat.apply(GravityCompat.END | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_LTR);
+ TestUtils.assertRectangleBounds("End / bottom aligned under LTR: ",
+ outRect, 100, 50, 200, 100);
+ }
+
+ @Test
+ public void testApplyNoOffsetsRtl() {
+ Rect outRect = new Rect();
+
+ // The following tests are expected to pass on all devices since they are using
+ // Gravity constants that are not RTL-aware
+
+ // Left / top aligned under RTL direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Left / top aligned under RTL: ",
+ outRect, 0, 0, 100, 50);
+
+ // Center / top aligned under RTL direction
+ GravityCompat.apply(Gravity.CENTER_HORIZONTAL | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Center / top aligned under RTL: ",
+ outRect, 50, 0, 150, 50);
+
+ // Right / top aligned under RTL direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Right / top aligned under RTL: ",
+ outRect, 100, 0, 200, 50);
+
+ // Left / center aligned under RTL direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Left / center aligned under RTL: ",
+ outRect, 0, 25, 100, 75);
+
+ // Center / center aligned under RTL direction
+ GravityCompat.apply(Gravity.CENTER, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Center / center aligned under RTL: ",
+ outRect, 50, 25, 150, 75);
+
+ // Right / center aligned under RTL direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Right / center aligned under RTL: ",
+ outRect, 100, 25, 200, 75);
+
+ // Left / bottom aligned under RTL direction
+ GravityCompat.apply(Gravity.LEFT | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Left / bottom aligned under RTL: ",
+ outRect, 0, 50, 100, 100);
+
+ // Center / bottom aligned under RTL direction
+ GravityCompat.apply(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Center / bottom aligned under RTL: ",
+ outRect, 50, 50, 150, 100);
+
+ // Right / bottom aligned under RTL direction
+ GravityCompat.apply(Gravity.RIGHT | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Right / bottom aligned under RTL: ",
+ outRect, 100, 50, 200, 100);
+
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ // The following tests are only expected to pass on v17+ devices since START under
+ // RTL is RIGHT and END under RTL is LEFT only on those devices.
+
+ // Start / top aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / top aligned under RTL: ",
+ outRect, 100, 0, 200, 50);
+
+ // End / top aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / top aligned under RTL: ",
+ outRect, 0, 0, 100, 50);
+
+ // Start / center aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / center aligned under RTL: ",
+ outRect, 100, 25, 200, 75);
+
+ // End / center aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / center aligned under RTL: ",
+ outRect, 0, 25, 100, 75);
+
+ // Start / bottom aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / bottom aligned under RTL: ",
+ outRect, 100, 50, 200, 100);
+
+ // End / bottom aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / bottom aligned under RTL: ",
+ outRect, 0, 50, 100, 100);
+ } else {
+ // And on older devices START is always LEFT, END is always RIGHT
+
+ // Start / top aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / top aligned under RTL: ",
+ outRect, 0, 0, 100, 50);
+
+ // End / top aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.TOP, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / top aligned under RTL: ",
+ outRect, 100, 0, 200, 50);
+
+ // Start / center aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / center aligned under RTL: ",
+ outRect, 0, 25, 100, 75);
+
+ // End / center aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.CENTER_VERTICAL, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / center aligned under RTL: ",
+ outRect, 100, 25, 200, 75);
+
+ // Start / bottom aligned under RTL direction
+ GravityCompat.apply(GravityCompat.START | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("Start / bottom aligned under RTL: ",
+ outRect, 0, 50, 100, 100);
+
+ // End / bottom aligned under RTL direction
+ GravityCompat.apply(GravityCompat.END | Gravity.BOTTOM, 100, 50,
+ new Rect(0, 0, 200, 100), outRect, ViewCompat.LAYOUT_DIRECTION_RTL);
+ TestUtils.assertRectangleBounds("End / bottom aligned under RTL: ",
+ outRect, 100, 50, 200, 100);
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
new file mode 100644
index 0000000..48d6dab
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/MarginLayoutParamsCompatTest.java
@@ -0,0 +1,183 @@
+/*
+ * 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.v4.view;
+
+import android.os.Build;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ViewGroup;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class MarginLayoutParamsCompatTest {
+ @Test
+ public void testLayoutDirection() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ assertEquals("Default LTR layout direction", ViewCompat.LAYOUT_DIRECTION_LTR,
+ MarginLayoutParamsCompat.getLayoutDirection(mlp));
+
+ MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
+ if (Build.VERSION.SDK_INT >= 17) {
+ assertEquals("RTL layout direction", ViewCompat.LAYOUT_DIRECTION_RTL,
+ MarginLayoutParamsCompat.getLayoutDirection(mlp));
+ } else {
+ assertEquals("Still LTR layout direction on older devices",
+ ViewCompat.LAYOUT_DIRECTION_LTR,
+ MarginLayoutParamsCompat.getLayoutDirection(mlp));
+ }
+
+ MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_LTR);
+ assertEquals("Back to LTR layout direction", ViewCompat.LAYOUT_DIRECTION_LTR,
+ MarginLayoutParamsCompat.getLayoutDirection(mlp));
+ }
+
+ @Test
+ public void testMappingOldMarginsToNewMarginsLtr() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ mlp.leftMargin = 50;
+ mlp.rightMargin = 80;
+
+ assertEquals("Mapping left to start under LTR", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ assertEquals("Mapping right to end under LTR", 80,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+ }
+
+ @Test
+ public void testMappingOldMarginsToNewMarginsRtl() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ mlp.leftMargin = 50;
+ mlp.rightMargin = 80;
+
+ MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ assertEquals("Mapping right to start under RTL", 80,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ assertEquals("Mapping left to end under RTL", 50,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+ } else {
+ assertEquals("Mapping left to start under RTL on older devices", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ assertEquals("Mapping right to end under RTL on older devices", 80,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+ }
+ }
+
+ @Test
+ public void testMappingNewMarginsToNewMarginsLtr() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ MarginLayoutParamsCompat.setMarginStart(mlp, 50);
+ assertEquals("Resolved start margin under LTR", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ // Check that initial end / right margins are still 0
+ assertEquals("Default end margin under LTR", 0,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+
+ MarginLayoutParamsCompat.setMarginEnd(mlp, 80);
+ assertEquals("Resolved end margin under LTR", 80,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+ // Check that start / left margins are still the same
+ assertEquals("Keeping start margin under LTR", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ }
+
+ @Test
+ public void testMappingNewMarginsToNewMarginsRtl() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ // Note that unlike the test that checks mapping of left/right to start/end and has
+ // to do platform-specific checks, the checks in this test are platform-agnostic,
+ // relying on the relevant MarginLayoutParamsCompat to do the right mapping internally.
+
+ MarginLayoutParamsCompat.setLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
+ MarginLayoutParamsCompat.setMarginStart(mlp, 50);
+
+ assertEquals("Resolved start margin under RTL", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ // Check that initial end / right margins are still 0
+ assertEquals("Default end margin under RTL", 0,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+
+ MarginLayoutParamsCompat.setMarginEnd(mlp, 80);
+ assertEquals("Resolved end margin under RTL", 80,
+ MarginLayoutParamsCompat.getMarginEnd(mlp));
+ // Check that start / left margins are still the same
+ assertEquals("Keeping start margin under RTL", 50,
+ MarginLayoutParamsCompat.getMarginStart(mlp));
+ }
+
+ @Test
+ public void testResolveMarginsLtr() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ MarginLayoutParamsCompat.setMarginStart(mlp, 50);
+ MarginLayoutParamsCompat.resolveLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_LTR);
+
+ // While there's no guarantee that left/right margin fields have been set / resolved
+ // prior to the resolveLayoutDirection call, they should be now
+ assertEquals("Resolved left margin field under LTR", 50, mlp.leftMargin);
+ assertEquals("Default right margin field under LTR", 0, mlp.rightMargin);
+
+ MarginLayoutParamsCompat.setMarginEnd(mlp, 80);
+ MarginLayoutParamsCompat.resolveLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_LTR);
+
+ assertEquals("Resolved right margin field under LTR", 80, mlp.rightMargin);
+ assertEquals("Keeping left margin field under LTR", 50, mlp.leftMargin);
+ }
+
+ @Test
+ public void testResolveMarginsRtl() {
+ ViewGroup.MarginLayoutParams mlp = new ViewGroup.MarginLayoutParams(0, 0);
+
+ MarginLayoutParamsCompat.setMarginStart(mlp, 50);
+ MarginLayoutParamsCompat.resolveLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
+
+ // While there's no guarantee that left/right margin fields have been set / resolved
+ // prior to the resolveLayoutDirection call, they should be now
+ if (Build.VERSION.SDK_INT >= 17) {
+ assertEquals("Default left margin field under RTL", 0, mlp.leftMargin);
+ assertEquals("Resolved right margin field under RTL", 50, mlp.rightMargin);
+ } else {
+ assertEquals("Resolved left margin field under RTL on older devices",
+ 50, mlp.leftMargin);
+ assertEquals("Default right margin field under RTL on older devices",
+ 0, mlp.rightMargin);
+ }
+
+ MarginLayoutParamsCompat.setMarginEnd(mlp, 80);
+ MarginLayoutParamsCompat.resolveLayoutDirection(mlp, ViewCompat.LAYOUT_DIRECTION_RTL);
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ assertEquals("Resolved left margin field under RTL", 80, mlp.leftMargin);
+ assertEquals("Keeping right margin field under RTL", 50, mlp.rightMargin);
+ } else {
+ assertEquals("Resolved right margin field under RTL on older devices",
+ 80, mlp.rightMargin);
+ assertEquals("Keeping left margin field under RTL on older devices",
+ 50, mlp.leftMargin);
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewCompatTest.java b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
new file mode 100644
index 0000000..c4217a5
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewCompatTest.java
@@ -0,0 +1,37 @@
+/*
+ * 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.v4.view;
+
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class ViewCompatTest {
+ @Test
+ public void testConstants() {
+ // Compat constants must match core constants since they can be used interchangeably
+ // in various support lib calls.
+ assertEquals("LTR constants", View.LAYOUT_DIRECTION_LTR, ViewCompat.LAYOUT_DIRECTION_LTR);
+ assertEquals("RTL constants", View.LAYOUT_DIRECTION_RTL, ViewCompat.LAYOUT_DIRECTION_RTL);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerActions.java b/v4/tests/java/android/support/v4/view/ViewPagerActions.java
new file mode 100644
index 0000000..0b64d16
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPagerActions.java
@@ -0,0 +1,360 @@
+/*
+ * 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.v4.view;
+
+import android.support.annotation.Nullable;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+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.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 {
+ /**
+ * View pager listener that serves as Espresso's {@link IdlingResource} and notifies the
+ * registered callback when the view pager gets to STATE_IDLE state.
+ */
+ private static class CustomViewPagerListener
+ implements ViewPager.OnPageChangeListener, IdlingResource {
+ private int mCurrState = ViewPager.SCROLL_STATE_IDLE;
+
+ @Nullable
+ private IdlingResource.ResourceCallback mCallback;
+
+ private boolean mNeedsIdle = false;
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ mCallback = resourceCallback;
+ }
+
+ @Override
+ public String getName() {
+ return "View pager listener";
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (!mNeedsIdle) {
+ return true;
+ } else {
+ return mCurrState == ViewPager.SCROLL_STATE_IDLE;
+ }
+ }
+
+ @Override
+ public void onPageSelected(int position) {
+ if (mCurrState == ViewPager.SCROLL_STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ @Override
+ public void onPageScrollStateChanged(int state) {
+ mCurrState = state;
+ if (mCurrState == ViewPager.SCROLL_STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ @Override
+ public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
+ }
+ }
+
+ private abstract static class WrappedViewAction implements ViewAction {
+ }
+
+ public static ViewAction wrap(final ViewAction baseAction) {
+ if (baseAction instanceof WrappedViewAction) {
+ throw new IllegalArgumentException("Don't wrap an already wrapped action");
+ }
+
+ return new WrappedViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return baseAction.getConstraints();
+ }
+
+ @Override
+ public String getDescription() {
+ return baseAction.getDescription();
+ }
+
+ @Override
+ public final void perform(UiController uiController, View view) {
+ final ViewPager viewPager = (ViewPager) view;
+ // Add a custom tracker listener
+ final CustomViewPagerListener customListener = new CustomViewPagerListener();
+ viewPager.addOnPageChangeListener(customListener);
+
+ // Note that we're running the following block in a try-finally construct. This
+ // is needed since some of the wrapped actions are going to throw (expected)
+ // exceptions. If that happens, we still need to clean up after ourselves to
+ // leave the system (Espesso) in a good state.
+ try {
+ // Register our listener as idling resource so that Espresso waits until the
+ // wrapped action results in the view pager getting to the STATE_IDLE state
+ Espresso.registerIdlingResources(customListener);
+ baseAction.perform(uiController, view);
+ customListener.mNeedsIdle = true;
+ uiController.loopMainThreadUntilIdle();
+ customListener.mNeedsIdle = false;
+ } finally {
+ // Unregister our idling resource
+ Espresso.unregisterIdlingResources(customListener);
+ // And remove our tracker listener from ViewPager
+ viewPager.removeOnPageChangeListener(customListener);
+ }
+ }
+ };
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the right by one page.
+ */
+ public static ViewAction scrollRight(final boolean smoothScroll) {
+ return wrap(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;
+ int current = viewPager.getCurrentItem();
+ viewPager.setCurrentItem(current + 1, smoothScroll);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the left by one page.
+ */
+ public static ViewAction scrollLeft(final boolean smoothScroll) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager move 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, smoothScroll);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the last page.
+ */
+ public static ViewAction scrollToLast(final boolean smoothScroll) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager move 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, smoothScroll);
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to the first page.
+ */
+ public static ViewAction scrollToFirst(final boolean smoothScroll) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager move 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, smoothScroll);
+ }
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to specific page.
+ */
+ public static ViewAction scrollToPage(final int page, final boolean smoothScroll) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayingAtLeast(90);
+ }
+
+ @Override
+ public String getDescription() {
+ return "ViewPager move to page";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewPager viewPager = (ViewPager) view;
+ viewPager.setCurrentItem(page, smoothScroll);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Moves <code>ViewPager</code> to specific page.
+ */
+ public static ViewAction setAdapter(final 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();
+ }
+ };
+ }
+
+ /**
+ * Clicks between two titles in a <code>ViewPager</code> title strip
+ */
+ public static ViewAction clickBetweenTwoTitles(final String title1, final String title2) {
+ return new GeneralClickAction(
+ Tap.SINGLE,
+ new CoordinatesProvider() {
+ @Override
+ public float[] calculateCoordinates(View view) {
+ PagerTitleStrip pagerStrip = (PagerTitleStrip) view;
+
+ // Get the screen position of the pager strip
+ final int[] viewScreenPosition = new int[2];
+ pagerStrip.getLocationOnScreen(viewScreenPosition);
+
+ // Get the left / right of the first title
+ int title1Left = 0, title1Right = 0, title2Left = 0, title2Right = 0;
+ final int childCount = pagerStrip.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = pagerStrip.getChildAt(i);
+ if (child instanceof TextView) {
+ final TextView textViewChild = (TextView) child;
+ final CharSequence childText = textViewChild.getText();
+ if (title1.equals(childText)) {
+ title1Left = textViewChild.getLeft();
+ title1Right = textViewChild.getRight();
+ } else if (title2.equals(childText)) {
+ title2Left = textViewChild.getLeft();
+ title2Right = textViewChild.getRight();
+ }
+ }
+ }
+
+ if (title1Right < title2Left) {
+ // Title 1 is to the left of title 2
+ return new float[] {
+ viewScreenPosition[0] + (title1Right + title2Left) / 2,
+ viewScreenPosition[1] + pagerStrip.getHeight() / 2 };
+ } else {
+ // The assumption here is that PagerTitleStrip prevents titles
+ // from overlapping, so if we get here it means that title 1
+ // is to the right of title 2
+ return new float[] {
+ viewScreenPosition[0] + (title2Right + title1Left) / 2,
+ viewScreenPosition[1] + pagerStrip.getHeight() / 2 };
+ }
+ }
+ },
+ Press.FINGER);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java
new file mode 100644
index 0000000..2cb3048
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v4.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.test.R;
+import android.view.WindowManager;
+
+public class ViewPagerWithTabStripActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(R.layout.view_pager_with_tab_strip);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java
new file mode 100644
index 0000000..91d1e06
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTabStripTest.java
@@ -0,0 +1,93 @@
+/*
+ * 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.v4.view;
+
+import android.support.v4.test.R;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v4.view.ViewPagerActions.clickBetweenTwoTitles;
+import static android.support.v4.view.ViewPagerActions.scrollRight;
+import static android.support.v4.view.ViewPagerActions.scrollToPage;
+import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Provides assertions that depend on the interactive nature of <code>PagerTabStrip</code>.
+ */
+public class ViewPagerWithTabStripTest extends BaseViewPagerTest<ViewPagerWithTabStripActivity> {
+ public ViewPagerWithTabStripTest() {
+ super(ViewPagerWithTabStripActivity.class);
+ }
+
+ @Override
+ protected Class getStripClass() {
+ return PagerTabStrip.class;
+ }
+
+ @Override
+ protected void assertStripInteraction(boolean smoothScroll) {
+ // The following block tests that ViewPager page selection changes on clicking titles of
+ // various tabs as PagerTabStrip is interactive
+
+ // Click the tab title for page #0 and verify that we're still on page #0
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Red"))).perform(click());
+ assertEquals("Click tab #0 on tab #0", 0, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #1 and verify that we're on page #1
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Green"))).perform(click());
+ assertEquals("Click tab #1 on tab #0", 1, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #0 and verify that we're on page #0
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Red"))).perform(click());
+ assertEquals("Click tab #0 on tab #1", 0, mViewPager.getCurrentItem());
+
+ // Go back to page #1
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+
+ // Click the tab title for page #1 and verify that we're still on page #1
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Green"))).perform(click());
+ assertEquals("Click tab #1 on tab #1", 1, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #2 and verify that we're on page #2
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Blue"))).perform(click());
+ assertEquals("Click tab #2 on tab #1", 2, mViewPager.getCurrentItem());
+
+ // The following block tests that ViewPager page selection changes on clicking in
+ // between titles of tabs as that functionality is exposed by PagerTabStrip
+
+ // Scroll back to page #0
+ onView(withId(R.id.pager)).perform(scrollToPage(0, smoothScroll));
+
+ // Click between titles of page #0 and page #1 and verify that we're on page #1
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Red", "Green"));
+ assertEquals("Click in between tabs #0 and #1 on tab #0", 1, mViewPager.getCurrentItem());
+
+ // Click between titles of page #0 and page #1 and verify that we're on page #0
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Red", "Green"));
+ assertEquals("Click in between tabs #0 and #1 on tab #1", 0, mViewPager.getCurrentItem());
+
+ // Go back to page #1
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+
+ // Click between titles of page #1 and page #2 and verify that we're on page #2
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Green", "Blue"));
+ assertEquals("Click in between tabs #1 and #2 on tab #1", 2, mViewPager.getCurrentItem());
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java
new file mode 100644
index 0000000..49d6e76
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.v4.view;
+
+import android.app.Activity;
+import android.os.Bundle;
+import android.support.v4.test.R;
+import android.view.WindowManager;
+
+public class ViewPagerWithTitleStripActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ setContentView(R.layout.view_pager_with_title_strip);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java
new file mode 100644
index 0000000..ec7c35c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPagerWithTitleStripTest.java
@@ -0,0 +1,94 @@
+/*
+ * 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.v4.view;
+
+import android.support.v4.test.R;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.action.ViewActions.click;
+import static android.support.test.espresso.matcher.ViewMatchers.isDescendantOfA;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v4.view.ViewPagerActions.clickBetweenTwoTitles;
+import static android.support.v4.view.ViewPagerActions.scrollRight;
+import static android.support.v4.view.ViewPagerActions.scrollToPage;
+import static org.hamcrest.Matchers.allOf;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * Provides assertions that depend on the non-interactive nature of <code>PagerTabStrip</code>.
+ */
+public class ViewPagerWithTitleStripTest
+ extends BaseViewPagerTest<ViewPagerWithTitleStripActivity> {
+ public ViewPagerWithTitleStripTest() {
+ super(ViewPagerWithTitleStripActivity.class);
+ }
+
+ @Override
+ protected Class getStripClass() {
+ return PagerTitleStrip.class;
+ }
+
+ @Override
+ protected void assertStripInteraction(boolean smoothScroll) {
+ // The following block tests that nothing happens on clicking titles of various tabs
+ // as PagerTitleStrip is not interactive
+
+ // Click the tab title for page #0 and verify that we're still on page #0
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Red"))).perform(click());
+ assertEquals("Click tab #0 on tab #0", 0, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #1 and verify that we're still on page #0
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Green"))).perform(click());
+ assertEquals("Click tab #1 on tab #0", 0, mViewPager.getCurrentItem());
+
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+
+ // Click the tab title for page #0 and verify that we're still on page #1
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Red"))).perform(click());
+ assertEquals("Click tab #0 on tab #1", 1, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #1 and verify that we're still on page #1
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Green"))).perform(click());
+ assertEquals("Click tab #1 on tab #1", 1, mViewPager.getCurrentItem());
+
+ // Click the tab title for page #2 and verify that we're still on page #1
+ onView(allOf(isDescendantOfA(withId(R.id.titles)), withText("Blue"))).perform(click());
+ assertEquals("Click tab #2 on tab #1", 1, mViewPager.getCurrentItem());
+
+
+ // The following block tests that nothing happens on clicking in between titles of various
+ // tabs as PagerTitleStrip is not interactive
+
+ // Scroll back to page #0
+ onView(withId(R.id.pager)).perform(scrollToPage(0, smoothScroll));
+
+ // Click between titles of page #0 and page #1 and verify that we're still on page #0
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Red", "Green"));
+ assertEquals("Click in between tabs #0 and #1 on tab #0", 0, mViewPager.getCurrentItem());
+
+ // Go to page #1
+ onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
+
+ // Click between titles of page #1 and page #2 and verify that we're still on page #1
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Green", "Blue"));
+ assertEquals("Click in between tabs #1 and #2 on tab #1", 1, mViewPager.getCurrentItem());
+
+ // Click between titles of page #0 and page #1 and verify that we're still on page #1
+ onView(withId(R.id.titles)).perform(clickBetweenTwoTitles("Red", "Green"));
+ assertEquals("Click in between tabs #0 and #1 on tab #1", 1, mViewPager.getCurrentItem());
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
new file mode 100644
index 0000000..84be6ec
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.app.Activity;
+import android.support.test.InstrumentationRegistry;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.Before;
+import org.junit.Test;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
+
+ private static final int WAIT_TIMEOUT_MS = 200;
+
+ private View mView;
+ private int mNumListenerCalls = 0;
+
+ public ViewPropertyAnimatorCompatTest() {
+ super(VpaActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ final Activity activity = mActivityTestRule.getActivity();
+ mView = activity.findViewById(R.id.view);
+ }
+
+ @Test
+ public void testWithEndAction() throws Throwable {
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(100).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ latch1.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch1.await(300, TimeUnit.MILLISECONDS));
+
+ // This test ensures that the endAction listener will be called exactly once
+ mNumListenerCalls = 0;
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(50).withEndAction(new Runnable() {
+ @Override
+ public void run() {
+ ++mNumListenerCalls;
+ ViewCompat.animate(mView).alpha(1);
+ latch2.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
+ waitAndCheckCallCount(1);
+ }
+
+ @Test
+ public void testWithStartAction() throws Throwable {
+ final CountDownLatch latch1 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(100).withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ latch1.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch1.await(100, TimeUnit.MILLISECONDS));
+
+ // This test ensures that the startAction listener will be called exactly once
+ mNumListenerCalls = 0;
+ final CountDownLatch latch2 = new CountDownLatch(1);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ ViewCompat.animate(mView).alpha(0).setDuration(50).withStartAction(new Runnable() {
+ @Override
+ public void run() {
+ ++mNumListenerCalls;
+ ViewCompat.animate(mView).alpha(1);
+ latch2.countDown();
+ }
+ });
+ }
+ });
+ assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
+ waitAndCheckCallCount(1);
+ }
+
+ void waitAndCheckCallCount(final int count) throws InterruptedException {
+ int timeLeft = WAIT_TIMEOUT_MS;
+ while (mNumListenerCalls != count) {
+ Thread.sleep(20);
+ timeLeft -= 20;
+ assertTrue(timeLeft > 0);
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/view/VpaActivity.java b/v4/tests/java/android/support/v4/view/VpaActivity.java
new file mode 100644
index 0000000..64d698a
--- /dev/null
+++ b/v4/tests/java/android/support/v4/view/VpaActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.view;
+
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
+
+public class VpaActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.vpa_activity;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
index 42d82e9..db4a497 100644
--- a/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/DonutScrollerCompatTest.java
@@ -15,14 +15,8 @@
*/
package android.support.v4.widget;
-import android.os.Build;
-
-/**
- * @hide
- */
public class DonutScrollerCompatTest extends ScrollerCompatTestBase {
-
public DonutScrollerCompatTest() {
- super(Build.VERSION_CODES.DONUT);
+ super(4);
}
}
diff --git a/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTest.java b/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTest.java
new file mode 100644
index 0000000..1319f2c
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTest.java
@@ -0,0 +1,150 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import android.graphics.Rect;
+import android.os.Build;
+import android.os.Bundle;
+import android.support.test.annotation.UiThreadTest;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+
+import java.util.List;
+
+import static junit.framework.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assume.assumeTrue;
+
+@SmallTest
+public class ExploreByTouchHelperTest extends BaseInstrumentationTestCase<ExploreByTouchHelperTestActivity> {
+ private View mHost;
+
+ public ExploreByTouchHelperTest() {
+ super(ExploreByTouchHelperTestActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ // Accessibility delegates are only supported on API 14+.
+ assumeTrue(Build.VERSION.SDK_INT >= 14);
+ mHost = mActivityTestRule.getActivity().findViewById(R.id.host_view);
+ }
+
+ @Test
+ @UiThreadTest
+ public void testBoundsInScreen() {
+ final ExploreByTouchHelper helper = new ParentBoundsHelper(mHost);
+ ViewCompat.setAccessibilityDelegate(mHost, helper);
+
+ final AccessibilityNodeInfoCompat node =
+ helper.getAccessibilityNodeProvider(mHost).createAccessibilityNodeInfo(1);
+ assertNotNull(node);
+
+ final Rect hostBounds = new Rect();
+ mHost.getLocalVisibleRect(hostBounds);
+ assertFalse("Host has not been laid out", hostBounds.isEmpty());
+
+ final Rect nodeBoundsInParent = new Rect();
+ node.getBoundsInParent(nodeBoundsInParent);
+ assertEquals("Wrong bounds in parent", hostBounds, nodeBoundsInParent);
+
+ final Rect hostBoundsOnScreen = getBoundsOnScreen(mHost);
+ final Rect nodeBoundsInScreen = new Rect();
+ node.getBoundsInScreen(nodeBoundsInScreen);
+ assertEquals("Wrong bounds in screen", hostBoundsOnScreen, nodeBoundsInScreen);
+
+ final int scrollX = 100;
+ final int scrollY = 50;
+ mHost.scrollTo(scrollX, scrollY);
+
+ // Generate a node for the new position.
+ final AccessibilityNodeInfoCompat scrolledNode =
+ helper.getAccessibilityNodeProvider(mHost).createAccessibilityNodeInfo(1);
+ assertNotNull(scrolledNode);
+
+ mHost.getLocalVisibleRect(hostBounds);
+ hostBounds.intersect(nodeBoundsInParent);
+ final Rect scrolledNodeBoundsInParent = new Rect();
+ scrolledNode.getBoundsInParent(scrolledNodeBoundsInParent);
+ assertEquals("Wrong bounds in parent after scrolling",
+ hostBounds, scrolledNodeBoundsInParent);
+
+ final Rect expectedBoundsInScreen = new Rect(hostBoundsOnScreen);
+ expectedBoundsInScreen.offset(-scrollX, -scrollY);
+ expectedBoundsInScreen.intersect(hostBoundsOnScreen);
+ scrolledNode.getBoundsInScreen(nodeBoundsInScreen);
+ assertEquals("Wrong bounds in screen after scrolling",
+ expectedBoundsInScreen, nodeBoundsInScreen);
+
+ ViewCompat.setAccessibilityDelegate(mHost, null);
+ }
+
+ private static Rect getBoundsOnScreen(View v) {
+ final int[] tempLocation = new int[2];
+ final Rect hostBoundsOnScreen = new Rect(0, 0, v.getWidth(), v.getHeight());
+ v.getLocationOnScreen(tempLocation);
+ hostBoundsOnScreen.offset(tempLocation[0], tempLocation[1]);
+ return hostBoundsOnScreen;
+ }
+
+ /**
+ * An extension of ExploreByTouchHelper that contains a single virtual view
+ * whose bounds match the host view.
+ */
+ private static class ParentBoundsHelper extends ExploreByTouchHelper {
+ private final View mHost;
+
+ public ParentBoundsHelper(View host) {
+ super(host);
+
+ mHost = host;
+ }
+
+ @Override
+ protected int getVirtualViewAt(float x, float y) {
+ return 1;
+ }
+
+ @Override
+ protected void getVisibleVirtualViews(List<Integer> virtualViewIds) {
+ virtualViewIds.add(1);
+ }
+
+ @Override
+ protected void onPopulateNodeForVirtualView(int virtualViewId, AccessibilityNodeInfoCompat node) {
+ if (virtualViewId == 1) {
+ node.setContentDescription("test");
+
+ final Rect hostBounds = new Rect(0, 0, mHost.getWidth(), mHost.getHeight());
+ node.setBoundsInParent(hostBounds);
+ }
+ }
+
+ @Override
+ protected boolean onPerformActionForVirtualView(int virtualViewId, int action, Bundle arguments) {
+ return false;
+ }
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTestActivity.java b/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTestActivity.java
new file mode 100644
index 0000000..6d21879
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/ExploreByTouchHelperTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
+
+public class ExploreByTouchHelperTestActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.explore_by_touch_helper_activity;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
index c777808..e947749 100644
--- a/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/GingerbreadScrollerCompatTest.java
@@ -15,11 +15,7 @@
*/
package android.support.v4.widget;
-/**
- * @hide
- */
public class GingerbreadScrollerCompatTest extends ScrollerCompatTestBase {
-
public GingerbreadScrollerCompatTest() {
super(9);
}
diff --git a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
index acbc89d..5d8a16d 100644
--- a/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/IcsScrollerCompatTest.java
@@ -15,13 +15,7 @@
*/
package android.support.v4.widget;
-import android.os.Build;
-
-/**
- * @hide
- */
public class IcsScrollerCompatTest extends ScrollerCompatTestBase {
-
public IcsScrollerCompatTest() {
super(14);
}
diff --git a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
index fc571a8..7b7c9f5 100644
--- a/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
+++ b/v4/tests/java/android/support/v4/widget/ScrollerCompatTestBase.java
@@ -16,20 +16,28 @@
package android.support.v4.widget;
import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.animation.AnimationUtils;
import android.view.animation.Interpolator;
import android.view.animation.LinearInterpolator;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
-/**
- * @hide
- */
-abstract public class ScrollerCompatTestBase extends AndroidTestCase {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@MediumTest
+public abstract class ScrollerCompatTestBase {
private static final boolean DEBUG = false;
@@ -50,9 +58,11 @@
Constructor<ScrollerCompat> constructor = ScrollerCompat.class
.getDeclaredConstructor(int.class, Context.class, Interpolator.class);
constructor.setAccessible(true);
- mScroller = constructor.newInstance(mApiLevel, getContext(), interpolator);
+ mScroller = constructor.newInstance(mApiLevel, InstrumentationRegistry.getContext(),
+ interpolator);
}
+ @Test
public void testTargetReached() throws Throwable {
if (DEBUG) {
Log.d(TAG, "testing if target is reached");
@@ -68,6 +78,7 @@
mScroller.getCurrY());
}
+ @Test
public void testAbort() throws Throwable {
if (DEBUG) {
Log.d(TAG, "testing abort");
diff --git a/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java
new file mode 100644
index 0000000..27b7be6
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActions.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+
+public class SwipeRefreshLayoutActions {
+ public static ViewAction setRefreshing() {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(SwipeRefreshLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set SwipeRefreshLayout refreshing state";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view;
+ swipeRefreshLayout.setRefreshing(true);
+
+ // Intentionally not waiting until idle here because it will not become idle due to
+ // the animation.
+ }
+ };
+ }
+
+ public static ViewAction setSize(final int size) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(SwipeRefreshLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Set SwipeRefreshLayout size";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ SwipeRefreshLayout swipeRefreshLayout = (SwipeRefreshLayout) view;
+ swipeRefreshLayout.setSize(size);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java
new file mode 100644
index 0000000..6f30aa2
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutActivity.java
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
+
+public class SwipeRefreshLayoutActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.swipe_refresh_layout_activity;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
new file mode 100644
index 0000000..fca1577
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/SwipeRefreshLayoutTest.java
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v4.widget.SwipeRefreshLayoutActions.setRefreshing;
+import static android.support.v4.widget.SwipeRefreshLayoutActions.setSize;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+
+/**
+ * Tests SwipeRefreshLayout widget.
+ */
+public class SwipeRefreshLayoutTest
+ extends BaseInstrumentationTestCase<SwipeRefreshLayoutActivity> {
+
+ private SwipeRefreshLayout mSwipeRefresh;
+
+ public SwipeRefreshLayoutTest() {
+ super(SwipeRefreshLayoutActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ mSwipeRefresh = (SwipeRefreshLayout) mActivityTestRule.getActivity().findViewById(
+ R.id.swipe_refresh);
+ }
+
+ @Test
+ @MediumTest
+ public void testStartAndStopRefreshing() throws Throwable {
+ assertFalse(mSwipeRefresh.isRefreshing());
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.swipe_refresh)).perform(setRefreshing());
+ assertTrue(mSwipeRefresh.isRefreshing());
+
+ // onView(..).perform(..) does not work when views are animated.
+ // Therefore this is using a posted task to turn off refreshing.
+ mSwipeRefresh.getHandler().post(new Runnable() {
+ @Override
+ public void run() {
+ mSwipeRefresh.setRefreshing(false);
+ }
+ });
+ long waitTime = 1000;
+ while (mSwipeRefresh.isRefreshing()) {
+ Thread.sleep(20);
+ waitTime -= 20;
+ assertTrue("Timed out while waiting for SwipeRefreshLayout to stop refreshing",
+ waitTime > 0);
+ }
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testSetSize() throws Throwable {
+ float density = mSwipeRefresh.getResources().getDisplayMetrics().density;
+ assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER * density),
+ mSwipeRefresh.getProgressCircleDiameter());
+ onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.LARGE));
+ assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER_LARGE * density),
+ mSwipeRefresh.getProgressCircleDiameter());
+ onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.DEFAULT));
+ assertEquals((int) (SwipeRefreshLayout.CIRCLE_DIAMETER * density),
+ mSwipeRefresh.getProgressCircleDiameter());
+ onView(withId(R.id.swipe_refresh)).perform(setSize(SwipeRefreshLayout.DEFAULT));
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/TestActivity.java b/v4/tests/java/android/support/v4/widget/TestActivity.java
deleted file mode 100644
index 9ab5188..0000000
--- a/v4/tests/java/android/support/v4/widget/TestActivity.java
+++ /dev/null
@@ -1,35 +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.v4.widget;
-
-import android.app.Activity;
-import android.os.Bundle;
-import android.view.WindowManager;
-import android.widget.FrameLayout;
-
-public class TestActivity extends Activity {
- FrameLayout mContainer;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- mContainer = new FrameLayout(this);
-
- getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
- setContentView(mContainer);
- }
-}
diff --git a/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java b/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
index a7d2339..3ef236f 100644
--- a/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
+++ b/v4/tests/java/android/support/v4/widget/TextViewCompatTest.java
@@ -17,63 +17,374 @@
package android.support.v4.widget;
-import org.junit.After;
+import android.content.res.Resources;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.v4.BaseInstrumentationTestCase;
+import android.support.v4.test.R;
+import android.support.v4.testutils.TestUtils;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
import org.junit.Before;
import org.junit.Test;
-import org.junit.runner.RunWith;
-import android.app.Instrumentation;
-import android.test.ActivityInstrumentationTestCase2;
-import android.support.test.InstrumentationRegistry;
-import android.support.v4.widget.TextViewCompat;
-import android.util.Log;
-import android.widget.TextView;
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v4.testutils.LayoutDirectionActions.setLayoutDirection;
+import static android.support.v4.testutils.TextViewActions.*;
+import static org.junit.Assert.*;
-import android.support.test.runner.AndroidJUnit4;
+public class TextViewCompatTest extends BaseInstrumentationTestCase<TextViewTestActivity> {
+ private static final String TAG = "TextViewCompatTest";
-@RunWith(AndroidJUnit4.class)
-public class TextViewCompatTest extends ActivityInstrumentationTestCase2<TestActivity> {
- private boolean mDebug;
+ private TextView mTextView;
- Throwable mainThreadException;
+ private class TestDrawable extends ColorDrawable {
+ private int mWidth;
+ private int mHeight;
- Thread mInstrumentationThread;
+ public TestDrawable(@ColorInt int color, int width, int height) {
+ super(color);
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+ }
public TextViewCompatTest() {
- super("android.support.v4.widget", TestActivity.class);
- mDebug = false;
+ super(TextViewTestActivity.class);
}
@Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- mInstrumentationThread = Thread.currentThread();
-
- // Note that injectInstrumentation was added in v5. Since this is v4 we have to use
- // the misspelled (and deprecated) inject API.
- injectInsrumentation(InstrumentationRegistry.getInstrumentation());
- }
-
- @After
- @Override
- public void tearDown() throws Exception {
- getInstrumentation().waitForIdleSync();
- super.tearDown();
+ public void setUp() {
+ mTextView = (TextView) mActivityTestRule.getActivity().findViewById(R.id.text_view);
}
@Test
+ @SmallTest
public void testMaxLines() throws Throwable {
- final TextView textView = new TextView(getActivity());
- textView.setMaxLines(4);
+ final int maxLinesCount = 4;
+ onView(withId(R.id.text_view)).perform(setMaxLines(maxLinesCount));
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- getActivity().mContainer.addView(textView);
- }
- });
+ assertEquals("Empty view: Max lines must match", TextViewCompat.getMaxLines(mTextView),
+ maxLinesCount);
- assertEquals("Max lines must match", TextViewCompat.getMaxLines(textView), 4);
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
+ assertEquals("Short text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
+ maxLinesCount);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+ assertEquals("Medium text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
+ maxLinesCount);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ assertEquals("Long text: Max lines must match", TextViewCompat.getMaxLines(mTextView),
+ maxLinesCount);
+ }
+
+ @Test
+ @SmallTest
+ public void testMinLines() throws Throwable {
+ final int minLinesCount = 3;
+ onView(withId(R.id.text_view)).perform(setMinLines(minLinesCount));
+
+ assertEquals("Empty view: Min lines must match", TextViewCompat.getMinLines(mTextView),
+ minLinesCount);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_short));
+ assertEquals("Short text: Min lines must match", TextViewCompat.getMinLines(mTextView),
+ minLinesCount);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+ assertEquals("Medium text: Min lines must match", TextViewCompat.getMinLines(mTextView),
+ minLinesCount);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ assertEquals("Long text: Min lines must match", TextViewCompat.getMinLines(mTextView),
+ minLinesCount);
+ }
+
+ @Test
+ @SmallTest
+ public void testStyle() throws Throwable {
+ onView(withId(R.id.text_view)).perform(setTextAppearance(R.style.TextMediumStyle));
+
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ assertTrue("Styled text view: style",
+ mTextView.getTypeface().isItalic() || (mTextView.getPaint().getTextSkewX() < 0));
+ assertEquals("Styled text view: color", mTextView.getTextColors().getDefaultColor(),
+ res.getColor(R.color.text_color));
+ assertEquals("Styled text view: size", mTextView.getTextSize(),
+ (float) res.getDimensionPixelSize(R.dimen.text_medium_size), 1.0f);
+ }
+
+ @Test
+ @SmallTest
+ public void testCompoundDrawablesRelative() throws Throwable {
+ final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
+ drawableStart.setBounds(0, 0, 20, 20);
+ final Drawable drawableTop = new ColorDrawable(0xFF00FF00);
+ drawableTop.setBounds(0, 0, 30, 25);
+ final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
+ drawableEnd.setBounds(0, 0, 25, 20);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
+ drawableTop, drawableEnd, null));
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+
+ assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableStart);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(), 20);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(), 20);
+
+ assertEquals("Compound drawable: top", drawablesAbsolute[1], drawableTop);
+ assertEquals("Compound drawable: top width",
+ drawablesAbsolute[1].getBounds().width(), 30);
+ assertEquals("Compound drawable: top height",
+ drawablesAbsolute[1].getBounds().height(), 25);
+
+ assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableEnd);
+ assertEquals("Compound drawable: right width",
+ drawablesAbsolute[2].getBounds().width(), 25);
+ assertEquals("Compound drawable: right height",
+ drawablesAbsolute[2].getBounds().height(), 20);
+
+ assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
+ }
+
+ @Test
+ @SmallTest
+ public void testCompoundDrawablesRelativeRtl() throws Throwable {
+ onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ final Drawable drawableStart = new ColorDrawable(0xFFFF0000);
+ drawableStart.setBounds(0, 0, 20, 20);
+ final Drawable drawableTop = new ColorDrawable(0xFF00FF00);
+ drawableTop.setBounds(0, 0, 30, 25);
+ final Drawable drawableEnd = new ColorDrawable(0xFF0000FF);
+ drawableEnd.setBounds(0, 0, 25, 20);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_medium));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelative(drawableStart,
+ drawableTop, drawableEnd, null));
+
+ // Check to see whether our text view is under RTL mode
+ if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
+ // This will happen on v17- devices
+ return;
+ }
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+
+ // End drawable should be returned as left
+ assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableEnd);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(), 25);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(), 20);
+
+ assertEquals("Compound drawable: top", drawablesAbsolute[1], drawableTop);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[1].getBounds().width(), 30);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[1].getBounds().height(), 25);
+
+ // Start drawable should be returned as right
+ assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableStart);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[2].getBounds().width(), 20);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[2].getBounds().height(), 20);
+
+ assertNull("Compound drawable: bottom", drawablesAbsolute[3]);
+ }
+
+ @Test
+ @SmallTest
+ public void testCompoundDrawablesRelativeWithIntrinsicBounds() throws Throwable {
+ final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
+ final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
+ final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+ drawableStart, null, drawableEnd, drawableBottom));
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+
+ assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableStart);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(), 30);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(), 20);
+
+ assertNull("Compound drawable: top", drawablesAbsolute[1]);
+
+ assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableEnd);
+ assertEquals("Compound drawable: right width",
+ drawablesAbsolute[2].getBounds().width(), 25);
+ assertEquals("Compound drawable: right height",
+ drawablesAbsolute[2].getBounds().height(), 45);
+
+ assertEquals("Compound drawable: bottom", drawablesAbsolute[3], drawableBottom);
+ assertEquals("Compound drawable: bottom width",
+ drawablesAbsolute[3].getBounds().width(), 15);
+ assertEquals("Compound drawable: bottom height",
+ drawablesAbsolute[3].getBounds().height(), 35);
+ }
+
+ @Test
+ @SmallTest
+ public void testCompoundDrawablesRelativeWithIntrinsicBoundsRtl() throws Throwable {
+ onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ final Drawable drawableStart = new TestDrawable(0xFFFF0000, 30, 20);
+ final Drawable drawableEnd = new TestDrawable(0xFF0000FF, 25, 45);
+ final Drawable drawableBottom = new TestDrawable(0xFF00FF00, 15, 35);
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+ drawableStart, null, drawableEnd, drawableBottom));
+
+ // Check to see whether our text view is under RTL mode
+ if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
+ // This will happen on v17- devices
+ return;
+ }
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+
+ // End drawable should be returned as left
+ assertEquals("Compound drawable: left", drawablesAbsolute[0], drawableEnd);
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(), 25);
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(), 45);
+
+ assertNull("Compound drawable: top", drawablesAbsolute[1]);
+
+ // Start drawable should be returned as right
+ assertEquals("Compound drawable: right", drawablesAbsolute[2], drawableStart);
+ assertEquals("Compound drawable: right width",
+ drawablesAbsolute[2].getBounds().width(), 30);
+ assertEquals("Compound drawable: right height",
+ drawablesAbsolute[2].getBounds().height(), 20);
+
+ assertEquals("Compound drawable: bottom", drawablesAbsolute[3], drawableBottom);
+ assertEquals("Compound drawable: bottom width",
+ drawablesAbsolute[3].getBounds().width(), 15);
+ assertEquals("Compound drawable: bottom height",
+ drawablesAbsolute[3].getBounds().height(), 35);
+ }
+
+ @Test
+ @MediumTest
+ public void testCompoundDrawablesRelativeWithIntrinsicBoundsById() throws Throwable {
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.test_drawable_red, 0,
+ R.drawable.test_drawable_green, R.drawable.test_drawable_blue));
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+ final Resources res = mActivityTestRule.getActivity().getResources();
+
+ // The entire left drawable should be the specific red color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
+ drawablesAbsolute[0], res.getColor(R.color.test_red));
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_small_size));
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_medium_size));
+
+ assertNull("Compound drawable: top", drawablesAbsolute[1]);
+
+ // The entire right drawable should be the specific green color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: right color",
+ drawablesAbsolute[2], res.getColor(R.color.test_green));
+ assertEquals("Compound drawable: right width",
+ drawablesAbsolute[2].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_medium_size));
+ assertEquals("Compound drawable: right height",
+ drawablesAbsolute[2].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_large_size));
+
+ // The entire bottom drawable should be the specific blue color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: bottom color",
+ drawablesAbsolute[3], res.getColor(R.color.test_blue));
+ assertEquals("Compound drawable: bottom width",
+ drawablesAbsolute[3].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_large_size));
+ assertEquals("Compound drawable: bottom height",
+ drawablesAbsolute[3].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_small_size));
+ }
+
+ @Test
+ @MediumTest
+ public void testCompoundDrawablesRelativeWithIntrinsicBoundsByIdRtl() throws Throwable {
+ onView(withId(R.id.text_view)).perform(setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ onView(withId(R.id.text_view)).perform(setText(R.string.test_text_long));
+ onView(withId(R.id.text_view)).perform(setCompoundDrawablesRelativeWithIntrinsicBounds(
+ R.drawable.test_drawable_red, 0,
+ R.drawable.test_drawable_green, R.drawable.test_drawable_blue));
+
+ // Check to see whether our text view is under RTL mode
+ if (ViewCompat.getLayoutDirection(mTextView) != ViewCompat.LAYOUT_DIRECTION_RTL) {
+ // This will happen on v17- devices
+ return;
+ }
+
+ final Drawable[] drawablesAbsolute = mTextView.getCompoundDrawables();
+ final Resources res = mActivityTestRule.getActivity().getResources();
+
+ // The entire left / end drawable should be the specific green color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: left color",
+ drawablesAbsolute[0], res.getColor(R.color.test_green));
+ assertEquals("Compound drawable: left width",
+ drawablesAbsolute[0].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_medium_size));
+ assertEquals("Compound drawable: left height",
+ drawablesAbsolute[0].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_large_size));
+
+ assertNull("Compound drawable: top", drawablesAbsolute[1]);
+
+ // The entire right drawable should be the specific red color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: right color",
+ drawablesAbsolute[2], res.getColor(R.color.test_red));
+ assertEquals("Compound drawable: right width",
+ drawablesAbsolute[2].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_small_size));
+ assertEquals("Compound drawable: right height",
+ drawablesAbsolute[2].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_medium_size));
+
+ // The entire bottom drawable should be the specific blue color
+ TestUtils.assertAllPixelsOfColor("Compound drawable: bottom color",
+ drawablesAbsolute[3], res.getColor(R.color.test_blue));
+ assertEquals("Compound drawable: bottom width",
+ drawablesAbsolute[3].getBounds().width(),
+ res.getDimensionPixelSize(R.dimen.drawable_large_size));
+ assertEquals("Compound drawable: bottom height",
+ drawablesAbsolute[3].getBounds().height(),
+ res.getDimensionPixelSize(R.dimen.drawable_small_size));
}
}
diff --git a/v4/tests/java/android/support/v4/widget/TextViewTestActivity.java b/v4/tests/java/android/support/v4/widget/TextViewTestActivity.java
new file mode 100644
index 0000000..dcaab70
--- /dev/null
+++ b/v4/tests/java/android/support/v4/widget/TextViewTestActivity.java
@@ -0,0 +1,27 @@
+/*
+ * 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.v4.widget;
+
+import android.support.v4.BaseTestActivity;
+import android.support.v4.test.R;
+
+public class TextViewTestActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.text_view_activity;
+ }
+}
diff --git a/v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java b/v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java
deleted file mode 100644
index 7366127..0000000
--- a/v4/tests/java/android/support/v4/widget/test/TextViewTestActivity.java
+++ /dev/null
@@ -1,24 +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.v4.widget.test;
-
-
-import android.app.Activity;
-
-public class TextViewTestActivity extends Activity {
-
-}
diff --git a/v4/tests/res/anim/fade_in.xml b/v4/tests/res/anim/fade_in.xml
new file mode 100644
index 0000000..92d5bbe
--- /dev/null
+++ b/v4/tests/res/anim/fade_in.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="300"
+ android:fromAlpha="0.0"
+ android:toAlpha="1.0"/>
diff --git a/v4/tests/res/anim/fade_out.xml b/v4/tests/res/anim/fade_out.xml
new file mode 100644
index 0000000..bc5a2ab
--- /dev/null
+++ b/v4/tests/res/anim/fade_out.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+** Copyright 2016, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+** http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:duration="300"
+ android:fromAlpha="1.0"
+ android:toAlpha="0.0"/>
diff --git a/v4/tests/res/anim/long_fade_in.xml b/v4/tests/res/anim/long_fade_in.xml
new file mode 100644
index 0000000..5d6f496
--- /dev/null
+++ b/v4/tests/res/anim/long_fade_in.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/decelerate_quad"
+ android:fromAlpha="0.0" android:toAlpha="1.0"
+ android:duration="5000" />
diff --git a/v4/tests/res/anim/long_fade_out.xml b/v4/tests/res/anim/long_fade_out.xml
new file mode 100644
index 0000000..fe9fc6a
--- /dev/null
+++ b/v4/tests/res/anim/long_fade_out.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+
+<alpha xmlns:android="http://schemas.android.com/apk/res/android"
+ android:interpolator="@android:interpolator/decelerate_quad"
+ android:fromAlpha="1.0" android:toAlpha="0.0"
+ android:duration="5000" />
diff --git a/v4/tests/res/color/complex_themed_selector.xml b/v4/tests/res/color/complex_themed_selector.xml
new file mode 100644
index 0000000..8ef5060
--- /dev/null
+++ b/v4/tests/res/color/complex_themed_selector.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:color="?attr/theme_color_focused" />
+ <item android:state_pressed="true" android:color="?attr/theme_color_pressed" />
+ <item android:color="?attr/theme_color_default"/>
+</selector>
+
diff --git a/v4/tests/res/color/complex_unthemed_selector.xml b/v4/tests/res/color/complex_unthemed_selector.xml
new file mode 100644
index 0000000..bdb93c9
--- /dev/null
+++ b/v4/tests/res/color/complex_unthemed_selector.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true" android:color="@color/selector_color_focused" />
+ <item android:state_pressed="true" android:color="@color/selector_color_pressed" />
+ <item android:color="@color/selector_color_default"/>
+</selector>
+
diff --git a/v4/tests/res/color/simple_themed_selector.xml b/v4/tests/res/color/simple_themed_selector.xml
new file mode 100644
index 0000000..e225add
--- /dev/null
+++ b/v4/tests/res/color/simple_themed_selector.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:color="?attr/theme_color_default"/> <!-- not selected -->
+</selector>
+
diff --git a/v4/tests/res/drawable-hdpi/density_aware_drawable.png b/v4/tests/res/drawable-hdpi/density_aware_drawable.png
new file mode 100644
index 0000000..dd8a7fe
--- /dev/null
+++ b/v4/tests/res/drawable-hdpi/density_aware_drawable.png
Binary files differ
diff --git a/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png b/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png
new file mode 100644
index 0000000..909b23a
--- /dev/null
+++ b/v4/tests/res/drawable-ldpi/aliased_drawable_alternate.png
Binary files differ
diff --git a/v4/tests/res/drawable-mdpi/density_aware_drawable.png b/v4/tests/res/drawable-mdpi/density_aware_drawable.png
new file mode 100644
index 0000000..5c0ff0e
--- /dev/null
+++ b/v4/tests/res/drawable-mdpi/density_aware_drawable.png
Binary files differ
diff --git a/v4/tests/res/drawable-mdpi/test_drawable.png b/v4/tests/res/drawable-mdpi/test_drawable.png
new file mode 100644
index 0000000..1ce5321e
--- /dev/null
+++ b/v4/tests/res/drawable-mdpi/test_drawable.png
Binary files differ
diff --git a/v4/tests/res/drawable-xhdpi/density_aware_drawable.png b/v4/tests/res/drawable-xhdpi/density_aware_drawable.png
new file mode 100644
index 0000000..ce7e2c6
--- /dev/null
+++ b/v4/tests/res/drawable-xhdpi/density_aware_drawable.png
Binary files differ
diff --git a/v4/tests/res/drawable-xxhdpi/density_aware_drawable.png b/v4/tests/res/drawable-xxhdpi/density_aware_drawable.png
new file mode 100644
index 0000000..c2ebfcb
--- /dev/null
+++ b/v4/tests/res/drawable-xxhdpi/density_aware_drawable.png
Binary files differ
diff --git a/v4/tests/res/drawable/test_drawable_blue.xml b/v4/tests/res/drawable/test_drawable_blue.xml
new file mode 100644
index 0000000..5d05ef2
--- /dev/null
+++ b/v4/tests/res/drawable/test_drawable_blue.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_large_size"
+ android:height="@dimen/drawable_small_size" />
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/v4/tests/res/drawable/test_drawable_green.xml b/v4/tests/res/drawable/test_drawable_green.xml
new file mode 100644
index 0000000..9f33104
--- /dev/null
+++ b/v4/tests/res/drawable/test_drawable_green.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_medium_size"
+ android:height="@dimen/drawable_large_size" />
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/v4/tests/res/drawable/test_drawable_red.xml b/v4/tests/res/drawable/test_drawable_red.xml
new file mode 100644
index 0000000..cd1af56
--- /dev/null
+++ b/v4/tests/res/drawable/test_drawable_red.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_small_size"
+ android:height="@dimen/drawable_medium_size" />
+ <solid
+ android:color="@color/test_red" />
+</shape>
\ No newline at end of file
diff --git a/v4/tests/res/drawable/themed_bitmap.xml b/v4/tests/res/drawable/themed_bitmap.xml
new file mode 100644
index 0000000..111d14e
--- /dev/null
+++ b/v4/tests/res/drawable/themed_bitmap.xml
@@ -0,0 +1,19 @@
+<?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.
+-->
+
+<bitmap xmlns:android="http://schemas.android.com/apk/res/android"
+ android:src="@drawable/test_drawable"
+ android:tint="?attr/theme_color_default" />
diff --git a/v4/tests/res/drawable/themed_drawable.xml b/v4/tests/res/drawable/themed_drawable.xml
new file mode 100644
index 0000000..08d89c7
--- /dev/null
+++ b/v4/tests/res/drawable/themed_drawable.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_large_size"
+ android:height="@dimen/drawable_small_size" />
+ <solid
+ android:color="?attr/theme_color_default" />
+</shape>
\ No newline at end of file
diff --git a/v4/tests/res/layout/activity_content.xml b/v4/tests/res/layout/activity_content.xml
new file mode 100644
index 0000000..8870e60
--- /dev/null
+++ b/v4/tests/res/layout/activity_content.xml
@@ -0,0 +1,20 @@
+<?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.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
diff --git a/v4/tests/res/layout/explore_by_touch_helper_activity.xml b/v4/tests/res/layout/explore_by_touch_helper_activity.xml
new file mode 100644
index 0000000..22c95b4
--- /dev/null
+++ b/v4/tests/res/layout/explore_by_touch_helper_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <View
+ android:id="@+id/host_view"
+ android:layout_width="200dp"
+ android:layout_height="200dp"
+ android:background="#f00" />
+</FrameLayout>
diff --git a/v4/tests/res/layout/fragment_a.xml b/v4/tests/res/layout/fragment_a.xml
new file mode 100644
index 0000000..38e0423
--- /dev/null
+++ b/v4/tests/res/layout/fragment_a.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/textA"
+ android:text="@string/hello"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/fragment_b.xml b/v4/tests/res/layout/fragment_b.xml
new file mode 100644
index 0000000..d8ed961
--- /dev/null
+++ b/v4/tests/res/layout/fragment_b.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/textB"
+ android:text="@string/hello"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/fragment_c.xml b/v4/tests/res/layout/fragment_c.xml
new file mode 100644
index 0000000..ed3c753
--- /dev/null
+++ b/v4/tests/res/layout/fragment_c.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:id="@+id/textC"
+ android:text="@string/hello"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/fragment_end.xml b/v4/tests/res/layout/fragment_end.xml
new file mode 100644
index 0000000..aa3d9e8
--- /dev/null
+++ b/v4/tests/res/layout/fragment_end.xml
@@ -0,0 +1,34 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:transitionName="destination"
+ android:id="@+id/hello"
+ android:text="@string/hello"/>
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#0F0"
+ android:id="@+id/greenSquare"/>
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#F00"
+ android:id="@+id/redSquare"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/fragment_middle.xml b/v4/tests/res/layout/fragment_middle.xml
new file mode 100644
index 0000000..7d1409b
--- /dev/null
+++ b/v4/tests/res/layout/fragment_middle.xml
@@ -0,0 +1,29 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#00F"
+ android:id="@+id/blueSquare"/>
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#FF0"
+ android:id="@+id/yellowSquare"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/fragment_start.xml b/v4/tests/res/layout/fragment_start.xml
new file mode 100644
index 0000000..793e9b5
--- /dev/null
+++ b/v4/tests/res/layout/fragment_start.xml
@@ -0,0 +1,34 @@
+<?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"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#0F0"
+ android:id="@+id/greenSquare"/>
+ <View android:layout_width="10dp"
+ android:layout_height="10dp"
+ android:background="#F00"
+ android:id="@+id/redSquare"/>
+ <TextView android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:transitionName="source"
+ android:id="@+id/hello"
+ android:text="@string/hello"/>
+</LinearLayout>
diff --git a/v4/tests/res/layout/strict_view_fragment.xml b/v4/tests/res/layout/strict_view_fragment.xml
new file mode 100644
index 0000000..324f8d0
--- /dev/null
+++ b/v4/tests/res/layout/strict_view_fragment.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+* Copyright 2016, The Android Open Source Project
+*
+* Licensed under the Apache License, Version 2.0 (the "License");
+* you may not use this file except in compliance with the License.
+* You may obtain a copy of the License at
+*
+* http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing, software
+* distributed under the License is distributed on an "AS IS" BASIS,
+* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+* See the License for the specific language governing permissions and
+* limitations under the License.
+*/
+-->
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/text1" />
diff --git a/v4/tests/res/layout/swipe_refresh_layout_activity.xml b/v4/tests/res/layout/swipe_refresh_layout_activity.xml
new file mode 100644
index 0000000..d925ecf
--- /dev/null
+++ b/v4/tests/res/layout/swipe_refresh_layout_activity.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<android.support.v4.widget.SwipeRefreshLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/swipe_refresh"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+<!-- some full screen pullable view that will be the offsetable content -->
+ <ListView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:id="@+id/content"/>
+</android.support.v4.widget.SwipeRefreshLayout>
\ No newline at end of file
diff --git a/v4/tests/res/layout/text_view_activity.xml b/v4/tests/res/layout/text_view_activity.xml
new file mode 100644
index 0000000..ba5d688
--- /dev/null
+++ b/v4/tests/res/layout/text_view_activity.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <TextView
+ android:id="@+id/text_view"
+ android:layout_width="200dip"
+ android:layout_height="60dip" />
+</FrameLayout>
diff --git a/v4/tests/res/layout/view_pager_with_tab_strip.xml b/v4/tests/res/layout/view_pager_with_tab_strip.xml
new file mode 100644
index 0000000..115b672
--- /dev/null
+++ b/v4/tests/res/layout/view_pager_with_tab_strip.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<android.support.v4.view.ViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.view.PagerTabStrip
+ android:id="@+id/titles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top" />
+
+</android.support.v4.view.ViewPager>
+
diff --git a/v4/tests/res/layout/view_pager_with_title_strip.xml b/v4/tests/res/layout/view_pager_with_title_strip.xml
new file mode 100644
index 0000000..de248df
--- /dev/null
+++ b/v4/tests/res/layout/view_pager_with_title_strip.xml
@@ -0,0 +1,30 @@
+<?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.
+-->
+
+<android.support.v4.view.ViewPager
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/pager"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <android.support.v4.view.PagerTitleStrip
+ android:id="@+id/titles"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_gravity="top" />
+
+</android.support.v4.view.ViewPager>
+
diff --git a/v4/tests/res/layout/vpa_activity.xml b/v4/tests/res/layout/vpa_activity.xml
new file mode 100644
index 0000000..2363160
--- /dev/null
+++ b/v4/tests/res/layout/vpa_activity.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <View
+ android:id="@+id/view"
+ android:background="#f00"
+ android:layout_width="400dp"
+ android:layout_height="200dp"/>
+
+</LinearLayout>
diff --git a/v4/tests/res/transition/change_bounds.xml b/v4/tests/res/transition/change_bounds.xml
new file mode 100644
index 0000000..766bcea3
--- /dev/null
+++ b/v4/tests/res/transition/change_bounds.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<changeBounds/>
diff --git a/v4/tests/res/transition/fade.xml b/v4/tests/res/transition/fade.xml
new file mode 100644
index 0000000..617f70e
--- /dev/null
+++ b/v4/tests/res/transition/fade.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<fade xmlns:android="http://schemas.android.com/apk/res/android"/>
diff --git a/v4/tests/res/values-hdpi/dimens.xml b/v4/tests/res/values-hdpi/dimens.xml
new file mode 100755
index 0000000..eb1ff54
--- /dev/null
+++ b/v4/tests/res/values-hdpi/dimens.xml
@@ -0,0 +1,18 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="density_aware_size">14dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values-mdpi/dimens.xml b/v4/tests/res/values-mdpi/dimens.xml
new file mode 100755
index 0000000..5766379
--- /dev/null
+++ b/v4/tests/res/values-mdpi/dimens.xml
@@ -0,0 +1,18 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="density_aware_size">12dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values-xhdpi/dimens.xml b/v4/tests/res/values-xhdpi/dimens.xml
new file mode 100755
index 0000000..a25d23d
--- /dev/null
+++ b/v4/tests/res/values-xhdpi/dimens.xml
@@ -0,0 +1,18 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="density_aware_size">16dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values-xxhdpi/dimens.xml b/v4/tests/res/values-xxhdpi/dimens.xml
new file mode 100755
index 0000000..399cde1
--- /dev/null
+++ b/v4/tests/res/values-xxhdpi/dimens.xml
@@ -0,0 +1,18 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="density_aware_size">18dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values/attrs.xml b/v4/tests/res/values/attrs.xml
new file mode 100644
index 0000000..36d1e9e
--- /dev/null
+++ b/v4/tests/res/values/attrs.xml
@@ -0,0 +1,21 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <attr name="theme_color_default" format="reference" />
+ <attr name="theme_color_focused" format="reference" />
+ <attr name="theme_color_pressed" format="reference" />
+ <attr name="theme_color_selected" format="reference" />
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values/colors.xml b/v4/tests/res/values/colors.xml
new file mode 100644
index 0000000..15158cf
--- /dev/null
+++ b/v4/tests/res/values/colors.xml
@@ -0,0 +1,37 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <color name="text_color">#FF8090</color>
+
+ <color name="selector_color_default">#70A0C0</color>
+ <color name="selector_color_focused">#70B0F0</color>
+ <color name="selector_color_pressed">#6080B0</color>
+
+ <color name="theme_color_yellow_default">#F0B000</color>
+ <color name="theme_color_yellow_focused">#F0A020</color>
+ <color name="theme_color_yellow_pressed">#E0A040</color>
+ <color name="theme_color_yellow_selected">#E8A848</color>
+
+ <color name="theme_color_lilac_default">#F080F0</color>
+ <color name="theme_color_lilac_focused">#F070D0</color>
+ <color name="theme_color_lilac_pressed">#E070A0</color>
+ <color name="theme_color_lilac_selected">#E878A8</color>
+
+ <color name="test_red">#FF6030</color>
+ <color name="test_green">#50E080</color>
+ <color name="test_blue">#3050CF</color>
+ <color name="test_yellow">#F0F000</color>
+</resources>
diff --git a/v4/tests/res/values/dimens.xml b/v4/tests/res/values/dimens.xml
new file mode 100644
index 0000000..5c82462
--- /dev/null
+++ b/v4/tests/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <dimen name="text_medium_size">20sp</dimen>
+
+ <dimen name="drawable_small_size">12dip</dimen>
+ <dimen name="drawable_medium_size">16dip</dimen>
+ <dimen name="drawable_large_size">20dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values/drawables.xml b/v4/tests/res/values/drawables.xml
new file mode 100644
index 0000000..b2955a5
--- /dev/null
+++ b/v4/tests/res/values/drawables.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+ <item type="drawable" name="aliased_drawable">@drawable/aliased_drawable_alternate</item>
+</resources>
diff --git a/v4/tests/res/values/ids.xml b/v4/tests/res/values/ids.xml
new file mode 100644
index 0000000..e5fcf63
--- /dev/null
+++ b/v4/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/v4/tests/res/values/strings.xml b/v4/tests/res/values/strings.xml
new file mode 100644
index 0000000..b804faf
--- /dev/null
+++ b/v4/tests/res/values/strings.xml
@@ -0,0 +1,24 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <!-- Short text for testing. -->
+ <string name="test_text_short">Lorem ipsum</string>
+ <!-- Medium text for testing. -->
+ <string name="test_text_medium">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam dui neque, suscipit quis rhoncus vitae, rhoncus hendrerit neque.</string>
+ <!-- Long text for testing. -->
+ <string name="test_text_long">Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam dui neque, suscipit quis rhoncus vitae, rhoncus hendrerit neque. Proin ac mauris cursus nulla aliquam viverra. Vivamus pharetra luctus magna, lacinia imperdiet leo mollis eget. Fusce a diam ipsum. Etiam sit amet nisl et velit aliquam dignissim eget nec nisi. Duis bibendum euismod tortor non pulvinar. Nunc quis neque ultricies nulla luctus aliquet. Sed consectetur, orci ac vehicula consectetur, metus sem pellentesque turpis, sed venenatis nisi lorem vitae ante.</string>
+ <string name="hello">Hello World</string>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/values/styles.xml b/v4/tests/res/values/styles.xml
new file mode 100644
index 0000000..447d5ec
--- /dev/null
+++ b/v4/tests/res/values/styles.xml
@@ -0,0 +1,37 @@
+<?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 xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <style name="TestActivityTheme">
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+ <style name="TextMediumStyle" parent="@android:style/TextAppearance.Medium">
+ <item name="android:textSize">@dimen/text_medium_size</item>
+ <item name="android:textColor">@color/text_color</item>
+ <item name="android:textStyle">italic</item>
+ </style>
+
+ <style name="YellowTheme" parent="@android:style/Theme.Light">
+ <item name="theme_color_default">@color/theme_color_yellow_default</item>
+ <item name="theme_color_focused">@color/theme_color_yellow_focused</item>
+ <item name="theme_color_pressed">@color/theme_color_yellow_pressed</item>
+ </style>
+
+ <style name="LilacTheme" parent="@android:style/Theme.Light">
+ <item name="theme_color_default">@color/theme_color_lilac_default</item>
+ <item name="theme_color_focused">@color/theme_color_lilac_focused</item>
+ <item name="theme_color_pressed">@color/theme_color_lilac_pressed</item>
+ </style>
+</resources>
\ No newline at end of file
diff --git a/v4/tests/res/xml/paths.xml b/v4/tests/res/xml/paths.xml
deleted file mode 100644
index 7f04099..0000000
--- a/v4/tests/res/xml/paths.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<paths xmlns:android="http://schemas.android.com/apk/res/android">
- <!-- / -->
- <root-path name="test_root" />
- <!-- /proc/1 -->
- <root-path name="test_init" path="proc/1/" />
- <!-- /data/data/com.example/files -->
- <files-path name="test_files" />
- <!-- /data/data/com.example/files/thumbs -->
- <files-path name="test_thumbs" path="thumbs/" />
- <!-- /data/data/com.example/cache -->
- <cache-path name="test_cache" />
- <!-- /storage/emulated/0 -->
- <external-path name="test_external" />
- <!-- /storage/emulated/0/Android/com.example/files -->
- <external-files-path name="test_external_files" />
- <!-- /storage/emulated/0/Android/com.example/cache -->
- <external-cache-path name="test_external_cache" />
-</paths>
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index eabd189..6d11fcb8 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -15,15 +15,25 @@
LOCAL_PATH := $(call my-dir)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-appcompat \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-appcompat
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
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 \
+ android-support-animatedvectordrawable
+LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_AAPT_FLAGS := --no-version-vectors
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/appcompat/AndroidManifest.xml b/v7/appcompat/AndroidManifest.xml
index dac4cb2..0e0aba8 100644
--- a/v7/appcompat/AndroidManifest.xml
+++ b/v7/appcompat/AndroidManifest.xml
@@ -14,7 +14,9 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
package="android.support.v7.appcompat">
- <uses-sdk android:minSdkVersion="7"/>
- <application />
+ <uses-sdk android:minSdkVersion="7"
+ tools:overrideLibrary="android.support.graphics.drawable.animated"/>
+ <application/>
</manifest>
diff --git a/v7/appcompat/api/23.1.1.txt b/v7/appcompat/api/23.1.1.txt
new file mode 100644
index 0000000..2d53759
--- /dev/null
+++ b/v7/appcompat/api/23.1.1.txt
@@ -0,0 +1,2080 @@
+package android.support.v7.app {
+
+ public abstract class ActionBar {
+ ctor public ActionBar();
+ method public abstract void addOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, boolean);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int);
+ method public abstract deprecated void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
+ method public abstract android.view.View getCustomView();
+ method public abstract int getDisplayOptions();
+ method public float getElevation();
+ method public abstract int getHeight();
+ method public int getHideOffset();
+ method public abstract deprecated int getNavigationItemCount();
+ method public abstract deprecated int getNavigationMode();
+ method public abstract deprecated int getSelectedNavigationIndex();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab getSelectedTab();
+ method public abstract java.lang.CharSequence getSubtitle();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab getTabAt(int);
+ method public abstract deprecated int getTabCount();
+ method public android.content.Context getThemedContext();
+ method public abstract java.lang.CharSequence getTitle();
+ method public abstract void hide();
+ method public boolean isHideOnContentScrollEnabled();
+ method public abstract boolean isShowing();
+ method public abstract deprecated android.support.v7.app.ActionBar.Tab newTab();
+ method public abstract deprecated void removeAllTabs();
+ method public abstract void removeOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
+ method public abstract deprecated void removeTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract deprecated void removeTabAt(int);
+ method public abstract deprecated void selectTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public abstract void setCustomView(android.view.View);
+ method public abstract void setCustomView(android.view.View, android.support.v7.app.ActionBar.LayoutParams);
+ method public abstract void setCustomView(int);
+ method public abstract void setDisplayHomeAsUpEnabled(boolean);
+ method public abstract void setDisplayOptions(int);
+ method public abstract void setDisplayOptions(int, int);
+ method public abstract void setDisplayShowCustomEnabled(boolean);
+ method public abstract void setDisplayShowHomeEnabled(boolean);
+ method public abstract void setDisplayShowTitleEnabled(boolean);
+ method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setElevation(float);
+ method public void setHideOffset(int);
+ method public void setHideOnContentScrollEnabled(boolean);
+ method public void setHomeActionContentDescription(java.lang.CharSequence);
+ method public void setHomeActionContentDescription(int);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
+ method public void setHomeAsUpIndicator(int);
+ method public void setHomeButtonEnabled(boolean);
+ method public abstract void setIcon(int);
+ method public abstract void setIcon(android.graphics.drawable.Drawable);
+ method public abstract deprecated void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
+ method public abstract void setLogo(int);
+ method public abstract void setLogo(android.graphics.drawable.Drawable);
+ method public abstract deprecated void setNavigationMode(int);
+ method public abstract deprecated void setSelectedNavigationItem(int);
+ method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public abstract void setSubtitle(java.lang.CharSequence);
+ method public abstract void setSubtitle(int);
+ method public abstract void setTitle(java.lang.CharSequence);
+ method public abstract void setTitle(int);
+ method public abstract void show();
+ field public static final int DISPLAY_HOME_AS_UP = 4; // 0x4
+ field public static final int DISPLAY_SHOW_CUSTOM = 16; // 0x10
+ field public static final int DISPLAY_SHOW_HOME = 2; // 0x2
+ field public static final int DISPLAY_SHOW_TITLE = 8; // 0x8
+ field public static final int DISPLAY_USE_LOGO = 1; // 0x1
+ field public static final deprecated int NAVIGATION_MODE_LIST = 1; // 0x1
+ field public static final deprecated int NAVIGATION_MODE_STANDARD = 0; // 0x0
+ field public static final deprecated int NAVIGATION_MODE_TABS = 2; // 0x2
+ }
+
+ public static class ActionBar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public ActionBar.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public ActionBar.LayoutParams(int, int);
+ ctor public ActionBar.LayoutParams(int, int, int);
+ ctor public ActionBar.LayoutParams(int);
+ ctor public ActionBar.LayoutParams(android.support.v7.app.ActionBar.LayoutParams);
+ ctor public ActionBar.LayoutParams(android.view.ViewGroup.LayoutParams);
+ field public int gravity;
+ }
+
+ public static abstract interface ActionBar.OnMenuVisibilityListener {
+ method public abstract void onMenuVisibilityChanged(boolean);
+ }
+
+ public static abstract deprecated interface ActionBar.OnNavigationListener {
+ method public abstract boolean onNavigationItemSelected(int, long);
+ }
+
+ public static abstract deprecated class ActionBar.Tab {
+ ctor public ActionBar.Tab();
+ method public abstract java.lang.CharSequence getContentDescription();
+ method public abstract android.view.View getCustomView();
+ method public abstract android.graphics.drawable.Drawable getIcon();
+ method public abstract int getPosition();
+ method public abstract java.lang.Object getTag();
+ method public abstract java.lang.CharSequence getText();
+ method public abstract void select();
+ method public abstract android.support.v7.app.ActionBar.Tab setContentDescription(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setContentDescription(java.lang.CharSequence);
+ method public abstract android.support.v7.app.ActionBar.Tab setCustomView(android.view.View);
+ method public abstract android.support.v7.app.ActionBar.Tab setCustomView(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setIcon(android.graphics.drawable.Drawable);
+ method public abstract android.support.v7.app.ActionBar.Tab setIcon(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setTabListener(android.support.v7.app.ActionBar.TabListener);
+ method public abstract android.support.v7.app.ActionBar.Tab setTag(java.lang.Object);
+ method public abstract android.support.v7.app.ActionBar.Tab setText(java.lang.CharSequence);
+ method public abstract android.support.v7.app.ActionBar.Tab setText(int);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static abstract deprecated interface ActionBar.TabListener {
+ method public abstract void onTabReselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ method public abstract void onTabSelected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ }
+
+ public deprecated class ActionBarActivity extends android.support.v7.app.AppCompatActivity {
+ ctor public ActionBarActivity();
+ }
+
+ public class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, int, int);
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, android.support.v7.widget.Toolbar, int, int);
+ method public android.view.View.OnClickListener getToolbarNavigationClickListener();
+ method public boolean isDrawerIndicatorEnabled();
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public void onDrawerClosed(android.view.View);
+ method public void onDrawerOpened(android.view.View);
+ method public void onDrawerSlide(android.view.View, float);
+ method public void onDrawerStateChanged(int);
+ method public boolean onOptionsItemSelected(android.view.MenuItem);
+ method public void setDrawerIndicatorEnabled(boolean);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
+ method public void setHomeAsUpIndicator(int);
+ method public void setToolbarNavigationClickListener(android.view.View.OnClickListener);
+ method public void syncState();
+ }
+
+ public static abstract interface ActionBarDrawerToggle.Delegate {
+ method public abstract android.content.Context getActionBarThemedContext();
+ method public abstract android.graphics.drawable.Drawable getThemeUpIndicator();
+ method public abstract boolean isNavigationVisible();
+ method public abstract void setActionBarDescription(int);
+ method public abstract void setActionBarUpIndicator(android.graphics.drawable.Drawable, int);
+ }
+
+ public static abstract interface ActionBarDrawerToggle.DelegateProvider {
+ method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ }
+
+ public class AlertDialog extends android.support.v7.app.AppCompatDialog implements android.content.DialogInterface {
+ ctor protected AlertDialog(android.content.Context);
+ ctor protected AlertDialog(android.content.Context, int);
+ ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
+ method public android.widget.Button getButton(int);
+ method public android.widget.ListView getListView();
+ method public void setButton(int, java.lang.CharSequence, android.os.Message);
+ method public void setButton(int, java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public void setCustomTitle(android.view.View);
+ method public void setIcon(int);
+ method public void setIcon(android.graphics.drawable.Drawable);
+ method public void setIconAttribute(int);
+ method public void setMessage(java.lang.CharSequence);
+ method public void setView(android.view.View);
+ method public void setView(android.view.View, int, int, int, int);
+ }
+
+ public static class AlertDialog.Builder {
+ ctor public AlertDialog.Builder(android.content.Context);
+ ctor public AlertDialog.Builder(android.content.Context, int);
+ method public android.support.v7.app.AlertDialog create();
+ method public android.content.Context getContext();
+ method public android.support.v7.app.AlertDialog.Builder setAdapter(android.widget.ListAdapter, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setCancelable(boolean);
+ method public android.support.v7.app.AlertDialog.Builder setCursor(android.database.Cursor, android.content.DialogInterface.OnClickListener, java.lang.String);
+ method public android.support.v7.app.AlertDialog.Builder setCustomTitle(android.view.View);
+ method public android.support.v7.app.AlertDialog.Builder setIcon(int);
+ method public android.support.v7.app.AlertDialog.Builder setIcon(android.graphics.drawable.Drawable);
+ method public android.support.v7.app.AlertDialog.Builder setIconAttribute(int);
+ method public android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
+ method public android.support.v7.app.AlertDialog.Builder setItems(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setItems(java.lang.CharSequence[], android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMessage(int);
+ method public android.support.v7.app.AlertDialog.Builder setMessage(java.lang.CharSequence);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(int, boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(java.lang.CharSequence[], boolean[], android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setMultiChoiceItems(android.database.Cursor, java.lang.String, java.lang.String, android.content.DialogInterface.OnMultiChoiceClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNegativeButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNegativeButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNeutralButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setNeutralButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnCancelListener(android.content.DialogInterface.OnCancelListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnDismissListener(android.content.DialogInterface.OnDismissListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
+ method public android.support.v7.app.AlertDialog.Builder setOnKeyListener(android.content.DialogInterface.OnKeyListener);
+ method public android.support.v7.app.AlertDialog.Builder setPositiveButton(int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setPositiveButton(java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(int, int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.database.Cursor, int, java.lang.String, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(java.lang.CharSequence[], int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setSingleChoiceItems(android.widget.ListAdapter, int, android.content.DialogInterface.OnClickListener);
+ method public android.support.v7.app.AlertDialog.Builder setTitle(int);
+ method public android.support.v7.app.AlertDialog.Builder setTitle(java.lang.CharSequence);
+ method public android.support.v7.app.AlertDialog.Builder setView(int);
+ method public android.support.v7.app.AlertDialog.Builder setView(android.view.View);
+ method public android.support.v7.app.AlertDialog show();
+ }
+
+ public class AppCompatActivity extends android.support.v4.app.FragmentActivity implements android.support.v7.app.ActionBarDrawerToggle.DelegateProvider android.support.v7.app.AppCompatCallback {
+ ctor public AppCompatActivity();
+ method public android.support.v7.app.AppCompatDelegate getDelegate();
+ method public android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ method public android.support.v7.app.ActionBar getSupportActionBar();
+ method public android.content.Intent getSupportParentActivityIntent();
+ method public void onCreateSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder);
+ method public final boolean onMenuItemSelected(int, android.view.MenuItem);
+ method public void onPrepareSupportNavigateUpTaskStack(android.support.v4.app.TaskStackBuilder);
+ method public void onSupportActionModeFinished(android.support.v7.view.ActionMode);
+ method public void onSupportActionModeStarted(android.support.v7.view.ActionMode);
+ method public deprecated void onSupportContentChanged();
+ method public boolean onSupportNavigateUp();
+ method public android.support.v7.view.ActionMode onWindowStartingSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ method public void setSupportActionBar(android.support.v7.widget.Toolbar);
+ method public deprecated void setSupportProgress(int);
+ method public deprecated void setSupportProgressBarIndeterminate(boolean);
+ method public deprecated void setSupportProgressBarIndeterminateVisibility(boolean);
+ method public deprecated void setSupportProgressBarVisibility(boolean);
+ method public android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ method public void supportNavigateUpTo(android.content.Intent);
+ method public boolean supportRequestWindowFeature(int);
+ method public boolean supportShouldUpRecreateTask(android.content.Intent);
+ }
+
+ public abstract interface AppCompatCallback {
+ method public abstract void onSupportActionModeFinished(android.support.v7.view.ActionMode);
+ method public abstract void onSupportActionModeStarted(android.support.v7.view.ActionMode);
+ method public abstract android.support.v7.view.ActionMode onWindowStartingSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ }
+
+ public abstract class AppCompatDelegate {
+ method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public static android.support.v7.app.AppCompatDelegate create(android.app.Activity, android.support.v7.app.AppCompatCallback);
+ method public static android.support.v7.app.AppCompatDelegate create(android.app.Dialog, android.support.v7.app.AppCompatCallback);
+ method public abstract android.view.View createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ method public abstract android.view.MenuInflater getMenuInflater();
+ method public abstract android.support.v7.app.ActionBar getSupportActionBar();
+ method public abstract boolean hasWindowFeature(int);
+ method public abstract void installViewFactory();
+ method public abstract void invalidateOptionsMenu();
+ method public abstract boolean isHandleNativeActionModesEnabled();
+ method public abstract void onConfigurationChanged(android.content.res.Configuration);
+ method public abstract void onCreate(android.os.Bundle);
+ method public abstract void onDestroy();
+ method public abstract void onPostCreate(android.os.Bundle);
+ method public abstract void onPostResume();
+ method public abstract void onStop();
+ method public abstract boolean requestWindowFeature(int);
+ method public abstract void setContentView(android.view.View);
+ method public abstract void setContentView(int);
+ method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public abstract void setHandleNativeActionModesEnabled(boolean);
+ method public abstract void setSupportActionBar(android.support.v7.widget.Toolbar);
+ method public abstract void setTitle(java.lang.CharSequence);
+ method public abstract android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ field public static final int FEATURE_SUPPORT_ACTION_BAR = 108; // 0x6c
+ field public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 109; // 0x6d
+ }
+
+ public class AppCompatDialog extends android.app.Dialog implements android.support.v7.app.AppCompatCallback {
+ ctor public AppCompatDialog(android.content.Context);
+ ctor public AppCompatDialog(android.content.Context, int);
+ ctor protected AppCompatDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
+ method public android.support.v7.app.AppCompatDelegate getDelegate();
+ method public android.support.v7.app.ActionBar getSupportActionBar();
+ method public void onSupportActionModeFinished(android.support.v7.view.ActionMode);
+ method public void onSupportActionModeStarted(android.support.v7.view.ActionMode);
+ method public android.support.v7.view.ActionMode onWindowStartingSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ method public boolean supportRequestWindowFeature(int);
+ }
+
+ public class AppCompatDialogFragment extends android.support.v4.app.DialogFragment {
+ ctor public AppCompatDialogFragment();
+ }
+
+ public class NotificationCompat extends android.support.v4.app.NotificationCompat {
+ ctor public NotificationCompat();
+ }
+
+ public static class NotificationCompat.Builder extends android.support.v4.app.NotificationCompat.Builder {
+ ctor public NotificationCompat.Builder(android.content.Context);
+ }
+
+ public static class NotificationCompat.MediaStyle extends android.support.v4.app.NotificationCompat.Style {
+ ctor public NotificationCompat.MediaStyle();
+ ctor public NotificationCompat.MediaStyle(android.support.v4.app.NotificationCompat.Builder);
+ method public android.support.v7.app.NotificationCompat.MediaStyle setCancelButtonIntent(android.app.PendingIntent);
+ method public android.support.v7.app.NotificationCompat.MediaStyle setMediaSession(android.support.v4.media.session.MediaSessionCompat.Token);
+ method public android.support.v7.app.NotificationCompat.MediaStyle setShowActionsInCompactView(int...);
+ method public android.support.v7.app.NotificationCompat.MediaStyle setShowCancelButton(boolean);
+ }
+
+}
+
+package android.support.v7.appcompat {
+
+ public final class R {
+ ctor public R();
+ }
+
+ public static final class R.anim {
+ ctor public R.anim();
+ field public static int abc_fade_in;
+ field public static int abc_fade_out;
+ field public static int abc_grow_fade_in_from_bottom;
+ field public static int abc_popup_enter;
+ field public static int abc_popup_exit;
+ field public static int abc_shrink_fade_out_from_bottom;
+ field public static int abc_slide_in_bottom;
+ field public static int abc_slide_in_top;
+ field public static int abc_slide_out_bottom;
+ field public static int abc_slide_out_top;
+ }
+
+ public static final class R.attr {
+ ctor public R.attr();
+ field public static int actionBarDivider;
+ field public static int actionBarItemBackground;
+ field public static int actionBarPopupTheme;
+ field public static int actionBarSize;
+ field public static int actionBarSplitStyle;
+ field public static int actionBarStyle;
+ field public static int actionBarTabBarStyle;
+ field public static int actionBarTabStyle;
+ field public static int actionBarTabTextStyle;
+ field public static int actionBarTheme;
+ field public static int actionBarWidgetTheme;
+ field public static int actionButtonStyle;
+ field public static int actionDropDownStyle;
+ field public static int actionLayout;
+ field public static int actionMenuTextAppearance;
+ field public static int actionMenuTextColor;
+ field public static int actionModeBackground;
+ field public static int actionModeCloseButtonStyle;
+ field public static int actionModeCloseDrawable;
+ field public static int actionModeCopyDrawable;
+ field public static int actionModeCutDrawable;
+ field public static int actionModeFindDrawable;
+ field public static int actionModePasteDrawable;
+ field public static int actionModePopupWindowStyle;
+ field public static int actionModeSelectAllDrawable;
+ field public static int actionModeShareDrawable;
+ field public static int actionModeSplitBackground;
+ field public static int actionModeStyle;
+ field public static int actionModeWebSearchDrawable;
+ field public static int actionOverflowButtonStyle;
+ field public static int actionOverflowMenuStyle;
+ field public static int actionProviderClass;
+ field public static int actionViewClass;
+ field public static int activityChooserViewStyle;
+ field public static int alertDialogButtonGroupStyle;
+ field public static int alertDialogCenterButtons;
+ field public static int alertDialogStyle;
+ field public static int alertDialogTheme;
+ field public static int allowStacking;
+ field public static int arrowHeadLength;
+ field public static int arrowShaftLength;
+ field public static int autoCompleteTextViewStyle;
+ field public static int background;
+ field public static int backgroundSplit;
+ field public static int backgroundStacked;
+ field public static int backgroundTint;
+ field public static int backgroundTintMode;
+ field public static int barLength;
+ field public static int borderlessButtonStyle;
+ field public static int buttonBarButtonStyle;
+ field public static int buttonBarNegativeButtonStyle;
+ field public static int buttonBarNeutralButtonStyle;
+ field public static int buttonBarPositiveButtonStyle;
+ field public static int buttonBarStyle;
+ field public static int buttonPanelSideLayout;
+ field public static int buttonStyle;
+ field public static int buttonStyleSmall;
+ field public static int buttonTint;
+ field public static int buttonTintMode;
+ field public static int checkboxStyle;
+ field public static int checkedTextViewStyle;
+ field public static int closeIcon;
+ field public static int closeItemLayout;
+ field public static int collapseContentDescription;
+ field public static int collapseIcon;
+ field public static int color;
+ field public static int colorAccent;
+ field public static int colorButtonNormal;
+ field public static int colorControlActivated;
+ field public static int colorControlHighlight;
+ field public static int colorControlNormal;
+ field public static int colorPrimary;
+ field public static int colorPrimaryDark;
+ field public static int colorSwitchThumbNormal;
+ field public static int commitIcon;
+ field public static int contentInsetEnd;
+ field public static int contentInsetLeft;
+ field public static int contentInsetRight;
+ field public static int contentInsetStart;
+ field public static int controlBackground;
+ field public static int customNavigationLayout;
+ field public static int defaultQueryHint;
+ field public static int dialogPreferredPadding;
+ field public static int dialogTheme;
+ field public static int displayOptions;
+ field public static int divider;
+ field public static int dividerHorizontal;
+ field public static int dividerPadding;
+ field public static int dividerVertical;
+ field public static int drawableSize;
+ field public static int drawerArrowStyle;
+ field public static int dropDownListViewStyle;
+ field public static int dropdownListPreferredItemHeight;
+ field public static int editTextBackground;
+ field public static int editTextColor;
+ field public static int editTextStyle;
+ field public static int elevation;
+ field public static int expandActivityOverflowButtonDrawable;
+ field public static int gapBetweenBars;
+ field public static int goIcon;
+ field public static int height;
+ field public static int hideOnContentScroll;
+ field public static int homeAsUpIndicator;
+ field public static int homeLayout;
+ field public static int icon;
+ field public static int iconifiedByDefault;
+ field public static int imageButtonStyle;
+ field public static int indeterminateProgressStyle;
+ field public static int initialActivityCount;
+ field public static int isLightTheme;
+ field public static int itemPadding;
+ field public static int layout;
+ field public static int listChoiceBackgroundIndicator;
+ field public static int listDividerAlertDialog;
+ field public static int listItemLayout;
+ field public static int listLayout;
+ field public static int listPopupWindowStyle;
+ field public static int listPreferredItemHeight;
+ field public static int listPreferredItemHeightLarge;
+ field public static int listPreferredItemHeightSmall;
+ field public static int listPreferredItemPaddingLeft;
+ field public static int listPreferredItemPaddingRight;
+ field public static int logo;
+ field public static int logoDescription;
+ field public static int maxButtonHeight;
+ field public static int measureWithLargestChild;
+ field public static int multiChoiceItemLayout;
+ field public static int navigationContentDescription;
+ field public static int navigationIcon;
+ field public static int navigationMode;
+ field public static int overlapAnchor;
+ field public static int paddingEnd;
+ field public static int paddingStart;
+ field public static int panelBackground;
+ field public static int panelMenuListTheme;
+ field public static int panelMenuListWidth;
+ field public static int popupMenuStyle;
+ field public static int popupTheme;
+ field public static int popupWindowStyle;
+ field public static int preserveIconSpacing;
+ field public static int progressBarPadding;
+ field public static int progressBarStyle;
+ field public static int queryBackground;
+ field public static int queryHint;
+ field public static int radioButtonStyle;
+ field public static int ratingBarStyle;
+ field public static int searchHintIcon;
+ field public static int searchIcon;
+ field public static int searchViewStyle;
+ field public static int seekBarStyle;
+ field public static int selectableItemBackground;
+ field public static int selectableItemBackgroundBorderless;
+ field public static int showAsAction;
+ field public static int showDividers;
+ field public static int showText;
+ field public static int singleChoiceItemLayout;
+ field public static int spinBars;
+ field public static int spinnerDropDownItemStyle;
+ field public static int spinnerStyle;
+ field public static int splitTrack;
+ field public static int state_above_anchor;
+ field public static int submitBackground;
+ field public static int subtitle;
+ field public static int subtitleTextAppearance;
+ field public static int subtitleTextColor;
+ field public static int subtitleTextStyle;
+ field public static int suggestionRowLayout;
+ field public static int switchMinWidth;
+ field public static int switchPadding;
+ field public static int switchStyle;
+ field public static int switchTextAppearance;
+ field public static int textAllCaps;
+ field public static int textAppearanceLargePopupMenu;
+ field public static int textAppearanceListItem;
+ field public static int textAppearanceListItemSmall;
+ field public static int textAppearanceSearchResultSubtitle;
+ field public static int textAppearanceSearchResultTitle;
+ field public static int textAppearanceSmallPopupMenu;
+ field public static int textColorAlertDialogListItem;
+ field public static int textColorSearchUrl;
+ field public static int theme;
+ field public static int thickness;
+ field public static int thumbTextPadding;
+ field public static int title;
+ field public static int titleMarginBottom;
+ field public static int titleMarginEnd;
+ field public static int titleMarginStart;
+ field public static int titleMarginTop;
+ field public static int titleMargins;
+ field public static int titleTextAppearance;
+ field public static int titleTextColor;
+ field public static int titleTextStyle;
+ field public static int toolbarNavigationButtonStyle;
+ field public static int toolbarStyle;
+ field public static int track;
+ field public static int voiceIcon;
+ field public static int windowActionBar;
+ field public static int windowActionBarOverlay;
+ field public static int windowActionModeOverlay;
+ field public static int windowFixedHeightMajor;
+ field public static int windowFixedHeightMinor;
+ field public static int windowFixedWidthMajor;
+ field public static int windowFixedWidthMinor;
+ field public static int windowMinWidthMajor;
+ field public static int windowMinWidthMinor;
+ field public static int windowNoTitle;
+ }
+
+ public static final class R.bool {
+ ctor public R.bool();
+ field public static int abc_action_bar_embed_tabs;
+ field public static int abc_action_bar_embed_tabs_pre_jb;
+ field public static int abc_action_bar_expanded_action_views_exclusive;
+ field public static int abc_allow_stacked_button_bar;
+ field public static int abc_config_actionMenuItemAllCaps;
+ field public static int abc_config_allowActionMenuItemTextWithIcon;
+ field public static int abc_config_closeDialogWhenTouchOutside;
+ field public static int abc_config_showMenuShortcutsWhenKeyboardPresent;
+ }
+
+ public static final class R.color {
+ ctor public R.color();
+ field public static int abc_background_cache_hint_selector_material_dark;
+ field public static int abc_background_cache_hint_selector_material_light;
+ field public static int abc_color_highlight_material;
+ field public static int abc_input_method_navigation_guard;
+ field public static int abc_primary_text_disable_only_material_dark;
+ field public static int abc_primary_text_disable_only_material_light;
+ field public static int abc_primary_text_material_dark;
+ field public static int abc_primary_text_material_light;
+ field public static int abc_search_url_text;
+ field public static int abc_search_url_text_normal;
+ field public static int abc_search_url_text_pressed;
+ field public static int abc_search_url_text_selected;
+ field public static int abc_secondary_text_material_dark;
+ field public static int abc_secondary_text_material_light;
+ field public static int accent_material_dark;
+ field public static int accent_material_light;
+ field public static int background_floating_material_dark;
+ field public static int background_floating_material_light;
+ field public static int background_material_dark;
+ field public static int background_material_light;
+ field public static int bright_foreground_disabled_material_dark;
+ field public static int bright_foreground_disabled_material_light;
+ field public static int bright_foreground_inverse_material_dark;
+ field public static int bright_foreground_inverse_material_light;
+ field public static int bright_foreground_material_dark;
+ field public static int bright_foreground_material_light;
+ field public static int button_material_dark;
+ field public static int button_material_light;
+ field public static int dim_foreground_disabled_material_dark;
+ field public static int dim_foreground_disabled_material_light;
+ field public static int dim_foreground_material_dark;
+ field public static int dim_foreground_material_light;
+ field public static int foreground_material_dark;
+ field public static int foreground_material_light;
+ field public static int highlighted_text_material_dark;
+ field public static int highlighted_text_material_light;
+ field public static int hint_foreground_material_dark;
+ field public static int hint_foreground_material_light;
+ field public static int material_blue_grey_800;
+ field public static int material_blue_grey_900;
+ field public static int material_blue_grey_950;
+ field public static int material_deep_teal_200;
+ field public static int material_deep_teal_500;
+ field public static int material_grey_100;
+ field public static int material_grey_300;
+ field public static int material_grey_50;
+ field public static int material_grey_600;
+ field public static int material_grey_800;
+ field public static int material_grey_850;
+ field public static int material_grey_900;
+ field public static int primary_dark_material_dark;
+ field public static int primary_dark_material_light;
+ field public static int primary_material_dark;
+ field public static int primary_material_light;
+ field public static int primary_text_default_material_dark;
+ field public static int primary_text_default_material_light;
+ field public static int primary_text_disabled_material_dark;
+ field public static int primary_text_disabled_material_light;
+ field public static int ripple_material_dark;
+ field public static int ripple_material_light;
+ field public static int secondary_text_default_material_dark;
+ field public static int secondary_text_default_material_light;
+ field public static int secondary_text_disabled_material_dark;
+ field public static int secondary_text_disabled_material_light;
+ field public static int switch_thumb_disabled_material_dark;
+ field public static int switch_thumb_disabled_material_light;
+ field public static int switch_thumb_material_dark;
+ field public static int switch_thumb_material_light;
+ field public static int switch_thumb_normal_material_dark;
+ field public static int switch_thumb_normal_material_light;
+ }
+
+ public static final class R.dimen {
+ ctor public R.dimen();
+ field public static int abc_action_bar_content_inset_material;
+ field public static int abc_action_bar_default_height_material;
+ field public static int abc_action_bar_default_padding_end_material;
+ field public static int abc_action_bar_default_padding_start_material;
+ field public static int abc_action_bar_icon_vertical_padding_material;
+ field public static int abc_action_bar_overflow_padding_end_material;
+ field public static int abc_action_bar_overflow_padding_start_material;
+ field public static int abc_action_bar_progress_bar_size;
+ field public static int abc_action_bar_stacked_max_height;
+ field public static int abc_action_bar_stacked_tab_max_width;
+ field public static int abc_action_bar_subtitle_bottom_margin_material;
+ field public static int abc_action_bar_subtitle_top_margin_material;
+ field public static int abc_action_button_min_height_material;
+ field public static int abc_action_button_min_width_material;
+ field public static int abc_action_button_min_width_overflow_material;
+ field public static int abc_alert_dialog_button_bar_height;
+ field public static int abc_button_inset_horizontal_material;
+ field public static int abc_button_inset_vertical_material;
+ field public static int abc_button_padding_horizontal_material;
+ field public static int abc_button_padding_vertical_material;
+ field public static int abc_config_prefDialogWidth;
+ field public static int abc_control_corner_material;
+ field public static int abc_control_inset_material;
+ field public static int abc_control_padding_material;
+ field public static int abc_dialog_fixed_height_major;
+ field public static int abc_dialog_fixed_height_minor;
+ field public static int abc_dialog_fixed_width_major;
+ field public static int abc_dialog_fixed_width_minor;
+ field public static int abc_dialog_list_padding_vertical_material;
+ field public static int abc_dialog_min_width_major;
+ field public static int abc_dialog_min_width_minor;
+ field public static int abc_dialog_padding_material;
+ field public static int abc_dialog_padding_top_material;
+ field public static int abc_disabled_alpha_material_dark;
+ field public static int abc_disabled_alpha_material_light;
+ field public static int abc_dropdownitem_icon_width;
+ field public static int abc_dropdownitem_text_padding_left;
+ field public static int abc_dropdownitem_text_padding_right;
+ field public static int abc_edit_text_inset_bottom_material;
+ field public static int abc_edit_text_inset_horizontal_material;
+ field public static int abc_edit_text_inset_top_material;
+ field public static int abc_floating_window_z;
+ field public static int abc_list_item_padding_horizontal_material;
+ field public static int abc_panel_menu_list_width;
+ field public static int abc_search_view_preferred_width;
+ field public static int abc_search_view_text_min_width;
+ field public static int abc_seekbar_track_background_height_material;
+ field public static int abc_seekbar_track_progress_height_material;
+ field public static int abc_select_dialog_padding_start_material;
+ field public static int abc_switch_padding;
+ field public static int abc_text_size_body_1_material;
+ field public static int abc_text_size_body_2_material;
+ field public static int abc_text_size_button_material;
+ field public static int abc_text_size_caption_material;
+ field public static int abc_text_size_display_1_material;
+ field public static int abc_text_size_display_2_material;
+ field public static int abc_text_size_display_3_material;
+ field public static int abc_text_size_display_4_material;
+ field public static int abc_text_size_headline_material;
+ field public static int abc_text_size_large_material;
+ field public static int abc_text_size_medium_material;
+ field public static int abc_text_size_menu_material;
+ field public static int abc_text_size_small_material;
+ field public static int abc_text_size_subhead_material;
+ field public static int abc_text_size_subtitle_material_toolbar;
+ field public static int abc_text_size_title_material;
+ field public static int abc_text_size_title_material_toolbar;
+ field public static int disabled_alpha_material_dark;
+ field public static int disabled_alpha_material_light;
+ field public static int highlight_alpha_material_colored;
+ field public static int highlight_alpha_material_dark;
+ field public static int highlight_alpha_material_light;
+ field public static int notification_large_icon_height;
+ field public static int notification_large_icon_width;
+ field public static int notification_subtext_size;
+ }
+
+ public static final class R.drawable {
+ ctor public R.drawable();
+ field public static int abc_ab_share_pack_mtrl_alpha;
+ 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_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_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;
+ field public static int abc_btn_switch_to_on_mtrl_00012;
+ field public static int abc_cab_background_internal_bg;
+ field public static int abc_cab_background_top_material;
+ field public static int abc_cab_background_top_mtrl_alpha;
+ field public static int abc_control_background_material;
+ 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_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_search_api_mtrl_alpha;
+ field public static int abc_ic_voice_search_api_mtrl_alpha;
+ 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;
+ field public static int abc_list_focused_holo;
+ field public static int abc_list_longpressed_holo;
+ field public static int abc_list_pressed_holo_dark;
+ field public static int abc_list_pressed_holo_light;
+ field public static int abc_list_selector_background_transition_holo_dark;
+ field public static int abc_list_selector_background_transition_holo_light;
+ field public static int abc_list_selector_disabled_holo_dark;
+ field public static int abc_list_selector_disabled_holo_light;
+ field public static int abc_list_selector_holo_dark;
+ field public static int abc_list_selector_holo_light;
+ field public static int abc_menu_hardkey_panel_mtrl_mult;
+ field public static int abc_popup_background_mtrl_mult;
+ field public static int abc_ratingbar_full_material;
+ field public static int abc_scrubber_control_off_mtrl_alpha;
+ field public static int abc_scrubber_control_to_pressed_mtrl_000;
+ field public static int abc_scrubber_control_to_pressed_mtrl_005;
+ field public static int abc_scrubber_primary_mtrl_alpha;
+ field public static int abc_scrubber_track_mtrl_alpha;
+ field public static int abc_seekbar_thumb_material;
+ field public static int abc_seekbar_track_material;
+ field public static int abc_spinner_mtrl_am_alpha;
+ field public static int abc_spinner_textfield_background_material;
+ field public static int abc_switch_thumb_material;
+ field public static int abc_switch_track_mtrl_alpha;
+ field public static int abc_tab_indicator_material;
+ field public static int abc_tab_indicator_mtrl_alpha;
+ field public static int abc_text_cursor_material;
+ field public static int abc_textfield_activated_mtrl_alpha;
+ field public static int abc_textfield_default_mtrl_alpha;
+ field public static int abc_textfield_search_activated_mtrl_alpha;
+ field public static int abc_textfield_search_default_mtrl_alpha;
+ field public static int abc_textfield_search_material;
+ field public static int notification_template_icon_bg;
+ }
+
+ public static final class R.id {
+ ctor public R.id();
+ field public static int action0;
+ field public static int action_bar;
+ field public static int action_bar_activity_content;
+ field public static int action_bar_container;
+ field public static int action_bar_root;
+ field public static int action_bar_spinner;
+ field public static int action_bar_subtitle;
+ field public static int action_bar_title;
+ field public static int action_context_bar;
+ field public static int action_divider;
+ field public static int action_menu_divider;
+ field public static int action_menu_presenter;
+ field public static int action_mode_bar;
+ field public static int action_mode_bar_stub;
+ field public static int action_mode_close_button;
+ field public static int activity_chooser_view_content;
+ field public static int alertTitle;
+ field public static int always;
+ field public static int beginning;
+ field public static int buttonPanel;
+ field public static int cancel_action;
+ field public static int checkbox;
+ field public static int chronometer;
+ field public static int collapseActionView;
+ field public static int contentPanel;
+ field public static int custom;
+ field public static int customPanel;
+ field public static int decor_content_parent;
+ field public static int default_activity_button;
+ field public static int disableHome;
+ field public static int edit_query;
+ field public static int end;
+ field public static int end_padder;
+ field public static int expand_activities_button;
+ field public static int expanded_menu;
+ field public static int home;
+ field public static int homeAsUp;
+ field public static int icon;
+ field public static int ifRoom;
+ field public static int image;
+ field public static int info;
+ field public static int line1;
+ field public static int line3;
+ field public static int listMode;
+ field public static int list_item;
+ field public static int media_actions;
+ field public static int middle;
+ field public static int multiply;
+ field public static int never;
+ field public static int none;
+ field public static int normal;
+ field public static int parentPanel;
+ field public static int progress_circular;
+ field public static int progress_horizontal;
+ field public static int radio;
+ field public static int screen;
+ field public static int scrollIndicatorDown;
+ field public static int scrollIndicatorUp;
+ field public static int scrollView;
+ field public static int search_badge;
+ field public static int search_bar;
+ field public static int search_button;
+ field public static int search_close_btn;
+ field public static int search_edit_frame;
+ field public static int search_go_btn;
+ field public static int search_mag_icon;
+ field public static int search_plate;
+ field public static int search_src_text;
+ field public static int search_voice_btn;
+ field public static int select_dialog_listview;
+ field public static int shortcut;
+ field public static int showCustom;
+ field public static int showHome;
+ field public static int showTitle;
+ field public static int spacer;
+ field public static int split_action_bar;
+ field public static int src_atop;
+ field public static int src_in;
+ field public static int src_over;
+ field public static int status_bar_latest_event_content;
+ field public static int submit_area;
+ field public static int tabMode;
+ field public static int text;
+ field public static int text2;
+ field public static int textSpacerNoButtons;
+ field public static int time;
+ field public static int title;
+ field public static int title_template;
+ field public static int topPanel;
+ field public static int up;
+ field public static int useLogo;
+ field public static int withText;
+ field public static int wrap_content;
+ }
+
+ public static final class R.integer {
+ ctor public R.integer();
+ field public static int abc_config_activityDefaultDur;
+ field public static int abc_config_activityShortDur;
+ field public static int abc_max_action_buttons;
+ field public static int cancel_button_image_alpha;
+ field public static int status_bar_notification_info_maxnum;
+ }
+
+ public static final class R.layout {
+ ctor public R.layout();
+ field public static int abc_action_bar_title_item;
+ field public static int abc_action_bar_up_container;
+ field public static int abc_action_bar_view_list_nav_layout;
+ field public static int abc_action_menu_item_layout;
+ field public static int abc_action_menu_layout;
+ field public static int abc_action_mode_bar;
+ field public static int abc_action_mode_close_item_material;
+ field public static int abc_activity_chooser_view;
+ field public static int abc_activity_chooser_view_list_item;
+ field public static int abc_alert_dialog_button_bar_material;
+ field public static int abc_alert_dialog_material;
+ field public static int abc_dialog_title_material;
+ field public static int abc_expanded_menu_layout;
+ field public static int abc_list_menu_item_checkbox;
+ field public static int abc_list_menu_item_icon;
+ field public static int abc_list_menu_item_layout;
+ field public static int abc_list_menu_item_radio;
+ field public static int abc_popup_menu_item_layout;
+ field public static int abc_screen_content_include;
+ field public static int abc_screen_simple;
+ field public static int abc_screen_simple_overlay_action_mode;
+ field public static int abc_screen_toolbar;
+ field public static int abc_search_dropdown_item_icons_2line;
+ field public static int abc_search_view;
+ field public static int abc_select_dialog_material;
+ field public static int notification_media_action;
+ field public static int notification_media_cancel_action;
+ field public static int notification_template_big_media;
+ field public static int notification_template_big_media_narrow;
+ field public static int notification_template_lines;
+ field public static int notification_template_media;
+ field public static int notification_template_part_chronometer;
+ field public static int notification_template_part_time;
+ field public static int select_dialog_item_material;
+ field public static int select_dialog_multichoice_material;
+ field public static int select_dialog_singlechoice_material;
+ field public static int support_simple_spinner_dropdown_item;
+ }
+
+ public static final class R.string {
+ ctor public R.string();
+ field public static int abc_action_bar_home_description;
+ field public static int abc_action_bar_home_description_format;
+ field public static int abc_action_bar_home_subtitle_description_format;
+ field public static int abc_action_bar_up_description;
+ field public static int abc_action_menu_overflow_description;
+ field public static int abc_action_mode_done;
+ field public static int abc_activity_chooser_view_see_all;
+ field public static int abc_activitychooserview_choose_application;
+ field public static int abc_capital_off;
+ field public static int abc_capital_on;
+ field public static int abc_search_hint;
+ field public static int abc_searchview_description_clear;
+ field public static int abc_searchview_description_query;
+ field public static int abc_searchview_description_search;
+ field public static int abc_searchview_description_submit;
+ field public static int abc_searchview_description_voice;
+ field public static int abc_shareactionprovider_share_with;
+ field public static int abc_shareactionprovider_share_with_application;
+ field public static int abc_toolbar_collapse_description;
+ field public static int status_bar_notification_info_overflow;
+ }
+
+ public static final class R.style {
+ ctor public R.style();
+ field public static int AlertDialog_AppCompat;
+ field public static int AlertDialog_AppCompat_Light;
+ field public static int Animation_AppCompat_Dialog;
+ field public static int Animation_AppCompat_DropDownUp;
+ field public static int Base_AlertDialog_AppCompat;
+ field public static int Base_AlertDialog_AppCompat_Light;
+ field public static int Base_Animation_AppCompat_Dialog;
+ field public static int Base_Animation_AppCompat_DropDownUp;
+ field public static int Base_DialogWindowTitleBackground_AppCompat;
+ field public static int Base_DialogWindowTitle_AppCompat;
+ field public static int Base_TextAppearance_AppCompat;
+ field public static int Base_TextAppearance_AppCompat_Body1;
+ field public static int Base_TextAppearance_AppCompat_Body2;
+ field public static int Base_TextAppearance_AppCompat_Button;
+ field public static int Base_TextAppearance_AppCompat_Caption;
+ field public static int Base_TextAppearance_AppCompat_Display1;
+ field public static int Base_TextAppearance_AppCompat_Display2;
+ field public static int Base_TextAppearance_AppCompat_Display3;
+ field public static int Base_TextAppearance_AppCompat_Display4;
+ field public static int Base_TextAppearance_AppCompat_Headline;
+ field public static int Base_TextAppearance_AppCompat_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Large;
+ field public static int Base_TextAppearance_AppCompat_Large_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Large;
+ field public static int Base_TextAppearance_AppCompat_Light_Widget_PopupMenu_Small;
+ field public static int Base_TextAppearance_AppCompat_Medium;
+ field public static int Base_TextAppearance_AppCompat_Medium_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Menu;
+ field public static int Base_TextAppearance_AppCompat_SearchResult;
+ field public static int Base_TextAppearance_AppCompat_SearchResult_Subtitle;
+ field public static int Base_TextAppearance_AppCompat_SearchResult_Title;
+ field public static int Base_TextAppearance_AppCompat_Small;
+ field public static int Base_TextAppearance_AppCompat_Small_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Subhead;
+ field public static int Base_TextAppearance_AppCompat_Subhead_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Title;
+ field public static int Base_TextAppearance_AppCompat_Title_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionBar_Menu;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionBar_Title;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionMode_Subtitle;
+ field public static int Base_TextAppearance_AppCompat_Widget_ActionMode_Title;
+ field public static int Base_TextAppearance_AppCompat_Widget_Button;
+ field public static int Base_TextAppearance_AppCompat_Widget_Button_Inverse;
+ field public static int Base_TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large;
+ field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small;
+ field public static int Base_TextAppearance_AppCompat_Widget_Switch;
+ field public static int Base_TextAppearance_AppCompat_Widget_TextView_SpinnerItem;
+ field public static int Base_TextAppearance_Widget_AppCompat_ExpandedMenu_Item;
+ field public static int Base_TextAppearance_Widget_AppCompat_Toolbar_Subtitle;
+ field public static int Base_TextAppearance_Widget_AppCompat_Toolbar_Title;
+ field public static int Base_ThemeOverlay_AppCompat;
+ field public static int Base_ThemeOverlay_AppCompat_ActionBar;
+ field public static int Base_ThemeOverlay_AppCompat_Dark;
+ field public static int Base_ThemeOverlay_AppCompat_Dark_ActionBar;
+ field public static int Base_ThemeOverlay_AppCompat_Light;
+ field public static int Base_Theme_AppCompat;
+ field public static int Base_Theme_AppCompat_CompactMenu;
+ field public static int Base_Theme_AppCompat_Dialog;
+ field public static int Base_Theme_AppCompat_DialogWhenLarge;
+ field public static int Base_Theme_AppCompat_Dialog_Alert;
+ field public static int Base_Theme_AppCompat_Dialog_FixedSize;
+ field public static int Base_Theme_AppCompat_Dialog_MinWidth;
+ field public static int Base_Theme_AppCompat_Light;
+ field public static int Base_Theme_AppCompat_Light_DarkActionBar;
+ field public static int Base_Theme_AppCompat_Light_Dialog;
+ field public static int Base_Theme_AppCompat_Light_DialogWhenLarge;
+ field public static int Base_Theme_AppCompat_Light_Dialog_Alert;
+ field public static int Base_Theme_AppCompat_Light_Dialog_FixedSize;
+ field public static int Base_Theme_AppCompat_Light_Dialog_MinWidth;
+ field public static int Base_V11_Theme_AppCompat_Dialog;
+ field public static int Base_V11_Theme_AppCompat_Light_Dialog;
+ field public static int Base_V12_Widget_AppCompat_AutoCompleteTextView;
+ field public static int Base_V12_Widget_AppCompat_EditText;
+ field public static int Base_V21_Theme_AppCompat;
+ field public static int Base_V21_Theme_AppCompat_Dialog;
+ field public static int Base_V21_Theme_AppCompat_Light;
+ field public static int Base_V21_Theme_AppCompat_Light_Dialog;
+ field public static int Base_V22_Theme_AppCompat;
+ field public static int Base_V22_Theme_AppCompat_Light;
+ field public static int Base_V23_Theme_AppCompat;
+ field public static int Base_V23_Theme_AppCompat_Light;
+ field public static int Base_V7_Theme_AppCompat;
+ field public static int Base_V7_Theme_AppCompat_Dialog;
+ field public static int Base_V7_Theme_AppCompat_Light;
+ field public static int Base_V7_Theme_AppCompat_Light_Dialog;
+ field public static int Base_V7_Widget_AppCompat_AutoCompleteTextView;
+ field public static int Base_V7_Widget_AppCompat_EditText;
+ field public static int Base_Widget_AppCompat_ActionBar;
+ field public static int Base_Widget_AppCompat_ActionBar_Solid;
+ field public static int Base_Widget_AppCompat_ActionBar_TabBar;
+ field public static int Base_Widget_AppCompat_ActionBar_TabText;
+ field public static int Base_Widget_AppCompat_ActionBar_TabView;
+ field public static int Base_Widget_AppCompat_ActionButton;
+ field public static int Base_Widget_AppCompat_ActionButton_CloseMode;
+ field public static int Base_Widget_AppCompat_ActionButton_Overflow;
+ field public static int Base_Widget_AppCompat_ActionMode;
+ field public static int Base_Widget_AppCompat_ActivityChooserView;
+ field public static int Base_Widget_AppCompat_AutoCompleteTextView;
+ field public static int Base_Widget_AppCompat_Button;
+ field public static int Base_Widget_AppCompat_ButtonBar;
+ field public static int Base_Widget_AppCompat_ButtonBar_AlertDialog;
+ field public static int Base_Widget_AppCompat_Button_Borderless;
+ field public static int Base_Widget_AppCompat_Button_Borderless_Colored;
+ field public static int Base_Widget_AppCompat_Button_ButtonBar_AlertDialog;
+ field public static int Base_Widget_AppCompat_Button_Colored;
+ field public static int Base_Widget_AppCompat_Button_Small;
+ field public static int Base_Widget_AppCompat_CompoundButton_CheckBox;
+ field public static int Base_Widget_AppCompat_CompoundButton_RadioButton;
+ field public static int Base_Widget_AppCompat_CompoundButton_Switch;
+ field public static int Base_Widget_AppCompat_DrawerArrowToggle;
+ field public static int Base_Widget_AppCompat_DrawerArrowToggle_Common;
+ field public static int Base_Widget_AppCompat_DropDownItem_Spinner;
+ field public static int Base_Widget_AppCompat_EditText;
+ field public static int Base_Widget_AppCompat_ImageButton;
+ field public static int Base_Widget_AppCompat_Light_ActionBar;
+ field public static int Base_Widget_AppCompat_Light_ActionBar_Solid;
+ field public static int Base_Widget_AppCompat_Light_ActionBar_TabBar;
+ field public static int Base_Widget_AppCompat_Light_ActionBar_TabText;
+ field public static int Base_Widget_AppCompat_Light_ActionBar_TabText_Inverse;
+ field public static int Base_Widget_AppCompat_Light_ActionBar_TabView;
+ field public static int Base_Widget_AppCompat_Light_PopupMenu;
+ field public static int Base_Widget_AppCompat_Light_PopupMenu_Overflow;
+ field public static int Base_Widget_AppCompat_ListPopupWindow;
+ field public static int Base_Widget_AppCompat_ListView;
+ field public static int Base_Widget_AppCompat_ListView_DropDown;
+ field public static int Base_Widget_AppCompat_ListView_Menu;
+ field public static int Base_Widget_AppCompat_PopupMenu;
+ field public static int Base_Widget_AppCompat_PopupMenu_Overflow;
+ field public static int Base_Widget_AppCompat_PopupWindow;
+ field public static int Base_Widget_AppCompat_ProgressBar;
+ field public static int Base_Widget_AppCompat_ProgressBar_Horizontal;
+ field public static int Base_Widget_AppCompat_RatingBar;
+ field public static int Base_Widget_AppCompat_SearchView;
+ field public static int Base_Widget_AppCompat_SearchView_ActionBar;
+ field public static int Base_Widget_AppCompat_SeekBar;
+ field public static int Base_Widget_AppCompat_Spinner;
+ field public static int Base_Widget_AppCompat_Spinner_Underlined;
+ field public static int Base_Widget_AppCompat_TextView_SpinnerItem;
+ field public static int Base_Widget_AppCompat_Toolbar;
+ field public static int Base_Widget_AppCompat_Toolbar_Button_Navigation;
+ field public static int Platform_AppCompat;
+ field public static int Platform_AppCompat_Light;
+ field public static int Platform_ThemeOverlay_AppCompat;
+ field public static int Platform_ThemeOverlay_AppCompat_Dark;
+ field public static int Platform_ThemeOverlay_AppCompat_Light;
+ field public static int Platform_V11_AppCompat;
+ field public static int Platform_V11_AppCompat_Light;
+ field public static int Platform_V14_AppCompat;
+ field public static int Platform_V14_AppCompat_Light;
+ field public static int Platform_Widget_AppCompat_Spinner;
+ field public static int RtlOverlay_DialogWindowTitle_AppCompat;
+ field public static int RtlOverlay_Widget_AppCompat_ActionBar_TitleItem;
+ field public static int RtlOverlay_Widget_AppCompat_DialogTitle_Icon;
+ field public static int RtlOverlay_Widget_AppCompat_PopupMenuItem;
+ field public static int RtlOverlay_Widget_AppCompat_PopupMenuItem_InternalGroup;
+ field public static int RtlOverlay_Widget_AppCompat_PopupMenuItem_Text;
+ field public static int RtlOverlay_Widget_AppCompat_SearchView_MagIcon;
+ field public static int RtlOverlay_Widget_AppCompat_Search_DropDown;
+ field public static int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon1;
+ field public static int RtlOverlay_Widget_AppCompat_Search_DropDown_Icon2;
+ field public static int RtlOverlay_Widget_AppCompat_Search_DropDown_Query;
+ field public static int RtlOverlay_Widget_AppCompat_Search_DropDown_Text;
+ field public static int RtlUnderlay_Widget_AppCompat_ActionButton;
+ field public static int RtlUnderlay_Widget_AppCompat_ActionButton_Overflow;
+ field public static int TextAppearance_AppCompat;
+ field public static int TextAppearance_AppCompat_Body1;
+ field public static int TextAppearance_AppCompat_Body2;
+ field public static int TextAppearance_AppCompat_Button;
+ field public static int TextAppearance_AppCompat_Caption;
+ field public static int TextAppearance_AppCompat_Display1;
+ field public static int TextAppearance_AppCompat_Display2;
+ field public static int TextAppearance_AppCompat_Display3;
+ field public static int TextAppearance_AppCompat_Display4;
+ field public static int TextAppearance_AppCompat_Headline;
+ field public static int TextAppearance_AppCompat_Inverse;
+ field public static int TextAppearance_AppCompat_Large;
+ field public static int TextAppearance_AppCompat_Large_Inverse;
+ field public static int TextAppearance_AppCompat_Light_SearchResult_Subtitle;
+ field public static int TextAppearance_AppCompat_Light_SearchResult_Title;
+ field public static int TextAppearance_AppCompat_Light_Widget_PopupMenu_Large;
+ field public static int TextAppearance_AppCompat_Light_Widget_PopupMenu_Small;
+ field public static int TextAppearance_AppCompat_Medium;
+ field public static int TextAppearance_AppCompat_Medium_Inverse;
+ field public static int TextAppearance_AppCompat_Menu;
+ field public static int TextAppearance_AppCompat_SearchResult_Subtitle;
+ field public static int TextAppearance_AppCompat_SearchResult_Title;
+ field public static int TextAppearance_AppCompat_Small;
+ field public static int TextAppearance_AppCompat_Small_Inverse;
+ field public static int TextAppearance_AppCompat_Subhead;
+ field public static int TextAppearance_AppCompat_Subhead_Inverse;
+ field public static int TextAppearance_AppCompat_Title;
+ field public static int TextAppearance_AppCompat_Title_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_ActionBar_Menu;
+ field public static int TextAppearance_AppCompat_Widget_ActionBar_Subtitle;
+ field public static int TextAppearance_AppCompat_Widget_ActionBar_Subtitle_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_ActionBar_Title;
+ field public static int TextAppearance_AppCompat_Widget_ActionBar_Title_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_ActionMode_Subtitle;
+ field public static int TextAppearance_AppCompat_Widget_ActionMode_Subtitle_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_ActionMode_Title;
+ field public static int TextAppearance_AppCompat_Widget_ActionMode_Title_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_Button;
+ field public static int TextAppearance_AppCompat_Widget_Button_Inverse;
+ field public static int TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int TextAppearance_AppCompat_Widget_PopupMenu_Large;
+ field public static int TextAppearance_AppCompat_Widget_PopupMenu_Small;
+ field public static int TextAppearance_AppCompat_Widget_Switch;
+ field public static int TextAppearance_AppCompat_Widget_TextView_SpinnerItem;
+ field public static int TextAppearance_StatusBar_EventContent;
+ field public static int TextAppearance_StatusBar_EventContent_Info;
+ field public static int TextAppearance_StatusBar_EventContent_Line2;
+ field public static int TextAppearance_StatusBar_EventContent_Time;
+ field public static int TextAppearance_StatusBar_EventContent_Title;
+ field public static int TextAppearance_Widget_AppCompat_ExpandedMenu_Item;
+ field public static int TextAppearance_Widget_AppCompat_Toolbar_Subtitle;
+ field public static int TextAppearance_Widget_AppCompat_Toolbar_Title;
+ field public static int ThemeOverlay_AppCompat;
+ field public static int ThemeOverlay_AppCompat_ActionBar;
+ field public static int ThemeOverlay_AppCompat_Dark;
+ field public static int ThemeOverlay_AppCompat_Dark_ActionBar;
+ field public static int ThemeOverlay_AppCompat_Light;
+ field public static int Theme_AppCompat;
+ field public static int Theme_AppCompat_CompactMenu;
+ field public static int Theme_AppCompat_Dialog;
+ field public static int Theme_AppCompat_DialogWhenLarge;
+ field public static int Theme_AppCompat_Dialog_Alert;
+ field public static int Theme_AppCompat_Dialog_MinWidth;
+ field public static int Theme_AppCompat_Light;
+ field public static int Theme_AppCompat_Light_DarkActionBar;
+ field public static int Theme_AppCompat_Light_Dialog;
+ field public static int Theme_AppCompat_Light_DialogWhenLarge;
+ field public static int Theme_AppCompat_Light_Dialog_Alert;
+ field public static int Theme_AppCompat_Light_Dialog_MinWidth;
+ field public static int Theme_AppCompat_Light_NoActionBar;
+ field public static int Theme_AppCompat_NoActionBar;
+ field public static int Widget_AppCompat_ActionBar;
+ field public static int Widget_AppCompat_ActionBar_Solid;
+ field public static int Widget_AppCompat_ActionBar_TabBar;
+ field public static int Widget_AppCompat_ActionBar_TabText;
+ field public static int Widget_AppCompat_ActionBar_TabView;
+ field public static int Widget_AppCompat_ActionButton;
+ field public static int Widget_AppCompat_ActionButton_CloseMode;
+ field public static int Widget_AppCompat_ActionButton_Overflow;
+ field public static int Widget_AppCompat_ActionMode;
+ field public static int Widget_AppCompat_ActivityChooserView;
+ field public static int Widget_AppCompat_AutoCompleteTextView;
+ field public static int Widget_AppCompat_Button;
+ field public static int Widget_AppCompat_ButtonBar;
+ field public static int Widget_AppCompat_ButtonBar_AlertDialog;
+ field public static int Widget_AppCompat_Button_Borderless;
+ field public static int Widget_AppCompat_Button_Borderless_Colored;
+ field public static int Widget_AppCompat_Button_ButtonBar_AlertDialog;
+ field public static int Widget_AppCompat_Button_Colored;
+ field public static int Widget_AppCompat_Button_Small;
+ field public static int Widget_AppCompat_CompoundButton_CheckBox;
+ field public static int Widget_AppCompat_CompoundButton_RadioButton;
+ field public static int Widget_AppCompat_CompoundButton_Switch;
+ field public static int Widget_AppCompat_DrawerArrowToggle;
+ field public static int Widget_AppCompat_DropDownItem_Spinner;
+ field public static int Widget_AppCompat_EditText;
+ field public static int Widget_AppCompat_ImageButton;
+ field public static int Widget_AppCompat_Light_ActionBar;
+ field public static int Widget_AppCompat_Light_ActionBar_Solid;
+ field public static int Widget_AppCompat_Light_ActionBar_Solid_Inverse;
+ field public static int Widget_AppCompat_Light_ActionBar_TabBar;
+ field public static int Widget_AppCompat_Light_ActionBar_TabBar_Inverse;
+ field public static int Widget_AppCompat_Light_ActionBar_TabText;
+ field public static int Widget_AppCompat_Light_ActionBar_TabText_Inverse;
+ field public static int Widget_AppCompat_Light_ActionBar_TabView;
+ field public static int Widget_AppCompat_Light_ActionBar_TabView_Inverse;
+ field public static int Widget_AppCompat_Light_ActionButton;
+ field public static int Widget_AppCompat_Light_ActionButton_CloseMode;
+ field public static int Widget_AppCompat_Light_ActionButton_Overflow;
+ field public static int Widget_AppCompat_Light_ActionMode_Inverse;
+ field public static int Widget_AppCompat_Light_ActivityChooserView;
+ field public static int Widget_AppCompat_Light_AutoCompleteTextView;
+ field public static int Widget_AppCompat_Light_DropDownItem_Spinner;
+ field public static int Widget_AppCompat_Light_ListPopupWindow;
+ field public static int Widget_AppCompat_Light_ListView_DropDown;
+ field public static int Widget_AppCompat_Light_PopupMenu;
+ field public static int Widget_AppCompat_Light_PopupMenu_Overflow;
+ field public static int Widget_AppCompat_Light_SearchView;
+ field public static int Widget_AppCompat_Light_Spinner_DropDown_ActionBar;
+ field public static int Widget_AppCompat_ListPopupWindow;
+ field public static int Widget_AppCompat_ListView;
+ field public static int Widget_AppCompat_ListView_DropDown;
+ field public static int Widget_AppCompat_ListView_Menu;
+ field public static int Widget_AppCompat_PopupMenu;
+ field public static int Widget_AppCompat_PopupMenu_Overflow;
+ field public static int Widget_AppCompat_PopupWindow;
+ field public static int Widget_AppCompat_ProgressBar;
+ field public static int Widget_AppCompat_ProgressBar_Horizontal;
+ field public static int Widget_AppCompat_RatingBar;
+ field public static int Widget_AppCompat_SearchView;
+ field public static int Widget_AppCompat_SearchView_ActionBar;
+ field public static int Widget_AppCompat_SeekBar;
+ field public static int Widget_AppCompat_Spinner;
+ field public static int Widget_AppCompat_Spinner_DropDown;
+ field public static int Widget_AppCompat_Spinner_DropDown_ActionBar;
+ field public static int Widget_AppCompat_Spinner_Underlined;
+ field public static int Widget_AppCompat_TextView_SpinnerItem;
+ field public static int Widget_AppCompat_Toolbar;
+ field public static int Widget_AppCompat_Toolbar_Button_Navigation;
+ }
+
+ public static final class R.styleable {
+ ctor public R.styleable();
+ field public static final int[] ActionBar;
+ field public static final int[] ActionBarLayout;
+ field public static int ActionBarLayout_android_layout_gravity;
+ field public static int ActionBar_background;
+ field public static int ActionBar_backgroundSplit;
+ field public static int ActionBar_backgroundStacked;
+ field public static int ActionBar_contentInsetEnd;
+ field public static int ActionBar_contentInsetLeft;
+ field public static int ActionBar_contentInsetRight;
+ field public static int ActionBar_contentInsetStart;
+ field public static int ActionBar_customNavigationLayout;
+ field public static int ActionBar_displayOptions;
+ field public static int ActionBar_divider;
+ field public static int ActionBar_elevation;
+ field public static int ActionBar_height;
+ field public static int ActionBar_hideOnContentScroll;
+ field public static int ActionBar_homeAsUpIndicator;
+ field public static int ActionBar_homeLayout;
+ field public static int ActionBar_icon;
+ field public static int ActionBar_indeterminateProgressStyle;
+ field public static int ActionBar_itemPadding;
+ field public static int ActionBar_logo;
+ field public static int ActionBar_navigationMode;
+ field public static int ActionBar_popupTheme;
+ field public static int ActionBar_progressBarPadding;
+ field public static int ActionBar_progressBarStyle;
+ field public static int ActionBar_subtitle;
+ field public static int ActionBar_subtitleTextStyle;
+ field public static int ActionBar_title;
+ field public static int ActionBar_titleTextStyle;
+ field public static final int[] ActionMenuItemView;
+ field public static int ActionMenuItemView_android_minWidth;
+ field public static final int[] ActionMenuView;
+ field public static final int[] ActionMode;
+ field public static int ActionMode_background;
+ field public static int ActionMode_backgroundSplit;
+ field public static int ActionMode_closeItemLayout;
+ field public static int ActionMode_height;
+ field public static int ActionMode_subtitleTextStyle;
+ field public static int ActionMode_titleTextStyle;
+ field public static final int[] ActivityChooserView;
+ field public static int ActivityChooserView_expandActivityOverflowButtonDrawable;
+ field public static int ActivityChooserView_initialActivityCount;
+ field public static final int[] AlertDialog;
+ field public static int AlertDialog_android_layout;
+ field public static int AlertDialog_buttonPanelSideLayout;
+ field public static int AlertDialog_listItemLayout;
+ field public static int AlertDialog_listLayout;
+ field public static int AlertDialog_multiChoiceItemLayout;
+ field public static int AlertDialog_singleChoiceItemLayout;
+ field public static final int[] AppCompatTextView;
+ field public static int AppCompatTextView_android_textAppearance;
+ field public static int AppCompatTextView_textAllCaps;
+ field public static int ButtonBarLayout_allowStacking;
+ field public static final int[] CompoundButton;
+ field public static int CompoundButton_android_button;
+ field public static int CompoundButton_buttonTint;
+ field public static int CompoundButton_buttonTintMode;
+ field public static final int[] DrawerArrowToggle;
+ field public static int DrawerArrowToggle_arrowHeadLength;
+ field public static int DrawerArrowToggle_arrowShaftLength;
+ field public static int DrawerArrowToggle_barLength;
+ field public static int DrawerArrowToggle_color;
+ field public static int DrawerArrowToggle_drawableSize;
+ field public static int DrawerArrowToggle_gapBetweenBars;
+ field public static int DrawerArrowToggle_spinBars;
+ field public static int DrawerArrowToggle_thickness;
+ field public static final int[] LinearLayoutCompat;
+ field public static final int[] LinearLayoutCompat_Layout;
+ field public static int LinearLayoutCompat_Layout_android_layout_gravity;
+ field public static int LinearLayoutCompat_Layout_android_layout_height;
+ field public static int LinearLayoutCompat_Layout_android_layout_weight;
+ field public static int LinearLayoutCompat_Layout_android_layout_width;
+ field public static int LinearLayoutCompat_android_baselineAligned;
+ field public static int LinearLayoutCompat_android_baselineAlignedChildIndex;
+ field public static int LinearLayoutCompat_android_gravity;
+ field public static int LinearLayoutCompat_android_orientation;
+ field public static int LinearLayoutCompat_android_weightSum;
+ field public static int LinearLayoutCompat_divider;
+ field public static int LinearLayoutCompat_dividerPadding;
+ field public static int LinearLayoutCompat_measureWithLargestChild;
+ field public static int LinearLayoutCompat_showDividers;
+ field public static final int[] ListPopupWindow;
+ field public static int ListPopupWindow_android_dropDownHorizontalOffset;
+ field public static int ListPopupWindow_android_dropDownVerticalOffset;
+ field public static final int[] MenuGroup;
+ field public static int MenuGroup_android_checkableBehavior;
+ field public static int MenuGroup_android_enabled;
+ field public static int MenuGroup_android_id;
+ field public static int MenuGroup_android_menuCategory;
+ field public static int MenuGroup_android_orderInCategory;
+ field public static int MenuGroup_android_visible;
+ field public static final int[] MenuItem;
+ field public static int MenuItem_actionLayout;
+ field public static int MenuItem_actionProviderClass;
+ field public static int MenuItem_actionViewClass;
+ field public static int MenuItem_android_alphabeticShortcut;
+ field public static int MenuItem_android_checkable;
+ field public static int MenuItem_android_checked;
+ field public static int MenuItem_android_enabled;
+ field public static int MenuItem_android_icon;
+ field public static int MenuItem_android_id;
+ field public static int MenuItem_android_menuCategory;
+ field public static int MenuItem_android_numericShortcut;
+ field public static int MenuItem_android_onClick;
+ field public static int MenuItem_android_orderInCategory;
+ field public static int MenuItem_android_title;
+ field public static int MenuItem_android_titleCondensed;
+ field public static int MenuItem_android_visible;
+ field public static int MenuItem_showAsAction;
+ field public static final int[] MenuView;
+ field public static int MenuView_android_headerBackground;
+ field public static int MenuView_android_horizontalDivider;
+ field public static int MenuView_android_itemBackground;
+ field public static int MenuView_android_itemIconDisabledAlpha;
+ field public static int MenuView_android_itemTextAppearance;
+ field public static int MenuView_android_verticalDivider;
+ field public static int MenuView_android_windowAnimationStyle;
+ field public static int MenuView_preserveIconSpacing;
+ field public static final int[] PopupWindow;
+ field public static final int[] PopupWindowBackgroundState;
+ field public static int PopupWindowBackgroundState_state_above_anchor;
+ field public static int PopupWindow_android_popupBackground;
+ field public static int PopupWindow_overlapAnchor;
+ field public static final int[] SearchView;
+ field public static int SearchView_android_focusable;
+ field public static int SearchView_android_imeOptions;
+ field public static int SearchView_android_inputType;
+ field public static int SearchView_android_maxWidth;
+ field public static int SearchView_closeIcon;
+ field public static int SearchView_commitIcon;
+ field public static int SearchView_defaultQueryHint;
+ field public static int SearchView_goIcon;
+ field public static int SearchView_iconifiedByDefault;
+ field public static int SearchView_layout;
+ field public static int SearchView_queryBackground;
+ field public static int SearchView_queryHint;
+ field public static int SearchView_searchHintIcon;
+ field public static int SearchView_searchIcon;
+ field public static int SearchView_submitBackground;
+ field public static int SearchView_suggestionRowLayout;
+ field public static int SearchView_voiceIcon;
+ field public static final int[] Spinner;
+ field public static int Spinner_android_dropDownWidth;
+ field public static int Spinner_android_popupBackground;
+ field public static int Spinner_android_prompt;
+ field public static int Spinner_popupTheme;
+ field public static final int[] SwitchCompat;
+ field public static int SwitchCompat_android_textOff;
+ field public static int SwitchCompat_android_textOn;
+ field public static int SwitchCompat_android_thumb;
+ field public static int SwitchCompat_showText;
+ field public static int SwitchCompat_splitTrack;
+ field public static int SwitchCompat_switchMinWidth;
+ field public static int SwitchCompat_switchPadding;
+ field public static int SwitchCompat_switchTextAppearance;
+ field public static int SwitchCompat_thumbTextPadding;
+ field public static int SwitchCompat_track;
+ field public static final int[] TextAppearance;
+ field public static int TextAppearance_android_shadowColor;
+ field public static int TextAppearance_android_shadowDx;
+ field public static int TextAppearance_android_shadowDy;
+ field public static int TextAppearance_android_shadowRadius;
+ field public static int TextAppearance_android_textColor;
+ field public static int TextAppearance_android_textSize;
+ field public static int TextAppearance_android_textStyle;
+ field public static int TextAppearance_android_typeface;
+ field public static int TextAppearance_textAllCaps;
+ field public static final int[] Theme;
+ field public static int Theme_actionBarDivider;
+ field public static int Theme_actionBarItemBackground;
+ field public static int Theme_actionBarPopupTheme;
+ field public static int Theme_actionBarSize;
+ field public static int Theme_actionBarSplitStyle;
+ field public static int Theme_actionBarStyle;
+ field public static int Theme_actionBarTabBarStyle;
+ field public static int Theme_actionBarTabStyle;
+ field public static int Theme_actionBarTabTextStyle;
+ field public static int Theme_actionBarTheme;
+ field public static int Theme_actionBarWidgetTheme;
+ field public static int Theme_actionButtonStyle;
+ field public static int Theme_actionDropDownStyle;
+ field public static int Theme_actionMenuTextAppearance;
+ field public static int Theme_actionMenuTextColor;
+ field public static int Theme_actionModeBackground;
+ field public static int Theme_actionModeCloseButtonStyle;
+ field public static int Theme_actionModeCloseDrawable;
+ field public static int Theme_actionModeCopyDrawable;
+ field public static int Theme_actionModeCutDrawable;
+ field public static int Theme_actionModeFindDrawable;
+ field public static int Theme_actionModePasteDrawable;
+ field public static int Theme_actionModePopupWindowStyle;
+ field public static int Theme_actionModeSelectAllDrawable;
+ field public static int Theme_actionModeShareDrawable;
+ field public static int Theme_actionModeSplitBackground;
+ field public static int Theme_actionModeStyle;
+ field public static int Theme_actionModeWebSearchDrawable;
+ field public static int Theme_actionOverflowButtonStyle;
+ field public static int Theme_actionOverflowMenuStyle;
+ field public static int Theme_activityChooserViewStyle;
+ field public static int Theme_alertDialogButtonGroupStyle;
+ field public static int Theme_alertDialogCenterButtons;
+ field public static int Theme_alertDialogStyle;
+ field public static int Theme_alertDialogTheme;
+ field public static int Theme_android_windowAnimationStyle;
+ field public static int Theme_android_windowIsFloating;
+ field public static int Theme_autoCompleteTextViewStyle;
+ field public static int Theme_borderlessButtonStyle;
+ field public static int Theme_buttonBarButtonStyle;
+ field public static int Theme_buttonBarNegativeButtonStyle;
+ field public static int Theme_buttonBarNeutralButtonStyle;
+ field public static int Theme_buttonBarPositiveButtonStyle;
+ field public static int Theme_buttonBarStyle;
+ field public static int Theme_buttonStyle;
+ field public static int Theme_buttonStyleSmall;
+ field public static int Theme_checkboxStyle;
+ field public static int Theme_checkedTextViewStyle;
+ field public static int Theme_colorAccent;
+ field public static int Theme_colorButtonNormal;
+ field public static int Theme_colorControlActivated;
+ field public static int Theme_colorControlHighlight;
+ field public static int Theme_colorControlNormal;
+ field public static int Theme_colorPrimary;
+ field public static int Theme_colorPrimaryDark;
+ field public static int Theme_colorSwitchThumbNormal;
+ field public static int Theme_controlBackground;
+ field public static int Theme_dialogPreferredPadding;
+ field public static int Theme_dialogTheme;
+ field public static int Theme_dividerHorizontal;
+ field public static int Theme_dividerVertical;
+ field public static int Theme_dropDownListViewStyle;
+ field public static int Theme_dropdownListPreferredItemHeight;
+ field public static int Theme_editTextBackground;
+ field public static int Theme_editTextColor;
+ field public static int Theme_editTextStyle;
+ field public static int Theme_homeAsUpIndicator;
+ field public static int Theme_imageButtonStyle;
+ field public static int Theme_listChoiceBackgroundIndicator;
+ field public static int Theme_listDividerAlertDialog;
+ field public static int Theme_listPopupWindowStyle;
+ field public static int Theme_listPreferredItemHeight;
+ field public static int Theme_listPreferredItemHeightLarge;
+ field public static int Theme_listPreferredItemHeightSmall;
+ field public static int Theme_listPreferredItemPaddingLeft;
+ field public static int Theme_listPreferredItemPaddingRight;
+ field public static int Theme_panelBackground;
+ field public static int Theme_panelMenuListTheme;
+ field public static int Theme_panelMenuListWidth;
+ field public static int Theme_popupMenuStyle;
+ field public static int Theme_popupWindowStyle;
+ field public static int Theme_radioButtonStyle;
+ field public static int Theme_ratingBarStyle;
+ field public static int Theme_searchViewStyle;
+ field public static int Theme_seekBarStyle;
+ field public static int Theme_selectableItemBackground;
+ field public static int Theme_selectableItemBackgroundBorderless;
+ field public static int Theme_spinnerDropDownItemStyle;
+ field public static int Theme_spinnerStyle;
+ field public static int Theme_switchStyle;
+ field public static int Theme_textAppearanceLargePopupMenu;
+ field public static int Theme_textAppearanceListItem;
+ field public static int Theme_textAppearanceListItemSmall;
+ field public static int Theme_textAppearanceSearchResultSubtitle;
+ field public static int Theme_textAppearanceSearchResultTitle;
+ field public static int Theme_textAppearanceSmallPopupMenu;
+ field public static int Theme_textColorAlertDialogListItem;
+ field public static int Theme_textColorSearchUrl;
+ field public static int Theme_toolbarNavigationButtonStyle;
+ field public static int Theme_toolbarStyle;
+ field public static int Theme_windowActionBar;
+ field public static int Theme_windowActionBarOverlay;
+ field public static int Theme_windowActionModeOverlay;
+ field public static int Theme_windowFixedHeightMajor;
+ field public static int Theme_windowFixedHeightMinor;
+ field public static int Theme_windowFixedWidthMajor;
+ field public static int Theme_windowFixedWidthMinor;
+ field public static int Theme_windowMinWidthMajor;
+ field public static int Theme_windowMinWidthMinor;
+ field public static int Theme_windowNoTitle;
+ field public static final int[] Toolbar;
+ field public static int Toolbar_android_gravity;
+ field public static int Toolbar_android_minHeight;
+ field public static int Toolbar_collapseContentDescription;
+ field public static int Toolbar_collapseIcon;
+ field public static int Toolbar_contentInsetEnd;
+ field public static int Toolbar_contentInsetLeft;
+ field public static int Toolbar_contentInsetRight;
+ field public static int Toolbar_contentInsetStart;
+ field public static int Toolbar_logo;
+ field public static int Toolbar_logoDescription;
+ field public static int Toolbar_maxButtonHeight;
+ field public static int Toolbar_navigationContentDescription;
+ field public static int Toolbar_navigationIcon;
+ field public static int Toolbar_popupTheme;
+ field public static int Toolbar_subtitle;
+ field public static int Toolbar_subtitleTextAppearance;
+ field public static int Toolbar_subtitleTextColor;
+ field public static int Toolbar_title;
+ field public static int Toolbar_titleMarginBottom;
+ field public static int Toolbar_titleMarginEnd;
+ field public static int Toolbar_titleMarginStart;
+ field public static int Toolbar_titleMarginTop;
+ field public static int Toolbar_titleMargins;
+ field public static int Toolbar_titleTextAppearance;
+ field public static int Toolbar_titleTextColor;
+ field public static final int[] View;
+ field public static final int[] ViewBackgroundHelper;
+ field public static int ViewBackgroundHelper_android_background;
+ field public static int ViewBackgroundHelper_backgroundTint;
+ field public static int ViewBackgroundHelper_backgroundTintMode;
+ field public static final int[] ViewStubCompat;
+ field public static int ViewStubCompat_android_id;
+ field public static int ViewStubCompat_android_inflatedId;
+ field public static int ViewStubCompat_android_layout;
+ field public static int View_android_focusable;
+ field public static int View_android_theme;
+ field public static int View_paddingEnd;
+ field public static int View_paddingStart;
+ field public static int View_theme;
+ }
+
+}
+
+package android.support.v7.graphics.drawable {
+
+ public class DrawerArrowDrawable extends android.graphics.drawable.Drawable {
+ ctor public DrawerArrowDrawable(android.content.Context);
+ method public void draw(android.graphics.Canvas);
+ method public float getArrowHeadLength();
+ method public float getArrowShaftLength();
+ method public float getBarLength();
+ method public float getBarThickness();
+ method public int getColor();
+ method public int getDirection();
+ method public float getGapSize();
+ method public int getOpacity();
+ method public final android.graphics.Paint getPaint();
+ method public float getProgress();
+ method public boolean isSpinEnabled();
+ method public void setAlpha(int);
+ method public void setArrowHeadLength(float);
+ method public void setArrowShaftLength(float);
+ method public void setBarLength(float);
+ method public void setBarThickness(float);
+ method public void setColor(int);
+ method public void setColorFilter(android.graphics.ColorFilter);
+ method public void setDirection(int);
+ method public void setGapSize(float);
+ method public void setProgress(float);
+ method public void setSpinEnabled(boolean);
+ method public void setVerticalMirror(boolean);
+ field public static final int ARROW_DIRECTION_END = 3; // 0x3
+ field public static final int ARROW_DIRECTION_LEFT = 0; // 0x0
+ field public static final int ARROW_DIRECTION_RIGHT = 1; // 0x1
+ field public static final int ARROW_DIRECTION_START = 2; // 0x2
+ }
+
+}
+
+package android.support.v7.view {
+
+ public abstract class ActionMode {
+ ctor public ActionMode();
+ method public abstract void finish();
+ method public abstract android.view.View getCustomView();
+ method public abstract android.view.Menu getMenu();
+ method public abstract android.view.MenuInflater getMenuInflater();
+ method public abstract java.lang.CharSequence getSubtitle();
+ method public java.lang.Object getTag();
+ method public abstract java.lang.CharSequence getTitle();
+ method public boolean getTitleOptionalHint();
+ method public abstract void invalidate();
+ method public boolean isTitleOptional();
+ method public abstract void setCustomView(android.view.View);
+ method public abstract void setSubtitle(java.lang.CharSequence);
+ method public abstract void setSubtitle(int);
+ method public void setTag(java.lang.Object);
+ method public abstract void setTitle(java.lang.CharSequence);
+ method public abstract void setTitle(int);
+ method public void setTitleOptionalHint(boolean);
+ }
+
+ public static abstract interface ActionMode.Callback {
+ method public abstract boolean onActionItemClicked(android.support.v7.view.ActionMode, android.view.MenuItem);
+ method public abstract boolean onCreateActionMode(android.support.v7.view.ActionMode, android.view.Menu);
+ method public abstract void onDestroyActionMode(android.support.v7.view.ActionMode);
+ method public abstract boolean onPrepareActionMode(android.support.v7.view.ActionMode, android.view.Menu);
+ }
+
+ public abstract interface CollapsibleActionView {
+ method public abstract void onActionViewCollapsed();
+ method public abstract void onActionViewExpanded();
+ }
+
+}
+
+package android.support.v7.widget {
+
+ public class ActionMenuView extends android.support.v7.widget.LinearLayoutCompat {
+ ctor public ActionMenuView(android.content.Context);
+ ctor public ActionMenuView(android.content.Context, android.util.AttributeSet);
+ method public void dismissPopupMenus();
+ method public android.view.Menu getMenu();
+ method public android.graphics.drawable.Drawable getOverflowIcon();
+ method public int getPopupTheme();
+ method public boolean hideOverflowMenu();
+ method public boolean isOverflowMenuShowing();
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public void onDetachedFromWindow();
+ method public void setOnMenuItemClickListener(android.support.v7.widget.ActionMenuView.OnMenuItemClickListener);
+ method public void setOverflowIcon(android.graphics.drawable.Drawable);
+ method public void setPopupTheme(int);
+ method public boolean showOverflowMenu();
+ }
+
+ public static class ActionMenuView.LayoutParams extends android.support.v7.widget.LinearLayoutCompat.LayoutParams {
+ ctor public ActionMenuView.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public ActionMenuView.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public ActionMenuView.LayoutParams(android.support.v7.widget.ActionMenuView.LayoutParams);
+ ctor public ActionMenuView.LayoutParams(int, int);
+ field public int cellsUsed;
+ field public boolean expandable;
+ field public int extraPixels;
+ field public boolean isOverflowButton;
+ field public boolean preventEdgeOffset;
+ }
+
+ public static abstract interface ActionMenuView.OnMenuItemClickListener {
+ method public abstract boolean onMenuItemClick(android.view.MenuItem);
+ }
+
+ public class AppCompatAutoCompleteTextView extends android.widget.AutoCompleteTextView {
+ ctor public AppCompatAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatButton extends android.widget.Button {
+ ctor public AppCompatButton(android.content.Context);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatButton(android.content.Context, android.util.AttributeSet, int);
+ method public void setSupportAllCaps(boolean);
+ }
+
+ public class AppCompatCheckBox extends android.widget.CheckBox {
+ ctor public AppCompatCheckBox(android.content.Context);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatCheckBox(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatCheckedTextView extends android.widget.CheckedTextView {
+ ctor public AppCompatCheckedTextView(android.content.Context);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatCheckedTextView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatEditText extends android.widget.EditText {
+ ctor public AppCompatEditText(android.content.Context);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatEditText(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatImageButton extends android.widget.ImageButton {
+ ctor public AppCompatImageButton(android.content.Context);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatImageButton(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatImageView extends android.widget.ImageView {
+ ctor public AppCompatImageView(android.content.Context);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatImageView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatMultiAutoCompleteTextView extends android.widget.MultiAutoCompleteTextView {
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatMultiAutoCompleteTextView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatRadioButton extends android.widget.RadioButton {
+ ctor public AppCompatRadioButton(android.content.Context);
+ ctor public AppCompatRadioButton(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatRadioButton(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatRatingBar extends android.widget.RatingBar {
+ ctor public AppCompatRatingBar(android.content.Context);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatRatingBar(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatSeekBar extends android.widget.SeekBar {
+ ctor public AppCompatSeekBar(android.content.Context);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatSeekBar(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class AppCompatSpinner extends android.widget.Spinner {
+ ctor public AppCompatSpinner(android.content.Context);
+ ctor public AppCompatSpinner(android.content.Context, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public AppCompatSpinner(android.content.Context, android.util.AttributeSet, int, int, android.content.res.Resources.Theme);
+ }
+
+ public class AppCompatTextView extends android.widget.TextView {
+ ctor public AppCompatTextView(android.content.Context);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet);
+ ctor public AppCompatTextView(android.content.Context, android.util.AttributeSet, int);
+ }
+
+ public class LinearLayoutCompat extends android.view.ViewGroup {
+ ctor public LinearLayoutCompat(android.content.Context);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet);
+ ctor public LinearLayoutCompat(android.content.Context, android.util.AttributeSet, int);
+ method public int getBaselineAlignedChildIndex();
+ method public android.graphics.drawable.Drawable getDividerDrawable();
+ method public int getDividerPadding();
+ method public int getOrientation();
+ method public int getShowDividers();
+ method public float getWeightSum();
+ method public boolean isBaselineAligned();
+ method public boolean isMeasureWithLargestChildEnabled();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setBaselineAligned(boolean);
+ method public void setBaselineAlignedChildIndex(int);
+ method public void setDividerDrawable(android.graphics.drawable.Drawable);
+ method public void setDividerPadding(int);
+ method public void setGravity(int);
+ method public void setHorizontalGravity(int);
+ method public void setMeasureWithLargestChildEnabled(boolean);
+ method public void setOrientation(int);
+ method public void setShowDividers(int);
+ method public void setVerticalGravity(int);
+ method public void setWeightSum(float);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int SHOW_DIVIDER_BEGINNING = 1; // 0x1
+ field public static final int SHOW_DIVIDER_END = 4; // 0x4
+ field public static final int SHOW_DIVIDER_MIDDLE = 2; // 0x2
+ field public static final int SHOW_DIVIDER_NONE = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class LinearLayoutCompat.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public LinearLayoutCompat.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public LinearLayoutCompat.LayoutParams(int, int);
+ ctor public LinearLayoutCompat.LayoutParams(int, int, float);
+ ctor public LinearLayoutCompat.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public LinearLayoutCompat.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public LinearLayoutCompat.LayoutParams(android.support.v7.widget.LinearLayoutCompat.LayoutParams);
+ field public int gravity;
+ field public float weight;
+ }
+
+ public class ListPopupWindow {
+ ctor public ListPopupWindow(android.content.Context);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet, int);
+ ctor public ListPopupWindow(android.content.Context, android.util.AttributeSet, int, int);
+ method public void clearListSelection();
+ method public android.view.View.OnTouchListener createDragToOpenListener(android.view.View);
+ method public void dismiss();
+ method public android.view.View getAnchorView();
+ method public int getAnimationStyle();
+ method public android.graphics.drawable.Drawable getBackground();
+ method public int getHeight();
+ method public int getHorizontalOffset();
+ method public int getInputMethodMode();
+ method public android.widget.ListView getListView();
+ method public int getPromptPosition();
+ method public java.lang.Object getSelectedItem();
+ method public long getSelectedItemId();
+ method public int getSelectedItemPosition();
+ method public android.view.View getSelectedView();
+ method public int getSoftInputMode();
+ method public int getVerticalOffset();
+ method public int getWidth();
+ method public boolean isInputMethodNotNeeded();
+ method public boolean isModal();
+ method public boolean isShowing();
+ method public boolean onKeyDown(int, android.view.KeyEvent);
+ method public boolean onKeyPreIme(int, android.view.KeyEvent);
+ method public boolean onKeyUp(int, android.view.KeyEvent);
+ method public boolean performItemClick(int);
+ method public void postShow();
+ method public void setAdapter(android.widget.ListAdapter);
+ method public void setAnchorView(android.view.View);
+ method public void setAnimationStyle(int);
+ method public void setBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setContentWidth(int);
+ method public void setDropDownGravity(int);
+ method public void setHeight(int);
+ method public void setHorizontalOffset(int);
+ method public void setInputMethodMode(int);
+ method public void setListSelector(android.graphics.drawable.Drawable);
+ method public void setModal(boolean);
+ method public void setOnDismissListener(android.widget.PopupWindow.OnDismissListener);
+ method public void setOnItemClickListener(android.widget.AdapterView.OnItemClickListener);
+ method public void setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener);
+ method public void setPromptPosition(int);
+ method public void setPromptView(android.view.View);
+ method public void setSelection(int);
+ method public void setSoftInputMode(int);
+ method public void setVerticalOffset(int);
+ method public void setWidth(int);
+ method public void setWindowLayoutType(int);
+ method public void show();
+ field public static final int INPUT_METHOD_FROM_FOCUSABLE = 0; // 0x0
+ field public static final int INPUT_METHOD_NEEDED = 1; // 0x1
+ field public static final int INPUT_METHOD_NOT_NEEDED = 2; // 0x2
+ field public static final int MATCH_PARENT = -1; // 0xffffffff
+ field public static final int POSITION_PROMPT_ABOVE = 0; // 0x0
+ field public static final int POSITION_PROMPT_BELOW = 1; // 0x1
+ field public static final int WRAP_CONTENT = -2; // 0xfffffffe
+ }
+
+ public class PopupMenu {
+ ctor public PopupMenu(android.content.Context, android.view.View);
+ ctor public PopupMenu(android.content.Context, android.view.View, int);
+ ctor public PopupMenu(android.content.Context, android.view.View, int, int, int);
+ method public void dismiss();
+ method public android.view.View.OnTouchListener getDragToOpenListener();
+ method public int getGravity();
+ method public android.view.Menu getMenu();
+ method public android.view.MenuInflater getMenuInflater();
+ method public void inflate(int);
+ method public void setGravity(int);
+ method public void setOnDismissListener(android.support.v7.widget.PopupMenu.OnDismissListener);
+ method public void setOnMenuItemClickListener(android.support.v7.widget.PopupMenu.OnMenuItemClickListener);
+ method public void show();
+ }
+
+ public static abstract interface PopupMenu.OnDismissListener {
+ method public abstract void onDismiss(android.support.v7.widget.PopupMenu);
+ }
+
+ public static abstract interface PopupMenu.OnMenuItemClickListener {
+ method public abstract boolean onMenuItemClick(android.view.MenuItem);
+ }
+
+ public class SearchView extends android.support.v7.widget.LinearLayoutCompat implements android.support.v7.view.CollapsibleActionView {
+ ctor public SearchView(android.content.Context);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet);
+ ctor public SearchView(android.content.Context, android.util.AttributeSet, int);
+ method public int getImeOptions();
+ method public int getInputType();
+ method public int getMaxWidth();
+ method public java.lang.CharSequence getQuery();
+ method public java.lang.CharSequence getQueryHint();
+ method public android.support.v4.widget.CursorAdapter getSuggestionsAdapter();
+ method public boolean isIconfiedByDefault();
+ method public boolean isIconified();
+ method public boolean isQueryRefinementEnabled();
+ method public boolean isSubmitButtonEnabled();
+ method public void onActionViewCollapsed();
+ method public void onActionViewExpanded();
+ method public void setIconified(boolean);
+ method public void setIconifiedByDefault(boolean);
+ method public void setImeOptions(int);
+ method public void setInputType(int);
+ method public void setMaxWidth(int);
+ method public void setOnCloseListener(android.support.v7.widget.SearchView.OnCloseListener);
+ method public void setOnQueryTextFocusChangeListener(android.view.View.OnFocusChangeListener);
+ method public void setOnQueryTextListener(android.support.v7.widget.SearchView.OnQueryTextListener);
+ method public void setOnSearchClickListener(android.view.View.OnClickListener);
+ method public void setOnSuggestionListener(android.support.v7.widget.SearchView.OnSuggestionListener);
+ method public void setQuery(java.lang.CharSequence, boolean);
+ method public void setQueryHint(java.lang.CharSequence);
+ method public void setQueryRefinementEnabled(boolean);
+ method public void setSearchableInfo(android.app.SearchableInfo);
+ method public void setSubmitButtonEnabled(boolean);
+ method public void setSuggestionsAdapter(android.support.v4.widget.CursorAdapter);
+ }
+
+ public static abstract interface SearchView.OnCloseListener {
+ method public abstract boolean onClose();
+ }
+
+ public static abstract interface SearchView.OnQueryTextListener {
+ method public abstract boolean onQueryTextChange(java.lang.String);
+ method public abstract boolean onQueryTextSubmit(java.lang.String);
+ }
+
+ public static abstract interface SearchView.OnSuggestionListener {
+ method public abstract boolean onSuggestionClick(int);
+ method public abstract boolean onSuggestionSelect(int);
+ }
+
+ public class ShareActionProvider extends android.support.v4.view.ActionProvider {
+ ctor public ShareActionProvider(android.content.Context);
+ method public android.view.View onCreateActionView();
+ method public void setOnShareTargetSelectedListener(android.support.v7.widget.ShareActionProvider.OnShareTargetSelectedListener);
+ method public void setShareHistoryFileName(java.lang.String);
+ method public void setShareIntent(android.content.Intent);
+ field public static final java.lang.String DEFAULT_SHARE_HISTORY_FILE_NAME = "share_history.xml";
+ }
+
+ public static abstract interface ShareActionProvider.OnShareTargetSelectedListener {
+ method public abstract boolean onShareTargetSelected(android.support.v7.widget.ShareActionProvider, android.content.Intent);
+ }
+
+ public class SwitchCompat extends android.widget.CompoundButton {
+ ctor public SwitchCompat(android.content.Context);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet);
+ ctor public SwitchCompat(android.content.Context, android.util.AttributeSet, int);
+ method public boolean getShowText();
+ method public boolean getSplitTrack();
+ method public int getSwitchMinWidth();
+ method public int getSwitchPadding();
+ method public java.lang.CharSequence getTextOff();
+ method public java.lang.CharSequence getTextOn();
+ method public android.graphics.drawable.Drawable getThumbDrawable();
+ method public int getThumbTextPadding();
+ method public android.graphics.drawable.Drawable getTrackDrawable();
+ method public void onMeasure(int, int);
+ method public void setShowText(boolean);
+ method public void setSplitTrack(boolean);
+ method public void setSwitchMinWidth(int);
+ method public void setSwitchPadding(int);
+ method public void setSwitchTextAppearance(android.content.Context, int);
+ method public void setSwitchTypeface(android.graphics.Typeface, int);
+ method public void setSwitchTypeface(android.graphics.Typeface);
+ method public void setTextOff(java.lang.CharSequence);
+ method public void setTextOn(java.lang.CharSequence);
+ method public void setThumbDrawable(android.graphics.drawable.Drawable);
+ method public void setThumbResource(int);
+ method public void setThumbTextPadding(int);
+ method public void setTrackDrawable(android.graphics.drawable.Drawable);
+ method public void setTrackResource(int);
+ }
+
+ public abstract interface ThemedSpinnerAdapter implements android.widget.SpinnerAdapter {
+ method public abstract android.content.res.Resources.Theme getDropDownViewTheme();
+ method public abstract void setDropDownViewTheme(android.content.res.Resources.Theme);
+ }
+
+ public static final class ThemedSpinnerAdapter.Helper {
+ ctor public ThemedSpinnerAdapter.Helper(android.content.Context);
+ method public android.view.LayoutInflater getDropDownViewInflater();
+ method public android.content.res.Resources.Theme getDropDownViewTheme();
+ method public void setDropDownViewTheme(android.content.res.Resources.Theme);
+ }
+
+ public class Toolbar extends android.view.ViewGroup {
+ ctor public Toolbar(android.content.Context);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet);
+ ctor public Toolbar(android.content.Context, android.util.AttributeSet, int);
+ method public void collapseActionView();
+ method public void dismissPopupMenus();
+ method public int getContentInsetEnd();
+ method public int getContentInsetLeft();
+ method public int getContentInsetRight();
+ method public int getContentInsetStart();
+ method public android.graphics.drawable.Drawable getLogo();
+ method public java.lang.CharSequence getLogoDescription();
+ method public android.view.Menu getMenu();
+ method public java.lang.CharSequence getNavigationContentDescription();
+ method public android.graphics.drawable.Drawable getNavigationIcon();
+ method public android.graphics.drawable.Drawable getOverflowIcon();
+ method public int getPopupTheme();
+ method public java.lang.CharSequence getSubtitle();
+ method public java.lang.CharSequence getTitle();
+ method public boolean hasExpandedActionView();
+ method public boolean hideOverflowMenu();
+ method public void inflateMenu(int);
+ method public boolean isOverflowMenuShowing();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setContentInsetsAbsolute(int, int);
+ method public void setContentInsetsRelative(int, int);
+ method public void setLogo(int);
+ method public void setLogo(android.graphics.drawable.Drawable);
+ method public void setLogoDescription(int);
+ method public void setLogoDescription(java.lang.CharSequence);
+ method public void setNavigationContentDescription(int);
+ method public void setNavigationContentDescription(java.lang.CharSequence);
+ method public void setNavigationIcon(int);
+ method public void setNavigationIcon(android.graphics.drawable.Drawable);
+ method public void setNavigationOnClickListener(android.view.View.OnClickListener);
+ method public void setOnMenuItemClickListener(android.support.v7.widget.Toolbar.OnMenuItemClickListener);
+ method public void setOverflowIcon(android.graphics.drawable.Drawable);
+ method public void setPopupTheme(int);
+ method public void setSubtitle(int);
+ method public void setSubtitle(java.lang.CharSequence);
+ method public void setSubtitleTextAppearance(android.content.Context, int);
+ method public void setSubtitleTextColor(int);
+ method public void setTitle(int);
+ method public void setTitle(java.lang.CharSequence);
+ method public void setTitleTextAppearance(android.content.Context, int);
+ method public void setTitleTextColor(int);
+ method public boolean showOverflowMenu();
+ }
+
+ public static class Toolbar.LayoutParams extends android.support.v7.app.ActionBar.LayoutParams {
+ ctor public Toolbar.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public Toolbar.LayoutParams(int, int);
+ ctor public Toolbar.LayoutParams(int, int, int);
+ ctor public Toolbar.LayoutParams(int);
+ ctor public Toolbar.LayoutParams(android.support.v7.widget.Toolbar.LayoutParams);
+ ctor public Toolbar.LayoutParams(android.support.v7.app.ActionBar.LayoutParams);
+ ctor public Toolbar.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public Toolbar.LayoutParams(android.view.ViewGroup.LayoutParams);
+ }
+
+ public static abstract interface Toolbar.OnMenuItemClickListener {
+ method public abstract boolean onMenuItemClick(android.view.MenuItem);
+ }
+
+ public static class Toolbar.SavedState extends android.view.View.BaseSavedState {
+ ctor public Toolbar.SavedState(android.os.Parcel);
+ ctor public Toolbar.SavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.v7.widget.Toolbar.SavedState> CREATOR;
+ }
+
+}
+
diff --git a/v7/appcompat/api/current.txt b/v7/appcompat/api/current.txt
index 8f35544..dc931b3 100644
--- a/v7/appcompat/api/current.txt
+++ b/v7/appcompat/api/current.txt
@@ -182,7 +182,7 @@
method public android.support.v7.app.AlertDialog.Builder setIcon(int);
method public android.support.v7.app.AlertDialog.Builder setIcon(android.graphics.drawable.Drawable);
method public android.support.v7.app.AlertDialog.Builder setIconAttribute(int);
- method public android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
+ method public deprecated android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
method public android.support.v7.app.AlertDialog.Builder setItems(int, android.content.DialogInterface.OnClickListener);
method public android.support.v7.app.AlertDialog.Builder setItems(java.lang.CharSequence[], android.content.DialogInterface.OnClickListener);
method public android.support.v7.app.AlertDialog.Builder setMessage(int);
@@ -244,33 +244,45 @@
public abstract class AppCompatDelegate {
method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public abstract boolean applyDayNight();
method public static android.support.v7.app.AppCompatDelegate create(android.app.Activity, android.support.v7.app.AppCompatCallback);
method public static android.support.v7.app.AppCompatDelegate create(android.app.Dialog, android.support.v7.app.AppCompatCallback);
method public abstract android.view.View createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ method public abstract android.view.View findViewById(int);
+ method public static int getDefaultNightMode();
method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
method public abstract android.view.MenuInflater getMenuInflater();
method public abstract android.support.v7.app.ActionBar getSupportActionBar();
method public abstract boolean hasWindowFeature(int);
method public abstract void installViewFactory();
method public abstract void invalidateOptionsMenu();
+ method public static boolean isCompatVectorFromResourcesEnabled();
method public abstract boolean isHandleNativeActionModesEnabled();
method public abstract void onConfigurationChanged(android.content.res.Configuration);
method public abstract void onCreate(android.os.Bundle);
method public abstract void onDestroy();
method public abstract void onPostCreate(android.os.Bundle);
method public abstract void onPostResume();
+ method public abstract void onSaveInstanceState(android.os.Bundle);
method public abstract void onStop();
method public abstract boolean requestWindowFeature(int);
+ method public static void setCompatVectorFromResourcesEnabled(boolean);
method public abstract void setContentView(android.view.View);
method public abstract void setContentView(int);
method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public static void setDefaultNightMode(int);
method public abstract void setHandleNativeActionModesEnabled(boolean);
+ method public abstract void setLocalNightMode(int);
method public abstract void setSupportActionBar(android.support.v7.widget.Toolbar);
method public abstract void setTitle(java.lang.CharSequence);
method public abstract android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
field public static final int FEATURE_SUPPORT_ACTION_BAR = 108; // 0x6c
field public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 109; // 0x6d
+ field public static final int MODE_NIGHT_AUTO = 0; // 0x0
+ field public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1; // 0xffffffff
+ field public static final int MODE_NIGHT_NO = 1; // 0x1
+ field public static final int MODE_NIGHT_YES = 2; // 0x2
}
public class AppCompatDialog extends android.app.Dialog implements android.support.v7.app.AppCompatCallback {
@@ -369,6 +381,7 @@
field public static int alertDialogStyle;
field public static int alertDialogTheme;
field public static int allowStacking;
+ field public static int alpha;
field public static int arrowHeadLength;
field public static int arrowShaftLength;
field public static int autoCompleteTextViewStyle;
@@ -384,6 +397,7 @@
field public static int buttonBarNeutralButtonStyle;
field public static int buttonBarPositiveButtonStyle;
field public static int buttonBarStyle;
+ field public static int buttonGravity;
field public static int buttonPanelSideLayout;
field public static int buttonStyle;
field public static int buttonStyleSmall;
@@ -397,6 +411,7 @@
field public static int collapseIcon;
field public static int color;
field public static int colorAccent;
+ field public static int colorBackgroundFloating;
field public static int colorButtonNormal;
field public static int colorControlActivated;
field public static int colorControlHighlight;
@@ -406,9 +421,11 @@
field public static int colorSwitchThumbNormal;
field public static int commitIcon;
field public static int contentInsetEnd;
+ field public static int contentInsetEndWithActions;
field public static int contentInsetLeft;
field public static int contentInsetRight;
field public static int contentInsetStart;
+ field public static int contentInsetStartWithNavigation;
field public static int controlBackground;
field public static int customNavigationLayout;
field public static int defaultQueryHint;
@@ -446,6 +463,7 @@
field public static int listDividerAlertDialog;
field public static int listItemLayout;
field public static int listLayout;
+ field public static int listMenuViewStyle;
field public static int listPopupWindowStyle;
field public static int listPreferredItemHeight;
field public static int listPreferredItemHeightLarge;
@@ -476,6 +494,8 @@
field public static int queryHint;
field public static int radioButtonStyle;
field public static int ratingBarStyle;
+ field public static int ratingBarStyleIndicator;
+ field public static int ratingBarStyleSmall;
field public static int searchHintIcon;
field public static int searchIcon;
field public static int searchViewStyle;
@@ -490,7 +510,9 @@
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 subMenuArrow;
field public static int submitBackground;
field public static int subtitle;
field public static int subtitleTextAppearance;
@@ -505,6 +527,7 @@
field public static int textAppearanceLargePopupMenu;
field public static int textAppearanceListItem;
field public static int textAppearanceListItemSmall;
+ field public static int textAppearancePopupMenuHeader;
field public static int textAppearanceSearchResultSubtitle;
field public static int textAppearanceSearchResultTitle;
field public static int textAppearanceSmallPopupMenu;
@@ -513,18 +536,26 @@
field public static int theme;
field public static int thickness;
field public static int thumbTextPadding;
+ field public static int thumbTint;
+ field public static int thumbTintMode;
+ field public static int tickMark;
+ field public static int tickMarkTint;
+ field public static int tickMarkTintMode;
field public static int title;
+ field public static int titleMargin;
field public static int titleMarginBottom;
field public static int titleMarginEnd;
field public static int titleMarginStart;
field public static int titleMarginTop;
- field public static int titleMargins;
+ field public static deprecated int titleMargins;
field public static int titleTextAppearance;
field public static int titleTextColor;
field public static int titleTextStyle;
field public static int toolbarNavigationButtonStyle;
field public static int toolbarStyle;
field public static int track;
+ field public static int trackTint;
+ field public static int trackTintMode;
field public static int voiceIcon;
field public static int windowActionBar;
field public static int windowActionBarOverlay;
@@ -541,11 +572,8 @@
public static final class R.bool {
ctor public R.bool();
field public static int abc_action_bar_embed_tabs;
- field public static int abc_action_bar_embed_tabs_pre_jb;
- field public static int abc_action_bar_expanded_action_views_exclusive;
field public static int abc_allow_stacked_button_bar;
field public static int abc_config_actionMenuItemAllCaps;
- field public static int abc_config_allowActionMenuItemTextWithIcon;
field public static int abc_config_closeDialogWhenTouchOutside;
field public static int abc_config_showMenuShortcutsWhenKeyboardPresent;
}
@@ -554,6 +582,7 @@
ctor public R.color();
field public static int abc_background_cache_hint_selector_material_dark;
field public static int abc_background_cache_hint_selector_material_light;
+ field public static int abc_btn_colored_borderless_text_material;
field public static int abc_color_highlight_material;
field public static int abc_input_method_navigation_guard;
field public static int abc_primary_text_disable_only_material_dark;
@@ -566,6 +595,13 @@
field public static int abc_search_url_text_selected;
field public static int abc_secondary_text_material_dark;
field public static int abc_secondary_text_material_light;
+ field public static int abc_tint_btn_checkable;
+ field public static int abc_tint_default;
+ field public static int abc_tint_edittext;
+ field public static int abc_tint_seek_thumb;
+ field public static int abc_tint_spinner;
+ field public static int abc_tint_switch_thumb;
+ field public static int abc_tint_switch_track;
field public static int accent_material_dark;
field public static int accent_material_light;
field public static int background_floating_material_dark;
@@ -627,6 +663,7 @@
public static final class R.dimen {
ctor public R.dimen();
field public static int abc_action_bar_content_inset_material;
+ field public static int abc_action_bar_content_inset_with_nav;
field public static int abc_action_bar_default_height_material;
field public static int abc_action_bar_default_padding_end_material;
field public static int abc_action_bar_default_padding_start_material;
@@ -646,6 +683,7 @@
field public static int abc_button_inset_vertical_material;
field public static int abc_button_padding_horizontal_material;
field public static int abc_button_padding_vertical_material;
+ field public static int abc_cascading_menus_min_smallest_width;
field public static int abc_config_prefDialogWidth;
field public static int abc_control_corner_material;
field public static int abc_control_inset_material;
@@ -670,8 +708,9 @@
field public static int abc_floating_window_z;
field public static int abc_list_item_padding_horizontal_material;
field public static int abc_panel_menu_list_width;
+ field public static int abc_progress_bar_height_material;
+ field public static int abc_search_view_preferred_height;
field public static int abc_search_view_preferred_width;
- field public static int abc_search_view_text_min_width;
field public static int abc_seekbar_track_background_height_material;
field public static int abc_seekbar_track_progress_height_material;
field public static int abc_select_dialog_padding_start_material;
@@ -687,6 +726,7 @@
field public static int abc_text_size_headline_material;
field public static int abc_text_size_large_material;
field public static int abc_text_size_medium_material;
+ field public static int abc_text_size_menu_header_material;
field public static int abc_text_size_menu_material;
field public static int abc_text_size_small_material;
field public static int abc_text_size_subhead_material;
@@ -716,29 +756,33 @@
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_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;
field public static int abc_btn_switch_to_on_mtrl_00012;
field public static int abc_cab_background_internal_bg;
field public static int abc_cab_background_top_material;
field public static int abc_cab_background_top_mtrl_alpha;
field public static int abc_control_background_material;
- field public static int abc_dialog_material_background_dark;
- field public static int abc_dialog_material_background_light;
+ field public static int abc_dialog_material_background;
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_arrow_drop_right_black_24dp;
+ 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_go_search_api_material;
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_overflow_material;
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_search_api_mtrl_alpha;
- field public static int abc_ic_voice_search_api_mtrl_alpha;
+ field public static int abc_ic_search_api_material;
+ 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_black_48dp;
+ 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_star_half_black_48dp;
+ 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;
@@ -754,13 +798,16 @@
field public static int abc_list_selector_holo_light;
field public static int abc_menu_hardkey_panel_mtrl_mult;
field public static int abc_popup_background_mtrl_mult;
- field public static int abc_ratingbar_full_material;
+ field public static int abc_ratingbar_indicator_material;
+ field public static int abc_ratingbar_material;
+ field public static int abc_ratingbar_small_material;
field public static int abc_scrubber_control_off_mtrl_alpha;
field public static int abc_scrubber_control_to_pressed_mtrl_000;
field public static int abc_scrubber_control_to_pressed_mtrl_005;
field public static int abc_scrubber_primary_mtrl_alpha;
field public static int abc_scrubber_track_mtrl_alpha;
field public static int abc_seekbar_thumb_material;
+ field public static int abc_seekbar_tick_mark_material;
field public static int abc_seekbar_track_material;
field public static int abc_spinner_mtrl_am_alpha;
field public static int abc_spinner_textfield_background_material;
@@ -795,9 +842,11 @@
field public static int action_mode_bar_stub;
field public static int action_mode_close_button;
field public static int activity_chooser_view_content;
+ field public static int add;
field public static int alertTitle;
field public static int always;
field public static int beginning;
+ field public static int bottom;
field public static int buttonPanel;
field public static int cancel_action;
field public static int checkbox;
@@ -859,6 +908,7 @@
field public static int src_in;
field public static int src_over;
field public static int status_bar_latest_event_content;
+ field public static int submenuarrow;
field public static int submit_area;
field public static int tabMode;
field public static int text;
@@ -867,6 +917,7 @@
field public static int time;
field public static int title;
field public static int title_template;
+ field public static int top;
field public static int topPanel;
field public static int up;
field public static int useLogo;
@@ -878,7 +929,6 @@
ctor public R.integer();
field public static int abc_config_activityDefaultDur;
field public static int abc_config_activityShortDur;
- field public static int abc_max_action_buttons;
field public static int cancel_button_image_alpha;
field public static int status_bar_notification_info_maxnum;
}
@@ -902,6 +952,7 @@
field public static int abc_list_menu_item_icon;
field public static int abc_list_menu_item_layout;
field public static int abc_list_menu_item_radio;
+ field public static int abc_popup_menu_header_item_layout;
field public static int abc_popup_menu_item_layout;
field public static int abc_screen_content_include;
field public static int abc_screen_simple;
@@ -936,6 +987,18 @@
field public static int abc_activitychooserview_choose_application;
field public static int abc_capital_off;
field public static int abc_capital_on;
+ field public static int abc_font_family_body_1_material;
+ field public static int abc_font_family_body_2_material;
+ field public static int abc_font_family_button_material;
+ field public static int abc_font_family_caption_material;
+ field public static int abc_font_family_display_1_material;
+ field public static int abc_font_family_display_2_material;
+ field public static int abc_font_family_display_3_material;
+ field public static int abc_font_family_display_4_material;
+ field public static int abc_font_family_headline_material;
+ field public static int abc_font_family_menu_material;
+ field public static int abc_font_family_subhead_material;
+ field public static int abc_font_family_title_material;
field public static int abc_search_hint;
field public static int abc_searchview_description_clear;
field public static int abc_searchview_description_query;
@@ -997,6 +1060,7 @@
field public static int Base_TextAppearance_AppCompat_Widget_Button;
field public static int Base_TextAppearance_AppCompat_Widget_Button_Inverse;
field public static int Base_TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Header;
field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Large;
field public static int Base_TextAppearance_AppCompat_Widget_PopupMenu_Small;
field public static int Base_TextAppearance_AppCompat_Widget_Switch;
@@ -1008,6 +1072,8 @@
field public static int Base_ThemeOverlay_AppCompat_ActionBar;
field public static int Base_ThemeOverlay_AppCompat_Dark;
field public static int Base_ThemeOverlay_AppCompat_Dark_ActionBar;
+ field public static int Base_ThemeOverlay_AppCompat_Dialog;
+ field public static int Base_ThemeOverlay_AppCompat_Dialog_Alert;
field public static int Base_ThemeOverlay_AppCompat_Light;
field public static int Base_Theme_AppCompat;
field public static int Base_Theme_AppCompat_CompactMenu;
@@ -1023,10 +1089,12 @@
field public static int Base_Theme_AppCompat_Light_Dialog_Alert;
field public static int Base_Theme_AppCompat_Light_Dialog_FixedSize;
field public static int Base_Theme_AppCompat_Light_Dialog_MinWidth;
+ field public static int Base_V11_ThemeOverlay_AppCompat_Dialog;
field public static int Base_V11_Theme_AppCompat_Dialog;
field public static int Base_V11_Theme_AppCompat_Light_Dialog;
field public static int Base_V12_Widget_AppCompat_AutoCompleteTextView;
field public static int Base_V12_Widget_AppCompat_EditText;
+ field public static int Base_V21_ThemeOverlay_AppCompat_Dialog;
field public static int Base_V21_Theme_AppCompat;
field public static int Base_V21_Theme_AppCompat_Dialog;
field public static int Base_V21_Theme_AppCompat_Light;
@@ -1035,6 +1103,7 @@
field public static int Base_V22_Theme_AppCompat_Light;
field public static int Base_V23_Theme_AppCompat;
field public static int Base_V23_Theme_AppCompat_Light;
+ field public static int Base_V7_ThemeOverlay_AppCompat_Dialog;
field public static int Base_V7_Theme_AppCompat;
field public static int Base_V7_Theme_AppCompat_Dialog;
field public static int Base_V7_Theme_AppCompat_Light;
@@ -1076,6 +1145,7 @@
field public static int Base_Widget_AppCompat_Light_ActionBar_TabView;
field public static int Base_Widget_AppCompat_Light_PopupMenu;
field public static int Base_Widget_AppCompat_Light_PopupMenu_Overflow;
+ field public static int Base_Widget_AppCompat_ListMenuView;
field public static int Base_Widget_AppCompat_ListPopupWindow;
field public static int Base_Widget_AppCompat_ListView;
field public static int Base_Widget_AppCompat_ListView_DropDown;
@@ -1086,9 +1156,12 @@
field public static int Base_Widget_AppCompat_ProgressBar;
field public static int Base_Widget_AppCompat_ProgressBar_Horizontal;
field public static int Base_Widget_AppCompat_RatingBar;
+ field public static int Base_Widget_AppCompat_RatingBar_Indicator;
+ field public static int Base_Widget_AppCompat_RatingBar_Small;
field public static int Base_Widget_AppCompat_SearchView;
field public static int Base_Widget_AppCompat_SearchView_ActionBar;
field public static int Base_Widget_AppCompat_SeekBar;
+ field public static int Base_Widget_AppCompat_SeekBar_Discrete;
field public static int Base_Widget_AppCompat_Spinner;
field public static int Base_Widget_AppCompat_Spinner_Underlined;
field public static int Base_Widget_AppCompat_TextView_SpinnerItem;
@@ -1158,6 +1231,7 @@
field public static int TextAppearance_AppCompat_Widget_Button;
field public static int TextAppearance_AppCompat_Widget_Button_Inverse;
field public static int TextAppearance_AppCompat_Widget_DropDownItem;
+ field public static int TextAppearance_AppCompat_Widget_PopupMenu_Header;
field public static int TextAppearance_AppCompat_Widget_PopupMenu_Large;
field public static int TextAppearance_AppCompat_Widget_PopupMenu_Small;
field public static int TextAppearance_AppCompat_Widget_Switch;
@@ -1174,9 +1248,18 @@
field public static int ThemeOverlay_AppCompat_ActionBar;
field public static int ThemeOverlay_AppCompat_Dark;
field public static int ThemeOverlay_AppCompat_Dark_ActionBar;
+ field public static int ThemeOverlay_AppCompat_Dialog;
+ field public static int ThemeOverlay_AppCompat_Dialog_Alert;
field public static int ThemeOverlay_AppCompat_Light;
field public static int Theme_AppCompat;
field public static int Theme_AppCompat_CompactMenu;
+ field public static int Theme_AppCompat_DayNight;
+ field public static int Theme_AppCompat_DayNight_DarkActionBar;
+ field public static int Theme_AppCompat_DayNight_Dialog;
+ field public static int Theme_AppCompat_DayNight_DialogWhenLarge;
+ field public static int Theme_AppCompat_DayNight_Dialog_Alert;
+ field public static int Theme_AppCompat_DayNight_Dialog_MinWidth;
+ field public static int Theme_AppCompat_DayNight_NoActionBar;
field public static int Theme_AppCompat_Dialog;
field public static int Theme_AppCompat_DialogWhenLarge;
field public static int Theme_AppCompat_Dialog_Alert;
@@ -1237,6 +1320,7 @@
field public static int Widget_AppCompat_Light_PopupMenu_Overflow;
field public static int Widget_AppCompat_Light_SearchView;
field public static int Widget_AppCompat_Light_Spinner_DropDown_ActionBar;
+ field public static int Widget_AppCompat_ListMenuView;
field public static int Widget_AppCompat_ListPopupWindow;
field public static int Widget_AppCompat_ListView;
field public static int Widget_AppCompat_ListView_DropDown;
@@ -1247,9 +1331,12 @@
field public static int Widget_AppCompat_ProgressBar;
field public static int Widget_AppCompat_ProgressBar_Horizontal;
field public static int Widget_AppCompat_RatingBar;
+ field public static int Widget_AppCompat_RatingBar_Indicator;
+ field public static int Widget_AppCompat_RatingBar_Small;
field public static int Widget_AppCompat_SearchView;
field public static int Widget_AppCompat_SearchView_ActionBar;
field public static int Widget_AppCompat_SeekBar;
+ field public static int Widget_AppCompat_SeekBar_Discrete;
field public static int Widget_AppCompat_Spinner;
field public static int Widget_AppCompat_Spinner_DropDown;
field public static int Widget_AppCompat_Spinner_DropDown_ActionBar;
@@ -1268,9 +1355,11 @@
field public static int ActionBar_backgroundSplit;
field public static int ActionBar_backgroundStacked;
field public static int ActionBar_contentInsetEnd;
+ field public static int ActionBar_contentInsetEndWithActions;
field public static int ActionBar_contentInsetLeft;
field public static int ActionBar_contentInsetRight;
field public static int ActionBar_contentInsetStart;
+ field public static int ActionBar_contentInsetStartWithNavigation;
field public static int ActionBar_customNavigationLayout;
field public static int ActionBar_displayOptions;
field public static int ActionBar_divider;
@@ -1311,10 +1400,138 @@
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[] AppCompatSeekBar;
+ field public static int AppCompatSeekBar_android_thumb;
+ field public static int AppCompatSeekBar_tickMark;
+ field public static int AppCompatSeekBar_tickMarkTint;
+ field public static int AppCompatSeekBar_tickMarkTintMode;
field public static final int[] AppCompatTextView;
field public static int AppCompatTextView_android_textAppearance;
field public static int AppCompatTextView_textAllCaps;
+ field public static final int[] AppCompatTheme;
+ field public static int AppCompatTheme_actionBarDivider;
+ field public static int AppCompatTheme_actionBarItemBackground;
+ field public static int AppCompatTheme_actionBarPopupTheme;
+ field public static int AppCompatTheme_actionBarSize;
+ field public static int AppCompatTheme_actionBarSplitStyle;
+ field public static int AppCompatTheme_actionBarStyle;
+ field public static int AppCompatTheme_actionBarTabBarStyle;
+ field public static int AppCompatTheme_actionBarTabStyle;
+ field public static int AppCompatTheme_actionBarTabTextStyle;
+ field public static int AppCompatTheme_actionBarTheme;
+ field public static int AppCompatTheme_actionBarWidgetTheme;
+ field public static int AppCompatTheme_actionButtonStyle;
+ field public static int AppCompatTheme_actionDropDownStyle;
+ field public static int AppCompatTheme_actionMenuTextAppearance;
+ field public static int AppCompatTheme_actionMenuTextColor;
+ field public static int AppCompatTheme_actionModeBackground;
+ field public static int AppCompatTheme_actionModeCloseButtonStyle;
+ field public static int AppCompatTheme_actionModeCloseDrawable;
+ field public static int AppCompatTheme_actionModeCopyDrawable;
+ field public static int AppCompatTheme_actionModeCutDrawable;
+ field public static int AppCompatTheme_actionModeFindDrawable;
+ field public static int AppCompatTheme_actionModePasteDrawable;
+ field public static int AppCompatTheme_actionModePopupWindowStyle;
+ field public static int AppCompatTheme_actionModeSelectAllDrawable;
+ field public static int AppCompatTheme_actionModeShareDrawable;
+ field public static int AppCompatTheme_actionModeSplitBackground;
+ field public static int AppCompatTheme_actionModeStyle;
+ field public static int AppCompatTheme_actionModeWebSearchDrawable;
+ field public static int AppCompatTheme_actionOverflowButtonStyle;
+ field public static int AppCompatTheme_actionOverflowMenuStyle;
+ field public static int AppCompatTheme_activityChooserViewStyle;
+ field public static int AppCompatTheme_alertDialogButtonGroupStyle;
+ field public static int AppCompatTheme_alertDialogCenterButtons;
+ field public static int AppCompatTheme_alertDialogStyle;
+ field public static int AppCompatTheme_alertDialogTheme;
+ field public static int AppCompatTheme_android_windowAnimationStyle;
+ field public static int AppCompatTheme_android_windowIsFloating;
+ field public static int AppCompatTheme_autoCompleteTextViewStyle;
+ field public static int AppCompatTheme_borderlessButtonStyle;
+ field public static int AppCompatTheme_buttonBarButtonStyle;
+ field public static int AppCompatTheme_buttonBarNegativeButtonStyle;
+ field public static int AppCompatTheme_buttonBarNeutralButtonStyle;
+ field public static int AppCompatTheme_buttonBarPositiveButtonStyle;
+ field public static int AppCompatTheme_buttonBarStyle;
+ field public static int AppCompatTheme_buttonStyle;
+ field public static int AppCompatTheme_buttonStyleSmall;
+ field public static int AppCompatTheme_checkboxStyle;
+ field public static int AppCompatTheme_checkedTextViewStyle;
+ field public static int AppCompatTheme_colorAccent;
+ field public static int AppCompatTheme_colorBackgroundFloating;
+ field public static int AppCompatTheme_colorButtonNormal;
+ field public static int AppCompatTheme_colorControlActivated;
+ field public static int AppCompatTheme_colorControlHighlight;
+ field public static int AppCompatTheme_colorControlNormal;
+ field public static int AppCompatTheme_colorPrimary;
+ field public static int AppCompatTheme_colorPrimaryDark;
+ field public static int AppCompatTheme_colorSwitchThumbNormal;
+ field public static int AppCompatTheme_controlBackground;
+ field public static int AppCompatTheme_dialogPreferredPadding;
+ field public static int AppCompatTheme_dialogTheme;
+ field public static int AppCompatTheme_dividerHorizontal;
+ field public static int AppCompatTheme_dividerVertical;
+ field public static int AppCompatTheme_dropDownListViewStyle;
+ field public static int AppCompatTheme_dropdownListPreferredItemHeight;
+ field public static int AppCompatTheme_editTextBackground;
+ field public static int AppCompatTheme_editTextColor;
+ field public static int AppCompatTheme_editTextStyle;
+ field public static int AppCompatTheme_homeAsUpIndicator;
+ field public static int AppCompatTheme_imageButtonStyle;
+ field public static int AppCompatTheme_listChoiceBackgroundIndicator;
+ field public static int AppCompatTheme_listDividerAlertDialog;
+ field public static int AppCompatTheme_listMenuViewStyle;
+ field public static int AppCompatTheme_listPopupWindowStyle;
+ field public static int AppCompatTheme_listPreferredItemHeight;
+ field public static int AppCompatTheme_listPreferredItemHeightLarge;
+ field public static int AppCompatTheme_listPreferredItemHeightSmall;
+ field public static int AppCompatTheme_listPreferredItemPaddingLeft;
+ field public static int AppCompatTheme_listPreferredItemPaddingRight;
+ field public static int AppCompatTheme_panelBackground;
+ field public static int AppCompatTheme_panelMenuListTheme;
+ field public static int AppCompatTheme_panelMenuListWidth;
+ field public static int AppCompatTheme_popupMenuStyle;
+ field public static int AppCompatTheme_popupWindowStyle;
+ field public static int AppCompatTheme_radioButtonStyle;
+ field public static int AppCompatTheme_ratingBarStyle;
+ field public static int AppCompatTheme_ratingBarStyleIndicator;
+ field public static int AppCompatTheme_ratingBarStyleSmall;
+ field public static int AppCompatTheme_searchViewStyle;
+ field public static int AppCompatTheme_seekBarStyle;
+ field public static int AppCompatTheme_selectableItemBackground;
+ field public static int AppCompatTheme_selectableItemBackgroundBorderless;
+ field public static int AppCompatTheme_spinnerDropDownItemStyle;
+ field public static int AppCompatTheme_spinnerStyle;
+ field public static int AppCompatTheme_switchStyle;
+ field public static int AppCompatTheme_textAppearanceLargePopupMenu;
+ field public static int AppCompatTheme_textAppearanceListItem;
+ field public static int AppCompatTheme_textAppearanceListItemSmall;
+ field public static int AppCompatTheme_textAppearancePopupMenuHeader;
+ field public static int AppCompatTheme_textAppearanceSearchResultSubtitle;
+ field public static int AppCompatTheme_textAppearanceSearchResultTitle;
+ field public static int AppCompatTheme_textAppearanceSmallPopupMenu;
+ field public static int AppCompatTheme_textColorAlertDialogListItem;
+ field public static int AppCompatTheme_textColorSearchUrl;
+ field public static int AppCompatTheme_toolbarNavigationButtonStyle;
+ field public static int AppCompatTheme_toolbarStyle;
+ field public static int AppCompatTheme_windowActionBar;
+ field public static int AppCompatTheme_windowActionBarOverlay;
+ field public static int AppCompatTheme_windowActionModeOverlay;
+ field public static int AppCompatTheme_windowFixedHeightMajor;
+ field public static int AppCompatTheme_windowFixedHeightMinor;
+ field public static int AppCompatTheme_windowFixedWidthMajor;
+ field public static int AppCompatTheme_windowFixedWidthMinor;
+ field public static int AppCompatTheme_windowMinWidthMajor;
+ field public static int AppCompatTheme_windowMinWidthMinor;
+ field public static int AppCompatTheme_windowNoTitle;
field public static int ButtonBarLayout_allowStacking;
+ field public static final int[] ColorStateListItem;
+ field public static int ColorStateListItem_alpha;
+ field public static int ColorStateListItem_android_alpha;
+ field public static int ColorStateListItem_android_color;
field public static final int[] CompoundButton;
field public static int CompoundButton_android_button;
field public static int CompoundButton_buttonTint;
@@ -1380,9 +1597,11 @@
field public static int MenuView_android_verticalDivider;
field public static int MenuView_android_windowAnimationStyle;
field public static int MenuView_preserveIconSpacing;
+ field public static int MenuView_subMenuArrow;
field public static final int[] PopupWindow;
field public static final int[] PopupWindowBackgroundState;
field public static int PopupWindowBackgroundState_state_above_anchor;
+ field public static int PopupWindow_android_popupAnimationStyle;
field public static int PopupWindow_android_popupBackground;
field public static int PopupWindow_overlapAnchor;
field public static final int[] SearchView;
@@ -1419,7 +1638,11 @@
field public static int SwitchCompat_switchPadding;
field public static int SwitchCompat_switchTextAppearance;
field public static int SwitchCompat_thumbTextPadding;
+ field public static int SwitchCompat_thumbTint;
+ field public static int SwitchCompat_thumbTintMode;
field public static int SwitchCompat_track;
+ field public static int SwitchCompat_trackTint;
+ field public static int SwitchCompat_trackTintMode;
field public static final int[] TextAppearance;
field public static int TextAppearance_android_shadowColor;
field public static int TextAppearance_android_shadowDx;
@@ -1430,126 +1653,18 @@
field public static int TextAppearance_android_textStyle;
field public static int TextAppearance_android_typeface;
field public static int TextAppearance_textAllCaps;
- field public static final int[] Theme;
- field public static int Theme_actionBarDivider;
- field public static int Theme_actionBarItemBackground;
- field public static int Theme_actionBarPopupTheme;
- field public static int Theme_actionBarSize;
- field public static int Theme_actionBarSplitStyle;
- field public static int Theme_actionBarStyle;
- field public static int Theme_actionBarTabBarStyle;
- field public static int Theme_actionBarTabStyle;
- field public static int Theme_actionBarTabTextStyle;
- field public static int Theme_actionBarTheme;
- field public static int Theme_actionBarWidgetTheme;
- field public static int Theme_actionButtonStyle;
- field public static int Theme_actionDropDownStyle;
- field public static int Theme_actionMenuTextAppearance;
- field public static int Theme_actionMenuTextColor;
- field public static int Theme_actionModeBackground;
- field public static int Theme_actionModeCloseButtonStyle;
- field public static int Theme_actionModeCloseDrawable;
- field public static int Theme_actionModeCopyDrawable;
- field public static int Theme_actionModeCutDrawable;
- field public static int Theme_actionModeFindDrawable;
- field public static int Theme_actionModePasteDrawable;
- field public static int Theme_actionModePopupWindowStyle;
- field public static int Theme_actionModeSelectAllDrawable;
- field public static int Theme_actionModeShareDrawable;
- field public static int Theme_actionModeSplitBackground;
- field public static int Theme_actionModeStyle;
- field public static int Theme_actionModeWebSearchDrawable;
- field public static int Theme_actionOverflowButtonStyle;
- field public static int Theme_actionOverflowMenuStyle;
- field public static int Theme_activityChooserViewStyle;
- field public static int Theme_alertDialogButtonGroupStyle;
- field public static int Theme_alertDialogCenterButtons;
- field public static int Theme_alertDialogStyle;
- field public static int Theme_alertDialogTheme;
- field public static int Theme_android_windowAnimationStyle;
- field public static int Theme_android_windowIsFloating;
- field public static int Theme_autoCompleteTextViewStyle;
- field public static int Theme_borderlessButtonStyle;
- field public static int Theme_buttonBarButtonStyle;
- field public static int Theme_buttonBarNegativeButtonStyle;
- field public static int Theme_buttonBarNeutralButtonStyle;
- field public static int Theme_buttonBarPositiveButtonStyle;
- field public static int Theme_buttonBarStyle;
- field public static int Theme_buttonStyle;
- field public static int Theme_buttonStyleSmall;
- field public static int Theme_checkboxStyle;
- field public static int Theme_checkedTextViewStyle;
- field public static int Theme_colorAccent;
- field public static int Theme_colorButtonNormal;
- field public static int Theme_colorControlActivated;
- field public static int Theme_colorControlHighlight;
- field public static int Theme_colorControlNormal;
- field public static int Theme_colorPrimary;
- field public static int Theme_colorPrimaryDark;
- field public static int Theme_colorSwitchThumbNormal;
- field public static int Theme_controlBackground;
- field public static int Theme_dialogPreferredPadding;
- field public static int Theme_dialogTheme;
- field public static int Theme_dividerHorizontal;
- field public static int Theme_dividerVertical;
- field public static int Theme_dropDownListViewStyle;
- field public static int Theme_dropdownListPreferredItemHeight;
- field public static int Theme_editTextBackground;
- field public static int Theme_editTextColor;
- field public static int Theme_editTextStyle;
- field public static int Theme_homeAsUpIndicator;
- field public static int Theme_imageButtonStyle;
- field public static int Theme_listChoiceBackgroundIndicator;
- field public static int Theme_listDividerAlertDialog;
- field public static int Theme_listPopupWindowStyle;
- field public static int Theme_listPreferredItemHeight;
- field public static int Theme_listPreferredItemHeightLarge;
- field public static int Theme_listPreferredItemHeightSmall;
- field public static int Theme_listPreferredItemPaddingLeft;
- field public static int Theme_listPreferredItemPaddingRight;
- field public static int Theme_panelBackground;
- field public static int Theme_panelMenuListTheme;
- field public static int Theme_panelMenuListWidth;
- field public static int Theme_popupMenuStyle;
- field public static int Theme_popupWindowStyle;
- field public static int Theme_radioButtonStyle;
- field public static int Theme_ratingBarStyle;
- field public static int Theme_searchViewStyle;
- field public static int Theme_seekBarStyle;
- field public static int Theme_selectableItemBackground;
- field public static int Theme_selectableItemBackgroundBorderless;
- field public static int Theme_spinnerDropDownItemStyle;
- field public static int Theme_spinnerStyle;
- field public static int Theme_switchStyle;
- field public static int Theme_textAppearanceLargePopupMenu;
- field public static int Theme_textAppearanceListItem;
- field public static int Theme_textAppearanceListItemSmall;
- field public static int Theme_textAppearanceSearchResultSubtitle;
- field public static int Theme_textAppearanceSearchResultTitle;
- field public static int Theme_textAppearanceSmallPopupMenu;
- field public static int Theme_textColorAlertDialogListItem;
- field public static int Theme_textColorSearchUrl;
- field public static int Theme_toolbarNavigationButtonStyle;
- field public static int Theme_toolbarStyle;
- field public static int Theme_windowActionBar;
- field public static int Theme_windowActionBarOverlay;
- field public static int Theme_windowActionModeOverlay;
- field public static int Theme_windowFixedHeightMajor;
- field public static int Theme_windowFixedHeightMinor;
- field public static int Theme_windowFixedWidthMajor;
- field public static int Theme_windowFixedWidthMinor;
- field public static int Theme_windowMinWidthMajor;
- field public static int Theme_windowMinWidthMinor;
- field public static int Theme_windowNoTitle;
- field public static final int[] Toolbar;
+ field public static final deprecated int[] Toolbar;
field public static int Toolbar_android_gravity;
field public static int Toolbar_android_minHeight;
+ field public static int Toolbar_buttonGravity;
field public static int Toolbar_collapseContentDescription;
field public static int Toolbar_collapseIcon;
field public static int Toolbar_contentInsetEnd;
+ field public static int Toolbar_contentInsetEndWithActions;
field public static int Toolbar_contentInsetLeft;
field public static int Toolbar_contentInsetRight;
field public static int Toolbar_contentInsetStart;
+ field public static int Toolbar_contentInsetStartWithNavigation;
field public static int Toolbar_logo;
field public static int Toolbar_logoDescription;
field public static int Toolbar_maxButtonHeight;
@@ -1560,11 +1675,12 @@
field public static int Toolbar_subtitleTextAppearance;
field public static int Toolbar_subtitleTextColor;
field public static int Toolbar_title;
+ field public static int Toolbar_titleMargin;
field public static int Toolbar_titleMarginBottom;
field public static int Toolbar_titleMarginEnd;
field public static int Toolbar_titleMarginStart;
field public static int Toolbar_titleMarginTop;
- field public static int Toolbar_titleMargins;
+ field public static deprecated int Toolbar_titleMargins;
field public static int Toolbar_titleTextAppearance;
field public static int Toolbar_titleTextColor;
field public static final int[] View;
@@ -1585,6 +1701,14 @@
}
+package android.support.v7.content.res {
+
+ public final class AppCompatResources {
+ method public static android.content.res.ColorStateList getColorStateList(android.content.Context, int);
+ }
+
+}
+
package android.support.v7.graphics.drawable {
public class DrawerArrowDrawable extends android.graphics.drawable.Drawable {
@@ -1977,7 +2101,11 @@
method public java.lang.CharSequence getTextOn();
method public android.graphics.drawable.Drawable getThumbDrawable();
method public int getThumbTextPadding();
+ method public android.content.res.ColorStateList getThumbTintList();
+ method public android.graphics.PorterDuff.Mode getThumbTintMode();
method public android.graphics.drawable.Drawable getTrackDrawable();
+ method public android.content.res.ColorStateList getTrackTintList();
+ method public android.graphics.PorterDuff.Mode getTrackTintMode();
method public void onMeasure(int, int);
method public void setShowText(boolean);
method public void setSplitTrack(boolean);
@@ -1991,8 +2119,12 @@
method public void setThumbDrawable(android.graphics.drawable.Drawable);
method public void setThumbResource(int);
method public void setThumbTextPadding(int);
+ method public void setThumbTintList(android.content.res.ColorStateList);
+ method public void setThumbTintMode(android.graphics.PorterDuff.Mode);
method public void setTrackDrawable(android.graphics.drawable.Drawable);
method public void setTrackResource(int);
+ method public void setTrackTintList(android.content.res.ColorStateList);
+ method public void setTrackTintMode(android.graphics.PorterDuff.Mode);
}
public abstract interface ThemedSpinnerAdapter implements android.widget.SpinnerAdapter {
@@ -2014,9 +2146,15 @@
method public void collapseActionView();
method public void dismissPopupMenus();
method public int getContentInsetEnd();
+ method public int getContentInsetEndWithActions();
method public int getContentInsetLeft();
method public int getContentInsetRight();
method public int getContentInsetStart();
+ method public int getContentInsetStartWithNavigation();
+ method public int getCurrentContentInsetEnd();
+ method public int getCurrentContentInsetLeft();
+ method public int getCurrentContentInsetRight();
+ method public int getCurrentContentInsetStart();
method public android.graphics.drawable.Drawable getLogo();
method public java.lang.CharSequence getLogoDescription();
method public android.view.Menu getMenu();
@@ -2026,11 +2164,17 @@
method public int getPopupTheme();
method public java.lang.CharSequence getSubtitle();
method public java.lang.CharSequence getTitle();
+ method public int getTitleMarginBottom();
+ method public int getTitleMarginEnd();
+ method public int getTitleMarginStart();
+ method public int getTitleMarginTop();
method public boolean hasExpandedActionView();
method public boolean hideOverflowMenu();
method public void inflateMenu(int);
method public boolean isOverflowMenuShowing();
method protected void onLayout(boolean, int, int, int, int);
+ method public void setContentInsetEndWithActions(int);
+ method public void setContentInsetStartWithNavigation(int);
method public void setContentInsetsAbsolute(int, int);
method public void setContentInsetsRelative(int, int);
method public void setLogo(int);
@@ -2051,6 +2195,11 @@
method public void setSubtitleTextColor(int);
method public void setTitle(int);
method public void setTitle(java.lang.CharSequence);
+ method public void setTitleMargin(int, int, int, int);
+ method public void setTitleMarginBottom(int);
+ method public void setTitleMarginEnd(int);
+ method public void setTitleMarginStart(int);
+ method public void setTitleMarginTop(int);
method public void setTitleTextAppearance(android.content.Context, int);
method public void setTitleTextColor(int);
method public boolean showOverflowMenu();
@@ -2071,8 +2220,9 @@
method public abstract boolean onMenuItemClick(android.view.MenuItem);
}
- public static class Toolbar.SavedState extends android.view.View.BaseSavedState {
+ public static class Toolbar.SavedState extends android.support.v4.view.AbsSavedState {
ctor public Toolbar.SavedState(android.os.Parcel);
+ ctor public Toolbar.SavedState(android.os.Parcel, java.lang.ClassLoader);
ctor public Toolbar.SavedState(android.os.Parcelable);
field public static final android.os.Parcelable.Creator<android.support.v7.widget.Toolbar.SavedState> CREATOR;
}
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index 5ff23ae..faa8439 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -1,19 +1,32 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'appcompat-v7'
dependencies {
compile project(':support-v4')
- androidTestCompile 'com.android.support.test:testing-support-lib:0.1'
+ compile project(':support-vector-drawable')
+ compile project(':support-animated-vector-drawable')
+
+ 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'
+ }
+ androidTestCompile 'org.mockito:mockito-core:1.9.5'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'junit:junit:4.12'
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
defaultConfig {
minSdkVersion 7
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ // This disables the builds tools automatic vector -> PNG generation
+ generatedDensities = []
}
sourceSets {
@@ -34,6 +47,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 3c812d7..f61ca8e 100644
--- a/v7/appcompat/res-public/values/public_attrs.xml
+++ b/v7/appcompat/res-public/values/public_attrs.xml
@@ -50,6 +50,7 @@
<public type="attr" name="actionViewClass"/>
<public type="attr" name="alertDialogStyle"/>
<public type="attr" name="alertDialogTheme"/>
+ <public type="attr" name="alpha"/>
<public type="attr" name="arrowHeadLength"/>
<public type="attr" name="arrowShaftLength"/>
<public type="attr" name="autoCompleteTextViewStyle"/>
@@ -65,6 +66,7 @@
<public type="attr" name="buttonBarNeutralButtonStyle"/>
<public type="attr" name="buttonBarPositiveButtonStyle"/>
<public type="attr" name="buttonBarStyle"/>
+ <public type="attr" name="buttonGravity" />
<public type="attr" name="buttonStyle"/>
<public type="attr" name="buttonStyleSmall"/>
<public type="attr" name="buttonTint"/>
@@ -77,6 +79,7 @@
<public type="attr" name="collapseIcon"/>
<public type="attr" name="color"/>
<public type="attr" name="colorAccent"/>
+ <public type="attr" name="colorBackgroundFloating"/>
<public type="attr" name="colorButtonNormal"/>
<public type="attr" name="colorControlActivated"/>
<public type="attr" name="colorControlHighlight"/>
@@ -86,9 +89,11 @@
<public type="attr" name="colorSwitchThumbNormal"/>
<public type="attr" name="commitIcon"/>
<public type="attr" name="contentInsetEnd"/>
+ <public type="attr" name="contentInsetEndWithActions"/>
<public type="attr" name="contentInsetLeft"/>
<public type="attr" name="contentInsetRight"/>
<public type="attr" name="contentInsetStart"/>
+ <public type="attr" name="contentInsetStartWithNavigation"/>
<public type="attr" name="customNavigationLayout"/>
<public type="attr" name="dialogPreferredPadding"/>
<public type="attr" name="dialogTheme"/>
@@ -99,6 +104,7 @@
<public type="attr" name="dividerVertical"/>
<public type="attr" name="drawableSize"/>
<public type="attr" name="drawerArrowStyle"/>
+ <public type="attr" name="dropDownListViewStyle"/>
<public type="attr" name="editTextBackground"/>
<public type="attr" name="editTextColor"/>
<public type="attr" name="editTextStyle"/>
@@ -111,9 +117,9 @@
<public type="attr" name="homeLayout"/>
<public type="attr" name="icon"/>
<public type="attr" name="iconifiedByDefault"/>
+ <public type="attr" name="imageButtonStyle"/>
<public type="attr" name="indeterminateProgressStyle"/>
<public type="attr" name="isLightTheme"/>
- <public type="attr" name="imageButtonStyle"/>
<public type="attr" name="itemPadding"/>
<public type="attr" name="layout"/>
<public type="attr" name="listChoiceBackgroundIndicator"/>
@@ -126,8 +132,8 @@
<public type="attr" name="listPreferredItemPaddingRight"/>
<public type="attr" name="logo"/>
<public type="attr" name="logoDescription"/>
+ <public type="attr" name="maxButtonHeight" />
<public type="attr" name="measureWithLargestChild"/>
- <public type="attr" name="middleBarArrowSize"/>
<public type="attr" name="navigationContentDescription"/>
<public type="attr" name="navigationIcon"/>
<public type="attr" name="navigationMode"/>
@@ -145,9 +151,12 @@
<public type="attr" name="queryHint"/>
<public type="attr" name="radioButtonStyle"/>
<public type="attr" name="ratingBarStyle"/>
+ <public type="attr" name="ratingBarStyleIndicator"/>
+ <public type="attr" name="ratingBarStyleSmall"/>
<public type="attr" name="searchHintIcon"/>
<public type="attr" name="searchIcon"/>
<public type="attr" name="searchViewStyle"/>
+ <public type="attr" name="seekBarStyle"/>
<public type="attr" name="selectableItemBackground"/>
<public type="attr" name="selectableItemBackgroundBorderless"/>
<public type="attr" name="showAsAction"/>
@@ -157,6 +166,7 @@
<public type="attr" name="spinnerDropDownItemStyle"/>
<public type="attr" name="spinnerStyle"/>
<public type="attr" name="splitTrack"/>
+ <public type="attr" name="srcCompat"/>
<public type="attr" name="submitBackground"/>
<public type="attr" name="subtitle"/>
<public type="attr" name="subtitleTextAppearance"/>
@@ -171,6 +181,7 @@
<public type="attr" name="textAppearanceLargePopupMenu"/>
<public type="attr" name="textAppearanceListItem"/>
<public type="attr" name="textAppearanceListItemSmall"/>
+ <public type="attr" name="textAppearancePopupMenuHeader"/>
<public type="attr" name="textAppearanceSearchResultSubtitle"/>
<public type="attr" name="textAppearanceSearchResultTitle"/>
<public type="attr" name="textAppearanceSmallPopupMenu"/>
@@ -178,9 +189,13 @@
<public type="attr" name="theme"/>
<public type="attr" name="thickness"/>
<public type="attr" name="thumbTextPadding"/>
+ <public type="attr" name="tickMark"/>
+ <public type="attr" name="tickMarkTint"/>
+ <public type="attr" name="tickMarkTintMode"/>
<public type="attr" name="title"/>
<public type="attr" name="titleMarginBottom"/>
<public type="attr" name="titleMarginEnd"/>
+ <public type="attr" name="titleMargin"/>
<public type="attr" name="titleMargins"/>
<public type="attr" name="titleMarginStart"/>
<public type="attr" name="titleMarginTop"/>
diff --git a/v7/appcompat/res-public/values/public_styles.xml b/v7/appcompat/res-public/values/public_styles.xml
index 991ab54..9e6df30 100644
--- a/v7/appcompat/res-public/values/public_styles.xml
+++ b/v7/appcompat/res-public/values/public_styles.xml
@@ -56,11 +56,19 @@
<public type="style" name="TextAppearance.AppCompat.Widget.Button"/>
<public type="style" name="TextAppearance.AppCompat.Widget.Button.Inverse"/>
<public type="style" name="TextAppearance.AppCompat.Widget.DropDownItem"/>
+ <public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Header"/>
<public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Large"/>
<public type="style" name="TextAppearance.AppCompat.Widget.PopupMenu.Small"/>
<public type="style" name="TextAppearance.AppCompat.Widget.Switch"/>
<public type="style" name="TextAppearance.AppCompat.Widget.TextView.SpinnerItem"/>
<public type="style" name="Theme.AppCompat"/>
+ <public type="style" name="Theme.AppCompat.DayNight"/>
+ <public type="style" name="Theme.AppCompat.DayNight.DarkActionBar"/>
+ <public type="style" name="Theme.AppCompat.DayNight.Dialog"/>
+ <public type="style" name="Theme.AppCompat.DayNight.Dialog.Alert"/>
+ <public type="style" name="Theme.AppCompat.DayNight.Dialog.MinWidth"/>
+ <public type="style" name="Theme.AppCompat.DayNight.DialogWhenLarge"/>
+ <public type="style" name="Theme.AppCompat.DayNight.NoActionBar"/>
<public type="style" name="Theme.AppCompat.Dialog"/>
<public type="style" name="Theme.AppCompat.Dialog.Alert"/>
<public type="style" name="Theme.AppCompat.Dialog.MinWidth"/>
@@ -77,6 +85,8 @@
<public type="style" name="ThemeOverlay.AppCompat.ActionBar"/>
<public type="style" name="ThemeOverlay.AppCompat.Dark"/>
<public type="style" name="ThemeOverlay.AppCompat.Dark.ActionBar"/>
+ <public type="style" name="ThemeOverlay.AppCompat.Dialog"/>
+ <public type="style" name="ThemeOverlay.AppCompat.Dialog.Alert"/>
<public type="style" name="ThemeOverlay.AppCompat.Light"/>
<public type="style" name="Widget.AppCompat.ActionBar"/>
<public type="style" name="Widget.AppCompat.ActionBar.Solid"/>
@@ -134,9 +144,12 @@
<public type="style" name="Widget.AppCompat.ProgressBar"/>
<public type="style" name="Widget.AppCompat.ProgressBar.Horizontal"/>
<public type="style" name="Widget.AppCompat.RatingBar"/>
+ <public type="style" name="Widget.AppCompat.RatingBar.Indicator"/>
+ <public type="style" name="Widget.AppCompat.RatingBar.Small"/>
<public type="style" name="Widget.AppCompat.SearchView"/>
<public type="style" name="Widget.AppCompat.SearchView.ActionBar"/>
<public type="style" name="Widget.AppCompat.SeekBar"/>
+ <public type="style" name="Widget.AppCompat.SeekBar.Discrete"/>
<public type="style" name="Widget.AppCompat.Spinner"/>
<public type="style" name="Widget.AppCompat.Spinner.DropDown"/>
<public type="style" name="Widget.AppCompat.Spinner.DropDown.ActionBar"/>
diff --git a/v7/appcompat/res/color-v23/abc_btn_colored_borderless_text_material.xml b/v7/appcompat/res/color-v23/abc_btn_colored_borderless_text_material.xml
new file mode 100644
index 0000000..468b155
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_btn_colored_borderless_text_material.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Used for the text of a borderless colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?android:attr/textColorSecondary" android:alpha="?android:attr/disabledAlpha"/>
+ <item android:color="?attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_btn_checkable.xml b/v7/appcompat/res/color-v23/abc_tint_btn_checkable.xml
new file mode 100644
index 0000000..e82eff4
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_btn_checkable.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016§ The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" android:alpha="?android:disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_default.xml b/v7/appcompat/res/color-v23/abc_tint_default.xml
new file mode 100644
index 0000000..abe3880
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_default.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" android:alpha="?android:disabledAlpha"/>
+ <item android:state_focused="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_pressed="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_activated="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_selected="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_edittext.xml b/v7/appcompat/res/color-v23/abc_tint_edittext.xml
new file mode 100644
index 0000000..0e05e07
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_edittext.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" android:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false" android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_seek_thumb.xml b/v7/appcompat/res/color-v23/abc_tint_seek_thumb.xml
new file mode 100644
index 0000000..4fc9626
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_seek_thumb.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorControlActivated" android:alpha="?android:attr/disabledAlpha"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_spinner.xml b/v7/appcompat/res/color-v23/abc_tint_spinner.xml
new file mode 100644
index 0000000..0e05e07
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_spinner.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" android:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false" android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_switch_thumb.xml b/v7/appcompat/res/color-v23/abc_tint_switch_thumb.xml
new file mode 100644
index 0000000..f589fdf
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_switch_thumb.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?attr/colorSwitchThumbNormal" android:alpha="?android:attr/disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorSwitchThumbNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color-v23/abc_tint_switch_track.xml b/v7/appcompat/res/color-v23/abc_tint_switch_track.xml
new file mode 100644
index 0000000..e663772
--- /dev/null
+++ b/v7/appcompat/res/color-v23/abc_tint_switch_track.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="?android:attr/colorForeground" android:alpha="0.1"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated" android:alpha="0.3"/>
+ <item android:color="?android:attr/colorForeground" android:alpha="0.3"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
new file mode 100644
index 0000000..1480046
--- /dev/null
+++ b/v7/appcompat/res/color/abc_btn_colored_borderless_text_material.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Used for the text of a borderless colored button. -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false"
+ app:alpha="?android:attr/disabledAlpha"
+ android:color="?android:attr/textColorSecondary"/>
+ <item android:color="?attr/colorAccent"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_btn_checkable.xml b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
new file mode 100644
index 0000000..0c663f6
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_btn_checkable.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_default.xml b/v7/appcompat/res/color/abc_tint_default.xml
new file mode 100644
index 0000000..8d7c391
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_default.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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_focused="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_pressed="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_activated="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_selected="true" android:color="?attr/colorControlActivated"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorControlNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_edittext.xml b/v7/appcompat/res/color/abc_tint_edittext.xml
new file mode 100644
index 0000000..536d77f
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_edittext.xml
@@ -0,0 +1,24 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal"
+ app:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false"
+ android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_seek_thumb.xml b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
new file mode 100644
index 0000000..cb53788
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_seek_thumb.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlActivated" app:alpha="?android:attr/disabledAlpha"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_spinner.xml b/v7/appcompat/res/color/abc_tint_spinner.xml
new file mode 100644
index 0000000..44333dd
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_spinner.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorControlNormal" app:alpha="?android:disabledAlpha"/>
+ <item android:state_pressed="false" android:state_focused="false" android:color="?attr/colorControlNormal"/>
+ <item android:color="?attr/colorControlActivated"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_thumb.xml b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
new file mode 100644
index 0000000..fc8bd24
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_thumb.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?attr/colorSwitchThumbNormal" app:alpha="?android:attr/disabledAlpha"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated"/>
+ <item android:color="?attr/colorSwitchThumbNormal"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/color/abc_tint_switch_track.xml b/v7/appcompat/res/color/abc_tint_switch_track.xml
new file mode 100644
index 0000000..22322f8
--- /dev/null
+++ b/v7/appcompat/res/color/abc_tint_switch_track.xml
@@ -0,0 +1,22 @@
+<?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.
+-->
+<selector xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto">
+ <item android:state_enabled="false" android:color="?android:attr/colorForeground" app:alpha="0.1"/>
+ <item android:state_checked="true" android:color="?attr/colorControlActivated" app:alpha="0.3"/>
+ <item android:color="?android:attr/colorForeground" app:alpha="0.3"/>
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
deleted file mode 100644
index b184dbc..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
deleted file mode 100644
index 6549c52..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 88f1767..4657a25 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index d5e1a00..3fd617bf 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_btn_switch_to_on_mtrl_00012.9.png
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 f61e8e3..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 0fd1556..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 b9ff1db..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
index 70eb073..706fc1f 100644
--- 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
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 9a87820..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_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_menu_selectall_mtrl_alpha.png
index 2d971a9..e631df7 100644
--- 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
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
index ee40812..cd1f57c 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index b9baa0c..0000000
--- a/v7/appcompat/res/drawable-hdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_16dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_16dp.png
new file mode 100644
index 0000000..a728afe
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_36dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_36dp.png
new file mode 100644
index 0000000..64b2aa7
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..54d3065
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_16dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_16dp.png
new file mode 100644
index 0000000..cf270fe
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_36dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_36dp.png
new file mode 100644
index 0000000..49ad6cd
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..5f0f27d
--- /dev/null
+++ b/v7/appcompat/res/drawable-hdpi/abc_ic_star_half_black_48dp.png
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 a87d2cd..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-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index 2cf413c..d077a25 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png
index 4efe298..208def6 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png
index 543dec3..c9c203e 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png
index 9930b3a..253e906 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png
index 4cfb1a7..ffb0096 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png
index 32ddf7a..e54950e 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
index 9de0263..0da5b1d 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
index 6ad9b1d..54df961 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
index 0078bf6..5440b1a 100644
--- a/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-hdpi/abc_textfield_default_mtrl_alpha.9.png
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 2e1062f..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_cut_mtrl_alpha.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_ic_menu_cut_mtrl_alpha.png
index 9ed43ca..d8eaf07 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
index 4cd8a27..ddbec8b 100644
--- a/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-hdpi/abc_spinner_mtrl_am_alpha.9.png
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 e300b7c..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
index 05b1e11..254f806 100644
--- 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
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
index aa7b323..efe4446 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
index d02a5da..c888ee0 100644
--- a/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-mdpi/abc_spinner_mtrl_am_alpha.9.png
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 a188f2f..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
index e95ba94..88e34c4a 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
index b097e48..09dadcc 100644
--- a/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xhdpi/abc_spinner_mtrl_am_alpha.9.png
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 de37158..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
index ac86165..fb54215 100644
--- 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
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
index 8b2adf6..3cdb6cf 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 0b89504..a58daf6 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
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 7dc6934..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
index 884cd12..fb91811 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 930630d..6ccac60 100644
--- a/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-ldrtl-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
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
index 827d634..6e18d40 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
deleted file mode 100644
index 0908475..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
deleted file mode 100644
index a5a437f..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index d890a62..d0a41a5 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 0620439..bebb1e2 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_btn_switch_to_on_mtrl_00012.9.png
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 8043d4c..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 e80681a..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_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
index 9603e76..6086f9c 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_commit_search_api_mtrl_alpha.png
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 44c1423..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
index 80c0695..559b835 100644
--- 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
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
index 3966d6ad..1c0a1e9 100644
--- 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
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 017e45e..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
index ec0cff4..1492ab6 100644
--- 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
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
index 966938b..7c011af 100644
--- 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
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
index d05f969..36f664c 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 451818c..0000000
--- a/v7/appcompat/res/drawable-mdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_16dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_16dp.png
new file mode 100644
index 0000000..3f5d25e
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_36dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_36dp.png
new file mode 100644
index 0000000..2ddcdd9
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..c636ce8
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_16dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_16dp.png
new file mode 100644
index 0000000..077f9b0
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_36dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_36dp.png
new file mode 100644
index 0000000..ac6ad11
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..28a1723
--- /dev/null
+++ b/v7/appcompat/res/drawable-mdpi/abc_ic_star_half_black_48dp.png
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-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index fe0ec49..3924664 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png
index 10df639..ddd6d0a 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png
index f83b1ef..9280f82 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png
index e9efb20f..f60817c 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png
index a4ab0a1..8878129 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png
index db9e172..869c8b0 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
index 00c81fc..ab8460f 100644
--- a/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-mdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-v21/abc_edit_text_material.xml b/v7/appcompat/res/drawable-v21/abc_edit_text_material.xml
new file mode 100644
index 0000000..d98b008
--- /dev/null
+++ b/v7/appcompat/res/drawable-v21/abc_edit_text_material.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="@dimen/abc_edit_text_inset_horizontal_material"
+ android:insetRight="@dimen/abc_edit_text_inset_horizontal_material"
+ android:insetTop="@dimen/abc_edit_text_inset_top_material"
+ android:insetBottom="@dimen/abc_edit_text_inset_bottom_material">
+ <selector>
+ <item android:state_enabled="false">
+ <nine-patch android:src="@drawable/abc_textfield_default_mtrl_alpha"
+ android:tint="?attr/colorControlNormal"
+ android:alpha="?android:attr/disabledAlpha"/>
+ </item>
+ <item android:state_pressed="false" android:state_focused="false">
+ <nine-patch android:src="@drawable/abc_textfield_default_mtrl_alpha"
+ android:tint="?attr/colorControlNormal"/>
+ </item>
+ <item>
+ <nine-patch android:src="@drawable/abc_textfield_activated_mtrl_alpha"
+ android:tint="?attr/colorControlActivated"/>
+ </item>
+ </selector>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
deleted file mode 100644
index c0333f9..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
deleted file mode 100644
index 2f29c39..0000000
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index a854864..1d29f9a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 726b1dc5..92b43ba 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
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 c465e82..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 76e07f0..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_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
index 1015e1f..ca303fd 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_commit_search_api_mtrl_alpha.png
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 b3fa6bc..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
index c8a6d25..6448549 100644
--- 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
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
index 3c5e683..cd38901 100644
--- 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
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 f87733a..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_selectall_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_menu_selectall_mtrl_alpha.png
index c039c8e..6a7161f 100644
--- 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
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
index b57ee19..6be7e09 100644
--- 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
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 76f2696..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_star_black_16dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_16dp.png
new file mode 100644
index 0000000..35fe965
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_36dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_36dp.png
new file mode 100644
index 0000000..45887c1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..7be2280
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_16dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_16dp.png
new file mode 100644
index 0000000..ea6033a
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_36dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_36dp.png
new file mode 100644
index 0000000..2f4818b
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..c401533
--- /dev/null
+++ b/v7/appcompat/res/drawable-xhdpi/abc_ic_star_half_black_48dp.png
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 d0385ba..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-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index 09d1022..99cf6de 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png
index 138f643..1627f21 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
index cd41d74..0486af1 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
index 8d67525..0b15d96 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png
index 2b4734d..fb4e42a 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png
index 805cb29..44b9a14 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
index f0752d2..7c56175 100644
--- a/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xhdpi/abc_switch_track_mtrl_alpha.9.png
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
index 92b712e..3038d70 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
deleted file mode 100644
index 78bbeba..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_off_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
deleted file mode 100644
index c4ba8e6..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_rating_star_on_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index f026a41..c079867 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index c8b9f68..3b9dc7c 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
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 39178bf..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 f54f4f9..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_commit_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
index 65cf0c1..7d9dfa4 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_commit_search_api_mtrl_alpha.png
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
index 9dff893e..90d6ba3 100644
--- 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
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
index a1f8c33..63e541f 100644
--- 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
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 28a3bbf..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
index 29a4e52..f71485c 100644
--- 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
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
index a1866ba..d95a3774 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index d967ae7..0000000
--- a/v7/appcompat/res/drawable-xxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_16dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_16dp.png
new file mode 100644
index 0000000..e5509b0
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_36dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_36dp.png
new file mode 100644
index 0000000..72685ab
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..918a395
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_16dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_16dp.png
new file mode 100644
index 0000000..8e985b4
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_36dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_36dp.png
new file mode 100644
index 0000000..19bbc12
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..25c5bf2
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_ic_star_half_black_48dp.png
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 5baef9f..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-xxhdpi/abc_list_selector_disabled_holo_dark.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
index 9cc3666..4f3b147 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_list_selector_disabled_holo_dark.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
index f7f306d..b5ceeac 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_menu_hardkey_panel_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
index ee4bfe7..4727a7d 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_popup_background_mtrl_mult.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png
index 5268745..eb6e91b 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_off_mtrl_alpha.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
index adffc14..dd0031c 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
index f3d16d5..9955cdfe 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png
index 6a82af5..3d9b961 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_primary_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png
index c3791fc..56a69df 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_scrubber_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 6940b60..2493858 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
index c74b3fc..d2fc99a 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_switch_track_mtrl_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
index d4f3650..bc21142 100644
--- a/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxhdpi/abc_textfield_search_default_mtrl_alpha.9.png
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
index 4dc870e..e40fa4e 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
index 6fd5bfe..8043c0b 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00001.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
index 99e68cc..355d5b7 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_btn_switch_to_on_mtrl_00012.9.png
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 16b0f1d..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 7b2a480..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
index fe93d87..715db8a 100644
--- 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
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
index 4b2d05a..397fd91 100644
--- 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
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
index 129d30f..1891b3d 100644
--- 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
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
index fa6ab02..591a1c9 100644
--- 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
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
index 77318c7..ba16aac 100644
--- 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
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
deleted file mode 100644
index 098c25a..0000000
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_search_api_mtrl_alpha.png
+++ /dev/null
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_16dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_16dp.png
new file mode 100644
index 0000000..cc81097
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_36dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_36dp.png
new file mode 100644
index 0000000..dc312c1
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png
new file mode 100644
index 0000000..67e25d5
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_black_48dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_16dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_16dp.png
new file mode 100644
index 0000000..1c7f66e
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_16dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_36dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_36dp.png
new file mode 100644
index 0000000..82e7293
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_36dp.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png
new file mode 100644
index 0000000..d714ad6
--- /dev/null
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_ic_star_half_black_48dp.png
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 76c4eeb..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-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
index e5a43bb..7dfaf7c 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_000.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
index eeb37c1..fe8f2e4 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_scrubber_control_to_pressed_mtrl_005.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
index 6b8bc0a..761e8de 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_spinner_mtrl_am_alpha.9.png
Binary files differ
diff --git a/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png b/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
index 85c81c1..5651d24 100644
--- a/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
+++ b/v7/appcompat/res/drawable-xxxhdpi/abc_switch_track_mtrl_alpha.9.png
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..f6e938f 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.
@@ -17,4 +17,4 @@
<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" />
-</selector>
\ No newline at end of file
+</selector>
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background.xml b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
new file mode 100644
index 0000000..18560fc
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_dialog_material_background.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<inset xmlns:android="http://schemas.android.com/apk/res/android"
+ android:insetLeft="16dp"
+ android:insetTop="16dp"
+ android:insetRight="16dp"
+ android:insetBottom="16dp">
+ <shape android:shape="rectangle">
+ <corners android:radius="2dp" />
+ <solid android:color="@android:color/white" />
+ </shape>
+</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml b/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
deleted file mode 100644
index 41c4a6f..0000000
--- a/v7/appcompat/res/drawable/abc_dialog_material_background_dark.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="16dp"
- android:insetTop="16dp"
- android:insetRight="16dp"
- android:insetBottom="16dp">
- <shape android:shape="rectangle">
- <corners android:radius="2dp" />
- <solid android:color="@color/background_floating_material_dark" />
- </shape>
-</inset>
\ No newline at end of file
diff --git a/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml b/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
deleted file mode 100644
index 248b13a..0000000
--- a/v7/appcompat/res/drawable/abc_dialog_material_background_light.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<inset xmlns:android="http://schemas.android.com/apk/res/android"
- android:insetLeft="16dp"
- android:insetTop="16dp"
- android:insetRight="16dp"
- android:insetBottom="16dp">
- <shape android:shape="rectangle">
- <corners android:radius="2dp" />
- <solid android:color="@color/background_floating_material_light" />
- </shape>
-</inset>
\ 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_arrow_drop_right_black_24dp.xml b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
new file mode 100644
index 0000000..68547eb
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_arrow_drop_right_black_24dp.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+ android:height="24dp"
+ android:viewportHeight="24.0"
+ android:viewportWidth="24.0"
+ android:width="24dp"
+ android:tint="?attr/colorControlNormal"
+ android:autoMirrored="true">
+
+ <group
+ android:name="arrow"
+ android:rotation="90.0"
+ android:pivotX="12.0"
+ android:pivotY="12.0">
+ <path android:fillColor="@android:color/black" android:pathData="M7,14 L12,9 L17,14 L7,14 Z" />
+ <path android:pathData="M0,0 L24,0 L24,24 L0,24 L0,0 Z" />
+ </group>
+</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_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_search_api_material.xml b/v7/appcompat/res/drawable/abc_ic_search_api_material.xml
new file mode 100644
index 0000000..b4cba34
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ic_search_api_material.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<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="M15.5,14l-0.8,0l-0.3,-0.3c1,-1.1 1.6,-2.6 1.6,-4.2C16,5.9 13.1,3 9.5,3C5.9,3 3,5.9 3,9.5S5.9,16 9.5,16c1.6,0 3.1,-0.6 4.2,-1.6l0.3,0.3l0,0.8l5,5l1.5,-1.5L15.5,14zM9.5,14C7,14 5,12 5,9.5S7,5 9.5,5C12,5 14,7 14,9.5S12,14 9.5,14z"
+ 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/drawable/abc_ratingbar_full_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
deleted file mode 100644
index 535e2da2..0000000
--- a/v7/appcompat/res/drawable/abc_ratingbar_full_material.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:id="@android:id/background"
- android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
- <item android:id="@android:id/secondaryProgress"
- android:drawable="@drawable/abc_btn_rating_star_off_mtrl_alpha" />
- <item android:id="@android:id/progress"
- android:drawable="@drawable/abc_btn_rating_star_on_mtrl_alpha" />
-</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml
new file mode 100644
index 0000000..bc339a3
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_indicator_material.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@android:id/background"
+ android:drawable="@drawable/abc_ic_star_black_36dp"/>
+ <item
+ android:id="@android:id/secondaryProgress"
+ android:drawable="@drawable/abc_ic_star_half_black_36dp"/>
+ <item android:id="@android:id/progress">
+ <bitmap
+ android:src="@drawable/abc_ic_star_black_36dp"
+ android:tileModeX="repeat"/>
+ </item>
+</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_material.xml
new file mode 100644
index 0000000..dde914e
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_material.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item
+ android:id="@android:id/background"
+ android:drawable="@drawable/abc_ic_star_black_48dp"/>
+ <item
+ android:id="@android:id/secondaryProgress"
+ android:drawable="@drawable/abc_ic_star_half_black_48dp"/>
+ <item android:id="@android:id/progress">
+ <bitmap
+ android:src="@drawable/abc_ic_star_black_48dp"
+ android:tileModeX="repeat"/>
+ </item>
+</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_ratingbar_small_material.xml b/v7/appcompat/res/drawable/abc_ratingbar_small_material.xml
new file mode 100644
index 0000000..6daff8b
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_ratingbar_small_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.
+-->
+
+<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@android:id/background"
+ android:drawable="@drawable/abc_ic_star_black_16dp" />
+ <item android:id="@android:id/secondaryProgress"
+ android:drawable="@drawable/abc_ic_star_half_black_16dp" />
+ <item android:id="@android:id/progress">
+ <bitmap
+ android:src="@drawable/abc_ic_star_black_16dp"
+ android:tileModeX="repeat"/>
+ </item>
+</layer-list>
diff --git a/v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml b/v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml
new file mode 100644
index 0000000..e2d86c9
--- /dev/null
+++ b/v7/appcompat/res/drawable/abc_seekbar_tick_mark_material.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="oval">
+ <size android:width="@dimen/abc_progress_bar_height_material"
+ android:height="@dimen/abc_progress_bar_height_material"/>
+ <solid android:color="@android:color/white"/>
+</shape>
\ No newline at end of file
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 2944d983..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/abc_alert_dialog_button_bar_material.xml b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
index d32ad10..08adfd1 100644
--- a/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
+++ b/v7/appcompat/res/layout/abc_alert_dialog_button_bar_material.xml
@@ -28,7 +28,6 @@
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:gravity="bottom"
- app:allowStacking="@bool/abc_allow_stacked_button_bar"
style="?attr/buttonBarStyle">
<Button
diff --git a/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
new file mode 100644
index 0000000..a40b6dd
--- /dev/null
+++ b/v7/appcompat/res/layout/abc_popup_menu_header_item_layout.xml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/dropdownListPreferredItemHeight"
+ android:minWidth="196dip"
+ android:paddingLeft="16dip"
+ android:paddingRight="16dip">
+
+ <TextView
+ android:id="@android:id/title"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textAppearance="?attr/textAppearancePopupMenuHeader"
+ android:layout_gravity="center_vertical"
+ android:singleLine="true"
+ android:ellipsize="marquee"
+ android:fadingEdge="horizontal"
+ android:textAlignment="viewStart" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
index 47125fe..bf630ff 100644
--- a/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
+++ b/v7/appcompat/res/layout/abc_popup_menu_item_layout.xml
@@ -56,6 +56,16 @@
</RelativeLayout>
+ <ImageView
+ android:id="@+id/submenuarrow"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center"
+ android:layout_marginStart="8dp"
+ android:layout_marginLeft="8dp"
+ android:scaleType="center"
+ android:visibility="gone" />
+
<!-- Checkbox, and/or radio button will be inserted here. -->
</android.support.v7.view.menu.ListMenuItemView>
diff --git a/v7/appcompat/res/layout/abc_search_view.xml b/v7/appcompat/res/layout/abc_search_view.xml
index a7446e3..1d9a98b 100644
--- a/v7/appcompat/res/layout/abc_search_view.xml
+++ b/v7/appcompat/res/layout/abc_search_view.xml
@@ -47,11 +47,8 @@
<LinearLayout
android:id="@+id/search_edit_frame"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_weight="1"
- android:layout_gravity="center_vertical"
- android:layout_marginTop="4dip"
- android:layout_marginBottom="4dip"
android:layout_marginLeft="8dip"
android:layout_marginRight="8dip"
android:orientation="horizontal"
@@ -70,7 +67,7 @@
<LinearLayout
android:id="@+id/search_plate"
android:layout_width="wrap_content"
- android:layout_height="wrap_content"
+ android:layout_height="match_parent"
android:layout_weight="1"
android:layout_gravity="center_vertical"
android:orientation="horizontal">
@@ -80,8 +77,7 @@
android:layout_height="36dip"
android:layout_width="0dp"
android:layout_weight="1"
- android:minWidth="@dimen/abc_search_view_text_min_width"
- android:layout_gravity="bottom"
+ android:layout_gravity="center_vertical"
android:paddingLeft="@dimen/abc_dropdownitem_text_padding_left"
android:paddingRight="@dimen/abc_dropdownitem_text_padding_right"
android:singleLine="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..c2bd8c2 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"
+ android:src="@android:drawable/ic_menu_close_clear_cancel"
android:gravity="center"
android:visibility="gone"/>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-az-rAZ/strings.xml b/v7/appcompat/res/values-az-rAZ/strings.xml
index 37d2399..d5e4b81 100644
--- a/v7/appcompat/res/values-az-rAZ/strings.xml
+++ b/v7/appcompat/res/values-az-rAZ/strings.xml
@@ -17,9 +17,9 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="abc_action_mode_done" msgid="4076576682505996667">"Hazırdır"</string>
- <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Evə get"</string>
+ <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Evə naviqasiya et"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuxarı get"</string>
- <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Daha çox seçim"</string>
+ <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Digər variantlar"</string>
<string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Dağıt"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
diff --git a/v7/appcompat/res/values-b+sr+Latn/strings.xml b/v7/appcompat/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..fc92231
--- /dev/null
+++ b/v7/appcompat/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2012 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+ -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="abc_action_mode_done" msgid="4076576682505996667">"Gotovo"</string>
+ <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Odlazak na Početnu"</string>
+ <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Kretanje nagore"</string>
+ <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Još opcija"</string>
+ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Skupi"</string>
+ <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
+ <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
+ <string name="abc_searchview_description_search" msgid="8264924765203268293">"Pretraga"</string>
+ <string name="abc_search_hint" msgid="7723749260725869598">"Pretražite..."</string>
+ <string name="abc_searchview_description_query" msgid="2550479030709304392">"Upit za pretragu"</string>
+ <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Brisanje upita"</string>
+ <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Slanje upita"</string>
+ <string name="abc_searchview_description_voice" msgid="893419373245838918">"Glasovna pretraga"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Izbor aplikacije"</string>
+ <string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Prikaži sve"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Deli sa aplikacijom %s"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Deli sa"</string>
+ <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">">999"</string>
+ <string name="abc_capital_on" msgid="3405795526292276155">"UKLJUČI"</string>
+ <string name="abc_capital_off" msgid="121134116657445385">"ISKLJUČI"</string>
+</resources>
diff --git a/v7/appcompat/res/values-bn-rBD/strings.xml b/v7/appcompat/res/values-bn-rBD/strings.xml
index 07c4b54..ef5fa1b 100644
--- a/v7/appcompat/res/values-bn-rBD/strings.xml
+++ b/v7/appcompat/res/values-bn-rBD/strings.xml
@@ -29,7 +29,7 @@
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"ক্যোয়ারী সাফ করুন"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ক্যোয়ারী জমা দিন"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ভয়েস অনুসন্ধান"</string>
- <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"একটি অ্যাপ্লিকেশান চয়ন করুন"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"একটি অ্যাপ্লিকেশান বেছে নিন"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"সবগুলো দেখুন"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s এর সাথে শেয়ার করুন"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"এর সাথে শেয়ার করুন"</string>
diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml
index f1e3dbb..f5efd38 100644
--- a/v7/appcompat/res/values-es-rUS/strings.xml
+++ b/v7/appcompat/res/values-es-rUS/strings.xml
@@ -34,6 +34,6 @@
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Compartir con %s"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Compartir con"</string>
<string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
- <string name="abc_capital_on" msgid="3405795526292276155">"ACTIVAR"</string>
- <string name="abc_capital_off" msgid="121134116657445385">"DESACTIVAR"</string>
+ <string name="abc_capital_on" msgid="3405795526292276155">"ACTIVADO"</string>
+ <string name="abc_capital_off" msgid="121134116657445385">"DESACTIVADO"</string>
</resources>
diff --git a/v7/appcompat/res/values-gu-rIN/strings.xml b/v7/appcompat/res/values-gu-rIN/strings.xml
index 6fea9bb..cbf7b5d 100644
--- a/v7/appcompat/res/values-gu-rIN/strings.xml
+++ b/v7/appcompat/res/values-gu-rIN/strings.xml
@@ -29,7 +29,7 @@
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"ક્વેરી સાફ કરો"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ક્વેરી સબમિટ કરો"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"વૉઇસ શોધ"</string>
- <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"એક એપ્લિકેશન પસંદ કરો"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"એક ઍપ્લિકેશન પસંદ કરો"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"બધું જુઓ"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s સાથે શેર કરો"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"આની સાથે શેર કરો"</string>
diff --git a/v7/appcompat/res/values-h320dp/bools.xml b/v7/appcompat/res/values-h320dp/bools.xml
deleted file mode 100644
index 5576c18..0000000
--- a/v7/appcompat/res/values-h320dp/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?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>
- <bool name="abc_allow_stacked_button_bar">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml
index 708275a..e906c97 100644
--- a/v7/appcompat/res/values-hy-rAM/strings.xml
+++ b/v7/appcompat/res/values-hy-rAM/strings.xml
@@ -31,8 +31,8 @@
<string name="abc_searchview_description_voice" msgid="893419373245838918">"Ձայնային որոնում"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"Ընտրել ծրագիր"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"Տեսնել բոլորը"</string>
- <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Տարածել ըստ %s"</string>
- <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Տարածել"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"Կիսվել %s-ի միջոցով"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Կիսվել"</string>
<string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"ՄԻԱՑՎԱԾ"</string>
<string name="abc_capital_off" msgid="121134116657445385">"ԱՆՋԱՏՎԱԾ"</string>
diff --git a/v7/appcompat/res/values-land/bools.xml b/v7/appcompat/res/values-land/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-land/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-land/config.xml b/v7/appcompat/res/values-land/config.xml
deleted file mode 100644
index d0d990d..0000000
--- a/v7/appcompat/res/values-land/config.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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>
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/bools.xml b/v7/appcompat/res/values-large/bools.xml
deleted file mode 100644
index 7d1a1af..0000000
--- a/v7/appcompat/res/values-large/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-large/config.xml b/v7/appcompat/res/values-large/config.xml
index c4f04a3..58e34a0 100644
--- a/v7/appcompat/res/values-large/config.xml
+++ b/v7/appcompat/res/values-large/config.xml
@@ -20,11 +20,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- Whether action menu items should obey the "withText" showAsAction.
- This may be set to false for situations where space is
- extremely limited. -->
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-
<!-- see comment in values/config.xml -->
<dimen name="abc_config_prefDialogWidth">440dp</dimen>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values-large/dimens.xml b/v7/appcompat/res/values-large/dimens.xml
index 16bb4f6..5afdda4 100644
--- a/v7/appcompat/res/values-large/dimens.xml
+++ b/v7/appcompat/res/values-large/dimens.xml
@@ -15,13 +15,6 @@
-->
<resources>
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">4</integer>
-
<item type="dimen" name="abc_dialog_fixed_width_major">60%</item>
<item type="dimen" name="abc_dialog_fixed_width_minor">90%</item>
<item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml b/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
deleted file mode 100644
index cba56bd..0000000
--- a/v7/appcompat/res/values-ldrtl-v23/styles_base.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
- Copyright (C) 2014 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
-
- <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
-
-</resources>
diff --git a/v7/appcompat/res/values-lt/strings.xml b/v7/appcompat/res/values-lt/strings.xml
index 992910c..226565e 100644
--- a/v7/appcompat/res/values-lt/strings.xml
+++ b/v7/appcompat/res/values-lt/strings.xml
@@ -35,5 +35,5 @@
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"Bendrinti naudojant"</string>
<string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"ĮJUNGTI"</string>
- <string name="abc_capital_off" msgid="121134116657445385">"IŠJUNGTI"</string>
+ <string name="abc_capital_off" msgid="121134116657445385">"IŠJUNGTA"</string>
</resources>
diff --git a/v7/appcompat/res/values-ml-rIN/strings.xml b/v7/appcompat/res/values-ml-rIN/strings.xml
index 8483786..2918ab5 100644
--- a/v7/appcompat/res/values-ml-rIN/strings.xml
+++ b/v7/appcompat/res/values-ml-rIN/strings.xml
@@ -28,7 +28,7 @@
<string name="abc_searchview_description_query" msgid="2550479030709304392">"തിരയൽ അന്വേഷണം"</string>
<string name="abc_searchview_description_clear" msgid="3691816814315814921">"അന്വേഷണം മായ്ക്കുക"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"അന്വേഷണം സമർപ്പിക്കുക"</string>
- <string name="abc_searchview_description_voice" msgid="893419373245838918">"ശബ്ദ തിരയൽ"</string>
+ <string name="abc_searchview_description_voice" msgid="893419373245838918">"ശബ്ദതിരയൽ"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ഒരു അപ്ലിക്കേഷൻ തിരഞ്ഞെടുക്കുക"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"എല്ലാം കാണുക"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s എന്നതുമായി പങ്കിടുക"</string>
diff --git a/v7/appcompat/res/values-my-rMM/strings.xml b/v7/appcompat/res/values-my-rMM/strings.xml
index f8690aa..28df59f 100644
--- a/v7/appcompat/res/values-my-rMM/strings.xml
+++ b/v7/appcompat/res/values-my-rMM/strings.xml
@@ -26,10 +26,10 @@
<string name="abc_searchview_description_search" msgid="8264924765203268293">"ရှာဖွေရန်"</string>
<string name="abc_search_hint" msgid="7723749260725869598">"ရှာဖွေပါ..."</string>
<string name="abc_searchview_description_query" msgid="2550479030709304392">"ရှာစရာ အချက်အလက်နေရာ"</string>
- <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ရှာစရာ အချက်အလက်များ ရှင်းလင်းရန်"</string>
+ <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ရှာစရာ အချက်အလက်များ ဖယ်ရှားရန်"</string>
<string name="abc_searchview_description_submit" msgid="8928215447528550784">"ရှာဖွေစရာ အချက်အလက်ကို အတည်ပြုရန်"</string>
<string name="abc_searchview_description_voice" msgid="893419373245838918">"အသံဖြင့် ရှာဖွေခြင်း"</string>
- <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"အပလီကေးရှင်း တစ်ခုခုကို ရွေးချယ်ပါ"</string>
+ <string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"အက်ပ် တစ်ခုခုကို ရွေးချယ်ပါ"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"အားလုံးကို ကြည့်ရန်"</string>
<string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ကို မျှဝေပါရန်"</string>
<string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"မျှဝေဖို့ ရွေးပါ"</string>
diff --git a/v7/appcompat/res/values-night/themes_daynight.xml b/v7/appcompat/res/values-night/themes_daynight.xml
new file mode 100644
index 0000000..42bbf53
--- /dev/null
+++ b/v7/appcompat/res/values-night/themes_daynight.xml
@@ -0,0 +1,50 @@
+<?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>
+
+ <!-- AppCompat theme (day/night vesion) for activities. -->
+ <style name="Theme.AppCompat.DayNight" parent="Theme.AppCompat" />
+
+ <!-- Variant of AppCompat.DayNight that has a solid (opaque) action bar
+ with an inverse color profile. The dark action bar sharply stands out against
+ the light content (when applicable). -->
+ <style name="Theme.AppCompat.DayNight.DarkActionBar" parent="Theme.AppCompat" />
+
+ <!-- Variant of AppCompat.DayNight with no action bar. -->
+ <style name="Theme.AppCompat.DayNight.NoActionBar" parent="Theme.AppCompat.NoActionBar" />
+
+ <!-- AppCompat theme (day/night vesion) for dialog windows and activities,
+ which is used by the {@code android.support.v7.app.Dialog} class. This changes
+ the window to be floating (not fill the entire screen), and puts a
+ frame around its contents. You can set this theme on an activity if
+ you would like to make an activity that looks like a Dialog. -->
+ <style name="Theme.AppCompat.DayNight.Dialog" parent="Theme.AppCompat.Dialog" />
+
+ <!-- Variant of Theme.AppCompat.DayNight.Dialog that has a nice minimum width for
+ a regular dialog. -->
+ <style name="Theme.AppCompat.DayNight.Dialog.MinWidth" parent="Theme.AppCompat.Dialog.MinWidth" />
+
+ <!-- Theme for a window that will be displayed either full-screen on
+ smaller screens (small, normal) or as a dialog on larger screens
+ (large, xlarge). -->
+ <style name="Theme.AppCompat.DayNight.DialogWhenLarge" parent="Theme.AppCompat.DialogWhenLarge" />
+
+ <!-- AppCompat user theme for alert dialog windows, which is used by the
+ {@code android.support.v7.app.AlertDialog} class. -->
+ <style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Dialog.Alert" />
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-pa-rIN/strings.xml b/v7/appcompat/res/values-pa-rIN/strings.xml
index 45b703a..c5937d2 100644
--- a/v7/appcompat/res/values-pa-rIN/strings.xml
+++ b/v7/appcompat/res/values-pa-rIN/strings.xml
@@ -31,8 +31,8 @@
<string name="abc_searchview_description_voice" msgid="893419373245838918">"ਵੌਇਸ ਖੋਜ"</string>
<string name="abc_activitychooserview_choose_application" msgid="2031811694353399454">"ਇੱਕ ਐਪ ਚੁਣੋ"</string>
<string name="abc_activity_chooser_view_see_all" msgid="7468859129482906941">"ਸਭ ਦੇਖੋ"</string>
- <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ਨਾਲ ਸ਼ੇਅਰ ਕਰੋ"</string>
- <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ਇਸ ਨਾਲ ਸ਼ੇਅਰ ਕਰੋ"</string>
+ <string name="abc_shareactionprovider_share_with_application" msgid="7165123711973476752">"%s ਨਾਲ ਸਾਂਝਾ ਕਰੋ"</string>
+ <string name="abc_shareactionprovider_share_with" msgid="3421042268587513524">"ਇਸ ਨਾਲ ਸਾਂਝਾ ਕਰੋ"</string>
<string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string>
<string name="abc_capital_on" msgid="3405795526292276155">"ਤੇ"</string>
<string name="abc_capital_off" msgid="121134116657445385">"ਬੰਦ"</string>
diff --git a/v7/appcompat/res/values-sw600dp/dimens.xml b/v7/appcompat/res/values-sw600dp/dimens.xml
index a4bc455..86f9c75 100644
--- a/v7/appcompat/res/values-sw600dp/dimens.xml
+++ b/v7/appcompat/res/values-sw600dp/dimens.xml
@@ -15,12 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
<!-- Use the default title sizes on tablets. -->
<dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
<!-- Use the default subtitle sizes on tablets. -->
@@ -29,6 +23,8 @@
<dimen name="abc_action_bar_default_height_material">64dp</dimen>
<!-- Default content inset of an action bar. -->
<dimen name="abc_action_bar_content_inset_material">24dp</dimen>
+ <!-- Default content inset of an action bar with navigation. -->
+ <dimen name="abc_action_bar_content_inset_with_nav">80dp</dimen>
<!-- Default start padding of an action bar. -->
<dimen name="abc_action_bar_default_padding_start_material">8dp</dimen>
<!-- Default end padding of an action bar. -->
diff --git a/v7/appcompat/res/values-uz-rUZ/strings.xml b/v7/appcompat/res/values-uz-rUZ/strings.xml
index 79f12f0..11eeca1 100644
--- a/v7/appcompat/res/values-uz-rUZ/strings.xml
+++ b/v7/appcompat/res/values-uz-rUZ/strings.xml
@@ -19,7 +19,7 @@
<string name="abc_action_mode_done" msgid="4076576682505996667">"Tayyor"</string>
<string name="abc_action_bar_home_description" msgid="4600421777120114993">"Boshiga o‘tish"</string>
<string name="abc_action_bar_up_description" msgid="1594238315039666878">"Yuqoriga o‘tish"</string>
- <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Qo‘shimcha sozlamalar"</string>
+ <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Boshqa parametrlar"</string>
<string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"Yig‘ish"</string>
<string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string>
<string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string>
diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml
index 017ebd1..2098ec7 100644
--- a/v7/appcompat/res/values-v11/themes_base.xml
+++ b/v7/appcompat/res/values-v11/themes_base.xml
@@ -33,8 +33,6 @@
<item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
<item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
- <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
<!-- Window colors -->
<item name="android:colorForeground">@color/foreground_material_dark</item>
<item name="android:colorForegroundInverse">@color/foreground_material_light</item>
@@ -85,8 +83,6 @@
<item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
<item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
- <item name="selectableItemBackground">?android:attr/selectableItemBackground</item>
-
<!-- Window colors -->
<item name="android:colorForeground">@color/foreground_material_light</item>
<item name="android:colorForegroundInverse">@color/foreground_material_dark</item>
@@ -146,4 +142,12 @@
<style name="Base.Theme.AppCompat.Dialog" parent="Base.V11.Theme.AppCompat.Dialog" />
<style name="Base.Theme.AppCompat.Light.Dialog" parent="Base.V11.Theme.AppCompat.Light.Dialog" />
+ <style name="Base.V11.ThemeOverlay.AppCompat.Dialog" parent="Base.V7.ThemeOverlay.AppCompat.Dialog">
+ <item name="android:buttonBarStyle">@style/Widget.AppCompat.ButtonBar.AlertDialog</item>
+ <item name="android:borderlessButtonStyle">@style/Widget.AppCompat.Button.Borderless</item>
+ <item name="android:windowCloseOnTouchOutside">@bool/abc_config_closeDialogWhenTouchOutside</item>
+ </style>
+
+ <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V11.ThemeOverlay.AppCompat.Dialog" />
+
</resources>
diff --git a/v7/appcompat/res/values-v13/bools.xml b/v7/appcompat/res/values-v13/bools.xml
new file mode 100644
index 0000000..d0c646a
--- /dev/null
+++ b/v7/appcompat/res/values-v13/bools.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <!-- Whether to allow vertically stacked button bars. This is disabled for
+ configurations with a small (e.g. less than 320dp) screen height. -->
+ <bool name="abc_allow_stacked_button_bar">false</bool>
+</resources>
diff --git a/v7/appcompat/res/values-v21/styles_base.xml b/v7/appcompat/res/values-v21/styles_base.xml
index bf21d77..d31f6f9 100644
--- a/v7/appcompat/res/values-v21/styles_base.xml
+++ b/v7/appcompat/res/values-v21/styles_base.xml
@@ -42,10 +42,6 @@
parent="android:Widget.Material.Light.ActionBar.TabText">
</style>
- <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu"
- parent="android:TextAppearance.Material.Widget.ActionBar.Menu">
- </style>
-
<style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Title"
parent="android:TextAppearance.Material.Widget.ActionBar.Title">
</style>
@@ -156,6 +152,12 @@
parent="android:TextAppearance.Material.Widget.PopupMenu.Small">
</style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+ <item name="android:fontFamily">@string/abc_font_family_title_material</item>
+ <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
+ </style>
+
<!-- Search View result styles -->
<style name="Base.TextAppearance.AppCompat.SearchResult.Title"
@@ -184,7 +186,9 @@
<style name="Base.Widget.AppCompat.Button.Borderless" parent="android:Widget.Material.Button.Borderless" />
- <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+ <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored">
+ <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
+ </style>
<style name="Base.Widget.AppCompat.ButtonBar" parent="android:Widget.Material.ButtonBar" />
diff --git a/v7/appcompat/res/values-v21/themes_base.xml b/v7/appcompat/res/values-v21/themes_base.xml
index 6c2aa26..4b479be 100644
--- a/v7/appcompat/res/values-v21/themes_base.xml
+++ b/v7/appcompat/res/values-v21/themes_base.xml
@@ -27,6 +27,9 @@
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
+ <item name="android:textColorLink">?android:attr/colorAccent</item>
+ <item name="android:textColorLinkInverse">?android:attr/colorAccent</item>
+
<item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
<item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
</style>
@@ -35,6 +38,9 @@
<item name="android:windowNoTitle">true</item>
<item name="android:windowActionBar">false</item>
+ <item name="android:textColorLink">?android:attr/colorAccent</item>
+ <item name="android:textColorLinkInverse">?android:attr/colorAccent</item>
+
<item name="android:buttonBarStyle">?attr/buttonBarStyle</item>
<item name="android:buttonBarButtonStyle">?attr/buttonBarButtonStyle</item>
</style>
@@ -64,7 +70,7 @@
<item name="borderlessButtonStyle">?android:borderlessButtonStyle</item>
<item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
<item name="dividerVertical">?android:attr/dividerVertical</item>
- <item name="editTextBackground">?android:attr/editTextBackground</item>
+ <item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/editTextColor</item>
<item name="listChoiceBackgroundIndicator">?android:attr/listChoiceBackgroundIndicator</item>
@@ -109,7 +115,7 @@
<item name="borderlessButtonStyle">?android:borderlessButtonStyle</item>
<item name="dividerHorizontal">?android:attr/dividerHorizontal</item>
<item name="dividerVertical">?android:attr/dividerVertical</item>
- <item name="editTextBackground">?android:attr/editTextBackground</item>
+ <item name="editTextBackground">@drawable/abc_edit_text_material</item>
<item name="editTextColor">?android:attr/editTextColor</item>
<item name="listChoiceBackgroundIndicator">?android:attr/listChoiceBackgroundIndicator</item>
@@ -158,4 +164,10 @@
<style name="Platform.ThemeOverlay.AppCompat.Light" />
+ <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V21.ThemeOverlay.AppCompat.Dialog" />
+
+ <style name="Base.V21.ThemeOverlay.AppCompat.Dialog" parent="Base.V11.ThemeOverlay.AppCompat.Dialog">
+ <item name="android:windowElevation">@dimen/abc_floating_window_z</item>
+ </style>
+
</resources>
diff --git a/v7/appcompat/res/values-v22/themes_base.xml b/v7/appcompat/res/values-v22/themes_base.xml
index 8a38724..8c39fac 100644
--- a/v7/appcompat/res/values-v22/themes_base.xml
+++ b/v7/appcompat/res/values-v22/themes_base.xml
@@ -22,10 +22,14 @@
<style name="Base.V22.Theme.AppCompat" parent="Base.V21.Theme.AppCompat">
<item name="actionModeShareDrawable">?android:attr/actionModeShareDrawable</item>
+ <!-- We use the framework provided edit text background on 22+ -->
+ <item name="editTextBackground">?android:attr/editTextBackground</item>
</style>
<style name="Base.V22.Theme.AppCompat.Light" parent="Base.V21.Theme.AppCompat.Light">
<item name="actionModeShareDrawable">?android:attr/actionModeShareDrawable</item>
+ <!-- We use the framework provided edit text background on 22+ -->
+ <item name="editTextBackground">?android:attr/editTextBackground</item>
</style>
</resources>
diff --git a/v7/appcompat/res/values-v23/styles_base.xml b/v7/appcompat/res/values-v23/styles_base.xml
index e1c8910..e6be6b9 100644
--- a/v7/appcompat/res/values-v23/styles_base.xml
+++ b/v7/appcompat/res/values-v23/styles_base.xml
@@ -17,6 +17,14 @@
<resources>
+ <style name="Base.Widget.AppCompat.Button.Borderless.Colored" parent="android:Widget.Material.Button.Borderless.Colored" />
+
<style name="Base.Widget.AppCompat.Button.Colored" parent="android:Widget.Material.Button.Colored" />
+ <style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.Material.RatingBar.Indicator" />
+
+ <style name="Base.Widget.AppCompat.RatingBar.Small" parent="android:Widget.Material.RatingBar.Small" />
+
+ <style name="Base.Widget.AppCompat.Spinner.Underlined" parent="android:Widget.Material.Spinner.Underlined" />
+
</resources>
diff --git a/v7/appcompat/res/values-v23/styles_base_text.xml b/v7/appcompat/res/values-v23/styles_base_text.xml
index 3fbae02..9f6bf2d 100644
--- a/v7/appcompat/res/values-v23/styles_base_text.xml
+++ b/v7/appcompat/res/values-v23/styles_base_text.xml
@@ -18,4 +18,7 @@
<style name="Base.TextAppearance.AppCompat.Widget.Button.Inverse" parent="android:TextAppearance.Material.Widget.Button.Inverse" />
+ <!-- We can use the fixed TextAppearance.Material.Widget.ActionBar.Menu on 23+ -->
+ <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu" parent="android:TextAppearance.Material.Widget.ActionBar.Menu" />
+
</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-v23/themes_base.xml b/v7/appcompat/res/values-v23/themes_base.xml
index 276a3c6..cff796c 100644
--- a/v7/appcompat/res/values-v23/themes_base.xml
+++ b/v7/appcompat/res/values-v23/themes_base.xml
@@ -21,6 +21,10 @@
<style name="Base.Theme.AppCompat.Light" parent="Base.V23.Theme.AppCompat.Light" />
<style name="Base.V23.Theme.AppCompat" parent="Base.V22.Theme.AppCompat">
+ <!-- We can use the platform styles on API 23+ -->
+ <item name="ratingBarStyleIndicator">?android:attr/ratingBarStyleIndicator</item>
+ <item name="ratingBarStyleSmall">?android:attr/ratingBarStyleSmall</item>
+
<!-- We can use the platform drawable on v23+ -->
<item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
<!-- We can use the platform styles on v23+ -->
@@ -31,6 +35,10 @@
</style>
<style name="Base.V23.Theme.AppCompat.Light" parent="Base.V22.Theme.AppCompat.Light">
+ <!-- We can use the platform styles on API 23+ -->
+ <item name="ratingBarStyleIndicator">?android:attr/ratingBarStyleIndicator</item>
+ <item name="ratingBarStyleSmall">?android:attr/ratingBarStyleSmall</item>
+
<!-- We can use the platform drawable on v23+ -->
<item name="actionBarItemBackground">?android:attr/actionBarItemBackground</item>
<!-- We can use the platform styles on v23+ -->
diff --git a/v7/appcompat/res/values-w360dp/dimens.xml b/v7/appcompat/res/values-w360dp/dimens.xml
deleted file mode 100644
index e5b2456..0000000
--- a/v7/appcompat/res/values-w360dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">3</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w480dp/bools.xml b/v7/appcompat/res/values-w480dp/bools.xml
deleted file mode 100644
index 470f89b..0000000
--- a/v7/appcompat/res/values-w480dp/bools.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
- <bool name="abc_action_bar_embed_tabs_pre_jb">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-w480dp/config.xml b/v7/appcompat/res/values-w480dp/config.xml
deleted file mode 100644
index e95b6ff..0000000
--- a/v7/appcompat/res/values-w480dp/config.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-<resources>
- <bool name="abc_config_allowActionMenuItemTextWithIcon">true</bool>
-</resources>
diff --git a/v7/appcompat/res/values-w500dp/dimens.xml b/v7/appcompat/res/values-w500dp/dimens.xml
deleted file mode 100644
index dd6458b..0000000
--- a/v7/appcompat/res/values-w500dp/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">4</integer>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w600dp/dimens.xml b/v7/appcompat/res/values-w600dp/dimens.xml
deleted file mode 100644
index 252ba6a..0000000
--- a/v7/appcompat/res/values-w600dp/dimens.xml
+++ /dev/null
@@ -1,25 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-w720dp/bools.xml b/v7/appcompat/res/values-w720dp/bools.xml
deleted file mode 100644
index 05c5aab..0000000
--- a/v7/appcompat/res/values-w720dp/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge-land/dimens.xml b/v7/appcompat/res/values-xlarge-land/dimens.xml
deleted file mode 100644
index dea6c74..0000000
--- a/v7/appcompat/res/values-xlarge-land/dimens.xml
+++ /dev/null
@@ -1,22 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">256dip</dimen>
-
-</resources>
diff --git a/v7/appcompat/res/values-xlarge/bools.xml b/v7/appcompat/res/values-xlarge/bools.xml
deleted file mode 100644
index 05c5aab..0000000
--- a/v7/appcompat/res/values-xlarge/bools.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2012 The Android Open Source Project
-
- Licensed under the Apache License, Version 2.0 (the "License");
- you may not use this file except in compliance with the License.
- You may obtain a copy of the License at
-
- http://www.apache.org/licenses/LICENSE-2.0
-
- Unless required by applicable law or agreed to in writing, software
- distributed under the License is distributed on an "AS IS" BASIS,
- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- See the License for the specific language governing permissions and
- limitations under the License.
--->
-
-<resources>
- <bool name="abc_action_bar_expanded_action_views_exclusive">false</bool>
-</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values-xlarge/dimens.xml b/v7/appcompat/res/values-xlarge/dimens.xml
index 0dd244a..f0d560d 100644
--- a/v7/appcompat/res/values-xlarge/dimens.xml
+++ b/v7/appcompat/res/values-xlarge/dimens.xml
@@ -15,15 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">5</integer>
-
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">192dip</dimen>
-
<item type="dimen" name="abc_dialog_fixed_width_major">50%</item>
<item type="dimen" name="abc_dialog_fixed_width_minor">70%</item>
<item type="dimen" name="abc_dialog_fixed_height_major">60%</item>
diff --git a/v7/appcompat/res/values/attrs.xml b/v7/appcompat/res/values/attrs.xml
index 83cb1b2..1980bb7 100644
--- a/v7/appcompat/res/values/attrs.xml
+++ b/v7/appcompat/res/values/attrs.xml
@@ -31,7 +31,7 @@
<attr name="isLightTheme" format="boolean" />
<!-- These are the standard attributes that make up a complete theme. -->
- <declare-styleable name="Theme">
+ <declare-styleable name="AppCompatTheme">
<!-- ============= -->
<!-- Window styles -->
@@ -174,6 +174,8 @@
<attr name="textAppearanceLargePopupMenu" format="reference"/>
<!-- Text color, typeface, size, and style for small text inside of a popup menu. -->
<attr name="textAppearanceSmallPopupMenu" format="reference"/>
+ <!-- Text color, typeface, size, and style for header text inside of a popup menu. -->
+ <attr name="textAppearancePopupMenuHeader" format="reference" />
<!-- =================== -->
@@ -331,6 +333,9 @@
<!-- The background used by framework controls. -->
<attr name="controlBackground" format="reference" />
+ <!-- Default color of background imagery for floating components, ex. dialogs, popups, and cards. -->
+ <attr name="colorBackgroundFloating" format="color" />
+
<!-- ============ -->
<!-- Alert Dialog styles -->
<!-- ============ -->
@@ -374,6 +379,10 @@
<attr name="radioButtonStyle" format="reference" />
<!-- Default RatingBar style. -->
<attr name="ratingBarStyle" format="reference" />
+ <!-- Indicator RatingBar style. -->
+ <attr name="ratingBarStyleIndicator" format="reference" />
+ <!-- Small indicator RatingBar style. -->
+ <attr name="ratingBarStyleSmall" format="reference" />
<!-- Default SeekBar style. -->
<attr name="seekBarStyle" format="reference" />
<!-- Default Spinner style. -->
@@ -381,6 +390,8 @@
<!-- Default style for the Switch widget. -->
<attr name="switchStyle" format="reference" />
+ <!-- Default menu-style ListView style. -->
+ <attr name="listMenuViewStyle" format="reference" />
</declare-styleable>
@@ -464,6 +475,12 @@
<!-- Minimum inset for content views within a bar. Navigation buttons and
menu views are excepted. Only valid for some themes and configurations. -->
<attr name="contentInsetRight" format="dimension"/>
+ <!-- Minimum inset for content views within a bar when a navigation button
+ is present, such as the Up button. Only valid for some themes and configurations. -->
+ <attr name="contentInsetStartWithNavigation" format="dimension" />
+ <!-- Minimum inset for content views within a bar when actions from a menu
+ are present. Only valid for some themes and configurations. -->
+ <attr name="contentInsetEndWithActions" format="dimension" />
<!-- Elevation for the action bar itself -->
<attr name="elevation" format="dimension" />
<!-- Reference to a theme that should be used to inflate popups
@@ -556,6 +573,8 @@
<attr name="android:itemIconDisabledAlpha"/>
<!-- Whether space should be reserved in layout when an icon is missing. -->
<attr name="preserveIconSpacing" format="boolean" />
+ <!-- Drawable for the arrow icon indicating a particular item is a submenu. -->
+ <attr name="subMenuArrow" format="reference" />
</declare-styleable>
<declare-styleable name="ActionMenuView">
<!-- Size of padding on either end of a divider. -->
@@ -797,17 +816,41 @@
<attr name="title" />
<attr name="subtitle" />
<attr name="android:gravity" />
- <attr name="titleMargins" format="dimension" />
+ <!-- Specifies extra space on the left, start, right and end sides
+ of the toolbar's title. Margin values should be positive. -->
+ <attr name="titleMargin" format="dimension" />
+ <!-- Specifies extra space on the start side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginStart" format="dimension" />
+ <!-- Specifies extra space on the end side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginEnd" format="dimension" />
+ <!-- Specifies extra space on the top side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginTop" format="dimension" />
+ <!-- Specifies extra space on the bottom side of the toolbar's title.
+ If both this attribute and titleMargin are specified, then this
+ attribute takes precedence. Margin values should be positive. -->
<attr name="titleMarginBottom" format="dimension" />
+ <!-- {@deprecated Use titleMargin} -->
+ <attr name="titleMargins" format="dimension" />
<attr name="contentInsetStart" />
<attr name="contentInsetEnd" />
<attr name="contentInsetLeft" />
<attr name="contentInsetRight" />
+ <attr name="contentInsetStartWithNavigation" />
+ <attr name="contentInsetEndWithActions" />
<attr name="maxButtonHeight" format="dimension" />
-
+ <attr name="buttonGravity">
+ <!-- Push object to the top of its container, not changing its size. -->
+ <flag name="top" value="0x30" />
+ <!-- Push object to the bottom of its container, not changing its size. -->
+ <flag name="bottom" value="0x50" />
+ </attr>
+ <!-- Icon drawable to use for the collapse button. -->
<attr name="collapseIcon" format="reference" />
<!-- Text to set as the content description for the collapse button. -->
<attr name="collapseContentDescription" format="string" />
@@ -820,10 +863,6 @@
<!-- Text to set as the content description for the navigation button
located at the start of the toolbar. -->
<attr name="navigationContentDescription" format="string" />
-
- <!-- Allows us to read in the minHeight attr pre-v16 -->
- <attr name="android:minHeight" />
-
<!-- Drawable to set as the logo that appears at the starting side of
the Toolbar, just after the navigation button. -->
<attr name="logo" />
@@ -834,6 +873,7 @@
<attr name="titleTextColor" format="color" />
<!-- A color to apply to the subtitle string. -->
<attr name="subtitleTextColor" format="color" />
+ <attr name="android:minHeight" />
</declare-styleable>
<declare-styleable name="PopupWindowBackgroundState">
@@ -852,6 +892,7 @@
<!-- Whether the popup window should overlap its anchor view. -->
<attr name="overlapAnchor" format="boolean" />
<attr name="android:popupBackground" />
+ <attr name="android:popupAnimationStyle"/>
</declare-styleable>
<declare-styleable name="DrawerArrowToggle">
@@ -912,8 +953,52 @@
<declare-styleable name="SwitchCompat">
<!-- Drawable to use as the "thumb" that switches back and forth. -->
<attr name="android:thumb" />
+ <!-- Tint to apply to the thumb drawable. -->
+ <attr name="thumbTint" format="color" />
+ <!-- Blending mode used to apply the thumb tint. -->
+ <attr name="thumbTintMode">
+ <!-- The tint is drawn on top of the drawable.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the drawable with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ <!-- Combines the tint and drawable color and alpha channels, clamping the
+ result to valid color values. Saturate(S + D) -->
+ <enum name="add" value="16" />
+ </attr>
<!-- Drawable to use as the "track" that the switch thumb slides within. -->
<attr name="track" format="reference" />
+ <!-- Tint to apply to the track. -->
+ <attr name="trackTint" format="color" />
+ <!-- Blending mode used to apply the track tint. -->
+ <attr name="trackTintMode">
+ <!-- The tint is drawn on top of the drawable.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the drawable with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ <!-- Combines the tint and drawable color and alpha channels, clamping the
+ result to valid color values. Saturate(S + D) -->
+ <enum name="add" value="16" />
+ </attr>
<!-- Text to use when the switch is in the checked/"on" state. -->
<attr name="android:textOn" />
<!-- Text to use when the switch is in the unchecked/"off" state. -->
@@ -961,4 +1046,47 @@
<attr name="allowStacking" format="boolean" />
</declare-styleable>
+ <!-- Attributes that can be assigned to a ColorStateList item. -->
+ <declare-styleable name="ColorStateListItem">
+ <!-- Base color for this state. -->
+ <attr name="android:color" />
+ <!-- Alpha multiplier applied to the base color. -->
+ <attr name="alpha" format="float" />
+ <attr name="android:alpha"/>
+ </declare-styleable>
+
+ <declare-styleable name="AppCompatImageView">
+ <attr name="android:src"/>
+ <!-- TODO -->
+ <attr name="srcCompat" format="reference" />
+ </declare-styleable>
+
+ <declare-styleable name="AppCompatSeekBar">
+ <attr name="android:thumb" />
+ <!-- Drawable displayed at each progress position on a seekbar. -->
+ <attr name="tickMark" format="reference" />
+ <!-- Tint to apply to the tick mark drawable. -->
+ <attr name="tickMarkTint" format="color" />
+ <!-- Blending mode used to apply the tick mark tint. -->
+ <attr name="tickMarkTintMode">
+ <!-- The tint is drawn on top of the drawable.
+ [Sa + (1 - Sa)*Da, Rc = Sc + (1 - Sa)*Dc] -->
+ <enum name="src_over" value="3" />
+ <!-- The tint is masked by the alpha channel of the drawable. The drawable’s
+ color channels are thrown out. [Sa * Da, Sc * Da] -->
+ <enum name="src_in" value="5" />
+ <!-- The tint is drawn above the drawable, but with the drawable’s alpha
+ channel masking the result. [Da, Sc * Da + (1 - Sa) * Dc] -->
+ <enum name="src_atop" value="9" />
+ <!-- Multiplies the color and alpha channels of the drawable with those of
+ the tint. [Sa * Da, Sc * Dc] -->
+ <enum name="multiply" value="14" />
+ <!-- [Sa + Da - Sa * Da, Sc + Dc - Sc * Dc] -->
+ <enum name="screen" value="15" />
+ <!-- Combines the tint and drawable color and alpha channels, clamping the
+ result to valid color values. Saturate(S + D) -->
+ <enum name="add" value="16" />
+ </attr>
+ </declare-styleable>
+
</resources>
diff --git a/v7/appcompat/res/values/bools.xml b/v7/appcompat/res/values/bools.xml
index 3508cf3..825ece0 100644
--- a/v7/appcompat/res/values/bools.xml
+++ b/v7/appcompat/res/values/bools.xml
@@ -17,12 +17,10 @@
<resources>
<bool name="abc_action_bar_embed_tabs">true</bool>
- <bool name="abc_action_bar_embed_tabs_pre_jb">false</bool>
- <bool name="abc_action_bar_expanded_action_views_exclusive">true</bool>
<bool name="abc_config_showMenuShortcutsWhenKeyboardPresent">false</bool>
- <!-- Whether to allow vertically stacked button bars. This is disabled for
- configurations with a small (e.g. less than 320dp) screen height. -->
- <bool name="abc_allow_stacked_button_bar">false</bool>
+ <!-- Whether to allow vertically stacked button bars. This is enabled for
+ all < v13 devices. -->
+ <bool name="abc_allow_stacked_button_bar">true</bool>
</resources>
diff --git a/v7/appcompat/res/values/config.xml b/v7/appcompat/res/values/config.xml
index e0c521b..6d986ea 100644
--- a/v7/appcompat/res/values/config.xml
+++ b/v7/appcompat/res/values/config.xml
@@ -17,11 +17,6 @@
<!-- These resources are around just to allow their values to be customized
for different hardware and product builds. -->
<resources>
- <!-- Whether action menu items should obey the "withText" showAsAction
- flag. This may be set to false for situations where space is
- extremely limited. -->
- <bool name="abc_config_allowActionMenuItemTextWithIcon">false</bool>
-
<!-- The maximum width we would prefer dialogs to be. 0 if there is no
maximum (let them grow as large as the screen). Actual values are
specified for -large and -xlarge configurations. -->
@@ -45,4 +40,4 @@
<integer name="status_bar_notification_info_maxnum">999</integer>
<integer name="cancel_button_image_alpha">127</integer>
-</resources>
\ No newline at end of file
+</resources>
diff --git a/v7/appcompat/res/values/dimens.xml b/v7/appcompat/res/values/dimens.xml
index 37130e3..69928ae 100644
--- a/v7/appcompat/res/values/dimens.xml
+++ b/v7/appcompat/res/values/dimens.xml
@@ -15,12 +15,6 @@
-->
<resources>
-
- <!-- The maximum number of action buttons that should be permitted within
- an action bar/action mode. This will be used to determine how many
- showAsAction="ifRoom" items can fit. "always" items can override this. -->
- <integer name="abc_max_action_buttons">2</integer>
-
<!-- Maximum width for a stacked action bar tab. This prevents
action bar tabs from becoming too wide on a wide screen when only
a few are present. -->
@@ -33,10 +27,9 @@
<dimen name="abc_panel_menu_list_width">296dp</dimen>
- <!-- Minimum width of the search view text entry area. -->
- <dimen name="abc_search_view_text_min_width">160dip</dimen>
<!-- Preferred width of the search view. -->
<dimen name="abc_search_view_preferred_width">320dip</dimen>
+ <dimen name="abc_search_view_preferred_height">48dip</dimen>
<!-- Text padding for dropdown items -->
<dimen name="abc_dropdownitem_text_padding_left">8dip</dimen>
@@ -99,6 +92,9 @@
be either a fraction or a dimension. -->
<item type="dimen" name="abc_dialog_min_width_minor">95%</item>
+ <!-- Minimum "smallest width" of the display for cascading menus to be enabled. -->
+ <dimen name="abc_cascading_menus_min_smallest_width">720dp</dimen>
+
<!-- The width of the big icons in notifications. -->
<dimen name="notification_large_icon_width">64dp</dimen>
diff --git a/v7/appcompat/res/values/dimens_material.xml b/v7/appcompat/res/values/dimens_material.xml
index 357dc3e..d9def7b 100644
--- a/v7/appcompat/res/values/dimens_material.xml
+++ b/v7/appcompat/res/values/dimens_material.xml
@@ -24,6 +24,8 @@
<dimen name="abc_action_bar_default_padding_end_material">0dp</dimen>
<!-- Default content inset of an action bar. -->
<dimen name="abc_action_bar_content_inset_material">16dp</dimen>
+ <!-- Default content inset of an action bar with navigation. -->
+ <dimen name="abc_action_bar_content_inset_with_nav">72dp</dimen>
<!-- Vertical padding around action bar icons. -->
<dimen name="abc_action_bar_icon_vertical_padding_material">16dp</dimen>
<!-- Top margin for action bar subtitles -->
@@ -54,6 +56,7 @@
<dimen name="abc_text_size_title_material_toolbar">20dp</dimen>
<dimen name="abc_text_size_subtitle_material_toolbar">16dp</dimen>
<dimen name="abc_text_size_menu_material">16sp</dimen>
+ <dimen name="abc_text_size_menu_header_material">14sp</dimen>
<dimen name="abc_text_size_body_2_material">14sp</dimen>
<dimen name="abc_text_size_body_1_material">14sp</dimen>
<dimen name="abc_text_size_caption_material">12sp</dimen>
@@ -71,4 +74,6 @@
<dimen name="abc_seekbar_track_background_height_material">2dp</dimen>
<dimen name="abc_seekbar_track_progress_height_material">2dp</dimen>
+ <dimen name="abc_progress_bar_height_material">4dp</dimen>
+
</resources>
diff --git a/v7/appcompat/res/values/donottranslate_material.xml b/v7/appcompat/res/values/donottranslate_material.xml
new file mode 100644
index 0000000..3f01f7a
--- /dev/null
+++ b/v7/appcompat/res/values/donottranslate_material.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+
+ <string name="abc_font_family_display_4_material">sans-serif-light</string>
+ <string name="abc_font_family_display_3_material">sans-serif</string>
+ <string name="abc_font_family_display_2_material">sans-serif</string>
+ <string name="abc_font_family_display_1_material">sans-serif</string>
+ <string name="abc_font_family_headline_material">sans-serif</string>
+ <string name="abc_font_family_title_material">sans-serif-medium</string>
+ <string name="abc_font_family_subhead_material">sans-serif</string>
+ <string name="abc_font_family_menu_material">sans-serif</string>
+ <string name="abc_font_family_body_2_material">sans-serif-medium</string>
+ <string name="abc_font_family_body_1_material">sans-serif</string>
+ <string name="abc_font_family_caption_material">sans-serif</string>
+ <string name="abc_font_family_button_material">sans-serif-medium</string>
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/res/values/styles.xml b/v7/appcompat/res/values/styles.xml
index 6c5c776..ad86cbd4 100644
--- a/v7/appcompat/res/values/styles.xml
+++ b/v7/appcompat/res/values/styles.xml
@@ -143,34 +143,22 @@
parent="Base.Widget.AppCompat.Light.PopupMenu.Overflow">
</style>
- <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu">
- </style>
+ <style name="Widget.AppCompat.PopupMenu" parent="Base.Widget.AppCompat.PopupMenu" />
- <style name="Widget.AppCompat.Light.PopupMenu"
- parent="Base.Widget.AppCompat.Light.PopupMenu">
- </style>
+ <style name="Widget.AppCompat.Light.PopupMenu" parent="Base.Widget.AppCompat.Light.PopupMenu" />
- <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu">
- </style>
+ <style name="Widget.AppCompat.ListView.Menu" parent="Base.Widget.AppCompat.ListView.Menu" />
+
+ <style name="Widget.AppCompat.ListMenuView" parent="Base.Widget.AppCompat.ListMenuView" />
<style name="Widget.AppCompat.PopupWindow" parent="Base.Widget.AppCompat.PopupWindow">
</style>
- <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large"
- parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" />
- <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small"
- parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" />
- <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large"
- parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large">
- </style>
-
- <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small"
- parent="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small">
- </style>
+ <style name="TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" />
<style name="TextAppearance.AppCompat.SearchResult.Title"
parent="Base.TextAppearance.AppCompat.SearchResult.Title">
@@ -201,6 +189,10 @@
<style name="Widget.AppCompat.RatingBar" parent="Base.Widget.AppCompat.RatingBar" />
+ <style name="Widget.AppCompat.RatingBar.Indicator" parent="Base.Widget.AppCompat.RatingBar.Indicator" />
+
+ <style name="Widget.AppCompat.RatingBar.Small" parent="Base.Widget.AppCompat.RatingBar.Small" />
+
<style name="Widget.AppCompat.Button" parent="Base.Widget.AppCompat.Button" />
<style name="Widget.AppCompat.Button.Small" parent="Base.Widget.AppCompat.Button.Small" />
@@ -227,6 +219,8 @@
<style name="Widget.AppCompat.SeekBar" parent="Base.Widget.AppCompat.SeekBar" />
+ <style name="Widget.AppCompat.SeekBar.Discrete" parent="Base.Widget.AppCompat.SeekBar.Discrete" />
+
<!-- Toolbar -->
<style name="Widget.AppCompat.Toolbar" parent="Base.Widget.AppCompat.Toolbar" />
@@ -325,6 +319,8 @@
<style name="Widget.AppCompat.Light.ListPopupWindow" parent="Widget.AppCompat.ListPopupWindow" />
<style name="Widget.AppCompat.Light.AutoCompleteTextView" parent="Widget.AppCompat.AutoCompleteTextView" />
<style name="Widget.AppCompat.Light.ActivityChooserView" parent="Widget.AppCompat.ActivityChooserView" />
+ <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Widget.PopupMenu.Large" />
+ <style name="TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Widget.PopupMenu.Small" />
<!-- These styles didn't exist on v7. Since we only use the media template in later versions
(ICS+), just define it here and use the correct references in values/v14 -->
diff --git a/v7/appcompat/res/values/styles_base.xml b/v7/appcompat/res/values/styles_base.xml
index b119217..d727a4c 100644
--- a/v7/appcompat/res/values/styles_base.xml
+++ b/v7/appcompat/res/values/styles_base.xml
@@ -38,6 +38,7 @@
<item name="android:gravity">center_vertical</item>
<item name="contentInsetStart">@dimen/abc_action_bar_content_inset_material</item>
+ <item name="contentInsetStartWithNavigation">@dimen/abc_action_bar_content_inset_with_nav</item>
<item name="contentInsetEnd">@dimen/abc_action_bar_content_inset_material</item>
<item name="elevation">8dp</item>
<item name="popupTheme">?attr/actionBarPopupTheme</item>
@@ -75,7 +76,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>
@@ -139,7 +140,7 @@
<style name="Base.TextAppearance.AppCompat.Widget.ActionMode.Subtitle" parent="TextAppearance.AppCompat.Widget.ActionBar.Subtitle"/>
- <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu" parent="TextAppearance.AppCompat.Menu">
+ <style name="Base.TextAppearance.AppCompat.Widget.ActionBar.Menu" parent="TextAppearance.AppCompat.Button">
<item name="android:textColor">?attr/actionMenuTextColor</item>
<item name="textAllCaps">@bool/abc_config_actionMenuItemAllCaps</item>
</style>
@@ -209,6 +210,10 @@
<item name="android:divider">@null</item>
</style>
+ <style name="Base.Widget.AppCompat.ListMenuView" parent="android:Widget">
+ <item name="subMenuArrow">@drawable/abc_ic_arrow_drop_right_black_24dp</item>
+ </style>
+
<style name="Base.TextAppearance.AppCompat.Widget.DropDownItem"
parent="android:TextAppearance.Small">
<item name="android:textColor">?android:attr/textColorPrimaryDisableOnly</item>
@@ -248,16 +253,13 @@
<style name="Base.Widget.AppCompat.Light.PopupMenu" parent="@style/Widget.AppCompat.ListPopupWindow">
</style>
- <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
- </style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu"/>
- <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
- </style>
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu"/>
- <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Large" parent="TextAppearance.AppCompat.Menu">
- </style>
-
- <style name="Base.TextAppearance.AppCompat.Light.Widget.PopupMenu.Small" parent="TextAppearance.AppCompat.Menu">
+ <style name="Base.TextAppearance.AppCompat.Widget.PopupMenu.Header" parent="TextAppearance.AppCompat">
+ <item name="android:textSize">@dimen/abc_text_size_menu_header_material</item>
+ <item name="android:textColor">?android:attr/textColorSecondary</item>
</style>
<style name="Base.TextAppearance.AppCompat.SearchResult" parent="">
@@ -300,11 +302,13 @@
<item name="titleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Title</item>
<item name="subtitleTextAppearance">@style/TextAppearance.Widget.AppCompat.Toolbar.Subtitle</item>
<item name="android:minHeight">?attr/actionBarSize</item>
- <item name="titleMargins">4dp</item>
- <item name="maxButtonHeight">56dp</item>
+ <item name="titleMargin">4dp</item>
+ <item name="maxButtonHeight">@dimen/abc_action_bar_default_height_material</item>
+ <item name="buttonGravity">top</item>
<item name="collapseIcon">?attr/homeAsUpIndicator</item>
<item name="collapseContentDescription">@string/abc_toolbar_collapse_description</item>
<item name="contentInsetStart">16dp</item>
+ <item name="contentInsetStartWithNavigation">@dimen/abc_action_bar_content_inset_with_nav</item>
<item name="android:paddingLeft">@dimen/abc_action_bar_default_padding_start_material</item>
<item name="android:paddingRight">@dimen/abc_action_bar_default_padding_end_material</item>
</style>
@@ -327,11 +331,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="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="closeIcon">@drawable/abc_ic_clear_material</item>
+ <item name="searchIcon">@drawable/abc_ic_search_api_material</item>
+ <item name="searchHintIcon">@drawable/abc_ic_search_api_material</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>
@@ -391,8 +395,26 @@
<style name="Base.TextAppearance.AppCompat.Widget.Switch" parent="TextAppearance.AppCompat.Button" />
<style name="Base.Widget.AppCompat.RatingBar" parent="android:Widget.RatingBar">
- <item name="android:progressDrawable">@drawable/abc_ratingbar_full_material</item>
- <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_full_material</item>
+ <item name="android:progressDrawable">@drawable/abc_ratingbar_material</item>
+ <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_material</item>
+ </style>
+
+ <style name="Base.Widget.AppCompat.RatingBar.Indicator" parent="android:Widget.RatingBar">
+ <item name="android:progressDrawable">@drawable/abc_ratingbar_indicator_material</item>
+ <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_indicator_material</item>
+ <item name="android:minHeight">36dp</item>
+ <item name="android:maxHeight">36dp</item>
+ <item name="android:isIndicator">true</item>
+ <item name="android:thumb">@null</item>
+ </style>
+
+ <style name="Base.Widget.AppCompat.RatingBar.Small" parent="android:Widget.RatingBar">
+ <item name="android:progressDrawable">@drawable/abc_ratingbar_small_material</item>
+ <item name="android:indeterminateDrawable">@drawable/abc_ratingbar_small_material</item>
+ <item name="android:minHeight">16dp</item>
+ <item name="android:maxHeight">16dp</item>
+ <item name="android:isIndicator">true</item>
+ <item name="android:thumb">@null</item>
</style>
<style name="Base.Widget.AppCompat.SeekBar" parent="android:Widget">
@@ -405,6 +427,11 @@
<item name="android:paddingRight">16dip</item>
</style>
+ <!-- A seek bar with tick marks at each progress value. -->
+ <style name="Base.Widget.AppCompat.SeekBar.Discrete">
+ <item name="tickMark">@drawable/abc_seekbar_tick_mark_material</item>
+ </style>
+
<!-- Bordered ink button -->
<style name="Base.Widget.AppCompat.Button" parent="android:Widget">
<item name="android:background">@drawable/abc_btn_default_mtrl_shape</item>
@@ -435,7 +462,7 @@
<!-- Colored borderless ink button -->
<style name="Base.Widget.AppCompat.Button.Borderless.Colored">
- <item name="android:textColor">?attr/colorAccent</item>
+ <item name="android:textColor">@color/abc_btn_colored_borderless_text_material</item>
</style>
<style name="Base.Widget.AppCompat.Button.ButtonBar.AlertDialog" parent="Widget.AppCompat.Button.Borderless.Colored">
diff --git a/v7/appcompat/res/values/themes.xml b/v7/appcompat/res/values/themes.xml
index f8962df..28ea38e 100644
--- a/v7/appcompat/res/values/themes.xml
+++ b/v7/appcompat/res/values/themes.xml
@@ -92,4 +92,7 @@
text color. -->
<style name="ThemeOverlay.AppCompat.Dark.ActionBar" parent="Base.ThemeOverlay.AppCompat.Dark.ActionBar" />
+ <style name="ThemeOverlay.AppCompat.Dialog" parent="Base.ThemeOverlay.AppCompat.Dialog" />
+ <style name="ThemeOverlay.AppCompat.Dialog.Alert" parent="Base.ThemeOverlay.AppCompat.Dialog.Alert" />
+
</resources>
diff --git a/v7/appcompat/res/values/themes_base.xml b/v7/appcompat/res/values/themes_base.xml
index f8e0ac6..ab45f55 100644
--- a/v7/appcompat/res/values/themes_base.xml
+++ b/v7/appcompat/res/values/themes_base.xml
@@ -117,13 +117,15 @@
<item name="windowActionModeOverlay">false</item>
<item name="actionBarPopupTheme">@null</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
+
<!-- Used by MediaRouter -->
<item name="isLightTheme">false</item>
<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,7 +154,7 @@
<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>
@@ -190,8 +192,10 @@
<item name="popupMenuStyle">@style/Widget.AppCompat.PopupMenu</item>
<item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Large</item>
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Widget.PopupMenu.Small</item>
+ <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
<item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
<item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+ <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
<!-- SearchView attributes -->
<item name="searchViewStyle">@style/Widget.AppCompat.SearchView</item>
@@ -231,6 +235,8 @@
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
<item name="ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+ <item name="ratingBarStyleIndicator">@style/Widget.AppCompat.RatingBar.Indicator</item>
+ <item name="ratingBarStyleSmall">@style/Widget.AppCompat.RatingBar.Small</item>
<item name="seekBarStyle">@style/Widget.AppCompat.SeekBar</item>
<!-- Button styles -->
@@ -247,10 +253,10 @@
<item name="buttonBarNeutralButtonStyle">?attr/buttonBarButtonStyle</item>
<!-- Dialog attributes -->
- <item name="dialogTheme">@style/Theme.AppCompat.Dialog</item>
+ <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
<item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
- <item name="alertDialogTheme">@style/Theme.AppCompat.Dialog.Alert</item>
+ <item name="alertDialogTheme">@style/ThemeOverlay.AppCompat.Dialog.Alert</item>
<item name="alertDialogStyle">@style/AlertDialog.AppCompat</item>
<item name="alertDialogCenterButtons">false</item>
<item name="textColorAlertDialogListItem">@color/abc_primary_text_material_dark</item>
@@ -272,13 +278,15 @@
<item name="windowActionModeOverlay">false</item>
<item name="actionBarPopupTheme">@null</item>
+ <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
+
<!-- Used by MediaRouter -->
<item name="isLightTheme">true</item>
<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>
@@ -304,7 +312,7 @@
<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>
@@ -345,8 +353,10 @@
<item name="popupMenuStyle">@style/Widget.AppCompat.Light.PopupMenu</item>
<item name="textAppearanceLargePopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Large</item>
<item name="textAppearanceSmallPopupMenu">@style/TextAppearance.AppCompat.Light.Widget.PopupMenu.Small</item>
+ <item name="textAppearancePopupMenuHeader">@style/TextAppearance.AppCompat.Widget.PopupMenu.Header</item>
<item name="listPopupWindowStyle">@style/Widget.AppCompat.ListPopupWindow</item>
<item name="dropDownListViewStyle">?android:attr/dropDownListViewStyle</item>
+ <item name="listMenuViewStyle">@style/Widget.AppCompat.ListMenuView</item>
<!-- SearchView attributes -->
<item name="searchViewStyle">@style/Widget.AppCompat.Light.SearchView</item>
@@ -386,6 +396,8 @@
<item name="switchStyle">@style/Widget.AppCompat.CompoundButton.Switch</item>
<item name="ratingBarStyle">@style/Widget.AppCompat.RatingBar</item>
+ <item name="ratingBarStyleIndicator">@style/Widget.AppCompat.RatingBar.Indicator</item>
+ <item name="ratingBarStyleSmall">@style/Widget.AppCompat.RatingBar.Small</item>
<item name="seekBarStyle">@style/Widget.AppCompat.SeekBar</item>
<!-- Button styles -->
@@ -402,7 +414,7 @@
<item name="buttonBarNeutralButtonStyle">?attr/buttonBarButtonStyle</item>
<!-- Dialog attributes -->
- <item name="dialogTheme">@style/Theme.AppCompat.Light.Dialog</item>
+ <item name="dialogTheme">@style/ThemeOverlay.AppCompat.Dialog</item>
<item name="dialogPreferredPadding">@dimen/abc_dialog_padding_material</item>
<item name="alertDialogTheme">@style/Theme.AppCompat.Light.Dialog.Alert</item>
@@ -445,13 +457,13 @@
</style>
<style name="Base.V7.Theme.AppCompat.Dialog" parent="Base.Theme.AppCompat">
- <item name="android:colorBackground">@color/background_floating_material_dark</item>
+ <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
<item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
- <item name="android:windowBackground">@drawable/abc_dialog_material_background_dark</item>
+ <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
@@ -468,13 +480,13 @@
</style>
<style name="Base.V7.Theme.AppCompat.Light.Dialog" parent="Base.Theme.AppCompat.Light">
- <item name="android:colorBackground">@color/background_floating_material_light</item>
+ <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowFrame">@null</item>
<item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
<item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
- <item name="android:windowBackground">@drawable/abc_dialog_material_background_light</item>
+ <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
<item name="android:windowIsFloating">true</item>
<item name="android:backgroundDimEnabled">true</item>
<item name="android:windowContentOverlay">@null</item>
@@ -623,4 +635,36 @@
<item name="searchViewStyle">@style/Widget.AppCompat.SearchView.ActionBar</item>
</style>
+ <!-- Theme overlay that overrides window properties to display as a dialog. -->
+ <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V7.ThemeOverlay.AppCompat.Dialog" />
+
+ <style name="Base.ThemeOverlay.AppCompat.Dialog.Alert">
+ <item name="windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item>
+ <item name="windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item>
+ </style>
+
+ <!-- Theme overlay that overrides window properties to display as a dialog. -->
+ <style name="Base.V7.ThemeOverlay.AppCompat.Dialog" parent="Base.ThemeOverlay.AppCompat">
+ <item name="android:colorBackgroundCacheHint">@null</item>
+ <item name="android:colorBackground">?attr/colorBackgroundFloating</item>
+
+ <item name="android:windowFrame">@null</item>
+ <item name="android:windowTitleStyle">@style/RtlOverlay.DialogWindowTitle.AppCompat</item>
+ <item name="android:windowTitleBackgroundStyle">@style/Base.DialogWindowTitleBackground.AppCompat</item>
+ <item name="android:windowBackground">@drawable/abc_dialog_material_background</item>
+ <item name="android:windowIsFloating">true</item>
+ <item name="android:backgroundDimEnabled">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ <item name="android:windowAnimationStyle">@style/Animation.AppCompat.Dialog</item>
+ <item name="android:windowSoftInputMode">stateUnspecified|adjustPan</item>
+
+ <item name="windowActionBar">false</item>
+ <item name="windowActionModeOverlay">true</item>
+
+ <item name="listPreferredItemPaddingLeft">24dip</item>
+ <item name="listPreferredItemPaddingRight">24dip</item>
+
+ <item name="android:listDivider">@null</item>
+ </style>
+
</resources>
diff --git a/v7/appcompat/res/values/themes_daynight.xml b/v7/appcompat/res/values/themes_daynight.xml
new file mode 100644
index 0000000..d20f818
--- /dev/null
+++ b/v7/appcompat/res/values/themes_daynight.xml
@@ -0,0 +1,50 @@
+<?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>
+
+ <!-- AppCompat theme (day/night vesion) for activities. -->
+ <style name="Theme.AppCompat.DayNight" parent="Theme.AppCompat.Light" />
+
+ <!-- Variant of AppCompat.DayNight that has a solid (opaque) action bar
+ with an inverse color profile. The dark action bar sharply stands out against
+ the light content (when applicable). -->
+ <style name="Theme.AppCompat.DayNight.DarkActionBar" parent="Theme.AppCompat.Light.DarkActionBar" />
+
+ <!-- Variant of AppCompat.DayNight with no action bar. -->
+ <style name="Theme.AppCompat.DayNight.NoActionBar" parent="Theme.AppCompat.Light.NoActionBar" />
+
+ <!-- AppCompat theme (day/night vesion) for dialog windows and activities,
+ which is used by the {@code android.support.v7.app.Dialog} class. This changes
+ the window to be floating (not fill the entire screen), and puts a
+ frame around its contents. You can set this theme on an activity if
+ you would like to make an activity that looks like a Dialog. -->
+ <style name="Theme.AppCompat.DayNight.Dialog" parent="Theme.AppCompat.Light.Dialog" />
+
+ <!-- Variant of Theme.AppCompat.DayNight.Dialog that has a nice minimum width for
+ a regular dialog. -->
+ <style name="Theme.AppCompat.DayNight.Dialog.MinWidth" parent="Theme.AppCompat.Light.Dialog.MinWidth" />
+
+ <!-- Theme for a window that will be displayed either full-screen on
+ smaller screens (small, normal) or as a dialog on larger screens
+ (large, xlarge). -->
+ <style name="Theme.AppCompat.DayNight.DialogWhenLarge" parent="Theme.AppCompat.Light.DialogWhenLarge" />
+
+ <!-- AppCompat user theme for alert dialog windows, which is used by the
+ {@code android.support.v7.app.AlertDialog} class. -->
+ <style name="Theme.AppCompat.DayNight.Dialog.Alert" parent="Theme.AppCompat.Light.Dialog.Alert" />
+
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/app/ActionBar.java b/v7/appcompat/src/android/support/v7/app/ActionBar.java
index 2564031..f545f47 100644
--- a/v7/appcompat/src/android/support/v7/app/ActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ActionBar.java
@@ -50,16 +50,15 @@
* AppCompat's {@link R.style#Theme_AppCompat AppCompat} theme (or one of its descendant themes).
* You may otherwise add the action bar by calling {@link
* AppCompatDelegate#requestWindowFeature(int) requestFeature(FEATURE_SUPPORT_ACTION_BAR)} or by
- * declaring it in a custom theme with the {@link R.styleable#Theme_windowActionBar windowActionBar}
- * property.
- * </p>
+ * declaring it in a custom theme with the
+ * {@link R.styleable#AppCompatTheme_windowActionBar windowActionBar} property.</p>
*
* <p>The action bar may be represented by any Toolbar widget within the application layout.
* The application may signal to the Activity which Toolbar should be treated as the Activity's
* action bar. Activities that use this feature should use one of the supplied
* <code>.NoActionBar</code> themes, set the
- * {@link R.styleable#Theme_windowActionBar windowActionBar} attribute to <code>false</code>
- * or otherwise not request the window feature.</p>
+ * {@link R.styleable#AppCompatTheme_windowActionBar windowActionBar} attribute to
+ * <code>false</code> or otherwise not request the window feature.</p>
*
* <p>If your activity has an options menu, you can make select items accessible directly from the
* action bar as "action items". You can also modify various characteristics of the action bar or
@@ -105,6 +104,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public static final int NAVIGATION_MODE_STANDARD = 0;
/**
@@ -117,6 +117,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public static final int NAVIGATION_MODE_LIST = 1;
/**
@@ -128,6 +129,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public static final int NAVIGATION_MODE_TABS = 2;
/** @hide */
@@ -328,6 +330,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void setListNavigationCallbacks(SpinnerAdapter adapter,
OnNavigationListener callback);
@@ -341,6 +344,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void setSelectedNavigationItem(int position);
/**
@@ -353,6 +357,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract int getSelectedNavigationIndex();
/**
@@ -365,6 +370,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract int getNavigationItemCount();
/**
@@ -568,6 +574,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
@NavigationMode
public abstract int getNavigationMode();
@@ -584,6 +591,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void setNavigationMode(@NavigationMode int mode);
/**
@@ -611,6 +619,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract Tab newTab();
/**
@@ -624,6 +633,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void addTab(Tab tab);
/**
@@ -637,6 +647,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void addTab(Tab tab, boolean setSelected);
/**
@@ -652,6 +663,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void addTab(Tab tab, int position);
/**
@@ -667,6 +679,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void addTab(Tab tab, int position, boolean setSelected);
/**
@@ -680,6 +693,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void removeTab(Tab tab);
/**
@@ -693,6 +707,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void removeTabAt(int position);
/**
@@ -703,6 +718,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void removeAllTabs();
/**
@@ -717,6 +733,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract void selectTab(Tab tab);
/**
@@ -730,6 +747,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
@Nullable
public abstract Tab getSelectedTab();
@@ -744,6 +762,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract Tab getTabAt(int index);
/**
@@ -756,6 +775,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public abstract int getTabCount();
/**
@@ -1067,6 +1087,21 @@
}
/**
+ * Attempts to move focus to the ActionBar if it does not already contain the focus.
+ *
+ * @return {@code true} if focus changes or {@code false} if focus doesn't change.
+ */
+ boolean requestFocus() {
+ return false;
+ }
+
+ /**
+ * Clean up any resources
+ */
+ void onDestroy() {
+ }
+
+ /**
* Listener interface for ActionBar navigation events.
*
* @deprecated Action bar navigation modes are deprecated and not supported by inline
@@ -1074,6 +1109,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public interface OnNavigationListener {
/**
* This method is called whenever a navigation item in your action bar
@@ -1112,6 +1148,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public static abstract class Tab {
/**
@@ -1269,6 +1306,7 @@
* <a href="http://developer.android.com/design/patterns/navigation.html">common
* navigation patterns</a> instead.
*/
+ @Deprecated
public interface TabListener {
/**
diff --git a/v7/appcompat/src/android/support/v7/app/AlertController.java b/v7/appcompat/src/android/support/v7/app/AlertController.java
index 5f8eb4b..0dca7fb 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertController.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertController.java
@@ -182,6 +182,9 @@
mListItemLayout = a.getResourceId(R.styleable.AlertDialog_listItemLayout, 0);
a.recycle();
+
+ /* We use a custom title so never request a window title */
+ di.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
}
static boolean canTextInput(View v) {
@@ -207,8 +210,6 @@
}
public void installContent() {
- /* We use a custom title so never request a window title */
- mDialog.supportRequestWindowFeature(Window.FEATURE_NO_TITLE);
final int contentView = selectContentView();
mDialog.setContentView(contentView);
setupView();
@@ -337,6 +338,7 @@
if (mIconView != null) {
if (resId != 0) {
+ mIconView.setVisibility(View.VISIBLE);
mIconView.setImageResource(mIconId);
} else {
mIconView.setVisibility(View.GONE);
@@ -355,6 +357,7 @@
if (mIconView != null) {
if (icon != null) {
+ mIconView.setVisibility(View.VISIBLE);
mIconView.setImageDrawable(icon);
} else {
mIconView.setVisibility(View.GONE);
diff --git a/v7/appcompat/src/android/support/v7/app/AlertDialog.java b/v7/appcompat/src/android/support/v7/app/AlertDialog.java
index a301242e..5c0a1d7 100644
--- a/v7/appcompat/src/android/support/v7/app/AlertDialog.java
+++ b/v7/appcompat/src/android/support/v7/app/AlertDialog.java
@@ -23,6 +23,13 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Message;
+import android.support.annotation.ArrayRes;
+import android.support.annotation.AttrRes;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StringRes;
+import android.support.annotation.StyleRes;
import android.support.v7.appcompat.R;
import android.util.TypedValue;
import android.view.ContextThemeWrapper;
@@ -62,7 +69,7 @@
*/
public class AlertDialog extends AppCompatDialog implements DialogInterface {
- private AlertController mAlert;
+ private final AlertController mAlert;
/**
* No layout hint.
@@ -74,33 +81,29 @@
*/
static final int LAYOUT_HINT_SIDE = 1;
- protected AlertDialog(Context context) {
- this(context, resolveDialogTheme(context, 0), true);
+ protected AlertDialog(@NonNull Context context) {
+ this(context, 0);
}
/**
* Construct an AlertDialog that uses an explicit theme. The actual style
* that an AlertDialog uses is a private implementation, however you can
* here supply either the name of an attribute in the theme from which
- * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}.
+ * to get the dialog's style (such as {@link R.attr#alertDialogTheme}.
*/
- protected AlertDialog(Context context, int theme) {
- this(context, theme, true);
- }
-
- AlertDialog(Context context, int theme, boolean createThemeContextWrapper) {
- super(context, resolveDialogTheme(context, theme));
+ protected AlertDialog(@NonNull Context context, @StyleRes int themeResId) {
+ super(context, resolveDialogTheme(context, themeResId));
mAlert = new AlertController(getContext(), this, getWindow());
}
- protected AlertDialog(Context context, boolean cancelable, OnCancelListener cancelListener) {
- super(context, resolveDialogTheme(context, 0));
+ protected AlertDialog(@NonNull Context context, boolean cancelable,
+ @Nullable OnCancelListener cancelListener) {
+ this(context, 0);
setCancelable(cancelable);
setOnCancelListener(cancelListener);
- mAlert = new AlertController(context, this, getWindow());
}
- static int resolveDialogTheme(Context context, int resid) {
+ private static int resolveDialogTheme(@NonNull Context context, @StyleRes int resid) {
if (resid >= 0x01000000) { // start of real resource IDs.
return resid;
} else {
@@ -141,25 +144,33 @@
/**
* @see Builder#setCustomTitle(View)
+ *
+ * This method has no effect if called after {@link #show()}.
*/
public void setCustomTitle(View customTitleView) {
mAlert.setCustomTitle(customTitleView);
}
+ /**
+ * Sets the message to display.
+ *
+ * @param message The message to display in the dialog.
+ */
public void setMessage(CharSequence message) {
mAlert.setMessage(message);
}
/**
- * Set the view to display in that dialog.
+ * Set the view to display in the dialog. This method has no effect if called
+ * after {@link #show()}.
*/
public void setView(View view) {
mAlert.setView(view);
}
/**
- * Set the view to display in that dialog, specifying the spacing to appear around that
- * view.
+ * Set the view to display in the dialog, specifying the spacing to appear around that
+ * view. This method has no effect if called after {@link #show()}.
*
* @param view The view to show in the content area of the dialog
* @param viewSpacingLeft Extra space to appear to the left of {@code view}
@@ -182,7 +193,8 @@
}
/**
- * Set a message to be sent when a button is pressed.
+ * Sets a message to be sent when a button is pressed. This method has no effect if called
+ * after {@link #show()}.
*
* @param whichButton Which button to set the message for, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
@@ -196,7 +208,8 @@
}
/**
- * Set a listener to be invoked when the positive button of the dialog is pressed.
+ * Sets a listener to be invoked when the positive button of the dialog is pressed. This method
+ * has no effect if called after {@link #show()}.
*
* @param whichButton Which button to set the listener on, can be one of
* {@link DialogInterface#BUTTON_POSITIVE},
@@ -211,20 +224,24 @@
/**
* Set resId to 0 if you don't want an icon.
- *
* @param resId the resourceId of the drawable to use as the icon or 0
- * if you don't want an icon.
+ * if you don't want an icon.
*/
public void setIcon(int resId) {
mAlert.setIcon(resId);
}
+ /**
+ * Set the {@link Drawable} to be used in the title.
+ *
+ * @param icon Drawable to use as the icon or null if you don't want an icon.
+ */
public void setIcon(Drawable icon) {
mAlert.setIcon(icon);
}
/**
- * Set an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
+ * Sets an icon as supplied by a theme attribute. e.g. android.R.attr.alertDialogIcon
*
* @param attrId ID of a theme attribute that points to a drawable resource.
*/
@@ -257,40 +274,64 @@
}
public static class Builder {
-
private final AlertController.AlertParams P;
-
- private int mTheme;
+ private final int mTheme;
/**
- * Constructor using a context for this builder and the {@link AlertDialog} it creates.
+ * Creates a builder for an alert dialog that uses the default alert
+ * dialog theme.
+ * <p>
+ * The default alert dialog theme is defined by
+ * {@link android.R.attr#alertDialogTheme} within the parent
+ * {@code context}'s theme.
+ *
+ * @param context the parent context
*/
- public Builder(Context context) {
+ public Builder(@NonNull Context context) {
this(context, resolveDialogTheme(context, 0));
}
/**
- * Constructor using a context and theme for this builder and
- * the {@link AlertDialog} it creates. The actual theme
- * that an AlertDialog uses is a private implementation, however you can
- * here supply either the name of an attribute in the theme from which
- * to get the dialog's style (such as {@link android.R.attr#alertDialogTheme}.
+ * Creates a builder for an alert dialog that uses an explicit theme
+ * resource.
+ * <p>
+ * The specified theme resource ({@code themeResId}) is applied on top
+ * of the parent {@code context}'s theme. It may be specified as a
+ * style resource containing a fully-populated theme, such as
+ * {@link R.style#Theme_AppCompat_Dialog}, to replace all
+ * attributes in the parent {@code context}'s theme including primary
+ * and accent colors.
+ * <p>
+ * To preserve attributes such as primary and accent colors, the
+ * {@code themeResId} may instead be specified as an overlay theme such
+ * as {@link R.style#ThemeOverlay_AppCompat_Dialog}. This will
+ * override only the window attributes necessary to style the alert
+ * window as a dialog.
+ * <p>
+ * Alternatively, the {@code themeResId} may be specified as {@code 0}
+ * to use the parent {@code context}'s resolved value for
+ * {@link android.R.attr#alertDialogTheme}.
+ *
+ * @param context the parent context
+ * @param themeResId the resource ID of the theme against which to inflate
+ * this dialog, or {@code 0} to use the parent
+ * {@code context}'s default alert dialog theme
*/
- public Builder(Context context, int theme) {
+ public Builder(@NonNull Context context, @StyleRes int themeResId) {
P = new AlertController.AlertParams(new ContextThemeWrapper(
- context, resolveDialogTheme(context, theme)));
- mTheme = theme;
+ context, resolveDialogTheme(context, themeResId)));
+ mTheme = themeResId;
}
/**
- * Returns a {@link Context} with the appropriate theme for dialogs created by this
- * Builder.
+ * Returns a {@link Context} with the appropriate theme for dialogs created by this Builder.
* Applications should use this Context for obtaining LayoutInflaters for inflating views
* that will be used in the resulting dialogs, as it will cause views to be inflated with
* the correct theme.
*
* @return A Context for built Dialogs.
*/
+ @NonNull
public Context getContext() {
return P.mContext;
}
@@ -300,7 +341,7 @@
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setTitle(int titleId) {
+ public Builder setTitle(@StringRes int titleId) {
P.mTitle = P.mContext.getText(titleId);
return this;
}
@@ -316,14 +357,20 @@
}
/**
- * Set the title using the custom view {@code customTitleView}. The
- * methods {@link #setTitle(int)} and {@link #setIcon(int)} should be
- * sufficient for most titles, but this is provided if the title needs
- * more customization. Using this will replace the title and icon set
- * via the other methods.
+ * Set the title using the custom view {@code customTitleView}.
+ * <p>
+ * The methods {@link #setTitle(int)} and {@link #setIcon(int)} should
+ * be sufficient for most titles, but this is provided if the title
+ * needs more customization. Using this will replace the title and icon
+ * set via the other methods.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
*
- * @param customTitleView The custom view to use as the title.
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param customTitleView the custom view to use as the title
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setCustomTitle(View customTitleView) {
P.mCustomTitleView = customTitleView;
@@ -335,7 +382,7 @@
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMessage(int messageId) {
+ public Builder setMessage(@StringRes int messageId) {
P.mMessage = P.mContext.getText(messageId);
return this;
}
@@ -357,15 +404,20 @@
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setIcon(int iconId) {
+ public Builder setIcon(@DrawableRes int iconId) {
P.mIconId = iconId;
return this;
}
/**
* Set the {@link Drawable} to be used in the title.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the drawable
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
*
- * @return This Builder object to allow for chaining of calls to set methods
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setIcon(Drawable icon) {
P.mIcon = icon;
@@ -381,7 +433,7 @@
*
* @param attrId ID of a theme attribute that points to a drawable resource.
*/
- public Builder setIconAttribute(int attrId) {
+ public Builder setIconAttribute(@AttrRes int attrId) {
TypedValue out = new TypedValue();
P.mContext.getTheme().resolveAttribute(attrId, out, true);
P.mIconId = out.resourceId;
@@ -390,12 +442,12 @@
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
- *
- * @param textId The resource id of the text to display in the positive button
+ * @param textId The resource id of the text to display in the positive button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setPositiveButton(int textId, final OnClickListener listener) {
+ public Builder setPositiveButton(@StringRes int textId, final OnClickListener listener) {
P.mPositiveButtonText = P.mContext.getText(textId);
P.mPositiveButtonListener = listener;
return this;
@@ -403,9 +455,9 @@
/**
* Set a listener to be invoked when the positive button of the dialog is pressed.
- *
- * @param text The text to display in the positive button
+ * @param text The text to display in the positive button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setPositiveButton(CharSequence text, final OnClickListener listener) {
@@ -416,12 +468,12 @@
/**
* Set a listener to be invoked when the negative button of the dialog is pressed.
- *
- * @param textId The resource id of the text to display in the negative button
+ * @param textId The resource id of the text to display in the negative button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setNegativeButton(int textId, final OnClickListener listener) {
+ public Builder setNegativeButton(@StringRes int textId, final OnClickListener listener) {
P.mNegativeButtonText = P.mContext.getText(textId);
P.mNegativeButtonListener = listener;
return this;
@@ -429,9 +481,9 @@
/**
* Set a listener to be invoked when the negative button of the dialog is pressed.
- *
- * @param text The text to display in the negative button
+ * @param text The text to display in the negative button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNegativeButton(CharSequence text, final OnClickListener listener) {
@@ -442,12 +494,12 @@
/**
* Set a listener to be invoked when the neutral button of the dialog is pressed.
- *
- * @param textId The resource id of the text to display in the neutral button
+ * @param textId The resource id of the text to display in the neutral button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setNeutralButton(int textId, final OnClickListener listener) {
+ public Builder setNeutralButton(@StringRes int textId, final OnClickListener listener) {
P.mNeutralButtonText = P.mContext.getText(textId);
P.mNeutralButtonListener = listener;
return this;
@@ -455,9 +507,9 @@
/**
* Set a listener to be invoked when the neutral button of the dialog is pressed.
- *
- * @param text The text to display in the neutral button
+ * @param text The text to display in the neutral button
* @param listener The {@link DialogInterface.OnClickListener} to use.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setNeutralButton(CharSequence text, final OnClickListener listener) {
@@ -489,6 +541,8 @@
* @return This Builder object to allow for chaining of calls to set methods
* @see #setCancelable(boolean)
* @see #setOnDismissListener(android.content.DialogInterface.OnDismissListener)
+ *
+ * @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setOnCancelListener(OnCancelListener onCancelListener) {
P.mOnCancelListener = onCancelListener;
@@ -516,21 +570,19 @@
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified of
- * the
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener. This should be an array type i.e. R.array.foo
*
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setItems(int itemsId, final OnClickListener listener) {
+ public Builder setItems(@ArrayRes int itemsId, final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
return this;
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified of
- * the
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
*
* @return This Builder object to allow for chaining of calls to set methods
@@ -546,8 +598,9 @@
* displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
*
- * @param adapter The {@link ListAdapter} to supply the list of items
+ * @param adapter The {@link ListAdapter} to supply the list of items
* @param listener The listener that will be called when an item is clicked.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setAdapter(final ListAdapter adapter, final OnClickListener listener) {
@@ -561,10 +614,11 @@
* displayed in the dialog as the content, you will be notified of the
* selected item via the supplied listener.
*
- * @param cursor The {@link Cursor} to supply the list of items
- * @param listener The listener that will be called when an item is clicked.
+ * @param cursor The {@link Cursor} to supply the list of items
+ * @param listener The listener that will be called when an item is clicked.
* @param labelColumn The column name on the cursor containing the string to display
- * in the label.
+ * in the label.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setCursor(final Cursor cursor, final OnClickListener listener,
@@ -583,20 +637,17 @@
* item. Clicking on an item in the list will not dismiss the dialog.
* Clicking on a button will dismiss the dialog.
*
- * @param itemsId the resource id of an array i.e. R.array.foo
- * @param checkedItems specifies which items are checked. It should be null in which case
- * no
- * items are checked. If non null it must be exactly the same length as
- * the array of
- * items.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * @param itemsId the resource id of an array i.e. R.array.foo
+ * @param checkedItems specifies which items are checked. It should be null in which case no
+ * items are checked. If non null it must be exactly the same length as the array of
+ * items.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMultiChoiceItems(int itemsId, boolean[] checkedItems,
+ public Builder setMultiChoiceItems(@ArrayRes int itemsId, boolean[] checkedItems,
final OnMultiChoiceClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnCheckboxClickListener = listener;
@@ -612,17 +663,14 @@
* for each checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
*
- * @param items the text of the items to be displayed in the list.
- * @param checkedItems specifies which items are checked. It should be null in which case
- * no
- * items are checked. If non null it must be exactly the same length as
- * the array of
- * items.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * @param items the text of the items to be displayed in the list.
+ * @param checkedItems specifies which items are checked. It should be null in which case no
+ * items are checked. If non null it must be exactly the same length as the array of
+ * items.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setMultiChoiceItems(CharSequence[] items, boolean[] checkedItems,
@@ -641,24 +689,19 @@
* for each checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
*
- * @param cursor the cursor used to provide the items.
+ * @param cursor the cursor used to provide the items.
* @param isCheckedColumn specifies the column name on the cursor to use to determine
- * whether a checkbox is checked or not. It must return an integer
- * value where 1
- * means checked and 0 means unchecked.
- * @param labelColumn The column name on the cursor containing the string to display in
- * the
- * label.
- * @param listener notified when an item on the list is clicked. The dialog will not
- * be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss
- * the dialog.
+ * whether a checkbox is checked or not. It must return an integer value where 1
+ * means checked and 0 means unchecked.
+ * @param labelColumn The column name on the cursor containing the string to display in the
+ * label.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn,
- String labelColumn,
+ public Builder setMultiChoiceItems(Cursor cursor, String isCheckedColumn, String labelColumn,
final OnMultiChoiceClickListener listener) {
P.mCursor = cursor;
P.mOnCheckboxClickListener = listener;
@@ -669,23 +712,21 @@
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified
- * of
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
* the selected item via the supplied listener. This should be an array type i.e.
* R.array.foo The list will have a check mark displayed to the right of the text for the
* checked item. Clicking on an item in the list will not dismiss the dialog. Clicking on a
* button will dismiss the dialog.
*
- * @param itemsId the resource id of an array i.e. R.array.foo
+ * @param itemsId the resource id of an array i.e. R.array.foo
* @param checkedItem specifies which item is checked. If -1 no items are checked.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setSingleChoiceItems(int itemsId, int checkedItem,
+ public Builder setSingleChoiceItems(@ArrayRes int itemsId, int checkedItem,
final OnClickListener listener) {
P.mItems = P.mContext.getResources().getTextArray(itemsId);
P.mOnClickListener = listener;
@@ -695,22 +736,19 @@
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified
- * of
- * the selected item via the supplied listener. The list will have a check mark displayed
- * to
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
*
- * @param cursor the cursor to retrieve the items from.
+ * @param cursor the cursor to retrieve the items from.
* @param checkedItem specifies which item is checked. If -1 no items are checked.
* @param labelColumn The column name on the cursor containing the string to display in the
- * label.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * label.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
public Builder setSingleChoiceItems(Cursor cursor, int checkedItem, String labelColumn,
@@ -724,24 +762,20 @@
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified
- * of
- * the selected item via the supplied listener. The list will have a check mark displayed
- * to
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
*
- * @param items the items to be displayed.
+ * @param items the items to be displayed.
* @param checkedItem specifies which item is checked. If -1 no items are checked.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem,
- final OnClickListener listener) {
+ public Builder setSingleChoiceItems(CharSequence[] items, int checkedItem, final OnClickListener listener) {
P.mItems = items;
P.mOnClickListener = listener;
P.mCheckedItem = checkedItem;
@@ -750,24 +784,20 @@
}
/**
- * Set a list of items to be displayed in the dialog as the content, you will be notified
- * of
- * the selected item via the supplied listener. The list will have a check mark displayed
- * to
+ * Set a list of items to be displayed in the dialog as the content, you will be notified of
+ * the selected item via the supplied listener. The list will have a check mark displayed to
* the right of the text for the checked item. Clicking on an item in the list will not
* dismiss the dialog. Clicking on a button will dismiss the dialog.
*
- * @param adapter The {@link ListAdapter} to supply the list of items
+ * @param adapter The {@link ListAdapter} to supply the list of items
* @param checkedItem specifies which item is checked. If -1 no items are checked.
- * @param listener notified when an item on the list is clicked. The dialog will not be
- * dismissed when an item is clicked. It will only be dismissed if
- * clicked on a
- * button, if no buttons are supplied it's up to the user to dismiss the
- * dialog.
+ * @param listener notified when an item on the list is clicked. The dialog will not be
+ * dismissed when an item is clicked. It will only be dismissed if clicked on a
+ * button, if no buttons are supplied it's up to the user to dismiss the dialog.
+ *
* @return This Builder object to allow for chaining of calls to set methods
*/
- public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem,
- final OnClickListener listener) {
+ public Builder setSingleChoiceItems(ListAdapter adapter, int checkedItem, final OnClickListener listener) {
P.mAdapter = adapter;
P.mOnClickListener = listener;
P.mCheckedItem = checkedItem;
@@ -778,12 +808,11 @@
/**
* Sets a listener to be invoked when an item in the list is selected.
*
- * @param listener The listener to be invoked.
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param listener the listener to be invoked
+ * @return this Builder object to allow for chaining of calls to set methods
* @see AdapterView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
*/
- public Builder setOnItemSelectedListener(
- final AdapterView.OnItemSelectedListener listener) {
+ public Builder setOnItemSelectedListener(final AdapterView.OnItemSelectedListener listener) {
P.mOnItemSelectedListener = listener;
return this;
}
@@ -793,8 +822,8 @@
* resource will be inflated, adding all top-level views to the screen.
*
* @param layoutResId Resource ID to be inflated.
- * @return This Builder object to allow for chaining of calls to set
- * methods
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setView(int layoutResId) {
P.mView = null;
@@ -804,11 +833,18 @@
}
/**
- * Set a custom view to be the contents of the Dialog. If the supplied view is an instance
- * of a {@link ListView} the light background will be used.
+ * Sets a custom view to be the contents of the alert dialog.
+ * <p>
+ * When using a pre-Holo theme, if the supplied view is an instance of
+ * a {@link ListView} then the light background will be used.
+ * <p>
+ * <strong>Note:</strong> To ensure consistent styling, the custom view
+ * should be inflated or constructed using the alert dialog's themed
+ * context obtained via {@link #getContext()}.
*
- * @param view The view to use as the contents of the Dialog.
- * @return This Builder object to allow for chaining of calls to set methods
+ * @param view the view to use as the contents of the alert dialog
+ * @return this Builder object to allow for chaining of calls to set
+ * methods
*/
public Builder setView(View view) {
P.mView = view;
@@ -839,6 +875,7 @@
* be able to put padding around the view.
* @hide
*/
+ @Deprecated
public Builder setView(View view, int viewSpacingLeft, int viewSpacingTop,
int viewSpacingRight, int viewSpacingBottom) {
P.mView = view;
@@ -857,7 +894,11 @@
*
* @param useInverseBackground Whether to use the inverse background
* @return This Builder object to allow for chaining of calls to set methods
+ * @deprecated This flag is only used for pre-Material themes. Instead,
+ * specify the window background using on the alert dialog
+ * theme.
*/
+ @Deprecated
public Builder setInverseBackgroundForced(boolean useInverseBackground) {
P.mForceInverseBackground = useInverseBackground;
return this;
@@ -873,13 +914,17 @@
/**
- * Creates a {@link AlertDialog} with the arguments supplied to this builder. It does not
- * {@link Dialog#show()} the dialog. This allows the user to do any extra processing
- * before displaying the dialog. Use {@link #show()} if you don't have any other processing
- * to do and want this to be created and displayed.
+ * Creates an {@link AlertDialog} with the arguments supplied to this
+ * builder.
+ * <p>
+ * Calling this method does not display the dialog. If no additional
+ * processing is needed, {@link #show()} may be called instead to both
+ * create and display the dialog.
*/
public AlertDialog create() {
- final AlertDialog dialog = new AlertDialog(P.mContext, mTheme, false);
+ // We can't use Dialog's 3-arg constructor with the createThemeContextWrapper param,
+ // so we always have to re-set the theme
+ final AlertDialog dialog = new AlertDialog(P.mContext, mTheme);
P.apply(dialog.mAlert);
dialog.setCancelable(P.mCancelable);
if (P.mCancelable) {
@@ -894,11 +939,17 @@
}
/**
- * Creates a {@link AlertDialog} with the arguments supplied to this builder and
- * {@link Dialog#show()}'s the dialog.
+ * Creates an {@link AlertDialog} with the arguments supplied to this
+ * builder and immediately displays the dialog.
+ * <p>
+ * Calling this method is functionally identical to:
+ * <pre>
+ * AlertDialog dialog = builder.create();
+ * dialog.show();
+ * </pre>
*/
public AlertDialog show() {
- AlertDialog dialog = create();
+ final AlertDialog dialog = create();
dialog.show();
return dialog;
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 378c558..843d4972 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -18,16 +18,25 @@
import android.content.Intent;
import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
import android.os.Bundle;
import android.support.annotation.CallSuper;
+import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
+import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
import android.support.v4.app.ActivityCompat;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.NavUtils;
import android.support.v4.app.TaskStackBuilder;
+import android.support.v4.view.KeyEventCompat;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
+import android.support.v7.widget.VectorEnabledTintResources;
+import android.util.DisplayMetrics;
+import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.View;
@@ -53,15 +62,37 @@
TaskStackBuilder.SupportParentable, ActionBarDrawerToggle.DelegateProvider {
private AppCompatDelegate mDelegate;
+ private int mThemeId = 0;
+ private boolean mEatKeyUpEvent;
+ private Resources mResources;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
- getDelegate().installViewFactory();
- getDelegate().onCreate(savedInstanceState);
+ final AppCompatDelegate delegate = getDelegate();
+ delegate.installViewFactory();
+ delegate.onCreate(savedInstanceState);
+ if (delegate.applyDayNight() && mThemeId != 0) {
+ // If DayNight has been applied, we need to re-apply the theme for
+ // the changes to take effect. On API 23+, we should bypass
+ // setTheme(), which will no-op if the theme ID is identical to the
+ // current theme ID.
+ if (Build.VERSION.SDK_INT >= 23) {
+ onApplyThemeResource(getTheme(), mThemeId, false);
+ } else {
+ setTheme(mThemeId);
+ }
+ }
super.onCreate(savedInstanceState);
}
@Override
+ public void setTheme(@StyleRes final int resid) {
+ super.setTheme(resid);
+ // Keep hold of the theme id so that we can re-set it later if needed
+ mThemeId = resid;
+ }
+
+ @Override
protected void onPostCreate(@Nullable Bundle savedInstanceState) {
super.onPostCreate(savedInstanceState);
getDelegate().onPostCreate(savedInstanceState);
@@ -80,20 +111,20 @@
}
/**
- * Set a {@link android.widget.Toolbar Toolbar} to act as the {@link android.support.v7.app.ActionBar} for this
- * Activity window.
+ * Set a {@link android.widget.Toolbar Toolbar} to act as the
+ * {@link android.support.v7.app.ActionBar} for this Activity window.
*
* <p>When set to a non-null value the {@link #getActionBar()} method will return
- * an {@link android.support.v7.app.ActionBar} object that can be used to control the given toolbar as if it were
- * a traditional window decor action bar. The toolbar's menu will be populated with the
- * Activity's options menu and the navigation button will be wired through the standard
- * {@link android.R.id#home home} menu select action.</p>
+ * an {@link android.support.v7.app.ActionBar} object that can be used to control the given
+ * toolbar as if it were a traditional window decor action bar. The toolbar's menu will be
+ * populated with the Activity's options menu and the navigation button will be wired through
+ * the standard {@link android.R.id#home home} menu select action.</p>
*
* <p>In order to use a Toolbar within the Activity's window content the application
* must not request the window feature
* {@link android.view.Window#FEATURE_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p>
*
- * @param toolbar Toolbar to set as the Activity's action bar
+ * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
*/
public void setSupportActionBar(@Nullable Toolbar toolbar) {
getDelegate().setSupportActionBar(toolbar);
@@ -128,6 +159,12 @@
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
getDelegate().onConfigurationChanged(newConfig);
+ if (mResources != null) {
+ // The real (and thus managed) resources object was already updated
+ // by ResourcesManager, so pull the current metrics from there.
+ final DisplayMetrics newMetrics = super.getResources().getDisplayMetrics();
+ mResources.updateConfiguration(newConfig, newMetrics);
+ }
}
@Override
@@ -142,6 +179,10 @@
getDelegate().onPostResume();
}
+ public View findViewById(@IdRes int id) {
+ return getDelegate().findViewById(id);
+ }
+
@Override
public final boolean onMenuItemSelected(int featureId, android.view.MenuItem item) {
if (super.onMenuItemSelected(featureId, item)) {
@@ -205,7 +246,7 @@
* @param mode The new action mode.
*/
@CallSuper
- public void onSupportActionModeStarted(ActionMode mode) {
+ public void onSupportActionModeStarted(@NonNull ActionMode mode) {
}
/**
@@ -215,7 +256,7 @@
* @param mode The action mode that just finished.
*/
@CallSuper
- public void onSupportActionModeFinished(ActionMode mode) {
+ public void onSupportActionModeFinished(@NonNull ActionMode mode) {
}
/**
@@ -229,11 +270,18 @@
*/
@Nullable
@Override
- public ActionMode onWindowStartingSupportActionMode(ActionMode.Callback callback) {
+ public ActionMode onWindowStartingSupportActionMode(@NonNull ActionMode.Callback callback) {
return null;
}
- public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+ /**
+ * Start an action mode.
+ *
+ * @param callback Callback that will manage lifecycle events for this context mode
+ * @return The ContextMode that was started, or null if it was canceled
+ */
+ @Nullable
+ public ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback) {
return getDelegate().startSupportActionMode(callback);
}
@@ -288,7 +336,7 @@
* @param builder An empty TaskStackBuilder - the application should add intents representing
* the desired task stack
*/
- public void onCreateSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+ public void onCreateSupportNavigateUpTaskStack(@NonNull TaskStackBuilder builder) {
builder.addParentStack(this);
}
@@ -307,7 +355,7 @@
* @param builder A TaskStackBuilder that has been populated with Intents by
* onCreateNavigateUpTaskStack.
*/
- public void onPrepareSupportNavigateUpTaskStack(TaskStackBuilder builder) {
+ public void onPrepareSupportNavigateUpTaskStack(@NonNull TaskStackBuilder builder) {
}
/**
@@ -387,7 +435,7 @@
* @return true if navigating up should recreate a new task stack, false if the same task
* should be used for the destination
*/
- public boolean supportShouldUpRecreateTask(Intent targetIntent) {
+ public boolean supportShouldUpRecreateTask(@NonNull Intent targetIntent) {
return NavUtils.shouldUpRecreateTask(this, targetIntent);
}
@@ -403,7 +451,7 @@
*
* @param upIntent An intent representing the target destination for up navigation
*/
- public void supportNavigateUpTo(Intent upIntent) {
+ public void supportNavigateUpTo(@NonNull Intent upIntent) {
NavUtils.navigateUpTo(this, upIntent);
}
@@ -448,13 +496,48 @@
super.onPanelClosed(featureId, menu);
}
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ getDelegate().onSaveInstanceState(outState);
+ }
+
/**
* @return The {@link AppCompatDelegate} being used by this Activity.
*/
+ @NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
+
+ @Override
+ public boolean dispatchKeyEvent(KeyEvent event) {
+ if (KeyEventCompat.isCtrlPressed(event) &&
+ event.getUnicodeChar(event.getMetaState() & ~KeyEvent.META_CTRL_MASK) == '<') {
+ // Capture the Control-< and send focus to the ActionBar
+ final int action = event.getAction();
+ if (action == KeyEvent.ACTION_DOWN) {
+ final ActionBar actionBar = getSupportActionBar();
+ if (actionBar != null && actionBar.isShowing() && actionBar.requestFocus()) {
+ mEatKeyUpEvent = true;
+ return true;
+ }
+ } else if (action == KeyEvent.ACTION_UP && mEatKeyUpEvent) {
+ mEatKeyUpEvent = false;
+ return true;
+ }
+ }
+ return super.dispatchKeyEvent(event);
+ }
+
+ @Override
+ public Resources getResources() {
+ if (mResources == null && VectorEnabledTintResources.shouldBeUsed()) {
+ mResources = new VectorEnabledTintResources(this, super.getResources());
+ }
+ return mResources == null ? super.getResources() : mResources;
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
index ef38511..785f1df 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegate.java
@@ -22,15 +22,19 @@
import android.content.res.Configuration;
import android.os.Build;
import android.os.Bundle;
+import android.support.annotation.IdRes;
import android.support.annotation.IntDef;
import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.WindowCompat;
import android.support.v7.appcompat.R;
import android.support.v7.view.ActionMode;
import android.support.v7.widget.Toolbar;
import android.util.AttributeSet;
+import android.util.Log;
import android.view.MenuInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -76,6 +80,58 @@
static final String TAG = "AppCompatDelegate";
/**
+ * Mode which means to not use night mode, and therefore prefer {@code notnight} qualified
+ * resources where available, regardless of the time.
+ *
+ * @see #setLocalNightMode(int)
+ */
+ public static final int MODE_NIGHT_NO = 1;
+
+ /**
+ * Mode which means to always use night mode, and therefore prefer {@code night} qualified
+ * resources where available, regardless of the time.
+ *
+ * @see #setLocalNightMode(int)
+ */
+ public static final int MODE_NIGHT_YES = 2;
+
+ /**
+ * Mode which means to use night mode when it is determined that it is night or not.
+ *
+ * <p>The calculation used to determine whether it is night or not makes use of the location
+ * APIs (if this app has the necessary permissions). This allows us to generate accurate
+ * sunrise and sunset times. If this app does not have permission to access the location APIs
+ * then we use hardcoded times which will be less accurate.</p>
+ *
+ * @see #setLocalNightMode(int)
+ */
+ public static final int MODE_NIGHT_AUTO = 0;
+
+ /**
+ * Mode which uses the system's night mode setting to determine if it is night or not.
+ *
+ * @see #setLocalNightMode(int)
+ */
+ public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1;
+
+ static final int MODE_NIGHT_UNSPECIFIED = -100;
+
+ @NightMode
+ private static int sDefaultNightMode = MODE_NIGHT_FOLLOW_SYSTEM;
+
+ private static boolean sCompatVectorFromResourcesEnabled = false;
+
+ /** @hide */
+ @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_AUTO, MODE_NIGHT_FOLLOW_SYSTEM,
+ MODE_NIGHT_UNSPECIFIED})
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface NightMode {}
+
+ @IntDef({MODE_NIGHT_NO, MODE_NIGHT_YES, MODE_NIGHT_FOLLOW_SYSTEM})
+ @Retention(RetentionPolicy.SOURCE)
+ @interface ApplyableNightMode {}
+
+ /**
* Flag for enabling the support Action Bar.
*
* <p>This is enabled by default for some devices. The Action Bar replaces the title bar and
@@ -129,7 +185,9 @@
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
- if (sdk >= 23) {
+ if (BuildCompat.isAtLeastN()) {
+ return new AppCompatDelegateImplN(context, window, callback);
+ } else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
@@ -150,6 +208,7 @@
*
* @return AppCompat's action bar, or null if it does not have one.
*/
+ @Nullable
public abstract ActionBar getSupportActionBar();
/**
@@ -165,9 +224,9 @@
* must not request the window feature
* {@link AppCompatDelegate#FEATURE_SUPPORT_ACTION_BAR FEATURE_SUPPORT_ACTION_BAR}.</p>
*
- * @param toolbar Toolbar to set as the Activity's action bar
+ * @param toolbar Toolbar to set as the Activity's action bar, or {@code null} to clear it
*/
- public abstract void setSupportActionBar(Toolbar toolbar);
+ public abstract void setSupportActionBar(@Nullable Toolbar toolbar);
/**
* Return the value of this call from your {@link Activity#getMenuInflater()}
@@ -210,6 +269,15 @@
public abstract void onPostResume();
/**
+ * Finds a view that was identified by the id attribute from the XML that
+ * was processed in {@link #onCreate}.
+ *
+ * @return The view if found or null otherwise.
+ */
+ @Nullable
+ public abstract View findViewById(@IdRes int id);
+
+ /**
* Should be called instead of {@link Activity#setContentView(android.view.View)}}
*/
public abstract void setContentView(View v);
@@ -234,7 +302,7 @@
/**
* Should be called from {@link Activity#onTitleChanged(CharSequence, int)}}
*/
- public abstract void setTitle(CharSequence title);
+ public abstract void setTitle(@Nullable CharSequence title);
/**
* Should be called from {@link Activity#invalidateOptionsMenu()}} or
@@ -251,6 +319,7 @@
* Returns an {@link ActionBarDrawerToggle.Delegate} which can be returned from your Activity
* if it implements {@link ActionBarDrawerToggle.DelegateProvider}.
*/
+ @Nullable
public abstract ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
/**
@@ -280,7 +349,8 @@
* @param callback Callback that will manage lifecycle events for this context mode
* @return The ContextMode that was started, or null if it was canceled
*/
- public abstract ActionMode startSupportActionMode(ActionMode.Callback callback);
+ @Nullable
+ public abstract ActionMode startSupportActionMode(@NonNull ActionMode.Callback callback);
/**
* Installs AppCompat's {@link android.view.LayoutInflater} Factory so that it can replace
@@ -311,7 +381,7 @@
* {@link android.view.LayoutInflater LayoutInflater} factory, and have therefore not
* installed the default factory via {@link #installViewFactory()}.
*/
- public abstract View createView(View parent, String name, @NonNull Context context,
+ public abstract View createView(@Nullable View parent, String name, @NonNull Context context,
@NonNull AttributeSet attrs);
/**
@@ -330,4 +400,121 @@
*/
public abstract boolean isHandleNativeActionModesEnabled();
+ /**
+ * Allows AppCompat to save instance state.
+ */
+ public abstract void onSaveInstanceState(Bundle outState);
+
+ /**
+ * Allow AppCompat to apply the {@code night} and {@code notnight} resource qualifiers.
+ *
+ * <p>Doing this enables the
+ * {@link R.style#Theme_AppCompat_DayNight Theme.AppCompat.DayNight}
+ * family of themes to work, using the computed twilight to automatically select a dark or
+ * light theme.</p>
+ *
+ * <p>You can override the night mode using {@link #setLocalNightMode(int)}.</p>
+ *
+ * <p>This only works on devices running
+ * {@link Build.VERSION_CODES#ICE_CREAM_SANDWICH ICE_CREAM_SANDWICH} and above.</p>
+ *
+ * @see #setDefaultNightMode(int)
+ * @see #setLocalNightMode(int)
+ *
+ * @return true if the night mode was applied, false if not
+ */
+ public abstract boolean applyDayNight();
+
+ /**
+ * Override the night mode used for this delegate's host component. This method only takes
+ * effect for those situtations where {@link #applyDayNight()} works.
+ *
+ * <p>Depending on when this is called, this may not take effect until the next time that
+ * the host component is created. You may use {@link Activity#recreate()} to force a
+ * recreation.</p>
+ *
+ * @see #applyDayNight()
+ */
+ public abstract void setLocalNightMode(@NightMode int mode);
+
+ /**
+ * Sets the default night mode. This is used across all activities/dialogs but can be overriden
+ * locally via {@link #setLocalNightMode(int)}.
+ *
+ * <p>This method only takes effect for those situtations where {@link #applyDayNight()} works.
+ * Defaults to {@link #MODE_NIGHT_NO}.</p>
+ *
+ * @see #setLocalNightMode(int)
+ * @see #getDefaultNightMode()
+ */
+ public static void setDefaultNightMode(@NightMode int mode) {
+ switch (mode) {
+ case MODE_NIGHT_AUTO:
+ case MODE_NIGHT_NO:
+ case MODE_NIGHT_YES:
+ case MODE_NIGHT_FOLLOW_SYSTEM:
+ sDefaultNightMode = mode;
+ break;
+ default:
+ Log.d(TAG, "setDefaultNightMode() called with an unknown mode");
+ break;
+ }
+ }
+
+ /**
+ * Returns the default night mode.
+ *
+ * @see #setDefaultNightMode(int)
+ */
+ @NightMode
+ public static int getDefaultNightMode() {
+ return sDefaultNightMode;
+ }
+
+ /**
+ * Sets whether vector drawables on older platforms (< API 21) can be used within
+ * {@link android.graphics.drawable.DrawableContainer} resources.
+ *
+ * <p>When enabled, AppCompat can intercept some drawable inflation from the framework, which
+ * enables implicit inflation of vector drawables within
+ * {@link android.graphics.drawable.DrawableContainer} resources. You can then use those
+ * drawables in places such as {@code android:src} on {@link android.widget.ImageView},
+ * or {@code android:drawableLeft} on {@link android.widget.TextView}. Example usage:</p>
+ *
+ * <pre>
+ * <selector xmlns:android="...">
+ * <item android:state_checked="true"
+ * android:drawable="@drawable/vector_checked_icon" />
+ * <item android:drawable="@drawable/vector_icon" />
+ * </selector>
+ *
+ * <TextView
+ * ...
+ * android:drawableLeft="@drawable/vector_state_list_icon" />
+ * </pre>
+ *
+ * <p>This feature defaults to disabled, since enabling it can cause issues with memory usage,
+ * and problems updating {@link Configuration} instances. If you update the configuration
+ * manually, then you probably do not want to enable this. You have been warned.</p>
+ *
+ * <p>Even with this disabled, you can still use vector resources through
+ * {@link android.support.v7.widget.AppCompatImageView#setImageResource(int)} and it's
+ * {@code app:srcCompat} attribute. They can also be used in anything which AppComapt inflates
+ * for you, such as menu resources.</p>
+ *
+ * <p>Please note: this only takes effect in Activities created after this call.</p>
+ */
+ public static void setCompatVectorFromResourcesEnabled(boolean enabled) {
+ sCompatVectorFromResourcesEnabled = enabled;
+ }
+
+ /**
+ * Returns whether vector drawables on older platforms (< API 21) can be accessed from within
+ * resources.
+ *
+ * @see #setCompatVectorFromResourcesEnabled(boolean)
+ */
+ public static boolean isCompatVectorFromResourcesEnabled() {
+ return sCompatVectorFromResourcesEnabled;
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
index 394cf60..5fe7a83 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplBase.java
@@ -18,12 +18,15 @@
import android.app.Activity;
import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Bundle;
import android.support.v7.appcompat.R;
import android.support.v7.view.ActionMode;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.WindowCallbackWrapper;
import android.support.v7.view.menu.MenuBuilder;
+import android.support.v7.widget.AppCompatDrawableManager;
import android.support.v7.widget.TintTypedArray;
import android.view.KeyEvent;
import android.view.Menu;
@@ -33,6 +36,8 @@
abstract class AppCompatDelegateImplBase extends AppCompatDelegate {
+ private static final int[] sWindowBackgroundStyleable = {android.R.attr.windowBackground};
+
final Context mContext;
final Window mWindow;
final Window.Callback mOriginalWindowCallback;
@@ -72,6 +77,14 @@
mAppCompatWindowCallback = wrapWindowCallback(mOriginalWindowCallback);
// Now install the new callback
mWindow.setCallback(mAppCompatWindowCallback);
+
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(
+ context, null, sWindowBackgroundStyleable);
+ final Drawable winBg = a.getDrawableIfKnown(0);
+ if (winBg != null) {
+ mWindow.setBackgroundDrawable(winBg);
+ }
+ a.recycle();
}
abstract void initWindowDecorActionBar();
@@ -113,6 +126,11 @@
abstract boolean onKeyShortcut(int keyCode, KeyEvent event);
@Override
+ public void setLocalNightMode(@NightMode int mode) {
+ // no-op
+ }
+
+ @Override
public final ActionBarDrawerToggle.Delegate getDrawerToggleDelegate() {
return new ActionBarDrawableToggleImpl();
}
@@ -174,7 +192,7 @@
abstract ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback);
@Override
- public final void onDestroy() {
+ public void onDestroy() {
mIsDestroyed = true;
}
@@ -189,6 +207,12 @@
return false;
}
+ @Override
+ public boolean applyDayNight() {
+ // no-op on v7
+ return false;
+ }
+
final boolean isDestroyed() {
return mIsDestroyed;
}
@@ -203,6 +227,11 @@
onTitleChanged(title);
}
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ // no-op
+ }
+
abstract void onTitleChanged(CharSequence title);
final CharSequence getTitle() {
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
new file mode 100644
index 0000000..8313e49
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplN.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v7.app;
+
+import android.content.Context;
+import android.view.KeyboardShortcutGroup;
+import android.view.Menu;
+import android.view.Window;
+
+import java.util.List;
+
+class AppCompatDelegateImplN extends AppCompatDelegateImplV23 {
+
+ AppCompatDelegateImplN(Context context, Window window, AppCompatCallback callback) {
+ super(context, window, callback);
+ }
+
+ @Override
+ Window.Callback wrapWindowCallback(Window.Callback callback) {
+ return new AppCompatWindowCallbackN(callback);
+ }
+
+ class AppCompatWindowCallbackN extends AppCompatWindowCallbackV23 {
+ AppCompatWindowCallbackN(Window.Callback callback) {
+ super(callback);
+ }
+
+ @Override
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ final PanelFeatureState panel = getPanelState(Window.FEATURE_OPTIONS_PANEL, true);
+ if (panel != null && panel.menu != null) {
+ // The menu provided is one created by PhoneWindow which we don't actually use.
+ // Instead we'll pass through our own...
+ super.onProvideKeyboardShortcuts(data, panel.menu, deviceId);
+ } else {
+ // If we don't have a menu, jump pass through the original instead
+ super.onProvideKeyboardShortcuts(data, menu, deviceId);
+ }
+ }
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV14.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV14.java
index 35e7ca3..bc79536 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV14.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV14.java
@@ -17,12 +17,24 @@
package android.support.v7.app;
import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Bundle;
import android.support.v7.view.SupportActionModeWrapper;
+import android.util.Log;
import android.view.ActionMode;
import android.view.Window;
class AppCompatDelegateImplV14 extends AppCompatDelegateImplV11 {
+ private static final String KEY_LOCAL_NIGHT_MODE = "appcompat:local_night_mode";
+
+ private static TwilightManager sTwilightManager;
+
+ @NightMode
+ private int mLocalNightMode = MODE_NIGHT_UNSPECIFIED;
+ private boolean mApplyDayNightCalled;
+
private boolean mHandleNativeActionModes = true; // defaults to true
AppCompatDelegateImplV14(Context context, Window window, AppCompatCallback callback) {
@@ -30,6 +42,18 @@
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ if (savedInstanceState != null && mLocalNightMode == MODE_NIGHT_UNSPECIFIED) {
+ // If we have a icicle and we haven't had a local night mode set yet, try and read
+ // it from the icicle
+ mLocalNightMode = savedInstanceState.getInt(KEY_LOCAL_NIGHT_MODE,
+ MODE_NIGHT_UNSPECIFIED);
+ }
+ }
+
+ @Override
Window.Callback wrapWindowCallback(Window.Callback callback) {
// Override the window callback so that we can intercept onWindowStartingActionMode()
// calls
@@ -46,6 +70,95 @@
return mHandleNativeActionModes;
}
+ @Override
+ public boolean applyDayNight() {
+ mApplyDayNightCalled = true;
+
+ final int modeToApply = mapNightMode(mLocalNightMode == MODE_NIGHT_UNSPECIFIED
+ ? getDefaultNightMode()
+ : mLocalNightMode);
+
+ if (modeToApply != MODE_NIGHT_FOLLOW_SYSTEM) {
+ // If we're not following the system, we many need to update the configuration
+ return updateConfigurationForNightMode(modeToApply);
+ }
+ return false;
+ }
+
+ @Override
+ public void setLocalNightMode(@NightMode final int mode) {
+ switch (mode) {
+ case MODE_NIGHT_AUTO:
+ case MODE_NIGHT_NO:
+ case MODE_NIGHT_YES:
+ case MODE_NIGHT_FOLLOW_SYSTEM:
+ if (mLocalNightMode != mode) {
+ mLocalNightMode = mode;
+ if (mApplyDayNightCalled) {
+ // If we've already applied day night, re-apply since we won't be
+ // called again
+ applyDayNight();
+ }
+ }
+ break;
+ default:
+ Log.d(TAG, "setLocalNightMode() called with an unknown mode");
+ break;
+ }
+ }
+
+ @ApplyableNightMode
+ int mapNightMode(@NightMode final int mode) {
+ switch (mode) {
+ case MODE_NIGHT_AUTO:
+ return getTwilightManager().isNight() ? MODE_NIGHT_YES : MODE_NIGHT_NO;
+ case MODE_NIGHT_UNSPECIFIED:
+ // If we don't have a mode specified, just let the system handle it
+ return MODE_NIGHT_FOLLOW_SYSTEM;
+ default:
+ return mode;
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ if (mLocalNightMode != MODE_NIGHT_UNSPECIFIED) {
+ // If we have a local night mode set, save it
+ outState.putInt(KEY_LOCAL_NIGHT_MODE, mLocalNightMode);
+ }
+ }
+
+ /**
+ * Updates the {@link Resources} configuration {@code uiMode} with the
+ * chosen {@code UI_MODE_NIGHT} value.
+ */
+ private boolean updateConfigurationForNightMode(@ApplyableNightMode final int mode) {
+ final Resources res = mContext.getResources();
+ final Configuration conf = res.getConfiguration();
+ final int currentNightMode = conf.uiMode & Configuration.UI_MODE_NIGHT_MASK;
+
+ final int newNightMode = (mode == MODE_NIGHT_YES)
+ ? Configuration.UI_MODE_NIGHT_YES
+ : Configuration.UI_MODE_NIGHT_NO;
+
+ if (currentNightMode != newNightMode) {
+ final Configuration newConf = new Configuration(conf);
+ newConf.uiMode = (newConf.uiMode & ~Configuration.UI_MODE_NIGHT_MASK) | newNightMode;
+ res.updateConfiguration(newConf, null);
+ return true;
+ }
+ return false;
+ }
+
+ private TwilightManager getTwilightManager() {
+ if (sTwilightManager == null) {
+ sTwilightManager = new TwilightManager(mContext.getApplicationContext());
+ }
+ return sTwilightManager;
+ }
+
class AppCompatWindowCallbackV14 extends AppCompatWindowCallbackBase {
AppCompatWindowCallbackV14(Window.Callback callback) {
super(callback);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV23.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV23.java
index 006827c..5b3beec 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV23.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV23.java
@@ -16,14 +16,19 @@
package android.support.v7.app;
+import android.app.UiModeManager;
import android.content.Context;
import android.view.ActionMode;
import android.view.Window;
class AppCompatDelegateImplV23 extends AppCompatDelegateImplV14 {
+ private final UiModeManager mUiModeManager;
+
AppCompatDelegateImplV23(Context context, Window window, AppCompatCallback callback) {
super(context, window, callback);
+
+ mUiModeManager = (UiModeManager) context.getSystemService(Context.UI_MODE_SERVICE);
}
@Override
@@ -33,6 +38,18 @@
return new AppCompatWindowCallbackV23(callback);
}
+ @ApplyableNightMode
+ @Override
+ int mapNightMode(@NightMode final int mode) {
+ if (mode == MODE_NIGHT_AUTO
+ && mUiModeManager.getNightMode() == UiModeManager.MODE_NIGHT_AUTO) {
+ // If we're set to AUTO and the system's auto night mode is already enabled,
+ // we'll just let the system handle it by returning FOLLOW_SYSTEM
+ return MODE_NIGHT_FOLLOW_SYSTEM;
+ }
+ return super.mapNightMode(mode);
+ }
+
class AppCompatWindowCallbackV23 extends AppCompatWindowCallbackV14 {
AppCompatWindowCallbackV23(Window.Callback callback) {
super(callback);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index e147b7d..fbf0144 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -29,7 +29,9 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.IdRes;
import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.app.NavUtils;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
@@ -57,6 +59,7 @@
import android.support.v7.widget.DecorContentParent;
import android.support.v7.widget.FitWindowsViewGroup;
import android.support.v7.widget.Toolbar;
+import android.support.v7.widget.VectorEnabledTintResources;
import android.support.v7.widget.ViewStubCompat;
import android.support.v7.widget.ViewUtils;
import android.text.TextUtils;
@@ -101,7 +104,6 @@
// true if we have installed a window sub-decor layout.
private boolean mSubDecorInstalled;
- private ViewGroup mWindowDecor;
private ViewGroup mSubDecor;
private TextView mTitleView;
@@ -146,8 +148,6 @@
@Override
public void onCreate(Bundle savedInstanceState) {
- mWindowDecor = (ViewGroup) mWindow.getDecorView();
-
if (mOriginalWindowCallback instanceof Activity) {
if (NavUtils.getParentActivityName((Activity) mOriginalWindowCallback) != null) {
// Peek at the Action Bar and update it if it already exists
@@ -199,14 +199,35 @@
"by the window decor. Do not request Window.FEATURE_SUPPORT_ACTION_BAR and set " +
"windowActionBar to false in your theme to use a Toolbar instead.");
}
- // Clear out the MenuInflater to make sure that it is valid for the new Action Bar
+
+ // If we reach here then we're setting a new action bar
+ // First clear out the MenuInflater to make sure that it is valid for the new Action Bar
mMenuInflater = null;
- ToolbarActionBar tbab = new ToolbarActionBar(toolbar, ((Activity) mContext).getTitle(),
- mAppCompatWindowCallback);
- mActionBar = tbab;
- mWindow.setCallback(tbab.getWrappedWindowCallback());
- tbab.invalidateOptionsMenu();
+ // If we have an action bar currently, destroy it
+ if (ab != null) {
+ ab.onDestroy();
+ }
+
+ if (toolbar != null) {
+ final ToolbarActionBar tbab = new ToolbarActionBar(toolbar,
+ ((Activity) mContext).getTitle(), mAppCompatWindowCallback);
+ mActionBar = tbab;
+ mWindow.setCallback(tbab.getWrappedWindowCallback());
+ } else {
+ mActionBar = null;
+ // Re-set the original window callback since we may have already set a Toolbar wrapper
+ mWindow.setCallback(mAppCompatWindowCallback);
+ }
+
+ invalidateOptionsMenu();
+ }
+
+ @Nullable
+ @Override
+ public View findViewById(@IdRes int id) {
+ ensureSubDecor();
+ return mWindow.findViewById(id);
}
@Override
@@ -221,6 +242,9 @@
ab.onConfigurationChanged(newConfig);
}
}
+
+ // Re-apply Day/Night to the new configuration
+ applyDayNight();
}
@Override
@@ -274,6 +298,15 @@
mOriginalWindowCallback.onContentChanged();
}
+ @Override
+ public void onDestroy() {
+ super.onDestroy();
+
+ if (mActionBar != null) {
+ mActionBar.onDestroy();
+ }
+ }
+
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
@@ -303,29 +336,32 @@
}
private ViewGroup createSubDecor() {
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
- if (!a.hasValue(R.styleable.Theme_windowActionBar)) {
+ if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
- if (a.getBoolean(R.styleable.Theme_windowNoTitle, false)) {
+ if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
- } else if (a.getBoolean(R.styleable.Theme_windowActionBar, false)) {
+ } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
}
- if (a.getBoolean(R.styleable.Theme_windowActionBarOverlay, false)) {
+ if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
}
- if (a.getBoolean(R.styleable.Theme_windowActionModeOverlay, false)) {
+ if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
}
- mIsFloating = a.getBoolean(R.styleable.Theme_android_windowIsFloating, false);
+ mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
+ // Now let's make sure that the Window has installed its decor by retrieving it
+ mWindow.getDecorView();
+
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
@@ -436,33 +472,35 @@
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
- final ViewGroup decorContent = (ViewGroup) mWindow.findViewById(android.R.id.content);
- final ContentFrameLayout abcContent = (ContentFrameLayout) subDecor.findViewById(
+ final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
- // There might be Views already added to the Window's content view so we need to
- // migrate them to our content view
- while (decorContent.getChildCount() > 0) {
- final View child = decorContent.getChildAt(0);
- decorContent.removeViewAt(0);
- abcContent.addView(child);
+ final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
+ if (windowContentView != null) {
+ // There might be Views already added to the Window's content view so we need to
+ // migrate them to our content view
+ while (windowContentView.getChildCount() > 0) {
+ final View child = windowContentView.getChildAt(0);
+ windowContentView.removeViewAt(0);
+ contentView.addView(child);
+ }
+
+ // Change our content FrameLayout to use the android.R.id.content id.
+ // Useful for fragments.
+ windowContentView.setId(View.NO_ID);
+ contentView.setId(android.R.id.content);
+
+ // The decorContent may have a foreground drawable set (windowContentOverlay).
+ // Remove this as we handle it ourselves
+ if (windowContentView instanceof FrameLayout) {
+ ((FrameLayout) windowContentView).setForeground(null);
+ }
}
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
- // Change our content FrameLayout to use the android.R.id.content id.
- // Useful for fragments.
- decorContent.setId(View.NO_ID);
- abcContent.setId(android.R.id.content);
-
- // The decorContent may have a foreground drawable set (windowContentOverlay).
- // Remove this as we handle it ourselves
- if (decorContent instanceof FrameLayout) {
- ((FrameLayout) decorContent).setForeground(null);
- }
-
- abcContent.setAttachListener(new ContentFrameLayout.OnAttachListener() {
+ contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@@ -484,25 +522,30 @@
// the decor view's size, meaning that any padding is inset for the min/max widths below.
// We don't control measurement at that level, so we need to workaround it by making sure
// that the decor view's padding is taken into account.
- cfl.setDecorPadding(mWindowDecor.getPaddingLeft(),
- mWindowDecor.getPaddingTop(), mWindowDecor.getPaddingRight(),
- mWindowDecor.getPaddingBottom());
+ final View windowDecor = mWindow.getDecorView();
+ cfl.setDecorPadding(windowDecor.getPaddingLeft(),
+ windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
+ windowDecor.getPaddingBottom());
- TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
- a.getValue(R.styleable.Theme_windowMinWidthMajor, cfl.getMinWidthMajor());
- a.getValue(R.styleable.Theme_windowMinWidthMinor, cfl.getMinWidthMinor());
+ TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
+ a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
+ a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
- if (a.hasValue(R.styleable.Theme_windowFixedWidthMajor)) {
- a.getValue(R.styleable.Theme_windowFixedWidthMajor, cfl.getFixedWidthMajor());
+ if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
+ a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
+ cfl.getFixedWidthMajor());
}
- if (a.hasValue(R.styleable.Theme_windowFixedWidthMinor)) {
- a.getValue(R.styleable.Theme_windowFixedWidthMinor, cfl.getFixedWidthMinor());
+ if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
+ a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
+ cfl.getFixedWidthMinor());
}
- if (a.hasValue(R.styleable.Theme_windowFixedHeightMajor)) {
- a.getValue(R.styleable.Theme_windowFixedHeightMajor, cfl.getFixedHeightMajor());
+ if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
+ a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
+ cfl.getFixedHeightMajor());
}
- if (a.hasValue(R.styleable.Theme_windowFixedHeightMinor)) {
- a.getValue(R.styleable.Theme_windowFixedHeightMinor, cfl.getFixedHeightMinor());
+ if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
+ a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
+ cfl.getFixedHeightMinor());
}
a.recycle();
@@ -629,7 +672,7 @@
}
@Override
- public ActionMode startSupportActionMode(ActionMode.Callback callback) {
+ public ActionMode startSupportActionMode(@NonNull final ActionMode.Callback callback) {
if (callback == null) {
throw new IllegalArgumentException("ActionMode callback can not be null.");
}
@@ -665,17 +708,21 @@
}
@Override
- ActionMode startSupportActionModeFromWindow(ActionMode.Callback callback) {
+ ActionMode startSupportActionModeFromWindow(@NonNull ActionMode.Callback callback) {
endOnGoingFadeAnimation();
if (mActionMode != null) {
mActionMode.finish();
}
- final ActionMode.Callback wrappedCallback = new ActionModeCallbackWrapperV7(callback);
+ if (!(callback instanceof ActionModeCallbackWrapperV7)) {
+ // If the callback hasn't been wrapped yet, wrap it
+ callback = new ActionModeCallbackWrapperV7(callback);
+ }
+
ActionMode mode = null;
if (mAppCompatCallback != null && !isDestroyed()) {
try {
- mode = mAppCompatCallback.onWindowStartingSupportActionMode(wrappedCallback);
+ mode = mAppCompatCallback.onWindowStartingSupportActionMode(callback);
} catch (AbstractMethodError ame) {
// Older apps might not implement this callback method.
}
@@ -723,21 +770,27 @@
mActionModeView,
Gravity.TOP | Gravity.FILL_HORIZONTAL, 0, 0);
endOnGoingFadeAnimation();
- ViewCompat.setAlpha(mActionModeView, 0f);
- mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
- mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(View view) {
- ViewCompat.setAlpha(mActionModeView, 1f);
- mFadeAnim.setListener(null);
- mFadeAnim = null;
- }
- @Override
- public void onAnimationStart(View view) {
- mActionModeView.setVisibility(View.VISIBLE);
- }
- });
+ if (shouldAnimateActionModeView()) {
+ ViewCompat.setAlpha(mActionModeView, 0f);
+ mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
+ mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ mActionModeView.setVisibility(View.VISIBLE);
+ }
+
+ @Override
+ public void onAnimationEnd(View view) {
+ ViewCompat.setAlpha(mActionModeView, 1f);
+ mFadeAnim.setListener(null);
+ mFadeAnim = null;
+ }
+ });
+ } else {
+ ViewCompat.setAlpha(mActionModeView, 1f);
+ mActionModeView.setVisibility(View.VISIBLE);
+ }
}
};
} else {
@@ -755,31 +808,43 @@
endOnGoingFadeAnimation();
mActionModeView.killMode();
mode = new StandaloneActionMode(mActionModeView.getContext(), mActionModeView,
- wrappedCallback, mActionModePopup == null);
+ callback, mActionModePopup == null);
if (callback.onCreateActionMode(mode, mode.getMenu())) {
mode.invalidate();
mActionModeView.initForMode(mode);
mActionMode = mode;
- ViewCompat.setAlpha(mActionModeView, 0f);
- mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
- mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
- @Override
- public void onAnimationEnd(View view) {
- ViewCompat.setAlpha(mActionModeView, 1f);
- mFadeAnim.setListener(null);
- mFadeAnim = null;
- }
- @Override
- public void onAnimationStart(View view) {
- mActionModeView.setVisibility(View.VISIBLE);
- mActionModeView.sendAccessibilityEvent(
- AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- if (mActionModeView.getParent() != null) {
- ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
+ if (shouldAnimateActionModeView()) {
+ ViewCompat.setAlpha(mActionModeView, 0f);
+ mFadeAnim = ViewCompat.animate(mActionModeView).alpha(1f);
+ mFadeAnim.setListener(new ViewPropertyAnimatorListenerAdapter() {
+ @Override
+ public void onAnimationStart(View view) {
+ mActionModeView.setVisibility(View.VISIBLE);
+ mActionModeView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (mActionModeView.getParent() != null) {
+ ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
+ }
}
+
+ @Override
+ public void onAnimationEnd(View view) {
+ ViewCompat.setAlpha(mActionModeView, 1f);
+ mFadeAnim.setListener(null);
+ mFadeAnim = null;
+ }
+ });
+ } else {
+ ViewCompat.setAlpha(mActionModeView, 1f);
+ mActionModeView.setVisibility(View.VISIBLE);
+ mActionModeView.sendAccessibilityEvent(
+ AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
+ if (mActionModeView.getParent() != null) {
+ ViewCompat.requestApplyInsets((View) mActionModeView.getParent());
}
- });
+ }
+
if (mActionModePopup != null) {
mWindow.getDecorView().post(mShowActionModePopup);
}
@@ -794,6 +859,12 @@
return mActionMode;
}
+ final boolean shouldAnimateActionModeView() {
+ // We only to animate the action mode in if the sub decor has already been laid out.
+ // If it hasn't been laid out, it hasn't been drawn to screen yet.
+ return mSubDecorInstalled && mSubDecor != null && ViewCompat.isLaidOut(mSubDecor);
+ }
+
private void endOnGoingFadeAnimation() {
if (mFadeAnim != null) {
mFadeAnim.cancel();
@@ -931,13 +1002,13 @@
mAppCompatViewInflater = new AppCompatViewInflater();
}
- // We only want the View to inherit it's context if we're running pre-v21
- final boolean inheritContext = isPre21 && mSubDecorInstalled
- && shouldInheritContext((ViewParent) parent);
+ // We only want the View to inherit its context if we're running pre-v21
+ final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
- true /* Read read app:theme as a fallback at all times for legacy reasons */
+ true, /* Read read app:theme as a fallback at all times for legacy reasons */
+ VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
@@ -946,6 +1017,7 @@
// The initial parent is null so just return false
return false;
}
+ final View windowDecor = mWindow.getDecorView();
while (true) {
if (parent == null) {
// Bingo. We've hit a view which has a null parent before being terminated from
@@ -953,7 +1025,7 @@
// call, therefore we should inherit. This works as the inflated layout is only
// added to the hierarchy at the end of the inflate() call.
return true;
- } else if (parent == mWindowDecor || !(parent instanceof View)
+ } else if (parent == windowDecor || !(parent instanceof View)
|| ViewCompat.isAttachedToWindow((View) parent)) {
// We have either hit the window's decor view, a parent which isn't a View
// (i.e. ViewRootImpl), or an attached view, so we know that the original parent
@@ -971,8 +1043,11 @@
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
- Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
- + " so we can not install AppCompat's");
+ if (!(LayoutInflaterCompat.getFactory(layoutInflater)
+ instanceof AppCompatDelegateImplV7)) {
+ Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ + " so we can not install AppCompat's");
+ }
}
}
@@ -1123,7 +1198,7 @@
// If we have a menu invalidation pending, do it now.
if (mInvalidatePanelMenuPosted &&
(mInvalidatePanelMenuFeatures & (1 << FEATURE_OPTIONS_PANEL)) != 0) {
- mWindowDecor.removeCallbacks(mInvalidatePanelMenuRunnable);
+ mWindow.getDecorView().removeCallbacks(mInvalidatePanelMenuRunnable);
mInvalidatePanelMenuRunnable.run();
}
@@ -1477,7 +1552,7 @@
return null;
}
- private PanelFeatureState getPanelState(int featureId, boolean required) {
+ protected PanelFeatureState getPanelState(int featureId, boolean required) {
PanelFeatureState[] ar;
if ((ar = mPanels) == null || ar.length <= featureId) {
PanelFeatureState[] nar = new PanelFeatureState[featureId + 1];
@@ -1522,8 +1597,8 @@
private void invalidatePanelMenu(int featureId) {
mInvalidatePanelMenuFeatures |= 1 << featureId;
- if (!mInvalidatePanelMenuPosted && mWindowDecor != null) {
- ViewCompat.postOnAnimation(mWindowDecor, mInvalidatePanelMenuRunnable);
+ if (!mInvalidatePanelMenuPosted) {
+ ViewCompat.postOnAnimation(mWindow.getDecorView(), mInvalidatePanelMenuRunnable);
mInvalidatePanelMenuPosted = true;
}
}
@@ -1662,7 +1737,7 @@
}
if (mActionModePopup != null) {
- mWindowDecor.removeCallbacks(mShowActionModePopup);
+ mWindow.getDecorView().removeCallbacks(mShowActionModePopup);
if (mActionModePopup.isShowing()) {
try {
mActionModePopup.dismiss();
@@ -1780,7 +1855,7 @@
}
}
- private static final class PanelFeatureState {
+ protected static final class PanelFeatureState {
/** Feature ID for this panel. */
int featureId;
@@ -1892,11 +1967,11 @@
listPresenterContext = context;
- TypedArray a = context.obtainStyledAttributes(R.styleable.Theme);
+ TypedArray a = context.obtainStyledAttributes(R.styleable.AppCompatTheme);
background = a.getResourceId(
- R.styleable.Theme_panelBackground, 0);
+ R.styleable.AppCompatTheme_panelBackground, 0);
windowAnimations = a.getResourceId(
- R.styleable.Theme_android_windowAnimationStyle, 0);
+ R.styleable.AppCompatTheme_android_windowAnimationStyle, 0);
a.recycle();
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java b/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java
index 11688e6..9a66004 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDialog.java
@@ -19,6 +19,7 @@
import android.app.Dialog;
import android.content.Context;
import android.os.Bundle;
+import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
import android.support.annotation.Nullable;
import android.support.v7.appcompat.R;
@@ -46,6 +47,9 @@
// To workaround this, we call onCreate(null) in the ctor, and then again as usual in
// onCreate().
getDelegate().onCreate(null);
+
+ // Apply AppCompat's DayNight resources if needed
+ getDelegate().applyDayNight();
}
protected AppCompatDialog(Context context, boolean cancelable,
@@ -86,6 +90,12 @@
getDelegate().setContentView(view, params);
}
+ @Nullable
+ @Override
+ public View findViewById(@IdRes int id) {
+ return getDelegate().findViewById(id);
+ }
+
@Override
public void setTitle(CharSequence title) {
super.setTitle(title);
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDialogFragment.java b/v7/appcompat/src/android/support/v7/app/AppCompatDialogFragment.java
index 3051e49..10457b7 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDialogFragment.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDialogFragment.java
@@ -33,7 +33,7 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AppCompatDialog(getActivity(), getTheme());
+ return new AppCompatDialog(getContext(), getTheme());
}
/** @hide */
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
index a74623e..dcf855c 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.TypedArray;
+import android.os.Build;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.v4.util.ArrayMap;
@@ -38,6 +39,7 @@
import android.support.v7.widget.AppCompatSeekBar;
import android.support.v7.widget.AppCompatSpinner;
import android.support.v7.widget.AppCompatTextView;
+import android.support.v7.widget.TintContextWrapper;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InflateException;
@@ -62,6 +64,12 @@
Context.class, AttributeSet.class};
private static final int[] sOnClickAttrs = new int[]{android.R.attr.onClick};
+ private static final String[] sClassPrefixList = {
+ "android.widget.",
+ "android.view.",
+ "android.webkit."
+ };
+
private static final String LOG_TAG = "AppCompatViewInflater";
private static final Map<String, Constructor<? extends View>> sConstructorMap
@@ -71,7 +79,7 @@
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
- boolean readAndroidTheme, boolean readAppTheme) {
+ boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
@@ -83,6 +91,9 @@
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
}
+ if (wrapContext) {
+ context = TintContextWrapper.wrap(context);
+ }
View view = null;
@@ -153,8 +164,13 @@
mConstructorArgs[1] = attrs;
if (-1 == name.indexOf('.')) {
- // try the android.widget prefix first...
- return createView(context, name, "android.widget.");
+ for (int i = 0; i < sClassPrefixList.length; i++) {
+ final View view = createView(context, name, sClassPrefixList[i]);
+ if (view != null) {
+ return view;
+ }
+ }
+ return null;
} else {
return createView(context, name, null);
}
@@ -177,9 +193,11 @@
private void checkOnClickListener(View view, AttributeSet attrs) {
final Context context = view.getContext();
- if (!ViewCompat.hasOnClickListeners(view) || !(context instanceof ContextWrapper)) {
- // Skip our compat functionality if: the view doesn't have an onClickListener,
- // or the Context isn't a ContextWrapper
+ if (!(context instanceof ContextWrapper) ||
+ (Build.VERSION.SDK_INT >= 15 && !ViewCompat.hasOnClickListeners(view))) {
+ // Skip our compat functionality if: the Context isn't a ContextWrapper, or
+ // the view doesn't have an OnClickListener (we can only rely on this on API 15+ so
+ // always use our compat code on older devices)
return;
}
diff --git a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
index 514ebef..cf4ef60 100644
--- a/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
+++ b/v7/appcompat/src/android/support/v7/app/NotificationCompat.java
@@ -78,6 +78,9 @@
super(context);
}
+ /**
+ * @hide
+ */
@Override
protected BuilderExtender getExtender() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
diff --git a/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java b/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
index aa1a0ae..2c9a124 100644
--- a/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/ToolbarActionBar.java
@@ -38,6 +38,7 @@
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.Window;
import android.widget.SpinnerAdapter;
@@ -233,6 +234,16 @@
}
@Override
+ public boolean requestFocus() {
+ final ViewGroup viewGroup = mDecorToolbar.getViewGroup();
+ if (viewGroup != null && !viewGroup.hasFocus()) {
+ viewGroup.requestFocus();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
public void setSubtitle(CharSequence subtitle) {
mDecorToolbar.setSubtitle(subtitle);
}
@@ -474,6 +485,12 @@
return true;
}
+ @Override
+ void onDestroy() {
+ // Remove any invalidation callbacks
+ mDecorToolbar.getViewGroup().removeCallbacks(mMenuInvalidator);
+ }
+
public void addOnMenuVisibilityListener(OnMenuVisibilityListener listener) {
mMenuVisibilityListeners.add(listener);
}
diff --git a/v7/appcompat/src/android/support/v7/app/VectorEnabledTintResources.java b/v7/appcompat/src/android/support/v7/app/VectorEnabledTintResources.java
new file mode 100644
index 0000000..3dffa07
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/app/VectorEnabledTintResources.java
@@ -0,0 +1,72 @@
+/*
+ * 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.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatDelegate;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class allows us to intercept calls so that we can tint resources (if applicable), and
+ * inflate vector resources from within drawable containers pre-L.
+ *
+ * @hide
+ */
+public class VectorEnabledTintResources extends Resources {
+
+ public static boolean shouldBeUsed() {
+ return AppCompatDelegate.isCompatVectorFromResourcesEnabled()
+ && Build.VERSION.SDK_INT <= MAX_SDK_WHERE_REQUIRED;
+ }
+
+ /**
+ * The maximum API level where this class is needed.
+ */
+ public static final int MAX_SDK_WHERE_REQUIRED = 20;
+
+ private final WeakReference<Context> mContextRef;
+
+ public VectorEnabledTintResources(@NonNull final Context context,
+ @NonNull final Resources res) {
+ super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+ mContextRef = new WeakReference<>(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 {
+ final Context context = mContextRef.get();
+ if (context != null) {
+ return AppCompatDrawableManager.get().onDrawableLoadedFromResources(context, this, id);
+ } else {
+ return super.getDrawable(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/app/WindowDecorActionBar.java b/v7/appcompat/src/android/support/v7/app/WindowDecorActionBar.java
index 2ec054b..5d4e94e 100644
--- a/v7/appcompat/src/android/support/v7/app/WindowDecorActionBar.java
+++ b/v7/appcompat/src/android/support/v7/app/WindowDecorActionBar.java
@@ -53,6 +53,7 @@
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.Window;
import android.view.accessibility.AccessibilityEvent;
@@ -434,6 +435,16 @@
mDecorToolbar.setWindowTitle(title);
}
+ @Override
+ public boolean requestFocus() {
+ final ViewGroup viewGroup = mDecorToolbar.getViewGroup();
+ if (viewGroup != null && !viewGroup.hasFocus()) {
+ viewGroup.requestFocus();
+ return true;
+ }
+ return false;
+ }
+
public void setSubtitle(CharSequence subtitle) {
mDecorToolbar.setSubtitle(subtitle);
}
@@ -494,11 +505,13 @@
mContextView.killMode();
ActionModeImpl mode = new ActionModeImpl(mContextView.getContext(), callback);
if (mode.dispatchOnCreate()) {
+ // This needs to be set before invalidate() so that it calls
+ // onPrepareActionMode()
+ mActionMode = mode;
mode.invalidate();
mContextView.initForMode(mode);
animateToMode(true);
mContextView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
- mActionMode = mode;
return mode;
}
return null;
@@ -840,28 +853,44 @@
hideForActionMode();
}
- ViewPropertyAnimatorCompat fadeIn, fadeOut;
- if (toActionMode) {
- // We use INVISIBLE for the Toolbar to make sure that the container has a non-zero
- // height throughout. The context view is GONE initially, so will not have been laid
- // out when the animation starts. This can lead to the container collapsing to 0px
- // height for a short period.
- fadeOut = mDecorToolbar.setupAnimatorToVisibility(View.INVISIBLE,
- FADE_OUT_DURATION_MS);
- fadeIn = mContextView.setupAnimatorToVisibility(View.VISIBLE,
- FADE_IN_DURATION_MS);
+ if (shouldAnimateContextView()) {
+ ViewPropertyAnimatorCompat fadeIn, fadeOut;
+ if (toActionMode) {
+ // We use INVISIBLE for the Toolbar to make sure that the container has a non-zero
+ // height throughout. The context view is GONE initially, so will not have been laid
+ // out when the animation starts. This can lead to the container collapsing to 0px
+ // height for a short period.
+ fadeOut = mDecorToolbar.setupAnimatorToVisibility(View.INVISIBLE,
+ FADE_OUT_DURATION_MS);
+ fadeIn = mContextView.setupAnimatorToVisibility(View.VISIBLE,
+ FADE_IN_DURATION_MS);
+ } else {
+ fadeIn = mDecorToolbar.setupAnimatorToVisibility(View.VISIBLE,
+ FADE_IN_DURATION_MS);
+ fadeOut = mContextView.setupAnimatorToVisibility(View.GONE,
+ FADE_OUT_DURATION_MS);
+ }
+ ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
+ set.playSequentially(fadeOut, fadeIn);
+ set.start();
} else {
- fadeIn = mDecorToolbar.setupAnimatorToVisibility(View.VISIBLE,
- FADE_IN_DURATION_MS);
- fadeOut = mContextView.setupAnimatorToVisibility(View.GONE,
- FADE_OUT_DURATION_MS);
+ if (toActionMode) {
+ mDecorToolbar.setVisibility(View.INVISIBLE);
+ mContextView.setVisibility(View.VISIBLE);
+ } else {
+ mDecorToolbar.setVisibility(View.VISIBLE);
+ mContextView.setVisibility(View.GONE);
+ }
}
- ViewPropertyAnimatorCompatSet set = new ViewPropertyAnimatorCompatSet();
- set.playSequentially(fadeOut, fadeIn);
- set.start();
// mTabScrollView's visibility is not affected by action mode.
}
+ private boolean shouldAnimateContextView() {
+ // We only to animate the action mode in if the container view has already been laid out.
+ // If it hasn't been laid out, it hasn't been drawn to screen yet.
+ return ViewCompat.isLaidOut(mContainerView);
+ }
+
public Context getThemedContext() {
if (mThemedContext == null) {
TypedValue outValue = new TypedValue();
diff --git a/v7/appcompat/src/android/support/v7/content/res/AppCompatColorStateListInflater.java b/v7/appcompat/src/android/support/v7/content/res/AppCompatColorStateListInflater.java
new file mode 100644
index 0000000..ca7f704
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/content/res/AppCompatColorStateListInflater.java
@@ -0,0 +1,171 @@
+/*
+ * 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.content.res;
+
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.graphics.Color;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.ColorUtils;
+import android.support.v7.appcompat.R;
+import android.util.AttributeSet;
+import android.util.StateSet;
+import android.util.Xml;
+
+import java.io.IOException;
+
+final class AppCompatColorStateListInflater {
+
+ private static final int DEFAULT_COLOR = Color.RED;
+
+ private AppCompatColorStateListInflater() {}
+
+ /**
+ * Creates a ColorStateList from an XML document using given a set of
+ * {@link Resources} and a {@link Theme}.
+ *
+ * @param r Resources against which the ColorStateList should be inflated.
+ * @param parser Parser for the XML document defining the ColorStateList.
+ * @param theme Optional theme to apply to the color state list, may be
+ * {@code null}.
+ * @return A new color state list.
+ */
+ @NonNull
+ public static ColorStateList createFromXml(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @Nullable Resources.Theme theme) throws XmlPullParserException, IOException {
+ final AttributeSet attrs = Xml.asAttributeSet(parser);
+
+ int type;
+ while ((type = parser.next()) != XmlPullParser.START_TAG
+ && type != XmlPullParser.END_DOCUMENT) {
+ // Seek parser to start tag.
+ }
+
+ if (type != XmlPullParser.START_TAG) {
+ throw new XmlPullParserException("No start tag found");
+ }
+
+ return createFromXmlInner(r, parser, attrs, theme);
+ }
+
+ /**
+ * Create from inside an XML document. Called on a parser positioned at a
+ * tag in an XML document, tries to create a ColorStateList from that tag.
+ *
+ * @throws XmlPullParserException if the current tag is not <selector>
+ * @return A new color state list for the current tag.
+ */
+ @NonNull
+ private static ColorStateList createFromXmlInner(@NonNull Resources r,
+ @NonNull XmlPullParser parser, @NonNull AttributeSet attrs,
+ @Nullable Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ final String name = parser.getName();
+ if (!name.equals("selector")) {
+ throw new XmlPullParserException(
+ parser.getPositionDescription() + ": invalid color state list tag " + name);
+ }
+
+ return inflate(r, parser, attrs, theme);
+ }
+
+ /**
+ * Fill in this object based on the contents of an XML "selector" element.
+ */
+ private static ColorStateList inflate(@NonNull Resources r, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme)
+ throws XmlPullParserException, IOException {
+ final int innerDepth = parser.getDepth() + 1;
+ int depth;
+ int type;
+ int defaultColor = DEFAULT_COLOR;
+
+ int[][] stateSpecList = new int[20][];
+ int[] colorList = new int[stateSpecList.length];
+ int listSize = 0;
+
+ while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
+ && ((depth = parser.getDepth()) >= innerDepth || type != XmlPullParser.END_TAG)) {
+ if (type != XmlPullParser.START_TAG || depth > innerDepth
+ || !parser.getName().equals("item")) {
+ continue;
+ }
+
+ final TypedArray a = obtainAttributes(r, theme, attrs, R.styleable.ColorStateListItem);
+ final int baseColor = a.getColor(R.styleable.ColorStateListItem_android_color,
+ Color.MAGENTA);
+
+ float alphaMod = 1.0f;
+ if (a.hasValue(R.styleable.ColorStateListItem_android_alpha)) {
+ alphaMod = a.getFloat(R.styleable.ColorStateListItem_android_alpha, alphaMod);
+ } else if (a.hasValue(R.styleable.ColorStateListItem_alpha)) {
+ alphaMod = a.getFloat(R.styleable.ColorStateListItem_alpha, alphaMod);
+ }
+
+ a.recycle();
+
+ // Parse all unrecognized attributes as state specifiers.
+ int j = 0;
+ final int numAttrs = attrs.getAttributeCount();
+ int[] stateSpec = new int[numAttrs];
+ for (int i = 0; i < numAttrs; i++) {
+ final int stateResId = attrs.getAttributeNameResource(i);
+ if (stateResId != android.R.attr.color && stateResId != android.R.attr.alpha
+ && stateResId != R.attr.alpha) {
+ // Unrecognized attribute, add to state set
+ stateSpec[j++] = attrs.getAttributeBooleanValue(i, false)
+ ? stateResId : -stateResId;
+ }
+ }
+ stateSpec = StateSet.trimStateSet(stateSpec, j);
+
+ // Apply alpha modulation. If we couldn't resolve the color or
+ // alpha yet, the default values leave us enough information to
+ // modulate again during applyTheme().
+ final int color = modulateColorAlpha(baseColor, alphaMod);
+ if (listSize == 0 || stateSpec.length == 0) {
+ defaultColor = color;
+ }
+
+ colorList = GrowingArrayUtils.append(colorList, listSize, color);
+ stateSpecList = GrowingArrayUtils.append(stateSpecList, listSize, stateSpec);
+ listSize++;
+ }
+
+ int[] colors = new int[listSize];
+ int[][] stateSpecs = new int[listSize][];
+ System.arraycopy(colorList, 0, colors, 0, listSize);
+ System.arraycopy(stateSpecList, 0, stateSpecs, 0, listSize);
+
+ return new ColorStateList(stateSpecs, colors);
+ }
+
+ private static TypedArray obtainAttributes(Resources res, Resources.Theme theme,
+ AttributeSet set, int[] attrs) {
+ return theme == null ? res.obtainAttributes(set, attrs)
+ : theme.obtainStyledAttributes(set, attrs, 0, 0);
+ }
+
+ private static int modulateColorAlpha(int color, float alphaMod) {
+ return ColorUtils.setAlphaComponent(color, Math.round(Color.alpha(color) * alphaMod));
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/content/res/AppCompatResources.java b/v7/appcompat/src/android/support/v7/content/res/AppCompatResources.java
new file mode 100644
index 0000000..2d49740
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/content/res/AppCompatResources.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.content.res;
+
+import org.xmlpull.v1.XmlPullParser;
+
+import android.content.Context;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.ColorRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
+import android.util.Log;
+import android.util.SparseArray;
+import android.util.TypedValue;
+
+import java.util.WeakHashMap;
+
+/**
+ * Class for accessing an application's resources through AppCompat, and thus any backward
+ * compatible functionality.
+ */
+public final class AppCompatResources {
+
+ private static final String LOG_TAG = "AppCompatResources";
+ private static final ThreadLocal<TypedValue> TL_TYPED_VALUE = new ThreadLocal<>();
+
+ private static final WeakHashMap<Context, SparseArray<ColorStateListCacheEntry>>
+ sColorStateCaches = new WeakHashMap<>(0);
+
+ private static final Object sColorStateCacheLock = new Object();
+
+ private AppCompatResources() {}
+
+ /**
+ * Returns the {@link ColorStateList} from the given resource. The resource can include
+ * themeable attributes, regardless of API level.
+ *
+ * @param context content to inflate against
+ * @param resId the resource identifier of the ColorStateList to retrieve
+ */
+ public static ColorStateList getColorStateList(@NonNull Context context, @ColorRes int resId) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ // On M+ we can use the framework
+ return context.getColorStateList(resId);
+ }
+
+ // Before that, we'll try handle it ourselves
+ ColorStateList csl = getCachedColorStateList(context, resId);
+ if (csl != null) {
+ return csl;
+ }
+ // Cache miss, so try and inflate it ourselves
+ csl = inflateColorStateList(context, resId);
+ if (csl != null) {
+ // If we inflated it, add it to the cache and return
+ addColorStateListToCache(context, resId, csl);
+ return csl;
+ }
+
+ // If we reach here then we couldn't inflate it, so let the framework handle it
+ return ContextCompat.getColorStateList(context, resId);
+ }
+
+ /**
+ * Inflates a {@link ColorStateList} from resources, honouring theme attributes.
+ */
+ @Nullable
+ private static ColorStateList inflateColorStateList(Context context, int resId) {
+ if (isColorInt(context, resId)) {
+ // The resource is a color int, we can't handle it so return null
+ return null;
+ }
+
+ final Resources r = context.getResources();
+ final XmlPullParser xml = r.getXml(resId);
+ try {
+ return AppCompatColorStateListInflater.createFromXml(r, xml, context.getTheme());
+ } catch (Exception e) {
+ Log.e(LOG_TAG, "Failed to inflate ColorStateList, leaving it to the framework", e);
+ }
+ return null;
+ }
+
+ @Nullable
+ private static ColorStateList getCachedColorStateList(@NonNull Context context,
+ @ColorRes int resId) {
+ synchronized (sColorStateCacheLock) {
+ final SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context);
+ if (entries != null && entries.size() > 0) {
+ final ColorStateListCacheEntry entry = entries.get(resId);
+ if (entry != null) {
+ if (entry.configuration.equals(context.getResources().getConfiguration())) {
+ // If the current configuration matches the entry's, we can use it
+ return entry.value;
+ } else {
+ // Otherwise we'll remove the entry
+ entries.remove(resId);
+ }
+ }
+ }
+ }
+ return null;
+ }
+
+ private static void addColorStateListToCache(@NonNull Context context, @ColorRes int resId,
+ @NonNull ColorStateList value) {
+ synchronized (sColorStateCacheLock) {
+ SparseArray<ColorStateListCacheEntry> entries = sColorStateCaches.get(context);
+ if (entries == null) {
+ entries = new SparseArray<>();
+ sColorStateCaches.put(context, entries);
+ }
+ entries.append(resId, new ColorStateListCacheEntry(value,
+ context.getResources().getConfiguration()));
+ }
+ }
+
+ private static boolean isColorInt(@NonNull Context context, @ColorRes int resId) {
+ final Resources r = context.getResources();
+
+ final TypedValue value = getTypedValue();
+ r.getValue(resId, value, true);
+
+ return value.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && value.type <= TypedValue.TYPE_LAST_COLOR_INT;
+ }
+
+ @NonNull
+ private static TypedValue getTypedValue() {
+ TypedValue tv = TL_TYPED_VALUE.get();
+ if (tv == null) {
+ tv = new TypedValue();
+ TL_TYPED_VALUE.set(tv);
+ }
+ return tv;
+ }
+
+ private static class ColorStateListCacheEntry {
+ final ColorStateList value;
+ final Configuration configuration;
+
+ ColorStateListCacheEntry(@NonNull ColorStateList value,
+ @NonNull Configuration configuration) {
+ this.value = value;
+ this.configuration = configuration;
+ }
+ }
+
+}
diff --git a/v7/appcompat/src/android/support/v7/content/res/GrowingArrayUtils.java b/v7/appcompat/src/android/support/v7/content/res/GrowingArrayUtils.java
new file mode 100644
index 0000000..3fe2eb8
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/content/res/GrowingArrayUtils.java
@@ -0,0 +1,194 @@
+/*
+ * 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.content.res;
+
+import java.lang.reflect.Array;
+
+/**
+ * A helper class that aims to provide comparable growth performance to ArrayList, but on primitive
+ * arrays. Common array operations are implemented for efficient use in dynamic containers.
+ *
+ * All methods in this class assume that the length of an array is equivalent to its capacity and
+ * NOT the number of elements in the array. The current size of the array is always passed in as a
+ * parameter.
+ */
+final class GrowingArrayUtils {
+
+ /**
+ * Appends an element to the end of the array, growing the array if there is no more room.
+ * @param array The array to which to append the element. This must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to append.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] append(T[] array, int currentSize, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+ growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive int version of {@link #append(Object[], int, Object)}.
+ */
+ public static int[] append(int[] array, int currentSize, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ int[] newArray = new int[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive long version of {@link #append(Object[], int, Object)}.
+ */
+ public static long[] append(long[] array, int currentSize, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ long[] newArray = new long[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Primitive boolean version of {@link #append(Object[], int, Object)}.
+ */
+ public static boolean[] append(boolean[] array, int currentSize, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 > array.length) {
+ boolean[] newArray = new boolean[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, currentSize);
+ array = newArray;
+ }
+ array[currentSize] = element;
+ return array;
+ }
+
+ /**
+ * Inserts an element into the array at the specified index, growing the array if there is no
+ * more room.
+ *
+ * @param array The array to which to append the element. Must NOT be null.
+ * @param currentSize The number of elements in the array. Must be less than or equal to
+ * array.length.
+ * @param element The element to insert.
+ * @return the array to which the element was appended. This may be different than the given
+ * array.
+ */
+ public static <T> T[] insert(T[] array, int currentSize, int index, T element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ T[] newArray = (T[]) Array.newInstance(array.getClass().getComponentType(),
+ growSize(currentSize));
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive int version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static int[] insert(int[] array, int currentSize, int index, int element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ int[] newArray = new int[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive long version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static long[] insert(long[] array, int currentSize, int index, long element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ long[] newArray = new long[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Primitive boolean version of {@link #insert(Object[], int, int, Object)}.
+ */
+ public static boolean[] insert(boolean[] array, int currentSize, int index, boolean element) {
+ assert currentSize <= array.length;
+
+ if (currentSize + 1 <= array.length) {
+ System.arraycopy(array, index, array, index + 1, currentSize - index);
+ array[index] = element;
+ return array;
+ }
+
+ boolean[] newArray = new boolean[growSize(currentSize)];
+ System.arraycopy(array, 0, newArray, 0, index);
+ newArray[index] = element;
+ System.arraycopy(array, index, newArray, index + 1, array.length - index);
+ return newArray;
+ }
+
+ /**
+ * Given the current size of an array, returns an ideal size to which the array should grow.
+ * This is typically double the given size, but should not be relied upon to do so in the
+ * future.
+ */
+ public static int growSize(int currentSize) {
+ return currentSize <= 4 ? 8 : currentSize * 2;
+ }
+
+ // Uninstantiable
+ private GrowingArrayUtils() {}
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableUtils.java b/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableUtils.java
deleted file mode 100644
index ffe68d59..0000000
--- a/v7/appcompat/src/android/support/v7/graphics/drawable/DrawableUtils.java
+++ /dev/null
@@ -1,40 +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.graphics.drawable;
-
-import android.graphics.PorterDuff;
-import android.os.Build;
-
-/**
- * @hide
- */
-public class DrawableUtils {
-
- public static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
- switch (value) {
- case 3: return PorterDuff.Mode.SRC_OVER;
- case 5: return PorterDuff.Mode.SRC_IN;
- case 9: return PorterDuff.Mode.SRC_ATOP;
- case 14: return PorterDuff.Mode.MULTIPLY;
- case 15: return PorterDuff.Mode.SCREEN;
- case 16: return Build.VERSION.SDK_INT >= 11 ? PorterDuff.Mode.valueOf("ADD")
- : defaultMode;
- default: return defaultMode;
- }
- }
-
-}
diff --git a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
index 0e50cc1..7d4f403 100644
--- a/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
+++ b/v7/appcompat/src/android/support/v7/view/ActionBarPolicy.java
@@ -17,11 +17,14 @@
package android.support.v7.view;
import android.content.Context;
+import android.content.res.Configuration;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.os.Build;
+import android.support.v4.content.res.ConfigurationHelper;
import android.support.v4.view.ViewConfigurationCompat;
import android.support.v7.appcompat.R;
+import android.util.DisplayMetrics;
import android.view.ViewConfiguration;
/**
@@ -42,8 +45,31 @@
mContext = context;
}
+ /**
+ * Returns the maximum number of action buttons that should be permitted within an action
+ * bar/action mode. This will be used to determine how many showAsAction="ifRoom" items can fit.
+ * "always" items can override this.
+ */
public int getMaxActionButtons() {
- return mContext.getResources().getInteger(R.integer.abc_max_action_buttons);
+ final Resources res = mContext.getResources();
+ final int widthDp = ConfigurationHelper.getScreenWidthDp(res);
+ final int heightDp = ConfigurationHelper.getScreenHeightDp(res);
+ final int smallest = ConfigurationHelper.getSmallestScreenWidthDp(res);
+
+ if (smallest > 600 || widthDp > 600 || (widthDp > 960 && heightDp > 720)
+ || (widthDp > 720 && heightDp > 960)) {
+ // For values-w600dp, values-sw600dp and values-xlarge.
+ return 5;
+ } else if (widthDp >= 500 || (widthDp > 640 && heightDp > 480)
+ || (widthDp > 480 && heightDp > 640)) {
+ // For values-w500dp and values-large.
+ return 4;
+ } else if (widthDp >= 360) {
+ // For values-w360dp.
+ return 3;
+ } else {
+ return 2;
+ }
}
public boolean showsOverflowMenuButton() {
@@ -59,14 +85,7 @@
}
public boolean hasEmbeddedTabs() {
- final int targetSdk = mContext.getApplicationInfo().targetSdkVersion;
- if (targetSdk >= Build.VERSION_CODES.JELLY_BEAN) {
- return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
- }
-
- // The embedded tabs policy changed in Jellybean; give older apps the old policy
- // so they get what they expect.
- return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs_pre_jb);
+ return mContext.getResources().getBoolean(R.bool.abc_action_bar_embed_tabs);
}
public int getTabContainerHeight() {
diff --git a/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java b/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
index 7f4c87e..38dbb02 100644
--- a/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
+++ b/v7/appcompat/src/android/support/v7/view/StandaloneActionMode.java
@@ -129,7 +129,7 @@
@Override
public MenuInflater getMenuInflater() {
- return new MenuInflater(mContextView.getContext());
+ return new SupportMenuInflater(mContextView.getContext());
}
public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
diff --git a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
index 45278bc..ea1395f 100644
--- a/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
+++ b/v7/appcompat/src/android/support/v7/view/WindowCallbackWrapper.java
@@ -18,6 +18,7 @@
import android.view.ActionMode;
import android.view.KeyEvent;
+import android.view.KeyboardShortcutGroup;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
@@ -27,6 +28,8 @@
import android.view.WindowManager;
import android.view.accessibility.AccessibilityEvent;
+import java.util.List;
+
/**
* A simple decorator stub for Window.Callback that passes through any calls
* to the wrapped instance as a base implementation. Call super.foo() to call into
@@ -159,4 +162,10 @@
public void onActionModeFinished(ActionMode mode) {
mWrapped.onActionModeFinished(mode);
}
+
+ @Override
+ public void onProvideKeyboardShortcuts(
+ List<KeyboardShortcutGroup> data, Menu menu, int deviceId) {
+ mWrapped.onProvideKeyboardShortcuts(data, menu, deviceId);
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
index 0b80cf2..34c7ff4 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ActionMenuItemView.java
@@ -23,14 +23,18 @@
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.os.Parcelable;
+import android.support.v4.content.res.ConfigurationHelper;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.widget.ActionMenuView;
import android.support.v7.widget.AppCompatTextView;
+import android.support.v7.widget.ForwardingListener;
import android.support.v7.widget.ListPopupWindow;
import android.text.TextUtils;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
@@ -49,7 +53,7 @@
private CharSequence mTitle;
private Drawable mIcon;
private MenuBuilder.ItemInvoker mItemInvoker;
- private ListPopupWindow.ForwardingListener mForwardingListener;
+ private ForwardingListener mForwardingListener;
private PopupCallback mPopupCallback;
private boolean mAllowTextWithIcon;
@@ -71,8 +75,7 @@
public ActionMenuItemView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
final Resources res = context.getResources();
- mAllowTextWithIcon = res.getBoolean(
- R.bool.abc_config_allowActionMenuItemTextWithIcon);
+ mAllowTextWithIcon = shouldAllowTextWithIcon();
TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ActionMenuItemView, defStyle, 0);
mMinWidth = a.getDimensionPixelSize(
@@ -86,6 +89,7 @@
setOnLongClickListener(this);
mSavedPaddingLeft = -1;
+ setSaveEnabled(false);
}
public void onConfigurationChanged(Configuration newConfig) {
@@ -93,11 +97,23 @@
super.onConfigurationChanged(newConfig);
}
- mAllowTextWithIcon = getContext().getResources().getBoolean(
- R.bool.abc_config_allowActionMenuItemTextWithIcon);
+ mAllowTextWithIcon = shouldAllowTextWithIcon();
updateTextButtonVisibility();
}
+ /**
+ * Whether action menu items should obey the "withText" showAsAction flag. This may be set to
+ * false for situations where space is extremely limited. -->
+ */
+ private boolean shouldAllowTextWithIcon() {
+ final Configuration config = getContext().getResources().getConfiguration();
+ final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
+ final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
+
+ return widthDp >= 480 || (widthDp >= 640 && heightDp >= 480)
+ || config.orientation == Configuration.ORIENTATION_LANDSCAPE;
+ }
+
@Override
public void setPadding(int l, int t, int r, int b) {
mSavedPaddingLeft = l;
@@ -291,13 +307,13 @@
}
}
- private class ActionMenuItemForwardingListener extends ListPopupWindow.ForwardingListener {
+ private class ActionMenuItemForwardingListener extends ForwardingListener {
public ActionMenuItemForwardingListener() {
super(ActionMenuItemView.this);
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mPopupCallback != null) {
return mPopupCallback.getPopup();
}
@@ -308,7 +324,7 @@
protected boolean onForwardingStarted() {
// Call the invoker, then check if the expected popup is showing.
if (mItemInvoker != null && mItemInvoker.invokeItem(mItemData)) {
- final ListPopupWindow popup = getPopup();
+ final ShowableListMenu popup = getPopup();
return popup != null && popup.isShowing();
}
return false;
@@ -322,7 +338,14 @@
// return false and make ListPopupWindow think it's still forwarding.
}
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ // This might get called with the state of ActionView since it shares the same ID with
+ // ActionMenuItemView. Do not restore this state as ActionMenuItemView never saved it.
+ super.onRestoreInstanceState(null);
+ }
+
public static abstract class PopupCallback {
- public abstract ListPopupWindow getPopup();
+ public abstract ShowableListMenu getPopup();
}
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
new file mode 100644
index 0000000..9cca026
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/CascadingMenuPopup.java
@@ -0,0 +1,744 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.os.Handler;
+import android.os.Parcelable;
+import android.os.SystemClock;
+import android.support.annotation.AttrRes;
+import android.support.annotation.IntDef;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuItemHoverListener;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AbsListView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.LinkedList;
+import java.util.List;
+
+/**
+ * A popup for a menu which will allow multiple submenus to appear in a cascading fashion, side by
+ * side.
+ */
+final class CascadingMenuPopup extends MenuPopup implements MenuPresenter, OnKeyListener,
+ PopupWindow.OnDismissListener {
+
+ @Retention(RetentionPolicy.SOURCE)
+ @IntDef({HORIZ_POSITION_LEFT, HORIZ_POSITION_RIGHT})
+ public @interface HorizPosition {}
+
+ private static final int HORIZ_POSITION_LEFT = 0;
+ private static final int HORIZ_POSITION_RIGHT = 1;
+
+ /**
+ * Delay between hovering over a menu item with a mouse and receiving
+ * side-effects (ex. opening a sub-menu or closing unrelated menus).
+ */
+ private static final int SUBMENU_TIMEOUT_MS = 200;
+
+ private final Context mContext;
+ private final int mMenuMaxWidth;
+ private final int mPopupStyleAttr;
+ private final int mPopupStyleRes;
+ private final boolean mOverflowOnly;
+ private final Handler mSubMenuHoverHandler;
+
+ /** List of menus that were added before this popup was shown. */
+ private final List<MenuBuilder> mPendingMenus = new LinkedList<>();
+
+ /**
+ * List of open menus. The first item is the root menu and each
+ * subsequent item is a direct submenu of the previous item.
+ */
+ private final List<CascadingMenuInfo> mShowingMenus = new ArrayList<>();
+
+ private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Only move the popup if it's showing and non-modal. We don't want
+ // to be moving around the only interactive window, since there's a
+ // good chance the user is interacting with it.
+ if (isShowing() && mShowingMenus.size() > 0
+ && !mShowingMenus.get(0).window.isModal()) {
+ final View anchor = mShownAnchorView;
+ if (anchor == null || !anchor.isShown()) {
+ dismiss();
+ } else {
+ // Recompute window sizes and positions.
+ for (CascadingMenuInfo info : mShowingMenus) {
+ info.window.show();
+ }
+ }
+ }
+ }
+ };
+
+ private final MenuItemHoverListener mMenuItemHoverListener = new MenuItemHoverListener() {
+ @Override
+ public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // If the mouse moves between two windows, hover enter/exit pairs
+ // may be received out of order. So, instead of canceling all
+ // pending runnables, only cancel runnables for the host menu.
+ mSubMenuHoverHandler.removeCallbacksAndMessages(menu);
+ }
+
+ @Override
+ public void onItemHoverEnter(
+ @NonNull final MenuBuilder menu, @NonNull final MenuItem item) {
+ // Something new was hovered, cancel all scheduled runnables.
+ mSubMenuHoverHandler.removeCallbacksAndMessages(null);
+
+ // Find the position of the hovered menu within the added menus.
+ int menuIndex = -1;
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ if (menu == mShowingMenus.get(i).menu) {
+ menuIndex = i;
+ break;
+ }
+ }
+
+ if (menuIndex == -1) {
+ return;
+ }
+
+ final CascadingMenuInfo nextInfo;
+ final int nextIndex = menuIndex + 1;
+ if (nextIndex < mShowingMenus.size()) {
+ nextInfo = mShowingMenus.get(nextIndex);
+ } else {
+ nextInfo = null;
+ }
+
+ final Runnable runnable = new Runnable() {
+ @Override
+ public void run() {
+ // Close any other submenus that might be open at the
+ // current or a deeper level.
+ if (nextInfo != null) {
+ // Disable exit animations to prevent overlapping
+ // fading out submenus.
+ mShouldCloseImmediately = true;
+ nextInfo.menu.close(false /* closeAllMenus */);
+ mShouldCloseImmediately = false;
+ }
+
+ // Then open the selected submenu, if there is one.
+ if (item.isEnabled() && item.hasSubMenu()) {
+ menu.performItemAction(item, 0);
+ }
+ }
+ };
+ final long uptimeMillis = SystemClock.uptimeMillis() + SUBMENU_TIMEOUT_MS;
+ mSubMenuHoverHandler.postAtTime(runnable, menu, uptimeMillis);
+ }
+ };
+
+ private int mRawDropDownGravity = Gravity.NO_GRAVITY;
+ private int mDropDownGravity = Gravity.NO_GRAVITY;
+ private View mAnchorView;
+ private View mShownAnchorView;
+ private int mLastPosition;
+ private boolean mHasXOffset;
+ private boolean mHasYOffset;
+ private int mXOffset;
+ private int mYOffset;
+ private boolean mForceShowIcon;
+ private boolean mShowTitle;
+ private Callback mPresenterCallback;
+ private ViewTreeObserver mTreeObserver;
+ private PopupWindow.OnDismissListener mOnDismissListener;
+
+ /** Whether popup menus should disable exit animations when closing. */
+ private boolean mShouldCloseImmediately;
+
+ /**
+ * Initializes a new cascading-capable menu popup.
+ *
+ * @param anchor A parent view to get the {@link android.view.View#getWindowToken()} token from.
+ */
+ public CascadingMenuPopup(@NonNull Context context, @NonNull View anchor,
+ @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes, boolean overflowOnly) {
+ mContext = context;
+ mAnchorView = anchor;
+ mPopupStyleAttr = popupStyleAttr;
+ mPopupStyleRes = popupStyleRes;
+ mOverflowOnly = overflowOnly;
+
+ mForceShowIcon = false;
+ mLastPosition = getInitialMenuPosition();
+
+ final Resources res = context.getResources();
+ mMenuMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+ res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+ mSubMenuHoverHandler = new Handler();
+ }
+
+ @Override
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ private MenuPopupWindow createPopupWindow() {
+ MenuPopupWindow popupWindow = new MenuPopupWindow(
+ mContext, null, mPopupStyleAttr, mPopupStyleRes);
+ popupWindow.setHoverListener(mMenuItemHoverListener);
+ popupWindow.setOnItemClickListener(this);
+ popupWindow.setOnDismissListener(this);
+ popupWindow.setAnchorView(mAnchorView);
+ popupWindow.setDropDownGravity(mDropDownGravity);
+ popupWindow.setModal(true);
+ return popupWindow;
+ }
+
+ @Override
+ public void show() {
+ if (isShowing()) {
+ return;
+ }
+
+ // Display all pending menus.
+ for (MenuBuilder menu : mPendingMenus) {
+ showMenu(menu);
+ }
+ mPendingMenus.clear();
+
+ mShownAnchorView = mAnchorView;
+
+ if (mShownAnchorView != null) {
+ final boolean addGlobalListener = mTreeObserver == null;
+ mTreeObserver = mShownAnchorView.getViewTreeObserver(); // Refresh to latest
+ if (addGlobalListener) {
+ mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ // Need to make another list to avoid a concurrent modification
+ // exception, as #onDismiss may clear mPopupWindows while we are
+ // iterating. Remove from the last added menu so that the callbacks
+ // are received in order from foreground to background.
+ final int length = mShowingMenus.size();
+ if (length > 0) {
+ final CascadingMenuInfo[] addedMenus =
+ mShowingMenus.toArray(new CascadingMenuInfo[length]);
+ for (int i = length - 1; i >= 0; i--) {
+ final CascadingMenuInfo info = addedMenus[i];
+ if (info.window.isShowing()) {
+ info.window.dismiss();
+ }
+ }
+ }
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Determines the proper initial menu position for the current LTR/RTL configuration.
+ * @return The initial position.
+ */
+ @HorizPosition
+ private int getInitialMenuPosition() {
+ final int layoutDirection = ViewCompat.getLayoutDirection(mAnchorView);
+ return layoutDirection == ViewCompat.LAYOUT_DIRECTION_RTL ? HORIZ_POSITION_LEFT :
+ HORIZ_POSITION_RIGHT;
+ }
+
+ /**
+ * Determines whether the next submenu (of the given width) should display on the right or on
+ * the left of the most recent menu.
+ *
+ * @param nextMenuWidth Width of the next submenu to display.
+ * @return The position to display it.
+ */
+ @HorizPosition
+ private int getNextMenuPosition(int nextMenuWidth) {
+ ListView lastListView = mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+
+ final int[] screenLocation = new int[2];
+ lastListView.getLocationOnScreen(screenLocation);
+
+ final Rect displayFrame = new Rect();
+ mShownAnchorView.getWindowVisibleDisplayFrame(displayFrame);
+
+ if (mLastPosition == HORIZ_POSITION_RIGHT) {
+ final int right = screenLocation[0] + lastListView.getWidth() + nextMenuWidth;
+ if (right > displayFrame.right) {
+ return HORIZ_POSITION_LEFT;
+ }
+ return HORIZ_POSITION_RIGHT;
+ } else { // LEFT
+ final int left = screenLocation[0] - nextMenuWidth;
+ if (left < 0) {
+ return HORIZ_POSITION_RIGHT;
+ }
+ return HORIZ_POSITION_LEFT;
+ }
+ }
+
+ @Override
+ public void addMenu(MenuBuilder menu) {
+ menu.addMenuPresenter(this, mContext);
+
+ if (isShowing()) {
+ showMenu(menu);
+ } else {
+ mPendingMenus.add(menu);
+ }
+ }
+
+ /**
+ * Prepares and shows the specified menu immediately.
+ *
+ * @param menu the menu to show
+ */
+ private void showMenu(@NonNull MenuBuilder menu) {
+ final LayoutInflater inflater = LayoutInflater.from(mContext);
+ final MenuAdapter adapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+
+ // Apply "force show icon" setting. There are 3 cases:
+ // (1) This is the top level menu and icon spacing is forced. Add spacing.
+ // (2) This is a submenu. Add spacing if any of the visible menu items has an icon.
+ // (3) This is the top level menu and icon spacing isn't forced. Do not add spacing.
+ if (!isShowing() && mForceShowIcon) {
+ // Case 1
+ adapter.setForceShowIcon(true);
+ } else if (isShowing()) {
+ // Case 2
+ adapter.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(menu));
+ }
+ // Case 3: Else, don't allow spacing for icons (default behavior; do nothing).
+
+ final int menuWidth = measureIndividualMenuWidth(adapter, null, mContext, mMenuMaxWidth);
+ final MenuPopupWindow popupWindow = createPopupWindow();
+ popupWindow.setAdapter(adapter);
+ popupWindow.setWidth(menuWidth);
+ popupWindow.setDropDownGravity(mDropDownGravity);
+
+ final CascadingMenuInfo parentInfo;
+ final View parentView;
+ if (mShowingMenus.size() > 0) {
+ parentInfo = mShowingMenus.get(mShowingMenus.size() - 1);
+ parentView = findParentViewForSubmenu(parentInfo, menu);
+ } else {
+ parentInfo = null;
+ parentView = null;
+ }
+
+ if (parentView != null) {
+ // This menu is a cascading submenu anchored to a parent view.
+ popupWindow.setTouchModal(false);
+ popupWindow.setEnterTransition(null);
+
+ final @HorizPosition int nextMenuPosition = getNextMenuPosition(menuWidth);
+ final boolean showOnRight = nextMenuPosition == HORIZ_POSITION_RIGHT;
+ mLastPosition = nextMenuPosition;
+
+ final int[] tempLocation = new int[2];
+
+ // This popup menu will be positioned relative to the top-left edge
+ // of the view representing its parent menu.
+ parentView.getLocationInWindow(tempLocation);
+ final int parentOffsetLeft = parentInfo.window.getHorizontalOffset() + tempLocation[0];
+ final int parentOffsetTop = parentInfo.window.getVerticalOffset() + tempLocation[1];
+
+ // By now, mDropDownGravity is the resolved absolute gravity, so
+ // this should work in both LTR and RTL.
+ final int x;
+ if ((mDropDownGravity & Gravity.RIGHT) == Gravity.RIGHT) {
+ if (showOnRight) {
+ x = parentOffsetLeft + menuWidth;
+ } else {
+ x = parentOffsetLeft - parentView.getWidth();
+ }
+ } else {
+ if (showOnRight) {
+ x = parentOffsetLeft + parentView.getWidth();
+ } else {
+ x = parentOffsetLeft - menuWidth;
+ }
+ }
+
+ popupWindow.setHorizontalOffset(x);
+
+ final int y = parentOffsetTop;
+ popupWindow.setVerticalOffset(y);
+ } else {
+ if (mHasXOffset) {
+ popupWindow.setHorizontalOffset(mXOffset);
+ }
+ if (mHasYOffset) {
+ popupWindow.setVerticalOffset(mYOffset);
+ }
+ final Rect epicenterBounds = getEpicenterBounds();
+ popupWindow.setEpicenterBounds(epicenterBounds);
+ }
+
+ final CascadingMenuInfo menuInfo = new CascadingMenuInfo(popupWindow, menu, mLastPosition);
+ mShowingMenus.add(menuInfo);
+
+ popupWindow.show();
+
+ // If this is the root menu, show the title if requested.
+ if (parentInfo == null && mShowTitle && menu.getHeaderTitle() != null) {
+ final ListView listView = popupWindow.getListView();
+ final FrameLayout titleItemView = (FrameLayout) inflater.inflate(
+ R.layout.abc_popup_menu_header_item_layout, listView, false);
+ final TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+ titleItemView.setEnabled(false);
+ titleView.setText(menu.getHeaderTitle());
+ listView.addHeaderView(titleItemView, null, false);
+
+ // Show again to update the title.
+ popupWindow.show();
+ }
+ }
+
+ /**
+ * Returns the menu item within the specified parent menu that owns
+ * specified submenu.
+ *
+ * @param parent the parent menu
+ * @param submenu the submenu for which the index should be returned
+ * @return the menu item that owns the submenu, or {@code null} if not
+ * present
+ */
+ private MenuItem findMenuItemForSubmenu(
+ @NonNull MenuBuilder parent, @NonNull MenuBuilder submenu) {
+ for (int i = 0, count = parent.size(); i < count; i++) {
+ final MenuItem item = parent.getItem(i);
+ if (item.hasSubMenu() && submenu == item.getSubMenu()) {
+ return item;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Attempts to find the view for the menu item that owns the specified
+ * submenu.
+ *
+ * @param parentInfo info for the parent menu
+ * @param submenu the submenu whose parent view should be obtained
+ * @return the parent view, or {@code null} if one could not be found
+ */
+ @Nullable
+ private View findParentViewForSubmenu(
+ @NonNull CascadingMenuInfo parentInfo, @NonNull MenuBuilder submenu) {
+ final MenuItem owner = findMenuItemForSubmenu(parentInfo.menu, submenu);
+ if (owner == null) {
+ // Couldn't find the submenu owner.
+ return null;
+ }
+
+ // The adapter may be wrapped. Adjust the index if necessary.
+ final int headersCount;
+ final MenuAdapter menuAdapter;
+ final ListView listView = parentInfo.getListView();
+ final ListAdapter listAdapter = listView.getAdapter();
+ if (listAdapter instanceof HeaderViewListAdapter) {
+ final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) listAdapter;
+ headersCount = headerAdapter.getHeadersCount();
+ menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+ } else {
+ headersCount = 0;
+ menuAdapter = (MenuAdapter) listAdapter;
+ }
+
+ // Find the index within the menu adapter's data set of the menu item.
+ int ownerPosition = AbsListView.INVALID_POSITION;
+ for (int i = 0, count = menuAdapter.getCount(); i < count; i++) {
+ if (owner == menuAdapter.getItem(i)) {
+ ownerPosition = i;
+ break;
+ }
+ }
+ if (ownerPosition == AbsListView.INVALID_POSITION) {
+ // Couldn't find the owner within the menu adapter.
+ return null;
+ }
+
+ // Adjust the index for the adapter used to display views.
+ ownerPosition += headersCount;
+
+ // Adjust the index for the visible views.
+ final int ownerViewPosition = ownerPosition - listView.getFirstVisiblePosition();
+ if (ownerViewPosition < 0 || ownerViewPosition >= listView.getChildCount()) {
+ // Not visible on screen.
+ return null;
+ }
+
+ return listView.getChildAt(ownerViewPosition);
+ }
+
+ /**
+ * @return {@code true} if the popup is currently showing, {@code false} otherwise.
+ */
+ @Override
+ public boolean isShowing() {
+ return mShowingMenus.size() > 0 && mShowingMenus.get(0).window.isShowing();
+ }
+
+ /**
+ * Called when one or more of the popup windows was dismissed.
+ */
+ @Override
+ public void onDismiss() {
+ // The dismiss listener doesn't pass the calling window, so walk
+ // through the stack to figure out which one was just dismissed.
+ CascadingMenuInfo dismissedInfo = null;
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ final CascadingMenuInfo info = mShowingMenus.get(i);
+ if (!info.window.isShowing()) {
+ dismissedInfo = info;
+ break;
+ }
+ }
+
+ // Close all menus starting from the dismissed menu, passing false
+ // since we are manually closing only a subset of windows.
+ if (dismissedInfo != null) {
+ dismissedInfo.menu.close(false);
+ }
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ for (CascadingMenuInfo info : mShowingMenus) {
+ toMenuAdapter(info.getListView().getAdapter()).notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ // Don't allow double-opening of the same submenu.
+ for (CascadingMenuInfo info : mShowingMenus) {
+ if (subMenu == info.menu) {
+ // Just re-focus that one.
+ info.getListView().requestFocus();
+ return true;
+ }
+ }
+
+ if (subMenu.hasVisibleItems()) {
+ addMenu(subMenu);
+
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Finds the index of the specified menu within the list of added menus.
+ *
+ * @param menu the menu to find
+ * @return the index of the menu, or {@code -1} if not present
+ */
+ private int findIndexOfAddedMenu(@NonNull MenuBuilder menu) {
+ for (int i = 0, count = mShowingMenus.size(); i < count; i++) {
+ final CascadingMenuInfo info = mShowingMenus.get(i);
+ if (menu == info.menu) {
+ return i;
+ }
+ }
+
+ return -1;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ final int menuIndex = findIndexOfAddedMenu(menu);
+ if (menuIndex < 0) {
+ return;
+ }
+
+ // Recursively close descendant menus.
+ final int nextMenuIndex = menuIndex + 1;
+ if (nextMenuIndex < mShowingMenus.size()) {
+ final CascadingMenuInfo childInfo = mShowingMenus.get(nextMenuIndex);
+ childInfo.menu.close(false /* closeAllMenus */);
+ }
+
+ // Close the target menu.
+ final CascadingMenuInfo info = mShowingMenus.remove(menuIndex);
+ info.menu.removeMenuPresenter(this);
+ if (mShouldCloseImmediately) {
+ // Disable all exit animations.
+ info.window.setExitTransition(null);
+ info.window.setAnimationStyle(0);
+ }
+ info.window.dismiss();
+
+ final int count = mShowingMenus.size();
+ if (count > 0) {
+ mLastPosition = mShowingMenus.get(count - 1).position;
+ } else {
+ mLastPosition = getInitialMenuPosition();
+ }
+
+ if (count == 0) {
+ // This was the last window. Clean up.
+ dismiss();
+
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, true);
+ }
+
+ if (mTreeObserver != null) {
+ if (mTreeObserver.isAlive()) {
+ mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+ }
+ mTreeObserver = null;
+ }
+
+
+ // If every [sub]menu was dismissed, that means the whole thing was
+ // dismissed, so notify the owner.
+ mOnDismissListener.onDismiss();
+ } else if (allMenusAreClosing) {
+ // Close all menus starting from the root. This will recursively
+ // close any remaining menus, so we don't need to propagate the
+ // "closeAllMenus" flag. The last window will clean up.
+ final CascadingMenuInfo rootInfo = mShowingMenus.get(0);
+ rootInfo.menu.close(false /* closeAllMenus */);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+
+ @Override
+ public void setGravity(int dropDownGravity) {
+ if (mRawDropDownGravity != dropDownGravity) {
+ mRawDropDownGravity = dropDownGravity;
+ mDropDownGravity = GravityCompat.getAbsoluteGravity(
+ dropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+ }
+ }
+
+ @Override
+ public void setAnchorView(@NonNull View anchor) {
+ if (mAnchorView != anchor) {
+ mAnchorView = anchor;
+
+ // Gravity resolution may have changed, update from raw gravity.
+ mDropDownGravity = GravityCompat.getAbsoluteGravity(
+ mRawDropDownGravity, ViewCompat.getLayoutDirection(mAnchorView));
+ }
+ }
+
+ @Override
+ public void setOnDismissListener(OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ @Override
+ public ListView getListView() {
+ return mShowingMenus.isEmpty()
+ ? null
+ : mShowingMenus.get(mShowingMenus.size() - 1).getListView();
+ }
+
+ @Override
+ public void setHorizontalOffset(int x) {
+ mHasXOffset = true;
+ mXOffset = x;
+ }
+
+ @Override
+ public void setVerticalOffset(int y) {
+ mHasYOffset = true;
+ mYOffset = y;
+ }
+
+ @Override
+ public void setShowTitle(boolean showTitle) {
+ mShowTitle = showTitle;
+ }
+
+ private static class CascadingMenuInfo {
+ public final MenuPopupWindow window;
+ public final MenuBuilder menu;
+ public final int position;
+
+ public CascadingMenuInfo(@NonNull MenuPopupWindow window, @NonNull MenuBuilder menu,
+ int position) {
+ this.window = window;
+ this.menu = menu;
+ this.position = position;
+ }
+
+ public ListView getListView() {
+ return window.getListView();
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
index 388e78f..2d6def1 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/ListMenuItemView.java
@@ -17,9 +17,9 @@
package android.support.v7.view.menu;
import android.content.Context;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.support.v7.appcompat.R;
+import android.support.v7.widget.TintTypedArray;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,7 +37,6 @@
* @hide
*/
public class ListMenuItemView extends LinearLayout implements MenuView.ItemView {
-
private static final String TAG = "ListMenuItemView";
private MenuItemImpl mItemData;
@@ -46,25 +45,29 @@
private TextView mTitleView;
private CheckBox mCheckBox;
private TextView mShortcutView;
+ private ImageView mSubMenuArrowView;
private Drawable mBackground;
private int mTextAppearance;
private Context mTextAppearanceContext;
private boolean mPreserveIconSpacing;
+ private Drawable mSubMenuArrow;
private int mMenuType;
- private Context mContext;
private LayoutInflater mInflater;
private boolean mForceShowIcon;
- public ListMenuItemView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs);
- mContext = context;
+ public ListMenuItemView(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.listMenuViewStyle);
+ }
- final TypedArray a = context.obtainStyledAttributes(
- attrs, R.styleable.MenuView, defStyle, 0);
+ public ListMenuItemView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs);
+
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(getContext(),
+ attrs, R.styleable.MenuView, defStyleAttr, 0);
mBackground = a.getDrawable(R.styleable.MenuView_android_itemBackground);
mTextAppearance = a.getResourceId(R.styleable.
@@ -72,14 +75,11 @@
mPreserveIconSpacing = a.getBoolean(
R.styleable.MenuView_preserveIconSpacing, false);
mTextAppearanceContext = context;
+ mSubMenuArrow = a.getDrawable(R.styleable.MenuView_subMenuArrow);
a.recycle();
}
- public ListMenuItemView(Context context, AttributeSet attrs) {
- this(context, attrs, 0);
- }
-
@Override
protected void onFinishInflate() {
super.onFinishInflate();
@@ -93,6 +93,10 @@
}
mShortcutView = (TextView) findViewById(R.id.shortcut);
+ mSubMenuArrowView = (ImageView) findViewById(R.id.submenuarrow);
+ if (mSubMenuArrowView != null) {
+ mSubMenuArrowView.setImageDrawable(mSubMenuArrow);
+ }
}
public void initialize(MenuItemImpl itemData, int menuType) {
@@ -106,6 +110,7 @@
setShortcut(itemData.shouldShowShortcut(), itemData.getShortcut());
setIcon(itemData.getIcon());
setEnabled(itemData.isEnabled());
+ setSubMenuArrowVisible(itemData.hasSubMenu());
}
public void setForceShowIcon(boolean forceShow) {
@@ -190,6 +195,12 @@
compoundButton.setChecked(checked);
}
+ private void setSubMenuArrowVisible(boolean hasSubmenu) {
+ if (mSubMenuArrowView != null) {
+ mSubMenuArrowView.setVisibility(hasSubmenu ? View.VISIBLE : View.GONE);
+ }
+ }
+
public void setShortcut(boolean showShortcut, char shortcutKey) {
final int newVisibility = (showShortcut && mItemData.shouldShowShortcut())
? VISIBLE : GONE;
@@ -274,7 +285,7 @@
private LayoutInflater getInflater() {
if (mInflater == null) {
- mInflater = LayoutInflater.from(mContext);
+ mInflater = LayoutInflater.from(getContext());
}
return mInflater;
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
new file mode 100644
index 0000000..08ec36d4
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuAdapter.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.view.menu;
+
+import android.support.v7.appcompat.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.BaseAdapter;
+
+import java.util.ArrayList;
+
+/**
+ * @hide
+ */
+public class MenuAdapter extends BaseAdapter {
+ static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+
+ MenuBuilder mAdapterMenu;
+
+ private int mExpandedIndex = -1;
+
+ private boolean mForceShowIcon;
+ private final boolean mOverflowOnly;
+ private final LayoutInflater mInflater;
+
+ public MenuAdapter(MenuBuilder menu, LayoutInflater inflater, boolean overflowOnly) {
+ mOverflowOnly = overflowOnly;
+ mInflater = inflater;
+ mAdapterMenu = menu;
+ findExpandedIndex();
+ }
+
+ public boolean getForceShowIcon() {
+ return mForceShowIcon;
+ }
+
+ public void setForceShowIcon(boolean forceShow) {
+ mForceShowIcon = forceShow;
+ }
+
+ public int getCount() {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex < 0) {
+ return items.size();
+ }
+ return items.size() - 1;
+ }
+
+ public MenuBuilder getAdapterMenu() {
+ return mAdapterMenu;
+ }
+
+ public MenuItemImpl getItem(int position) {
+ ArrayList<MenuItemImpl> items = mOverflowOnly ?
+ mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
+ if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
+ position++;
+ }
+ return items.get(position);
+ }
+
+ public long getItemId(int position) {
+ // Since a menu item's ID is optional, we'll use the position as an
+ // ID for the item in the AdapterView
+ return position;
+ }
+
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
+ }
+
+ MenuView.ItemView itemView = (MenuView.ItemView) convertView;
+ if (mForceShowIcon) {
+ ((ListMenuItemView) convertView).setForceShowIcon(true);
+ }
+ itemView.initialize(getItem(position), 0);
+ return convertView;
+ }
+
+ void findExpandedIndex() {
+ final MenuItemImpl expandedItem = mAdapterMenu.getExpandedItem();
+ if (expandedItem != null) {
+ final ArrayList<MenuItemImpl> items = mAdapterMenu.getNonActionItems();
+ final int count = items.size();
+ for (int i = 0; i < count; i++) {
+ final MenuItemImpl item = items.get(i);
+ if (item == expandedItem) {
+ mExpandedIndex = i;
+ return;
+ }
+ }
+ }
+ mExpandedIndex = -1;
+ }
+
+ @Override
+ public void notifyDataSetChanged() {
+ findExpandedIndex();
+ super.notifyDataSetChanged();
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
index ace7a42..f77c461 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java
@@ -26,6 +26,7 @@
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.internal.view.SupportMenu;
import android.support.v4.internal.view.SupportMenuItem;
@@ -69,7 +70,6 @@
};
private final Context mContext;
-
private final Resources mResources;
/**
@@ -214,14 +214,13 @@
public MenuBuilder(Context context) {
mContext = context;
mResources = context.getResources();
+ mItems = new ArrayList<>();
- mItems = new ArrayList<MenuItemImpl>();
-
- mVisibleItems = new ArrayList<MenuItemImpl>();
+ mVisibleItems = new ArrayList<>();
mIsVisibleItemsStale = true;
- mActionItems = new ArrayList<MenuItemImpl>();
- mNonActionItems = new ArrayList<MenuItemImpl>();
+ mActionItems = new ArrayList<>();
+ mNonActionItems = new ArrayList<>();
mIsActionItemsStale = true;
setShortcutsVisibleInner(true);
@@ -842,7 +841,7 @@
}
if ((flags & FLAG_ALWAYS_PERFORM_CLOSE) != 0) {
- close(true);
+ close(true /* closeAllMenus */);
}
return handled;
@@ -961,10 +960,10 @@
final boolean providerHasSubMenu = provider != null && provider.hasSubMenu();
if (itemImpl.hasCollapsibleActionView()) {
invoked |= itemImpl.expandActionView();
- if (invoked) close(true);
+ if (invoked) {
+ close(true /* closeAllMenus */);
+ }
} else if (itemImpl.hasSubMenu() || providerHasSubMenu) {
- close(false);
-
if (!itemImpl.hasSubMenu()) {
itemImpl.setSubMenu(new SubMenuBuilder(getContext(), this, itemImpl));
}
@@ -974,10 +973,12 @@
provider.onPrepareSubMenu(subMenu);
}
invoked |= dispatchSubMenuSelected(subMenu, preferredPresenter);
- if (!invoked) close(true);
+ if (!invoked) {
+ close(true /* closeAllMenus */);
+ }
} else {
if ((flags & FLAG_PERFORM_NO_CLOSE) == 0) {
- close(true);
+ close(true /* closeAllMenus */);
}
}
@@ -985,15 +986,14 @@
}
/**
- * Closes the visible menu.
+ * Closes the menu.
*
- * @param allMenusAreClosing Whether the menus are completely closing (true),
- * or whether there is another menu coming in this menu's place
- * (false). For example, if the menu is closing because a
- * sub menu is about to be shown, <var>allMenusAreClosing</var>
- * is false.
+ * @param closeAllMenus {@code true} if all displayed menus and submenus
+ * should be completely closed (as when a menu item is
+ * selected) or {@code false} if only this menu should
+ * be closed
*/
- public final void close(boolean allMenusAreClosing) {
+ public final void close(boolean closeAllMenus) {
if (mIsClosing) return;
mIsClosing = true;
@@ -1002,7 +1002,7 @@
if (presenter == null) {
mPresenters.remove(ref);
} else {
- presenter.onCloseMenu(this, allMenusAreClosing);
+ presenter.onCloseMenu(this, closeAllMenus);
}
}
mIsClosing = false;
@@ -1010,7 +1010,7 @@
@Override
public void close() {
- close(true);
+ close(true /* closeAllMenus */);
}
/**
@@ -1076,6 +1076,7 @@
onItemsChanged(true);
}
+ @NonNull
public ArrayList<MenuItemImpl> getVisibleItems() {
if (!mIsVisibleItemsStale) return mVisibleItems;
@@ -1284,7 +1285,6 @@
/**
* Gets the root menu (if this is a submenu, find its root menu).
- *
* @return The root menu.
*/
public MenuBuilder getRootMenu() {
@@ -1302,9 +1302,6 @@
mCurrentMenuInfo = menuInfo;
}
- /**
- * @hide
- */
public void setOptionalIconsVisible(boolean visible) {
mOptionalIconsVisible = visible;
}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
new file mode 100644
index 0000000..b861643
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuHelper.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.view.menu;
+
+/**
+ * Interface for a helper capable of presenting a menu.
+ */
+interface MenuHelper {
+ void setPresenterCallback(MenuPresenter.Callback cb);
+ void dismiss();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
new file mode 100644
index 0000000..3f4ceb0
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopup.java
@@ -0,0 +1,210 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.view.menu;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.View.MeasureSpec;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.FrameLayout;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+/**
+ * Base class for a menu popup abstraction - i.e., some type of menu, housed in a popup window
+ * environment.
+ */
+abstract class MenuPopup implements ShowableListMenu, MenuPresenter,
+ AdapterView.OnItemClickListener {
+ private Rect mEpicenterBounds;
+
+ public abstract void setForceShowIcon(boolean forceShow);
+
+ /**
+ * Adds the given menu to the popup, if it is capable of displaying submenus within itself.
+ * If menu is the first menu shown, it won't be displayed until show() is called.
+ * If the popup was already showing, adding a submenu via this method will cause that new
+ * submenu to be shown immediately (that is, if this MenuPopup implementation is capable of
+ * showing its own submenus).
+ *
+ * @param menu
+ */
+ public abstract void addMenu(MenuBuilder menu);
+
+ public abstract void setGravity(int dropDownGravity);
+
+ public abstract void setAnchorView(View anchor);
+
+ public abstract void setHorizontalOffset(int x);
+
+ public abstract void setVerticalOffset(int y);
+
+ /**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
+ * @return anchor-relative bounds of the popup's transition epicenter
+ */
+ public Rect getEpicenterBounds() {
+ return mEpicenterBounds;
+ }
+
+ /**
+ * Set whether a title entry should be shown in the popup menu (if a title exists for the
+ * menu).
+ *
+ * @param showTitle
+ */
+ public abstract void setShowTitle(boolean showTitle);
+
+ /**
+ * Set a listener to receive a callback when the popup is dismissed.
+ *
+ * @param listener Listener that will be notified when the popup is dismissed.
+ */
+ public abstract void setOnDismissListener(PopupWindow.OnDismissListener listener);
+
+ @Override
+ public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
+ // Don't need to do anything; we added as a presenter in the constructor.
+ }
+
+ @Override
+ public MenuView getMenuView(ViewGroup root) {
+ throw new UnsupportedOperationException("MenuPopups manage their own views");
+ }
+
+ @Override
+ public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
+ return false;
+ }
+
+ @Override
+ public int getId() {
+ return 0;
+ }
+
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ ListAdapter outerAdapter = (ListAdapter) parent.getAdapter();
+ MenuAdapter wrappedAdapter = toMenuAdapter(outerAdapter);
+
+ // Use the position from the outer adapter so that if a header view was added, we don't get
+ // an off-by-1 error in position.
+ wrappedAdapter.mAdapterMenu.performItemAction((MenuItem) outerAdapter.getItem(position), 0);
+ }
+
+ /**
+ * Measures the width of the given menu view.
+ *
+ * @param view The view to measure.
+ * @return The width.
+ */
+ protected static int measureIndividualMenuWidth(ListAdapter adapter, ViewGroup parent,
+ Context context, int maxAllowedWidth) {
+ // Menus don't tend to be long, so this is more sane than it looks.
+ int maxWidth = 0;
+ View itemView = null;
+ int itemType = 0;
+
+ final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
+ final int count = adapter.getCount();
+ for (int i = 0; i < count; i++) {
+ final int positionType = adapter.getItemViewType(i);
+ if (positionType != itemType) {
+ itemType = positionType;
+ itemView = null;
+ }
+
+ if (parent == null) {
+ parent = new FrameLayout(context);
+ }
+
+ itemView = adapter.getView(i, itemView, parent);
+ itemView.measure(widthMeasureSpec, heightMeasureSpec);
+
+ final int itemWidth = itemView.getMeasuredWidth();
+ if (itemWidth >= maxAllowedWidth) {
+ return maxAllowedWidth;
+ } else if (itemWidth > maxWidth) {
+ maxWidth = itemWidth;
+ }
+ }
+
+ return maxWidth;
+ }
+
+ /**
+ * Converts the given ListAdapter originating from a menu, to a MenuAdapter, accounting for
+ * the possibility of the parameter adapter actually wrapping the MenuAdapter. (That could
+ * happen if a header view was added on the menu.)
+ *
+ * @param adapter
+ * @return
+ */
+ protected static MenuAdapter toMenuAdapter(ListAdapter adapter) {
+ if (adapter instanceof HeaderViewListAdapter) {
+ return (MenuAdapter) ((HeaderViewListAdapter) adapter).getWrappedAdapter();
+ }
+ return (MenuAdapter) adapter;
+ }
+
+ /**
+ * Returns whether icon spacing needs to be preserved for the given menu, based on whether any
+ * of its items contains an icon.
+ *
+ * NOTE: This should only be used for non-overflow-only menus, because this method does not
+ * take into account whether the menu items are being shown as part of the popup or or being
+ * shown as actions in the action bar.
+ *
+ * @param menu
+ * @return Whether to preserve icon spacing.
+ */
+ protected static boolean shouldPreserveIconSpacing(MenuBuilder menu) {
+ boolean preserveIconSpacing = false;
+ final int count = menu.size();
+
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = menu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+
+ return preserveIconSpacing;
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
index c0e0fb5..9ecc829 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/MenuPopupHelper.java
@@ -17,110 +17,118 @@
package android.support.v7.view.menu;
import android.content.Context;
-import android.content.res.Resources;
-import android.os.Parcelable;
+import android.graphics.Point;
+import android.graphics.Rect;
+import android.os.Build;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
-import android.support.v7.widget.ListPopupWindow;
+import android.view.Display;
import android.view.Gravity;
-import android.view.KeyEvent;
-import android.view.LayoutInflater;
-import android.view.MenuItem;
import android.view.View;
-import android.view.View.MeasureSpec;
-import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
-import android.widget.AdapterView;
-import android.widget.BaseAdapter;
-import android.widget.FrameLayout;
-import android.widget.ListAdapter;
-import android.widget.PopupWindow;
-
-import java.util.ArrayList;
+import android.view.WindowManager;
+import android.widget.PopupWindow.OnDismissListener;
/**
* Presents a menu as a small, simple popup anchored to another view.
*
* @hide
*/
-public class MenuPopupHelper implements AdapterView.OnItemClickListener, View.OnKeyListener,
- ViewTreeObserver.OnGlobalLayoutListener, PopupWindow.OnDismissListener,
- MenuPresenter {
-
- private static final String TAG = "MenuPopupHelper";
-
- static final int ITEM_LAYOUT = R.layout.abc_popup_menu_item_layout;
+public class MenuPopupHelper implements MenuHelper {
+ private static final int TOUCH_EPICENTER_SIZE_DP = 48;
private final Context mContext;
- private final LayoutInflater mInflater;
+
+ // Immutable cached popup menu properties.
private final MenuBuilder mMenu;
- private final MenuAdapter mAdapter;
private final boolean mOverflowOnly;
- private final int mPopupMaxWidth;
private final int mPopupStyleAttr;
private final int mPopupStyleRes;
+ // Mutable cached popup menu properties.
private View mAnchorView;
- private ListPopupWindow mPopup;
- private ViewTreeObserver mTreeObserver;
- private Callback mPresenterCallback;
+ private int mDropDownGravity = Gravity.START;
+ private boolean mForceShowIcon;
+ private MenuPresenter.Callback mPresenterCallback;
- boolean mForceShowIcon;
+ private MenuPopup mPopup;
+ private OnDismissListener mOnDismissListener;
- private ViewGroup mMeasureParent;
-
- /** Whether the cached content width value is valid. */
- private boolean mHasContentWidth;
-
- /** Cached content width from {@link #measureContentWidth}. */
- private int mContentWidth;
-
- private int mDropDownGravity = Gravity.NO_GRAVITY;
-
- public MenuPopupHelper(Context context, MenuBuilder menu) {
- this(context, menu, null, false, R.attr.popupMenuStyle);
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu) {
+ this(context, menu, null, false, R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView) {
- this(context, menu, anchorView, false, R.attr.popupMenuStyle);
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView) {
+ this(context, menu, anchorView, false, R.attr.popupMenuStyle, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView,
+ boolean overflowOnly, @AttrRes int popupStyleAttr) {
this(context, menu, anchorView, overflowOnly, popupStyleAttr, 0);
}
- public MenuPopupHelper(Context context, MenuBuilder menu, View anchorView,
- boolean overflowOnly, int popupStyleAttr, int popupStyleRes) {
+ public MenuPopupHelper(@NonNull Context context, @NonNull MenuBuilder menu,
+ @NonNull View anchorView, boolean overflowOnly, @AttrRes int popupStyleAttr,
+ @StyleRes int popupStyleRes) {
mContext = context;
- mInflater = LayoutInflater.from(context);
mMenu = menu;
- mAdapter = new MenuAdapter(mMenu);
+ mAnchorView = anchorView;
mOverflowOnly = overflowOnly;
mPopupStyleAttr = popupStyleAttr;
mPopupStyleRes = popupStyleRes;
-
- final Resources res = context.getResources();
- mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
- res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
-
- mAnchorView = anchorView;
-
- // Present the menu using our context, not the menu builder's context.
- menu.addMenuPresenter(this, context);
}
- public void setAnchorView(View anchor) {
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ /**
+ * Sets the view to which the popup window is anchored.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param anchor the view to which the popup window should be anchored
+ */
+ public void setAnchorView(@NonNull View anchor) {
mAnchorView = anchor;
}
- public void setForceShowIcon(boolean forceShow) {
- mForceShowIcon = forceShow;
+ /**
+ * Sets whether the popup menu's adapter is forced to show icons in the
+ * menu item views.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param forceShowIcon {@code true} to force icons to be shown, or
+ * {@code false} for icons to be optionally shown
+ */
+ public void setForceShowIcon(boolean forceShowIcon) {
+ mForceShowIcon = forceShowIcon;
+ if (mPopup != null) {
+ mPopup.setForceShowIcon(forceShowIcon);
+ }
}
+ /**
+ * Sets the alignment of the popup window relative to the anchor view.
+ * <p>
+ * Changes take effect on the next call to show().
+ *
+ * @param gravity alignment of the popup relative to the anchor
+ */
public void setGravity(int gravity) {
mDropDownGravity = gravity;
}
+ /**
+ * @return alignment of the popup relative to the anchor
+ */
public int getGravity() {
return mDropDownGravity;
}
@@ -131,53 +139,176 @@
}
}
- public ListPopupWindow getPopup() {
+ public void show(int x, int y) {
+ if (!tryShow(x, y)) {
+ throw new IllegalStateException("MenuPopupHelper cannot be used without an anchor");
+ }
+ }
+
+ @NonNull
+ public MenuPopup getPopup() {
+ if (mPopup == null) {
+ mPopup = createPopup();
+ }
return mPopup;
}
+ /**
+ * Attempts to show the popup anchored to the view specified by {@link #setAnchorView(View)}.
+ *
+ * @return {@code true} if the popup was shown or was already showing prior to calling this
+ * method, {@code false} otherwise
+ */
public boolean tryShow() {
- mPopup = new ListPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
- mPopup.setOnDismissListener(this);
- mPopup.setOnItemClickListener(this);
- mPopup.setAdapter(mAdapter);
- mPopup.setModal(true);
+ if (isShowing()) {
+ return true;
+ }
- View anchor = mAnchorView;
- if (anchor != null) {
- final boolean addGlobalListener = mTreeObserver == null;
- mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
- if (addGlobalListener) mTreeObserver.addOnGlobalLayoutListener(this);
- mPopup.setAnchorView(anchor);
- mPopup.setDropDownGravity(mDropDownGravity);
- } else {
+ if (mAnchorView == null) {
return false;
}
- if (!mHasContentWidth) {
- mContentWidth = measureContentWidth();
- mHasContentWidth = true;
- }
-
- mPopup.setContentWidth(mContentWidth);
- mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
- mPopup.show();
- mPopup.getListView().setOnKeyListener(this);
+ showPopup(0, 0, false, false);
return true;
}
+ /**
+ * Shows the popup menu and makes a best-effort to anchor it to the
+ * specified (x,y) coordinate relative to the anchor view.
+ * <p>
+ * Additionally, the popup's transition epicenter (see
+ * {@link android.widget.PopupWindow#setEpicenterBounds(Rect)} will be
+ * centered on the specified coordinate, rather than using the bounds of
+ * the anchor view.
+ * <p>
+ * If the popup's resolved gravity is {@link Gravity#LEFT}, this will
+ * display the popup with its top-left corner at (x,y) relative to the
+ * anchor view. If the resolved gravity is {@link Gravity#RIGHT}, the
+ * popup's top-right corner will be at (x,y).
+ * <p>
+ * If the popup cannot be displayed fully on-screen, this method will
+ * attempt to scroll the anchor view's ancestors and/or offset the popup
+ * such that it may be displayed fully on-screen.
+ *
+ * @param x x coordinate relative to the anchor view
+ * @param y y coordinate relative to the anchor view
+ * @return {@code true} if the popup was shown or was already showing prior
+ * to calling this method, {@code false} otherwise
+ */
+ public boolean tryShow(int x, int y) {
+ if (isShowing()) {
+ return true;
+ }
+
+ if (mAnchorView == null) {
+ return false;
+ }
+
+ showPopup(x, y, true, true);
+ return true;
+ }
+
+ /**
+ * Creates the popup and assigns cached properties.
+ *
+ * @return an initialized popup
+ */
+ @NonNull
+ private MenuPopup createPopup() {
+ final WindowManager windowManager = (WindowManager) mContext.getSystemService(
+ Context.WINDOW_SERVICE);
+ final Display display = windowManager.getDefaultDisplay();
+ final Point displaySize = new Point();
+
+ if (Build.VERSION.SDK_INT >= 17) {
+ display.getRealSize(displaySize);
+ } else if (Build.VERSION.SDK_INT >= 13) {
+ display.getSize(displaySize);
+ } else {
+ displaySize.set(display.getWidth(), display.getHeight());
+ }
+
+ final int smallestWidth = Math.min(displaySize.x, displaySize.y);
+ final int minSmallestWidthCascading = mContext.getResources().getDimensionPixelSize(
+ R.dimen.abc_cascading_menus_min_smallest_width);
+ final boolean enableCascadingSubmenus = smallestWidth >= minSmallestWidthCascading;
+
+ final MenuPopup popup;
+ if (enableCascadingSubmenus) {
+ popup = new CascadingMenuPopup(mContext, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ } else {
+ popup = new StandardMenuPopup(mContext, mMenu, mAnchorView, mPopupStyleAttr,
+ mPopupStyleRes, mOverflowOnly);
+ }
+
+ // Assign immutable properties.
+ popup.addMenu(mMenu);
+ popup.setOnDismissListener(mInternalOnDismissListener);
+
+ // Assign mutable properties. These may be reassigned later.
+ popup.setAnchorView(mAnchorView);
+ popup.setCallback(mPresenterCallback);
+ popup.setForceShowIcon(mForceShowIcon);
+ popup.setGravity(mDropDownGravity);
+
+ return popup;
+ }
+
+ private void showPopup(int xOffset, int yOffset, boolean useOffsets, boolean showTitle) {
+ final MenuPopup popup = getPopup();
+ popup.setShowTitle(showTitle);
+
+ if (useOffsets) {
+ // If the resolved drop-down gravity is RIGHT, the popup's right
+ // edge will be aligned with the anchor view. Adjust by the anchor
+ // width such that the top-right corner is at the X offset.
+ final int hgrav = GravityCompat.getAbsoluteGravity(mDropDownGravity,
+ ViewCompat.getLayoutDirection(mAnchorView)) & Gravity.HORIZONTAL_GRAVITY_MASK;
+ if (hgrav == Gravity.RIGHT) {
+ xOffset -= mAnchorView.getWidth();
+ }
+
+ popup.setHorizontalOffset(xOffset);
+ popup.setVerticalOffset(yOffset);
+
+ // Set the transition epicenter to be roughly finger (or mouse
+ // cursor) sized and centered around the offset position. This
+ // will give the appearance that the window is emerging from
+ // the touch point.
+ final float density = mContext.getResources().getDisplayMetrics().density;
+ final int halfSize = (int) (TOUCH_EPICENTER_SIZE_DP * density / 2);
+ final Rect epicenter = new Rect(xOffset - halfSize, yOffset - halfSize,
+ xOffset + halfSize, yOffset + halfSize);
+ popup.setEpicenterBounds(epicenter);
+ }
+
+ popup.show();
+ }
+
+ /**
+ * Dismisses the popup, if showing.
+ */
+ @Override
public void dismiss() {
if (isShowing()) {
mPopup.dismiss();
}
}
- public void onDismiss() {
+ /**
+ * Called after the popup has been dismissed.
+ * <p>
+ * <strong>Note:</strong> Subclasses should call the super implementation
+ * last to ensure that any necessary tear down has occurred before the
+ * listener specified by {@link #setOnDismissListener(OnDismissListener)}
+ * is called.
+ */
+ protected void onDismiss() {
mPopup = null;
- mMenu.close();
- if (mTreeObserver != null) {
- if (!mTreeObserver.isAlive()) mTreeObserver = mAnchorView.getViewTreeObserver();
- mTreeObserver.removeGlobalOnLayoutListener(this);
- mTreeObserver = null;
+
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
}
}
@@ -186,223 +317,20 @@
}
@Override
- public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MenuAdapter adapter = mAdapter;
- adapter.mAdapterMenu.performItemAction(adapter.getItem(position), 0);
- }
-
- public boolean onKey(View v, int keyCode, KeyEvent event) {
- if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
- dismiss();
- return true;
- }
- return false;
- }
-
- private int measureContentWidth() {
- // Menus don't tend to be long, so this is more sane than it looks.
- int maxWidth = 0;
- View itemView = null;
- int itemType = 0;
-
- final ListAdapter adapter = mAdapter;
- final int widthMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int heightMeasureSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
- final int count = adapter.getCount();
- for (int i = 0; i < count; i++) {
- final int positionType = adapter.getItemViewType(i);
- if (positionType != itemType) {
- itemType = positionType;
- itemView = null;
- }
-
- if (mMeasureParent == null) {
- mMeasureParent = new FrameLayout(mContext);
- }
-
- itemView = adapter.getView(i, itemView, mMeasureParent);
- itemView.measure(widthMeasureSpec, heightMeasureSpec);
-
- final int itemWidth = itemView.getMeasuredWidth();
- if (itemWidth >= mPopupMaxWidth) {
- return mPopupMaxWidth;
- } else if (itemWidth > maxWidth) {
- maxWidth = itemWidth;
- }
- }
-
- return maxWidth;
- }
-
- @Override
- public void onGlobalLayout() {
- if (isShowing()) {
- final View anchor = mAnchorView;
- if (anchor == null || !anchor.isShown()) {
- dismiss();
- } else if (isShowing()) {
- // Recompute window size and position
- mPopup.show();
- }
- }
- }
-
- @Override
- public void initForMenu(Context context, MenuBuilder menu) {
- // Don't need to do anything; we added as a presenter in the constructor.
- }
-
- @Override
- public MenuView getMenuView(ViewGroup root) {
- throw new UnsupportedOperationException("MenuPopupHelpers manage their own views");
- }
-
- @Override
- public void updateMenuView(boolean cleared) {
- mHasContentWidth = false;
-
- if (mAdapter != null) {
- mAdapter.notifyDataSetChanged();
- }
- }
-
- @Override
- public void setCallback(Callback cb) {
+ public void setPresenterCallback(@Nullable MenuPresenter.Callback cb) {
mPresenterCallback = cb;
- }
-
- @Override
- public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
- if (subMenu.hasVisibleItems()) {
- MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu, mAnchorView);
- subPopup.setCallback(mPresenterCallback);
-
- boolean preserveIconSpacing = false;
- final int count = subMenu.size();
- for (int i = 0; i < count; i++) {
- MenuItem childItem = subMenu.getItem(i);
- if (childItem.isVisible() && childItem.getIcon() != null) {
- preserveIconSpacing = true;
- break;
- }
- }
- subPopup.setForceShowIcon(preserveIconSpacing);
-
- if (subPopup.tryShow()) {
- if (mPresenterCallback != null) {
- mPresenterCallback.onOpenSubMenu(subMenu);
- }
- return true;
- }
- }
- return false;
- }
-
- @Override
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- // Only care about the (sub)menu we're presenting.
- if (menu != mMenu) return;
-
- dismiss();
- if (mPresenterCallback != null) {
- mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ if (mPopup != null) {
+ mPopup.setCallback(cb);
}
}
- @Override
- public boolean flagActionItems() {
- return false;
- }
-
- public boolean expandItemActionView(MenuBuilder menu, MenuItemImpl item) {
- return false;
- }
-
- public boolean collapseItemActionView(MenuBuilder menu, MenuItemImpl item) {
- return false;
- }
-
- @Override
- public int getId() {
- return 0;
- }
-
- @Override
- public Parcelable onSaveInstanceState() {
- return null;
- }
-
- @Override
- public void onRestoreInstanceState(Parcelable state) {
- }
-
- private class MenuAdapter extends BaseAdapter {
- private MenuBuilder mAdapterMenu;
- private int mExpandedIndex = -1;
-
- public MenuAdapter(MenuBuilder menu) {
- mAdapterMenu = menu;
- findExpandedIndex();
- }
-
- public int getCount() {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex < 0) {
- return items.size();
- }
- return items.size() - 1;
- }
-
- public MenuItemImpl getItem(int position) {
- ArrayList<MenuItemImpl> items = mOverflowOnly ?
- mAdapterMenu.getNonActionItems() : mAdapterMenu.getVisibleItems();
- if (mExpandedIndex >= 0 && position >= mExpandedIndex) {
- position++;
- }
- return items.get(position);
- }
-
- public long getItemId(int position) {
- // Since a menu item's ID is optional, we'll use the position as an
- // ID for the item in the AdapterView
- return position;
- }
-
- public View getView(int position, View convertView, ViewGroup parent) {
- if (convertView == null) {
- convertView = mInflater.inflate(ITEM_LAYOUT, parent, false);
- }
-
- MenuView.ItemView itemView = (MenuView.ItemView) convertView;
- if (mForceShowIcon) {
- ((ListMenuItemView) convertView).setForceShowIcon(true);
- }
- itemView.initialize(getItem(position), 0);
- return convertView;
- }
-
- void findExpandedIndex() {
- final MenuItemImpl expandedItem = mMenu.getExpandedItem();
- if (expandedItem != null) {
- final ArrayList<MenuItemImpl> items = mMenu.getNonActionItems();
- final int count = items.size();
- for (int i = 0; i < count; i++) {
- final MenuItemImpl item = items.get(i);
- if (item == expandedItem) {
- mExpandedIndex = i;
- return;
- }
- }
- }
- mExpandedIndex = -1;
- }
-
+ /**
+ * Listener used to proxy dismiss callbacks to the helper's owner.
+ */
+ private final OnDismissListener mInternalOnDismissListener = new OnDismissListener() {
@Override
- public void notifyDataSetChanged() {
- findExpandedIndex();
- super.notifyDataSetChanged();
+ public void onDismiss() {
+ MenuPopupHelper.this.onDismiss();
}
- }
+ };
}
-
diff --git a/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
new file mode 100644
index 0000000..addebd7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/ShowableListMenu.java
@@ -0,0 +1,37 @@
+/*
+ * 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.view.menu;
+
+import android.widget.ListView;
+
+/**
+ * A list menu which can be shown and hidden and which is internally represented by a ListView.
+ *
+ * @hide
+ */
+public interface ShowableListMenu {
+ public void show();
+
+ public void dismiss();
+
+ public boolean isShowing();
+
+ /**
+ * @return The internal ListView for the visible menu.
+ */
+ public ListView getListView();
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
new file mode 100644
index 0000000..55ed1a1
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/view/menu/StandardMenuPopup.java
@@ -0,0 +1,330 @@
+/*
+ * 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.view.menu;
+
+import android.content.Context;
+import android.content.res.Resources;
+import android.os.Parcelable;
+import android.support.v7.appcompat.R;
+import android.support.v7.widget.MenuPopupWindow;
+import android.view.Gravity;
+import android.view.KeyEvent;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.View.OnAttachStateChangeListener;
+import android.view.View.OnKeyListener;
+import android.view.ViewTreeObserver;
+import android.view.ViewTreeObserver.OnGlobalLayoutListener;
+import android.widget.AdapterView.OnItemClickListener;
+import android.widget.FrameLayout;
+import android.widget.ListView;
+import android.widget.PopupWindow;
+import android.widget.PopupWindow.OnDismissListener;
+import android.widget.TextView;
+
+/**
+ * A standard menu popup in which when a submenu is opened, it replaces its parent menu in the
+ * viewport.
+ */
+final class StandardMenuPopup extends MenuPopup implements OnDismissListener, OnItemClickListener,
+ MenuPresenter, OnKeyListener {
+
+ private final Context mContext;
+
+ private final MenuBuilder mMenu;
+ private final MenuAdapter mAdapter;
+ private final boolean mOverflowOnly;
+ private final int mPopupMaxWidth;
+ private final int mPopupStyleAttr;
+ private final int mPopupStyleRes;
+ // The popup window is final in order to couple its lifecycle to the lifecycle of the
+ // StandardMenuPopup.
+ private final MenuPopupWindow mPopup;
+
+ private final OnGlobalLayoutListener mGlobalLayoutListener = new OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ // Only move the popup if it's showing and non-modal. We don't want
+ // to be moving around the only interactive window, since there's a
+ // good chance the user is interacting with it.
+ if (isShowing() && !mPopup.isModal()) {
+ final View anchor = mShownAnchorView;
+ if (anchor == null || !anchor.isShown()) {
+ dismiss();
+ } else {
+ // Recompute window size and position
+ mPopup.show();
+ }
+ }
+ }
+ };
+
+ private PopupWindow.OnDismissListener mOnDismissListener;
+
+ private View mAnchorView;
+ private View mShownAnchorView;
+ private Callback mPresenterCallback;
+ private ViewTreeObserver mTreeObserver;
+
+ /** Whether the popup has been dismissed. Once dismissed, it cannot be opened again. */
+ private boolean mWasDismissed;
+
+ /** Whether the cached content width value is valid. */
+ private boolean mHasContentWidth;
+
+ /** Cached content width. */
+ private int mContentWidth;
+
+ private int mDropDownGravity = Gravity.NO_GRAVITY;
+
+ private boolean mShowTitle;
+
+ public StandardMenuPopup(Context context, MenuBuilder menu, View anchorView, int popupStyleAttr,
+ int popupStyleRes, boolean overflowOnly) {
+ mContext = context;
+ mMenu = menu;
+ mOverflowOnly = overflowOnly;
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ mAdapter = new MenuAdapter(menu, inflater, mOverflowOnly);
+ mPopupStyleAttr = popupStyleAttr;
+ mPopupStyleRes = popupStyleRes;
+
+ final Resources res = context.getResources();
+ mPopupMaxWidth = Math.max(res.getDisplayMetrics().widthPixels / 2,
+ res.getDimensionPixelSize(R.dimen.abc_config_prefDialogWidth));
+
+ mAnchorView = anchorView;
+
+ mPopup = new MenuPopupWindow(mContext, null, mPopupStyleAttr, mPopupStyleRes);
+
+ // Present the menu using our context, not the menu builder's context.
+ menu.addMenuPresenter(this, context);
+ }
+
+ @Override
+ public void setForceShowIcon(boolean forceShow) {
+ mAdapter.setForceShowIcon(forceShow);
+ }
+
+ @Override
+ public void setGravity(int gravity) {
+ mDropDownGravity = gravity;
+ }
+
+ private boolean tryShow() {
+ if (isShowing()) {
+ return true;
+ }
+
+ if (mWasDismissed || mAnchorView == null) {
+ return false;
+ }
+
+ mShownAnchorView = mAnchorView;
+
+ mPopup.setOnDismissListener(this);
+ mPopup.setOnItemClickListener(this);
+ mPopup.setModal(true);
+
+ final View anchor = mShownAnchorView;
+ final boolean addGlobalListener = mTreeObserver == null;
+ mTreeObserver = anchor.getViewTreeObserver(); // Refresh to latest
+ if (addGlobalListener) {
+ mTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener);
+ }
+ mPopup.setAnchorView(anchor);
+ mPopup.setDropDownGravity(mDropDownGravity);
+
+ if (!mHasContentWidth) {
+ mContentWidth = measureIndividualMenuWidth(mAdapter, null, mContext, mPopupMaxWidth);
+ mHasContentWidth = true;
+ }
+
+ mPopup.setContentWidth(mContentWidth);
+ mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NOT_NEEDED);
+ mPopup.setEpicenterBounds(getEpicenterBounds());
+ mPopup.show();
+
+ final ListView listView = mPopup.getListView();
+ listView.setOnKeyListener(this);
+
+ if (mShowTitle && mMenu.getHeaderTitle() != null) {
+ FrameLayout titleItemView =
+ (FrameLayout) LayoutInflater.from(mContext).inflate(
+ R.layout.abc_popup_menu_header_item_layout, listView, false);
+ TextView titleView = (TextView) titleItemView.findViewById(android.R.id.title);
+ if (titleView != null) {
+ titleView.setText(mMenu.getHeaderTitle());
+ }
+ titleItemView.setEnabled(false);
+ listView.addHeaderView(titleItemView, null, false);
+ }
+
+ // Since addHeaderView() needs to be called before setAdapter() pre-v14, we have to set the
+ // adapter as late as possible, and then call show again to update
+ mPopup.setAdapter(mAdapter);
+ mPopup.show();
+
+ return true;
+ }
+
+ @Override
+ public void show() {
+ if (!tryShow()) {
+ throw new IllegalStateException("StandardMenuPopup cannot be used without an anchor");
+ }
+ }
+
+ @Override
+ public void dismiss() {
+ if (isShowing()) {
+ mPopup.dismiss();
+ }
+ }
+
+ @Override
+ public void addMenu(MenuBuilder menu) {
+ // No-op: standard implementation has only one menu which is set in the constructor.
+ }
+
+ @Override
+ public boolean isShowing() {
+ return !mWasDismissed && mPopup.isShowing();
+ }
+
+ @Override
+ public void onDismiss() {
+ mWasDismissed = true;
+ mMenu.close();
+
+ if (mTreeObserver != null) {
+ if (!mTreeObserver.isAlive()) mTreeObserver = mShownAnchorView.getViewTreeObserver();
+ mTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener);
+ mTreeObserver = null;
+ }
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss();
+ }
+ }
+
+ @Override
+ public void updateMenuView(boolean cleared) {
+ mHasContentWidth = false;
+
+ if (mAdapter != null) {
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+
+ @Override
+ public void setCallback(Callback cb) {
+ mPresenterCallback = cb;
+ }
+
+ @Override
+ public boolean onSubMenuSelected(SubMenuBuilder subMenu) {
+ if (subMenu.hasVisibleItems()) {
+ final MenuPopupHelper subPopup = new MenuPopupHelper(mContext, subMenu,
+ mShownAnchorView, mOverflowOnly, mPopupStyleAttr, mPopupStyleRes);
+ subPopup.setPresenterCallback(mPresenterCallback);
+ subPopup.setForceShowIcon(MenuPopup.shouldPreserveIconSpacing(subMenu));
+
+ // Pass responsibility for handling onDismiss to the submenu.
+ subPopup.setOnDismissListener(mOnDismissListener);
+ mOnDismissListener = null;
+
+ // Close this menu popup to make room for the submenu popup.
+ mMenu.close(false /* closeAllMenus */);
+
+ // Show the new sub-menu popup at the same location as this popup.
+ final int horizontalOffset = mPopup.getHorizontalOffset();
+ final int verticalOffset = mPopup.getVerticalOffset();
+ if (subPopup.tryShow(horizontalOffset, verticalOffset)) {
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onOpenSubMenu(subMenu);
+ }
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @Override
+ public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
+ // Only care about the (sub)menu we're presenting.
+ if (menu != mMenu) return;
+
+ dismiss();
+ if (mPresenterCallback != null) {
+ mPresenterCallback.onCloseMenu(menu, allMenusAreClosing);
+ }
+ }
+
+ @Override
+ public boolean flagActionItems() {
+ return false;
+ }
+
+ @Override
+ public Parcelable onSaveInstanceState() {
+ return null;
+ }
+
+ @Override
+ public void onRestoreInstanceState(Parcelable state) {
+ }
+
+ @Override
+ public void setAnchorView(View anchor) {
+ mAnchorView = anchor;
+ }
+
+ @Override
+ public boolean onKey(View v, int keyCode, KeyEvent event) {
+ if (event.getAction() == KeyEvent.ACTION_UP && keyCode == KeyEvent.KEYCODE_MENU) {
+ dismiss();
+ return true;
+ }
+ return false;
+ }
+
+ @Override
+ public void setOnDismissListener(OnDismissListener listener) {
+ mOnDismissListener = listener;
+ }
+
+ @Override
+ public ListView getListView() {
+ return mPopup.getListView();
+ }
+
+
+ @Override
+ public void setHorizontalOffset(int x) {
+ mPopup.setHorizontalOffset(x);
+ }
+
+ @Override
+ public void setVerticalOffset(int y) {
+ mPopup.setVerticalOffset(y);
+ }
+
+ @Override
+ public void setShowTitle(boolean showTitle) {
+ mShowTitle = showTitle;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/view/menu/SubMenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/SubMenuBuilder.java
index a213b77..7a6273f 100644
--- a/v7/appcompat/src/android/support/v7/view/menu/SubMenuBuilder.java
+++ b/v7/appcompat/src/android/support/v7/view/menu/SubMenuBuilder.java
@@ -76,7 +76,7 @@
@Override
public MenuBuilder getRootMenu() {
- return mParentMenu;
+ return mParentMenu.getRootMenu();
}
@Override
@@ -96,28 +96,23 @@
}
public SubMenu setHeaderIcon(Drawable icon) {
- super.setHeaderIconInt(icon);
- return this;
+ return (SubMenu) super.setHeaderIconInt(icon);
}
public SubMenu setHeaderIcon(int iconRes) {
- super.setHeaderIconInt(ContextCompat.getDrawable(getContext(), iconRes));
- return this;
+ return (SubMenu) super.setHeaderIconInt(iconRes);
}
public SubMenu setHeaderTitle(CharSequence title) {
- super.setHeaderTitleInt(title);
- return this;
+ return (SubMenu) super.setHeaderTitleInt(title);
}
public SubMenu setHeaderTitle(int titleRes) {
- super.setHeaderTitleInt(getContext().getResources().getString(titleRes));
- return this;
+ return (SubMenu) super.setHeaderTitleInt(titleRes);
}
public SubMenu setHeaderView(View view) {
- super.setHeaderViewInt(view);
- return this;
+ return (SubMenu) super.setHeaderViewInt(view);
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java b/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
index c8c69ea..00e7d4c 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionBarContainer.java
@@ -224,15 +224,17 @@
return mTabContainer;
}
- //@Override
- public ActionMode startActionModeForChild(View child, ActionMode.Callback callback) {
+ public android.view.ActionMode startActionModeForChild(View child,
+ android.view.ActionMode.Callback callback) {
// No starting an action mode for an action bar child! (Where would it go?)
return null;
}
- @Override
- public android.view.ActionMode startActionModeForChild(View originalView,
- android.view.ActionMode.Callback callback) {
+ public android.view.ActionMode startActionModeForChild(View child,
+ android.view.ActionMode.Callback callback, int type) {
+ if (type != android.view.ActionMode.TYPE_PRIMARY) {
+ return super.startActionModeForChild(child, callback, type);
+ }
return null;
}
diff --git a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
index e0e1a83..c3d9893 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActionMenuPresenter.java
@@ -22,6 +22,8 @@
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.ActionProvider;
import android.support.v4.view.GravityCompat;
@@ -34,6 +36,7 @@
import android.support.v7.view.menu.MenuItemImpl;
import android.support.v7.view.menu.MenuPopupHelper;
import android.support.v7.view.menu.MenuView;
+import android.support.v7.view.menu.ShowableListMenu;
import android.support.v7.view.menu.SubMenuBuilder;
import android.util.SparseBooleanArray;
import android.view.MenuItem;
@@ -86,7 +89,7 @@
}
@Override
- public void initForMenu(Context context, MenuBuilder menu) {
+ public void initForMenu(@NonNull Context context, @Nullable MenuBuilder menu) {
super.initForMenu(context, menu);
final Resources res = context.getResources();
@@ -132,8 +135,7 @@
public void onConfigurationChanged(Configuration newConfig) {
if (!mMaxItemsSet) {
- mMaxItems = mContext.getResources().getInteger(
- R.integer.abc_max_action_buttons);
+ mMaxItems = ActionBarPolicy.get(mContext).getMaxActionButtons();
}
if (mMenu != null) {
mMenu.onItemsChanged(true);
@@ -180,8 +182,11 @@
@Override
public MenuView getMenuView(ViewGroup root) {
+ MenuView oldMenuView = mMenuView;
MenuView result = super.getMenuView(root);
- ((ActionMenuView) result).setPresenter(this);
+ if (oldMenuView != result) {
+ ((ActionMenuView) result).setPresenter(this);
+ }
return result;
}
@@ -288,14 +293,29 @@
}
View anchor = findViewForItem(topSubMenu.getItem());
if (anchor == null) {
- if (mOverflowButton == null) return false;
- anchor = mOverflowButton;
+ // This means the submenu was opened from an overflow menu item, indicating the
+ // MenuPopupHelper will handle opening the submenu via its MenuPopup. Return false to
+ // ensure that the MenuPopup acts as presenter for the submenu, and acts on its
+ // responsibility to display the new submenu.
+ return false;
}
mOpenSubMenuId = subMenu.getItem().getItemId();
- mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu);
- mActionButtonPopup.setAnchorView(anchor);
+
+ boolean preserveIconSpacing = false;
+ final int count = subMenu.size();
+ for (int i = 0; i < count; i++) {
+ MenuItem childItem = subMenu.getItem(i);
+ if (childItem.isVisible() && childItem.getIcon() != null) {
+ preserveIconSpacing = true;
+ break;
+ }
+ }
+
+ mActionButtonPopup = new ActionButtonSubmenu(mContext, subMenu, anchor);
+ mActionButtonPopup.setForceShowIcon(preserveIconSpacing);
mActionButtonPopup.show();
+
super.onSubMenuSelected(subMenu);
return true;
}
@@ -398,8 +418,16 @@
}
public boolean flagActionItems() {
- final ArrayList<MenuItemImpl> visibleItems = mMenu.getVisibleItems();
- final int itemsSize = visibleItems.size();
+ final ArrayList<MenuItemImpl> visibleItems;
+ final int itemsSize;
+ if (mMenu != null) {
+ visibleItems = mMenu.getVisibleItems();
+ itemsSize = visibleItems.size();
+ } else {
+ visibleItems = null;
+ itemsSize = 0;
+ }
+
int maxActions = mMaxItems;
int widthLimit = mActionItemWidthLimit;
final int querySpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED);
@@ -546,6 +574,10 @@
@Override
public void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ return;
+ }
+
SavedState saved = (SavedState) state;
if (saved.openSubMenuId > 0) {
MenuItem item = mMenu.findItem(saved.openSubMenuId);
@@ -561,8 +593,8 @@
if (isVisible) {
// Not a submenu, but treat it like one.
super.onSubMenuSelected(null);
- } else {
- mMenu.close(false);
+ } else if (mMenu != null) {
+ mMenu.close(false /* closeAllMenus */);
}
}
@@ -615,9 +647,9 @@
setVisibility(VISIBLE);
setEnabled(true);
- setOnTouchListener(new ListPopupWindow.ForwardingListener(this) {
+ setOnTouchListener(new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
if (mOverflowPopup == null) {
return null;
}
@@ -691,31 +723,27 @@
}
private class OverflowPopup extends MenuPopupHelper {
-
public OverflowPopup(Context context, MenuBuilder menu, View anchorView,
boolean overflowOnly) {
super(context, menu, anchorView, overflowOnly, R.attr.actionOverflowMenuStyle);
setGravity(GravityCompat.END);
- setCallback(mPopupPresenterCallback);
+ setPresenterCallback(mPopupPresenterCallback);
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
if (mMenu != null) {
mMenu.close();
}
mOverflowPopup = null;
+
+ super.onDismiss();
}
}
private class ActionButtonSubmenu extends MenuPopupHelper {
- private SubMenuBuilder mSubMenu;
-
- public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu) {
- super(context, subMenu, null, false,
- R.attr.actionOverflowMenuStyle);
- mSubMenu = subMenu;
+ public ActionButtonSubmenu(Context context, SubMenuBuilder subMenu, View anchorView) {
+ super(context, subMenu, anchorView, false, R.attr.actionOverflowMenuStyle);
MenuItemImpl item = (MenuItemImpl) subMenu.getItem();
if (!item.isActionButton()) {
@@ -723,30 +751,19 @@
setAnchorView(mOverflowButton == null ? (View) mMenuView : mOverflowButton);
}
- setCallback(mPopupPresenterCallback);
-
- boolean preserveIconSpacing = false;
- final int count = subMenu.size();
- for (int i = 0; i < count; i++) {
- MenuItem childItem = subMenu.getItem(i);
- if (childItem.isVisible() && childItem.getIcon() != null) {
- preserveIconSpacing = true;
- break;
- }
- }
- setForceShowIcon(preserveIconSpacing);
+ setPresenterCallback(mPopupPresenterCallback);
}
@Override
- public void onDismiss() {
- super.onDismiss();
+ protected void onDismiss() {
mActionButtonPopup = null;
mOpenSubMenuId = 0;
+
+ super.onDismiss();
}
}
private class PopupPresenterCallback implements Callback {
-
@Override
public boolean onOpenSubMenu(MenuBuilder subMenu) {
if (subMenu == null) return false;
@@ -759,7 +776,7 @@
@Override
public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
if (menu instanceof SubMenuBuilder) {
- ((SubMenuBuilder) menu).getRootMenu().close(false);
+ menu.getRootMenu().close(false /* closeAllMenus */);
}
final Callback cb = getCallback();
if (cb != null) {
@@ -776,7 +793,9 @@
}
public void run() {
- mMenu.changeMenuMode();
+ if (mMenu != null) {
+ mMenu.changeMenuMode();
+ }
final View menuView = (View) mMenuView;
if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) {
mOverflowPopup = mPopup;
@@ -787,7 +806,7 @@
private class ActionMenuPopupCallback extends ActionMenuItemView.PopupCallback {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return mActionButtonPopup != null ? mActionButtonPopup.getPopup() : null;
}
}
diff --git a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
index 3ea3a2e..8d5c45d 100644
--- a/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
+++ b/v7/appcompat/src/android/support/v7/widget/ActivityChooserView.java
@@ -27,6 +27,7 @@
import android.support.v4.view.ActionProvider;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.view.LayoutInflater;
import android.view.View;
@@ -237,9 +238,9 @@
final FrameLayout expandButton = (FrameLayout) findViewById(R.id.expand_activities_button);
expandButton.setOnClickListener(mCallbacks);
- expandButton.setOnTouchListener(new ListPopupWindow.ForwardingListener(expandButton) {
+ expandButton.setOnTouchListener(new ForwardingListener(expandButton) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return getListPopupWindow();
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
index 90d823c..9d11416 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java
@@ -17,12 +17,12 @@
package android.support.v7.widget;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
-import android.support.v7.graphics.drawable.DrawableUtils;
import android.util.AttributeSet;
import android.view.View;
@@ -33,6 +33,7 @@
private TintInfo mInternalBackgroundTint;
private TintInfo mBackgroundTint;
+ private TintInfo mTmpInfo;
AppCompatBackgroundHelper(View view, AppCompatDrawableManager drawableManager) {
mView = view;
@@ -40,7 +41,7 @@
}
void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
- TypedArray a = mView.getContext().obtainStyledAttributes(attrs,
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
R.styleable.ViewBackgroundHelper, defStyleAttr, 0);
try {
if (a.hasValue(R.styleable.ViewBackgroundHelper_android_background)) {
@@ -108,9 +109,17 @@
void applySupportBackgroundTint() {
final Drawable background = mView.getBackground();
if (background != null) {
+ if (Build.VERSION.SDK_INT == 21 && applyFrameworkTintUsingColorFilter(background)) {
+ // GradientDrawable doesn't implement setTintList on API 21, and since there is
+ // no nice way to unwrap DrawableContainers we have to blanket apply this
+ // on API 21. This needs to be called before the internal tints below so it takes
+ // effect on any widgets using the compat tint on API 21 (EditText)
+ return;
+ }
+
if (mBackgroundTint != null) {
- AppCompatDrawableManager
- .tintDrawable(background, mBackgroundTint, mView.getDrawableState());
+ AppCompatDrawableManager.tintDrawable(background, mBackgroundTint,
+ mView.getDrawableState());
} else if (mInternalBackgroundTint != null) {
AppCompatDrawableManager.tintDrawable(background, mInternalBackgroundTint,
mView.getDrawableState());
@@ -130,4 +139,35 @@
}
applySupportBackgroundTint();
}
+
+ /**
+ * Applies the framework background tint to a view, but using the compat method (ColorFilter)
+ *
+ * @return true if a tint was applied
+ */
+ private boolean applyFrameworkTintUsingColorFilter(@NonNull Drawable background) {
+ if (mTmpInfo == null) {
+ mTmpInfo = new TintInfo();
+ }
+ final TintInfo info = mTmpInfo;
+ info.clear();
+
+ final ColorStateList tintList = ViewCompat.getBackgroundTintList(mView);
+ if (tintList != null) {
+ info.mHasTintList = true;
+ info.mTintList = tintList;
+ }
+ final PorterDuff.Mode mode = ViewCompat.getBackgroundTintMode(mView);
+ if (mode != null) {
+ info.mHasTintMode = true;
+ info.mTintMode = mode;
+ }
+
+ if (info.mHasTintList || info.mHasTintMode) {
+ AppCompatDrawableManager.tintDrawable(background, info, mView.getDrawableState());
+ return true;
+ }
+
+ return false;
+ }
}
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/AppCompatCompoundButtonHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatCompoundButtonHelper.java
index ad3e6d3..5b26d8d 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatCompoundButtonHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatCompoundButtonHelper.java
@@ -25,7 +25,6 @@
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.widget.CompoundButtonCompat;
import android.support.v7.appcompat.R;
-import android.support.v7.graphics.drawable.DrawableUtils;
import android.util.AttributeSet;
import android.widget.CompoundButton;
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 4fcaf13..8251dd8 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -16,31 +16,44 @@
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;
import android.graphics.drawable.Drawable;
-import android.graphics.drawable.DrawableContainer;
-import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.Drawable.ConstantState;
import android.graphics.drawable.LayerDrawable;
import android.graphics.drawable.StateListDrawable;
import android.os.Build;
+import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.graphics.drawable.AnimatedVectorDrawableCompat;
+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.LongSparseArray;
import android.support.v4.util.LruCache;
import android.support.v7.appcompat.R;
+import android.support.v7.widget.VectorEnabledTintResources;
+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.ref.WeakReference;
import java.util.WeakHashMap;
+import static android.support.v7.content.res.AppCompatResources.getColorStateList;
import static android.support.v7.widget.ThemeUtils.getDisabledThemeAttrColor;
import static android.support.v7.widget.ThemeUtils.getThemeAttrColor;
import static android.support.v7.widget.ThemeUtils.getThemeAttrColorStateList;
@@ -50,32 +63,42 @@
*/
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);
+ private interface InflateDelegate {
+ Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme);
}
- private static final String TAG = "TintManager";
+ private static final String TAG = "AppCompatDrawableManager";
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 final String PLATFORM_VD_CLAZZ = "android.graphics.drawable.VectorDrawable";
private static AppCompatDrawableManager INSTANCE;
public static AppCompatDrawableManager get() {
if (INSTANCE == null) {
INSTANCE = new AppCompatDrawableManager();
+ installDefaultInflateDelegates(INSTANCE);
}
return INSTANCE;
}
+ private static void installDefaultInflateDelegates(@NonNull AppCompatDrawableManager manager) {
+ final int sdk = Build.VERSION.SDK_INT;
+ if (sdk < 23) {
+ // We only want to use the automatic VectorDrawableCompat handling where it's
+ // needed: on devices running before Lollipop
+ manager.addDelegate("vector", new VdcInflateDelegate());
+
+ if (sdk >= 11) {
+ // AnimatedVectorDrawableCompat only works on API v11+
+ manager.addDelegate("animated-vector", new AvdcInflateDelegate());
+ }
+ }
+ }
+
private static final ColorFilterLruCache COLOR_FILTER_CACHE = new ColorFilterLruCache(6);
/**
@@ -93,18 +116,13 @@
* {@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_seekbar_tick_mark_material,
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
+ R.drawable.abc_ic_menu_paste_mtrl_am_alpha
};
/**
@@ -133,16 +151,8 @@
* {@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_textfield_search_material
};
/**
@@ -156,7 +166,16 @@
};
private WeakHashMap<Context, SparseArray<ColorStateList>> mTintLists;
- private ArrayList<InflateDelegate> mDelegates;
+ private ArrayMap<String, InflateDelegate> mDelegates;
+ private SparseArray<String> mKnownDrawableIdTags;
+
+ private final Object mDrawableCacheLock = new Object();
+ private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>>
+ mDrawableCaches = new WeakHashMap<>(0);
+
+ private TypedValue mTypedValue;
+
+ private boolean mHasCheckedVectorDrawableSetup;
public Drawable getDrawable(@NonNull Context context, @DrawableRes int resId) {
return getDrawable(context, resId, false);
@@ -164,63 +183,247 @@
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;
- }
- }
+ checkVectorDrawableSetup(context);
+
+ Drawable drawable = loadDrawableFromDelegates(context, resId);
+ if (drawable == null) {
+ drawable = createDrawableIfNeeded(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) {
- if (Build.VERSION.SDK_INT >= 8) {
- // Mutate can cause NPEs on 2.1
+ // Tint it if needed
+ drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
+ }
+ if (drawable != null) {
+ // See if we need to 'fix' the drawable
+ DrawableUtils.fixDrawable(drawable);
+ }
+ return drawable;
+ }
+
+ private static long createCacheKey(TypedValue tv) {
+ return (((long) tv.assetCookie) << 32) | tv.data;
+ }
+
+ private Drawable createDrawableIfNeeded(@NonNull Context context,
+ @DrawableRes final int resId) {
+ if (mTypedValue == null) {
+ mTypedValue = new TypedValue();
+ }
+ final TypedValue tv = mTypedValue;
+ context.getResources().getValue(resId, tv, true);
+ final long key = createCacheKey(tv);
+
+ Drawable dr = getCachedDrawable(context, key);
+ if (dr != null) {
+ // If we got a cached drawable, return it
+ return dr;
+ }
+
+ // Else we need to try and create one...
+ if (resId == R.drawable.abc_cab_background_top_material) {
+ dr = new LayerDrawable(new Drawable[]{
+ getDrawable(context, R.drawable.abc_cab_background_internal_bg),
+ getDrawable(context, R.drawable.abc_cab_background_top_mtrl_alpha)
+ });
+ }
+
+ if (dr != null) {
+ dr.setChangingConfigurations(tv.changingConfigurations);
+ // If we reached here then we created a new drawable, add it to the cache
+ addDrawableToCache(context, key, dr);
+ }
+
+ return dr;
+ }
+
+ 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 (DrawableUtils.canSafelyMutateDrawable(drawable)) {
drawable = drawable.mutate();
}
+ drawable = DrawableCompat.wrap(drawable);
+ DrawableCompat.setTintList(drawable, tintList);
- final ColorStateList tintList = getTintList(context, resId);
- if (tintList != null) {
- // First wrap the Drawable and set the tint list
- 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 {
- 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;
- }
+ // 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_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_material
+ || 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()) {
+ if (mKnownDrawableIdTags != null) {
+ final String 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);
+
+ final long key = createCacheKey(tv);
+
+ Drawable dr = getCachedDrawable(context, key);
+ if (dr != null) {
+ if (DEBUG) {
+ Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
+ context.getResources().getResourceName(resId));
+ }
+ // We have a cached drawable, return it!
+ return dr;
+ }
+
+ 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();
+ // Add the tag name 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) {
+ dr = delegate.createFromXmlInner(context, parser, attrs,
+ context.getTheme());
+ }
+ if (dr != null) {
+ // Add it to the drawable cache
+ dr.setChangingConfigurations(tv.changingConfigurations);
+ if (addDrawableToCache(context, key, dr) && DEBUG) {
+ Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
+ context.getResources().getResourceName(resId));
+ }
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Exception while inflating drawable", e);
+ }
+ }
+ if (dr == null) {
+ // 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 dr;
+ }
+
+ return null;
+ }
+
+ private Drawable getCachedDrawable(@NonNull final Context context, final long key) {
+ synchronized (mDrawableCacheLock) {
+ final LongSparseArray<WeakReference<ConstantState>> cache
+ = mDrawableCaches.get(context);
+ if (cache == null) {
+ return null;
+ }
+
+ final WeakReference<ConstantState> wr = cache.get(key);
+ if (wr != null) {
+ // We have the key, and the secret
+ ConstantState entry = wr.get();
+ if (entry != null) {
+ return entry.newDrawable(context.getResources());
+ } else {
+ // Our entry has been purged
+ cache.delete(key);
+ }
+ }
+ }
+ return null;
+ }
+
+ private boolean addDrawableToCache(@NonNull final Context context, final long key,
+ @NonNull final Drawable drawable) {
+ final ConstantState cs = drawable.getConstantState();
+ if (cs != null) {
+ synchronized (mDrawableCacheLock) {
+ LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
+ if (cache == null) {
+ cache = new LongSparseArray<>();
+ mDrawableCaches.put(context, cache);
+ }
+ cache.put(key, new WeakReference<ConstantState>(cs));
+ }
+ return true;
+ }
+ return false;
+ }
+
+ public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
+ @NonNull VectorEnabledTintResources 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;
+ }
+
+ static boolean tintDrawableUsingColorFilter(@NonNull Context context,
@DrawableRes final int resId, @NonNull Drawable drawable) {
PorterDuff.Mode tintMode = DEFAULT_MODE;
boolean colorAttrSet = false;
@@ -241,9 +444,16 @@
colorAttr = android.R.attr.colorForeground;
colorAttrSet = true;
alpha = Math.round(0.16f * 255);
+ } else if (resId == R.drawable.abc_dialog_material_background) {
+ colorAttr = android.R.attr.colorBackground;
+ colorAttrSet = true;
}
if (colorAttrSet) {
+ if (DrawableUtils.canSafelyMutateDrawable(drawable)) {
+ drawable = drawable.mutate();
+ }
+
final int color = getThemeAttrColor(context, colorAttr);
drawable.setColorFilter(getPorterDuffColorFilter(color, tintMode));
@@ -252,7 +462,8 @@
}
if (DEBUG) {
- Log.d(TAG, "Tinted Drawable: " + context.getResources().getResourceName(resId) +
+ Log.d(TAG, "[tintDrawableUsingColorFilter] Tinted "
+ + context.getResources().getResourceName(resId) +
" with color: #" + Integer.toHexString(color));
}
return true;
@@ -260,18 +471,16 @@
return false;
}
- public void addDelegate(@NonNull InflateDelegate delegate) {
+ private 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);
+ private void removeDelegate(@NonNull String tagName, @NonNull InflateDelegate delegate) {
+ if (mDelegates != null && mDelegates.get(tagName) == delegate) {
+ mDelegates.remove(tagName);
}
}
@@ -301,27 +510,28 @@
if (tint == null) {
// ...if the cache did not contain a color state list, try and create one
if (resId == R.drawable.abc_edit_text_material) {
- tint = createEditTextColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_edittext);
} else if (resId == R.drawable.abc_switch_track_mtrl_alpha) {
- tint = createSwitchTrackColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_switch_track);
} else if (resId == R.drawable.abc_switch_thumb_material) {
- tint = createSwitchThumbColorStateList(context);
- } else if (resId == R.drawable.abc_btn_default_mtrl_shape
- || resId == R.drawable.abc_btn_borderless_material) {
+ tint = getColorStateList(context, R.color.abc_tint_switch_thumb);
+ } else if (resId == R.drawable.abc_btn_default_mtrl_shape) {
tint = createDefaultButtonColorStateList(context);
+ } else if (resId == R.drawable.abc_btn_borderless_material) {
+ tint = createBorderlessButtonColorStateList(context);
} else if (resId == R.drawable.abc_btn_colored_material) {
tint = createColoredButtonColorStateList(context);
} else if (resId == R.drawable.abc_spinner_mtrl_am_alpha
|| resId == R.drawable.abc_spinner_textfield_background_material) {
- tint = createSpinnerColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_spinner);
} else if (arrayContains(TINT_COLOR_CONTROL_NORMAL, resId)) {
tint = getThemeAttrColorStateList(context, R.attr.colorControlNormal);
} else if (arrayContains(TINT_COLOR_CONTROL_STATE_LIST, resId)) {
- tint = createDefaultColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_default);
} else if (arrayContains(TINT_CHECKABLE_BUTTON_LIST, resId)) {
- tint = createCheckableButtonColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_btn_checkable);
} else if (resId == R.drawable.abc_seekbar_thumb_material) {
- tint = createSeekbarThumbColorStateList(context);
+ tint = getColorStateList(context, R.color.abc_tint_seek_thumb);
}
if (tint != null) {
@@ -352,178 +562,24 @@
themeTints.append(resId, tintList);
}
- private ColorStateList createDefaultColorStateList(Context context) {
- /**
- * Generate the default color state list which uses the colorControl attributes.
- * Order is important here. The default enabled state needs to go at the bottom.
- */
-
- final int colorControlNormal = getThemeAttrColor(context, R.attr.colorControlNormal);
- final int colorControlActivated = getThemeAttrColor(context, R.attr.colorControlActivated);
-
- final int[][] states = new int[7][];
- final int[] colors = new int[7];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.FOCUSED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.ACTIVATED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.PRESSED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- states[i] = ThemeUtils.SELECTED_STATE_SET;
- colors[i] = colorControlActivated;
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = colorControlNormal;
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createCheckableButtonColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSwitchTrackColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.1f);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated, 0.3f);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, android.R.attr.colorForeground, 0.3f);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSwitchThumbColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- final ColorStateList thumbColor = getThemeAttrColorStateList(context,
- R.attr.colorSwitchThumbNormal);
-
- if (thumbColor != null && thumbColor.isStateful()) {
- // If colorSwitchThumbNormal is a valid ColorStateList, extract the default and
- // disabled colors from it
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = thumbColor.getColorForState(states[i], 0);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = thumbColor.getDefaultColor();
- i++;
- } else {
- // Else we'll use an approximation using the default disabled alpha
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
- i++;
-
- states[i] = ThemeUtils.CHECKED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorSwitchThumbNormal);
- i++;
- }
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createEditTextColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- // Default enabled state
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
private ColorStateList createDefaultButtonColorStateList(Context context) {
- return createButtonColorStateList(context, R.attr.colorButtonNormal);
+ return createButtonColorStateList(context,
+ getThemeAttrColor(context, R.attr.colorButtonNormal));
+ }
+
+ private ColorStateList createBorderlessButtonColorStateList(Context context) {
+ return createButtonColorStateList(context, Color.TRANSPARENT);
}
private ColorStateList createColoredButtonColorStateList(Context context) {
- return createButtonColorStateList(context, R.attr.colorAccent);
+ return createButtonColorStateList(context, getThemeAttrColor(context, R.attr.colorAccent));
}
- private ColorStateList createButtonColorStateList(Context context, int baseColorAttr) {
+ private ColorStateList createButtonColorStateList(Context context, @ColorInt int baseColor) {
final int[][] states = new int[4][];
final int[] colors = new int[4];
int i = 0;
- final int baseColor = getThemeAttrColor(context, baseColorAttr);
final int colorControlHighlight = getThemeAttrColor(context, R.attr.colorControlHighlight);
// Disabled state
@@ -547,44 +603,6 @@
return new ColorStateList(states, colors);
}
- private ColorStateList createSpinnerColorStateList(Context context) {
- final int[][] states = new int[3][];
- final int[] colors = new int[3];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.NOT_PRESSED_OR_FOCUSED_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlNormal);
- i++;
-
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
- private ColorStateList createSeekbarThumbColorStateList(Context context) {
- final int[][] states = new int[2][];
- final int[] colors = new int[2];
- int i = 0;
-
- // Disabled state
- states[i] = ThemeUtils.DISABLED_STATE_SET;
- colors[i] = getDisabledThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- states[i] = ThemeUtils.EMPTY_STATE_SET;
- colors[i] = getThemeAttrColor(context, R.attr.colorControlActivated);
- i++;
-
- return new ColorStateList(states, colors);
- }
-
private static class ColorFilterLruCache extends LruCache<Integer, PorterDuffColorFilter> {
public ColorFilterLruCache(int maxSize) {
@@ -608,7 +626,8 @@
}
public static void tintDrawable(Drawable drawable, TintInfo tint, int[] state) {
- if (shouldMutateBackground(drawable) && drawable.mutate() != drawable) {
+ if (DrawableUtils.canSafelyMutateDrawable(drawable)
+ && drawable.mutate() != drawable) {
Log.d(TAG, "Mutated drawable is not the same instance as the input.");
return;
}
@@ -629,30 +648,6 @@
}
}
- private static boolean shouldMutateBackground(Drawable drawable) {
- if (drawable instanceof LayerDrawable) {
- return Build.VERSION.SDK_INT >= 16;
- } else if (drawable instanceof InsetDrawable) {
- return Build.VERSION.SDK_INT >= 14;
- } else if (drawable instanceof StateListDrawable) {
- // StateListDrawable has a bug in mutate() on API 7
- return Build.VERSION.SDK_INT >= 8;
- } else if (drawable instanceof DrawableContainer) {
- // If we have a DrawableContainer, let's traverse it's child array
- final Drawable.ConstantState state = drawable.getConstantState();
- if (state instanceof DrawableContainer.DrawableContainerState) {
- final DrawableContainer.DrawableContainerState containerState =
- (DrawableContainer.DrawableContainerState) state;
- for (Drawable child : containerState.getChildren()) {
- if (!shouldMutateBackground(child)) {
- return false;
- }
- }
- }
- }
- return true;
- }
-
private static PorterDuffColorFilter createTintFilter(ColorStateList tint,
PorterDuff.Mode tintMode, final int[] state) {
if (tint == null || tintMode == null) {
@@ -676,6 +671,59 @@
}
private static void setPorterDuffColorFilter(Drawable d, int color, PorterDuff.Mode mode) {
+ if (DrawableUtils.canSafelyMutateDrawable(d)) {
+ d = d.mutate();
+ }
d.setColorFilter(getPorterDuffColorFilter(color, mode == null ? DEFAULT_MODE : mode));
}
+
+ private void checkVectorDrawableSetup(@NonNull Context context) {
+ if (mHasCheckedVectorDrawableSetup) {
+ // We've already checked so return now...
+ return;
+ }
+ // Here we will check that a known Vector drawable resource inside AppCompat can be
+ // correctly decoded. We use one that will almost definitely be used in the future to
+ // negate any wasted work
+ mHasCheckedVectorDrawableSetup = true;
+ final Drawable d = getDrawable(context, R.drawable.abc_ic_ab_back_material);
+ if (d == null || !isVectorDrawable(d)) {
+ mHasCheckedVectorDrawableSetup = false;
+ throw new IllegalStateException("This app has been built with an incorrect "
+ + "configuration. Please configure your build for VectorDrawableCompat.");
+ }
+ }
+
+ private static boolean isVectorDrawable(@NonNull Drawable d) {
+ return d instanceof VectorDrawableCompat
+ || PLATFORM_VD_CLAZZ.equals(d.getClass().getName());
+ }
+
+ private static class VdcInflateDelegate implements InflateDelegate {
+ @Override
+ public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
+ try {
+ return VectorDrawableCompat
+ .createFromXmlInner(context.getResources(), parser, attrs, theme);
+ } catch (Exception e) {
+ Log.e("VdcInflateDelegate", "Exception while inflating <vector>", e);
+ return null;
+ }
+ }
+ }
+
+ private static class AvdcInflateDelegate implements InflateDelegate {
+ @Override
+ public Drawable createFromXmlInner(@NonNull Context context, @NonNull XmlPullParser parser,
+ @NonNull AttributeSet attrs, @Nullable Resources.Theme theme) {
+ try {
+ return AnimatedVectorDrawableCompat
+ .createFromXmlInner(context, context.getResources(), parser, attrs, theme);
+ } catch (Exception e) {
+ Log.e("AvdcInflateDelegate", "Exception while inflating <animated-vector>", e);
+ return null;
+ }
+ }
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageButton.java
index 18c07c9..a274ba6 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();
@@ -146,4 +146,8 @@
mBackgroundTintHelper.applySupportBackgroundTint();
}
}
+
+ public boolean hasOverlappingRendering() {
+ return mImageHelper.hasOverlappingRendering() && super.hasOverlappingRendering();
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
index f6e5bae..25c7700 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatImageHelper.java
@@ -16,41 +16,78 @@
package android.support.v7.widget;
+import android.graphics.drawable.Drawable;
+import android.os.Build;
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};
+/**
+ * @hide
+ */
+public class AppCompatImageHelper {
private final ImageView mView;
private final AppCompatDrawableManager mDrawableManager;
- AppCompatImageHelper(ImageView view, AppCompatDrawableManager drawableManager) {
+ public AppCompatImageHelper(ImageView view, AppCompatDrawableManager drawableManager) {
mView = view;
mDrawableManager = drawableManager;
}
- void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
- TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
- VIEW_ATTRS, defStyleAttr, 0);
+ public void loadFromAttributes(AttributeSet attrs, int defStyleAttr) {
+ TintTypedArray a = null;
try {
- if (a.hasValue(0)) {
- mView.setImageDrawable(a.getDrawable(0));
+ Drawable drawable = mView.getDrawable();
+
+ if (drawable == null) {
+ a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
+ R.styleable.AppCompatImageView, defStyleAttr, 0);
+
+ // If the view doesn't already have a drawable (from android:src), try loading
+ // it from srcCompat
+ final int id = a.getResourceId(R.styleable.AppCompatImageView_srcCompat, -1);
+ if (id != -1) {
+ drawable = mDrawableManager.getDrawable(mView.getContext(), id);
+ if (drawable != null) {
+ mView.setImageDrawable(drawable);
+ }
+ }
+ }
+
+ if (drawable != null) {
+ DrawableUtils.fixDrawable(drawable);
}
} finally {
- a.recycle();
+ if (a != null) {
+ a.recycle();
+ }
}
}
- void setImageResource(int resId) {
+ public void setImageResource(int resId) {
if (resId != 0) {
- mView.setImageDrawable(mDrawableManager != null
+ final Drawable d = mDrawableManager != null
? mDrawableManager.getDrawable(mView.getContext(), resId)
- : ContextCompat.getDrawable(mView.getContext(), resId));
+ : ContextCompat.getDrawable(mView.getContext(), resId);
+ if (d != null) {
+ DrawableUtils.fixDrawable(d);
+ }
+ mView.setImageDrawable(d);
} else {
mView.setImageDrawable(null);
}
}
+
+ boolean hasOverlappingRendering() {
+ final Drawable background = mView.getBackground();
+ if (Build.VERSION.SDK_INT >= 21
+ && background instanceof android.graphics.drawable.RippleDrawable) {
+ // RippleDrawable has an issue on L+ when used with an alpha animation.
+ // This workaround should be disabled when the platform bug is fixed. See b/27715789
+ return false;
+ }
+ return true;
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java b/v7/appcompat/src/android/support/v7/widget/AppCompatImageView.java
index 0d85121..b8b1b2f 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();
@@ -146,4 +146,8 @@
mBackgroundTintHelper.applySupportBackgroundTint();
}
}
+
+ public boolean hasOverlappingRendering() {
+ return mImageHelper.hasOverlappingRendering() && super.hasOverlappingRendering();
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/AppCompatPopupWindow.java
index 0cce845..88b4620 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatPopupWindow.java
@@ -19,6 +19,10 @@
import android.annotation.TargetApi;
import android.content.Context;
import android.os.Build;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
import android.support.v4.widget.PopupWindowCompat;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
@@ -30,26 +34,48 @@
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
-/**
- * @hide
- */
-public class AppCompatPopupWindow extends PopupWindow {
+class AppCompatPopupWindow extends PopupWindow {
private static final String TAG = "AppCompatPopupWindow";
private static final boolean COMPAT_OVERLAP_ANCHOR = Build.VERSION.SDK_INT < 21;
private boolean mOverlapAnchor;
- public AppCompatPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+ public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
super(context, attrs, defStyleAttr);
+ init(context, attrs, defStyleAttr, 0);
+ }
+ @TargetApi(11)
+ public AppCompatPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ init(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ private void init(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
- R.styleable.PopupWindow, defStyleAttr, 0);
+ R.styleable.PopupWindow, defStyleAttr, defStyleRes);
if (a.hasValue(R.styleable.PopupWindow_overlapAnchor)) {
setSupportOverlapAnchor(a.getBoolean(R.styleable.PopupWindow_overlapAnchor, false));
}
// We re-set this for tinting purposes
setBackgroundDrawable(a.getDrawable(R.styleable.PopupWindow_android_popupBackground));
+
+ final int sdk = Build.VERSION.SDK_INT;
+ if (defStyleRes != 0 && sdk < 11) {
+ // If we have a defStyleRes, but we're on < API 11, we need to manually set attributes
+ // from the style
+ if (sdk >= 9) {
+ // android:popupAnimationStyle was added in API 9
+ if (a.hasValue(R.styleable.PopupWindow_android_popupAnimationStyle)) {
+ setAnimationStyle(a.getResourceId(
+ R.styleable.PopupWindow_android_popupAnimationStyle, -1));
+ }
+ }
+ }
+
a.recycle();
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java
index 209a6c2..eaa9cef 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatProgressBarHelper.java
@@ -96,7 +96,8 @@
return newBg;
} else if (drawable instanceof BitmapDrawable) {
- final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap();
+ final BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
+ final Bitmap tileBitmap = bitmapDrawable.getBitmap();
if (mSampleTile == null) {
mSampleTile = tileBitmap;
}
@@ -105,6 +106,7 @@
final BitmapShader bitmapShader = new BitmapShader(tileBitmap,
Shader.TileMode.REPEAT, Shader.TileMode.CLAMP);
shapeDrawable.getPaint().setShader(bitmapShader);
+ shapeDrawable.getPaint().setColorFilter(bitmapDrawable.getPaint().getColorFilter());
return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT,
ClipDrawable.HORIZONTAL) : shapeDrawable;
}
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/AppCompatSeekBar.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
index 4f3ee05..5355660 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBar.java
@@ -17,6 +17,7 @@
package android.support.v7.widget;
import android.content.Context;
+import android.graphics.Canvas;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.widget.SeekBar;
@@ -49,4 +50,21 @@
mAppCompatSeekBarHelper.loadFromAttributes(attrs, defStyleAttr);
}
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ mAppCompatSeekBarHelper.drawTickMarks(canvas);
+ }
+
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+ mAppCompatSeekBarHelper.drawableStateChanged();
+ }
+
+ @Override
+ public void jumpDrawablesToCurrentState() {
+ super.jumpDrawablesToCurrentState();
+ mAppCompatSeekBarHelper.jumpDrawablesToCurrentState();
+ }
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
index 2af2acc..e26faee 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSeekBarHelper.java
@@ -16,18 +16,27 @@
package android.support.v7.widget;
+import android.content.res.ColorStateList;
+import android.graphics.Canvas;
+import android.graphics.PorterDuff;
import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
+import android.support.v4.graphics.drawable.DrawableCompat;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.widget.SeekBar;
class AppCompatSeekBarHelper extends AppCompatProgressBarHelper {
- private static final int[] TINT_ATTRS = {
- android.R.attr.thumb
- };
-
private final SeekBar mView;
+ private Drawable mTickMark;
+ private ColorStateList mTickMarkTintList = null;
+ private PorterDuff.Mode mTickMarkTintMode = null;
+ private boolean mHasTickMarkTint = false;
+ private boolean mHasTickMarkTintMode = false;
+
AppCompatSeekBarHelper(SeekBar view, AppCompatDrawableManager drawableManager) {
super(view, drawableManager);
mView = view;
@@ -37,11 +46,137 @@
super.loadFromAttributes(attrs, defStyleAttr);
TintTypedArray a = TintTypedArray.obtainStyledAttributes(mView.getContext(), attrs,
- TINT_ATTRS, defStyleAttr, 0);
- Drawable drawable = a.getDrawableIfKnown(0);
+ R.styleable.AppCompatSeekBar, defStyleAttr, 0);
+ final Drawable drawable = a.getDrawableIfKnown(R.styleable.AppCompatSeekBar_android_thumb);
if (drawable != null) {
mView.setThumb(drawable);
}
+
+ final Drawable tickMark = a.getDrawable(R.styleable.AppCompatSeekBar_tickMark);
+ setTickMark(tickMark);
+
+ if (a.hasValue(R.styleable.AppCompatSeekBar_tickMarkTintMode)) {
+ mTickMarkTintMode = DrawableUtils.parseTintMode(a.getInt(
+ R.styleable.AppCompatSeekBar_tickMarkTintMode, -1), mTickMarkTintMode);
+ mHasTickMarkTintMode = true;
+ }
+
+ if (a.hasValue(R.styleable.AppCompatSeekBar_tickMarkTint)) {
+ mTickMarkTintList = a.getColorStateList(R.styleable.AppCompatSeekBar_tickMarkTint);
+ mHasTickMarkTint = true;
+ }
+
a.recycle();
+
+ applyTickMarkTint();
}
+
+ void setTickMark(@Nullable Drawable tickMark) {
+ if (mTickMark != null) {
+ mTickMark.setCallback(null);
+ }
+
+ mTickMark = tickMark;
+
+ if (tickMark != null) {
+ tickMark.setCallback(mView);
+ DrawableCompat.setLayoutDirection(tickMark, ViewCompat.getLayoutDirection(mView));
+ if (tickMark.isStateful()) {
+ tickMark.setState(mView.getDrawableState());
+ }
+ applyTickMarkTint();
+ }
+
+ mView.invalidate();
+ }
+
+ @Nullable
+ Drawable getTickMark() {
+ return mTickMark;
+ }
+
+ void setTickMarkTintList(@Nullable ColorStateList tint) {
+ mTickMarkTintList = tint;
+ mHasTickMarkTint = true;
+
+ applyTickMarkTint();
+ }
+
+ @Nullable
+ ColorStateList getTickMarkTintList() {
+ return mTickMarkTintList;
+ }
+
+ void setTickMarkTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mTickMarkTintMode = tintMode;
+ mHasTickMarkTintMode = true;
+
+ applyTickMarkTint();
+ }
+
+ @Nullable
+ PorterDuff.Mode getTickMarkTintMode() {
+ return mTickMarkTintMode;
+ }
+
+ private void applyTickMarkTint() {
+ if (mTickMark != null && (mHasTickMarkTint || mHasTickMarkTintMode)) {
+ mTickMark = mTickMark.mutate();
+
+ if (mHasTickMarkTint) {
+ mTickMark.setTintList(mTickMarkTintList);
+ }
+
+ if (mHasTickMarkTintMode) {
+ mTickMark.setTintMode(mTickMarkTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mTickMark.isStateful()) {
+ mTickMark.setState(mView.getDrawableState());
+ }
+ }
+ }
+
+ void jumpDrawablesToCurrentState() {
+ if (mTickMark != null) {
+ mTickMark.jumpToCurrentState();
+ }
+ }
+
+ void drawableStateChanged() {
+ final Drawable tickMark = mTickMark;
+ if (tickMark != null && tickMark.isStateful()
+ && tickMark.setState(mView.getDrawableState())) {
+ mView.invalidateDrawable(tickMark);
+ }
+ }
+
+ /**
+ * Draw the tick marks.
+ */
+ void drawTickMarks(Canvas canvas) {
+ if (mTickMark != null) {
+ final int count = mView.getMax();
+ if (count > 1) {
+ final int w = mTickMark.getIntrinsicWidth();
+ final int h = mTickMark.getIntrinsicHeight();
+ final int halfW = w >= 0 ? w / 2 : 1;
+ final int halfH = h >= 0 ? h / 2 : 1;
+ mTickMark.setBounds(-halfW, -halfH, halfW, halfH);
+
+ final float spacing = (mView.getWidth() - mView.getPaddingLeft()
+ - mView.getPaddingRight()) / (float) count;
+ final int saveCount = canvas.save();
+ canvas.translate(mView.getPaddingLeft(), mView.getHeight() / 2);
+ for (int i = 0; i <= count; i++) {
+ mTickMark.draw(canvas);
+ canvas.translate(spacing, 0);
+ }
+ canvas.restoreToCount(saveCount);
+ }
+ }
+ }
+
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
index 964ea76..aa19c7e 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatSpinner.java
@@ -27,10 +27,12 @@
import android.os.Build;
import android.support.annotation.DrawableRes;
import android.support.annotation.Nullable;
+import android.support.v4.content.ContextCompat;
import android.support.v4.view.TintableBackgroundView;
import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.support.v7.view.ContextThemeWrapper;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
@@ -47,14 +49,14 @@
/**
- * A {@link Spinner} which supports compatible features on older version of the platform,
+ * A {@link Spinner} which supports compatible features on older versions of the platform,
* including:
* <ul>
- * <li>Allows dynamic tint of it background via the background tint methods in
+ * <li>Dynamic tinting of the background via the background tint methods in
* {@link android.support.v4.view.ViewCompat}.</li>
- * <li>Allows setting of the background tint using {@link R.attr#backgroundTint} and
+ * <li>Configuring the background tint using {@link R.attr#backgroundTint} and
* {@link R.attr#backgroundTintMode}.</li>
- * <li>Allows setting of the popups theme using {@link R.attr#popupTheme}.</li>
+ * <li>Setting the popup theme using {@link R.attr#popupTheme}.</li>
* </ul>
*
* <p>This will automatically be used when you use {@link Spinner} in your layouts.
@@ -83,7 +85,7 @@
private Context mPopupContext;
/** Forwarding listener used to implement drag-to-open. */
- private ListPopupWindow.ForwardingListener mForwardingListener;
+ private ForwardingListener mForwardingListener;
/** Temporary holder for setAdapter() calls from the super constructor. */
private SpinnerAdapter mTempAdapter;
@@ -249,9 +251,9 @@
pa.recycle();
mPopup = popup;
- mForwardingListener = new ListPopupWindow.ForwardingListener(this) {
+ mForwardingListener = new ForwardingListener(this) {
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
return popup;
}
@@ -268,8 +270,8 @@
final CharSequence[] entries = a.getTextArray(R.styleable.Spinner_android_entries);
if (entries != null) {
- final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(context,
- R.layout.support_simple_spinner_dropdown_item, entries);
+ final ArrayAdapter<CharSequence> adapter = new ArrayAdapter<>(
+ context, android.R.layout.simple_spinner_item, entries);
adapter.setDropDownViewResource(R.layout.support_simple_spinner_dropdown_item);
setAdapter(adapter);
}
@@ -309,7 +311,7 @@
}
public void setPopupBackgroundResource(@DrawableRes int resId) {
- setPopupBackgroundDrawable(getPopupContext().getDrawable(resId));
+ setPopupBackgroundDrawable(ContextCompat.getDrawable(getPopupContext(), resId));
}
public Drawable getPopupBackground() {
@@ -427,10 +429,15 @@
@Override
public boolean performClick() {
- if (mPopup != null && !mPopup.isShowing()) {
- mPopup.show();
+ if (mPopup != null) {
+ // If we have a popup, show it if needed, or just consume the click...
+ if (!mPopup.isShowing()) {
+ mPopup.show();
+ }
return true;
}
+
+ // Else let the platform handle the click
return super.performClick();
}
diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
index d8e7784..d410b2c 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatTextHelper.java
@@ -18,11 +18,11 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.support.v7.appcompat.R;
import android.support.v7.text.AllCapsTransformationMethod;
+import android.text.method.PasswordTransformationMethod;
import android.util.AttributeSet;
import android.widget.TextView;
@@ -35,10 +35,13 @@
return new AppCompatTextHelper(textView);
}
- private static final int[] VIEW_ATTRS = {android.R.attr.textAppearance,
- android.R.attr.drawableLeft, android.R.attr.drawableTop,
- android.R.attr.drawableRight, android.R.attr.drawableBottom };
- private static final int[] TEXT_APPEARANCE_ATTRS = {R.attr.textAllCaps};
+ private static final int[] VIEW_ATTRS = {
+ android.R.attr.textAppearance,
+ android.R.attr.drawableLeft,
+ android.R.attr.drawableTop,
+ android.R.attr.drawableRight,
+ android.R.attr.drawableBottom
+ };
final TextView mView;
@@ -56,9 +59,9 @@
final AppCompatDrawableManager drawableManager = AppCompatDrawableManager.get();
// First read the TextAppearance style id
- TypedArray a = context.obtainStyledAttributes(attrs, VIEW_ATTRS, defStyleAttr, 0);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs,
+ VIEW_ATTRS, defStyleAttr, 0);
final int ap = a.getResourceId(0, -1);
-
// Now read the compound drawable and grab any tints
if (a.hasValue(1)) {
mDrawableLeftTint = createTintInfo(context, drawableManager, a.getResourceId(1, 0));
@@ -74,29 +77,76 @@
}
a.recycle();
- // Now check TextAppearance's textAllCaps value
+ // PasswordTransformationMethod wipes out all other TransformationMethod instances
+ // in TextView's constructor, so we should only set a new transformation method
+ // if we don't have a PasswordTransformationMethod currently...
+ final boolean hasPwdTm =
+ mView.getTransformationMethod() instanceof PasswordTransformationMethod;
+ boolean allCaps = false;
+ boolean allCapsSet = false;
+ ColorStateList textColor = null;
+
+ // First check TextAppearance's textAllCaps value
if (ap != -1) {
- TypedArray appearance = context.obtainStyledAttributes(ap, R.styleable.TextAppearance);
- if (appearance.hasValue(R.styleable.TextAppearance_textAllCaps)) {
- setAllCaps(appearance.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
+ a = TintTypedArray.obtainStyledAttributes(context, ap, R.styleable.TextAppearance);
+ if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+ allCapsSet = true;
+ allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
}
- appearance.recycle();
+ if (Build.VERSION.SDK_INT < 23
+ && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, the text color may contain theme references
+ // so let's re-set using our own inflater
+ textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ }
+ a.recycle();
}
- // Now read the style's value
- a = context.obtainStyledAttributes(attrs, TEXT_APPEARANCE_ATTRS, defStyleAttr, 0);
- if (a.getBoolean(0, false)) {
- setAllCaps(true);
+ // Now read the style's values
+ a = TintTypedArray.obtainStyledAttributes(context, attrs, R.styleable.TextAppearance,
+ defStyleAttr, 0);
+ if (!hasPwdTm && a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+ allCapsSet = true;
+ allCaps = a.getBoolean(R.styleable.TextAppearance_textAllCaps, false);
+ }
+ if (Build.VERSION.SDK_INT < 23
+ && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, the text color may contain theme references
+ // so let's re-set using our own inflater
+ textColor = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
}
a.recycle();
+
+ if (textColor != null) {
+ mView.setTextColor(textColor);
+ }
+
+ if (!hasPwdTm && allCapsSet) {
+ setAllCaps(allCaps);
+ }
}
void onSetTextAppearance(Context context, int resId) {
- TypedArray appearance = context.obtainStyledAttributes(resId, TEXT_APPEARANCE_ATTRS);
- if (appearance.hasValue(0)) {
- setAllCaps(appearance.getBoolean(0, false));
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(context,
+ resId, R.styleable.TextAppearance);
+ if (a.hasValue(R.styleable.TextAppearance_textAllCaps)) {
+ // This breaks away slightly from the logic in TextView.setTextAppearance that serves
+ // as an "overlay" on the current state of the TextView. Since android:textAllCaps
+ // may have been set to true in this text appearance, we need to make sure that
+ // app:textAllCaps has the chance to override it
+ setAllCaps(a.getBoolean(R.styleable.TextAppearance_textAllCaps, false));
}
- appearance.recycle();
+ if (Build.VERSION.SDK_INT < 23
+ && a.hasValue(R.styleable.TextAppearance_android_textColor)) {
+ // If we're running on < API 23, the text color may contain theme references
+ // so let's re-set using our own inflater
+ final ColorStateList textColor
+ = a.getColorStateList(R.styleable.TextAppearance_android_textColor);
+ if (textColor != null) {
+ mView.setTextColor(textColor);
+ }
+ }
+ a.recycle();
}
void setAllCaps(boolean allCaps) {
@@ -129,6 +179,7 @@
final TintInfo tintInfo = new TintInfo();
tintInfo.mHasTintList = true;
tintInfo.mTintList = tintList;
+ return tintInfo;
}
return null;
}
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/ButtonBarLayout.java b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
index 20fa89d..a2827b6 100644
--- a/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
+++ b/v7/appcompat/src/android/support/v7/widget/ButtonBarLayout.java
@@ -17,6 +17,9 @@
import android.content.Context;
import android.content.res.TypedArray;
+import android.os.Build;
+import android.support.v4.content.res.ConfigurationHelper;
+import android.support.v4.view.ViewCompat;
import android.support.v7.appcompat.R;
import android.util.AttributeSet;
import android.view.Gravity;
@@ -30,6 +33,9 @@
* @hide
*/
public class ButtonBarLayout extends LinearLayout {
+ // Whether to allow vertically stacked button bars. This is disabled for
+ // configurations with a small (e.g. less than 320dp) screen height. -->
+ private static final int ALLOW_STACKING_MIN_HEIGHT_DP = 320;
/** Whether the current configuration allows stacking. */
private boolean mAllowStacking;
@@ -37,8 +43,12 @@
public ButtonBarLayout(Context context, AttributeSet attrs) {
super(context, attrs);
+ final boolean allowStackingDefault =
+ ConfigurationHelper.getScreenHeightDp(getResources())
+ >= ALLOW_STACKING_MIN_HEIGHT_DP;
final TypedArray ta = context.obtainStyledAttributes(attrs, R.styleable.ButtonBarLayout);
- mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking, false);
+ mAllowStacking = ta.getBoolean(R.styleable.ButtonBarLayout_allowStacking,
+ allowStackingDefault);
ta.recycle();
}
@@ -76,9 +86,24 @@
}
super.onMeasure(initialWidthMeasureSpec, heightMeasureSpec);
if (mAllowStacking && !isStacked()) {
- final int measuredWidth = getMeasuredWidthAndState();
- final int measuredWidthState = measuredWidth & MEASURED_STATE_MASK;
- if (measuredWidthState == MEASURED_STATE_TOO_SMALL) {
+ final boolean stack;
+
+ if (Build.VERSION.SDK_INT >= 11) {
+ // On API v11+ we can use MEASURED_STATE_MASK and MEASURED_STATE_TOO_SMALL
+ final int measuredWidth = ViewCompat.getMeasuredWidthAndState(this);
+ final int measuredWidthState = measuredWidth & ViewCompat.MEASURED_STATE_MASK;
+ stack = measuredWidthState == ViewCompat.MEASURED_STATE_TOO_SMALL;
+ } else {
+ // Before that we need to manually total up the children's preferred width.
+ // This isn't perfect but works well enough for a workaround.
+ int childWidthTotal = 0;
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ childWidthTotal += getChildAt(i).getMeasuredWidth();
+ }
+ stack = (childWidthTotal + getPaddingLeft() + getPaddingRight()) > widthSize;
+ }
+
+ if (stack) {
setStacked(true);
// Measure again in the new orientation.
needsRemeasure = true;
@@ -107,4 +132,4 @@
private boolean isStacked() {
return getOrientation() == LinearLayout.VERTICAL;
}
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java b/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
index b57b40f..283d43b 100644
--- a/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
+++ b/v7/appcompat/src/android/support/v7/widget/DrawableUtils.java
@@ -16,23 +16,34 @@
package android.support.v7.widget;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.graphics.drawable.DrawableContainer;
+import android.graphics.drawable.GradientDrawable;
+import android.graphics.drawable.InsetDrawable;
+import android.graphics.drawable.LayerDrawable;
+import android.graphics.drawable.ScaleDrawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.Build;
+import android.support.annotation.NonNull;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.util.Log;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
-class DrawableUtils {
+/** @hide */
+public class DrawableUtils {
private static final String TAG = "DrawableUtils";
public static final Rect INSETS_NONE = new Rect();
-
private static Class<?> sInsetsClazz;
+ private static final String VECTOR_DRAWABLE_CLAZZ_NAME
+ = "android.graphics.drawable.VectorDrawable";
+
static {
if (Build.VERSION.SDK_INT >= 18) {
try {
@@ -93,4 +104,92 @@
return INSETS_NONE;
}
+ /**
+ * Attempt the fix any issues in the given drawable, usually caused by platform bugs in the
+ * implementation. This method should be call after retrieval from
+ * {@link android.content.res.Resources} or a {@link android.content.res.TypedArray}.
+ */
+ static void fixDrawable(@NonNull final Drawable drawable) {
+ if (Build.VERSION.SDK_INT == 21
+ && VECTOR_DRAWABLE_CLAZZ_NAME.equals(drawable.getClass().getName())) {
+ fixVectorDrawableTinting(drawable);
+ }
+ }
+
+ /**
+ * Some drawable implementations have problems with mutation. This method returns false if
+ * there is a known issue in the given drawable's implementation.
+ */
+ public static boolean canSafelyMutateDrawable(@NonNull Drawable drawable) {
+ if (Build.VERSION.SDK_INT < 8 && drawable instanceof StateListDrawable) {
+ // StateListDrawable has a bug in mutate() on API 7
+ return false;
+ } else if (Build.VERSION.SDK_INT < 15 && drawable instanceof InsetDrawable) {
+ return false;
+ } else if (Build.VERSION.SDK_INT < 15 && drawable instanceof GradientDrawable) {
+ // GradientDrawable has a bug pre-ICS which results in mutate() resulting
+ // in loss of color
+ return false;
+ } else if (Build.VERSION.SDK_INT < 17 && drawable instanceof LayerDrawable) {
+ return false;
+ }
+
+ if (drawable instanceof DrawableContainer) {
+ // If we have a DrawableContainer, let's traverse it's child array
+ final Drawable.ConstantState state = drawable.getConstantState();
+ if (state instanceof DrawableContainer.DrawableContainerState) {
+ final DrawableContainer.DrawableContainerState containerState =
+ (DrawableContainer.DrawableContainerState) state;
+ for (final Drawable child : containerState.getChildren()) {
+ if (!canSafelyMutateDrawable(child)) {
+ return false;
+ }
+ }
+ }
+ } else if (drawable instanceof android.support.v4.graphics.drawable.DrawableWrapper) {
+ return canSafelyMutateDrawable(
+ ((android.support.v4.graphics.drawable.DrawableWrapper) drawable)
+ .getWrappedDrawable());
+ } else if (drawable instanceof android.support.v7.graphics.drawable.DrawableWrapper) {
+ return canSafelyMutateDrawable(
+ ((android.support.v7.graphics.drawable.DrawableWrapper) drawable)
+ .getWrappedDrawable());
+ } else if (drawable instanceof ScaleDrawable) {
+ return canSafelyMutateDrawable(((ScaleDrawable) drawable).getDrawable());
+ }
+
+ return true;
+ }
+
+ /**
+ * VectorDrawable has an issue on API 21 where it sometimes doesn't create its tint filter.
+ * Fixed by toggling it's state to force a filter creation.
+ */
+ private static void fixVectorDrawableTinting(final Drawable drawable) {
+ final int[] originalState = drawable.getState();
+ if (originalState == null || originalState.length == 0) {
+ // The drawable doesn't have a state, so set it to be checked
+ drawable.setState(ThemeUtils.CHECKED_STATE_SET);
+ } else {
+ // Else the drawable does have a state, so clear it
+ drawable.setState(ThemeUtils.EMPTY_STATE_SET);
+ }
+ // Now set the original state
+ drawable.setState(originalState);
+ }
+
+ static PorterDuff.Mode parseTintMode(int value, PorterDuff.Mode defaultMode) {
+ switch (value) {
+ case 3: return PorterDuff.Mode.SRC_OVER;
+ case 5: return PorterDuff.Mode.SRC_IN;
+ case 9: return PorterDuff.Mode.SRC_ATOP;
+ case 14: return PorterDuff.Mode.MULTIPLY;
+ case 15: return PorterDuff.Mode.SCREEN;
+ case 16: return Build.VERSION.SDK_INT >= 11
+ ? PorterDuff.Mode.valueOf("ADD")
+ : defaultMode;
+ default: return defaultMode;
+ }
+ }
+
}
diff --git a/v7/appcompat/src/android/support/v7/widget/DropDownListView.java b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
new file mode 100644
index 0000000..f6a75f7
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/DropDownListView.java
@@ -0,0 +1,277 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.os.Build;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v4.view.ViewPropertyAnimatorCompat;
+import android.support.v4.widget.ListViewAutoScrollHelper;
+import android.support.v7.appcompat.R;
+import android.view.MotionEvent;
+import android.view.View;
+
+/**
+ * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
+ * make sure the list uses the appropriate drawables and states when
+ * displayed on screen within a drop down. The focus is never actually
+ * passed to the drop down in this mode; the list only looks focused.</p>
+ */
+class DropDownListView extends ListViewCompat {
+
+ /*
+ * WARNING: This is a workaround for a touch mode issue.
+ *
+ * Touch mode is propagated lazily to windows. This causes problems in
+ * the following scenario:
+ * - Type something in the AutoCompleteTextView and get some results
+ * - Move down with the d-pad to select an item in the list
+ * - Move up with the d-pad until the selection disappears
+ * - Type more text in the AutoCompleteTextView *using the soft keyboard*
+ * and get new results; you are now in touch mode
+ * - The selection comes back on the first item in the list, even though
+ * the list is supposed to be in touch mode
+ *
+ * Using the soft keyboard triggers the touch mode change but that change
+ * is propagated to our window only after the first list layout, therefore
+ * after the list attempts to resurrect the selection.
+ *
+ * The trick to work around this issue is to pretend the list is in touch
+ * mode when we know that the selection should not appear, that is when
+ * we know the user moved the selection away from the list.
+ *
+ * This boolean is set to true whenever we explicitly hide the list's
+ * selection and reset to false whenever we know the user moved the
+ * selection back to the list.
+ *
+ * When this boolean is true, isInTouchMode() returns true, otherwise it
+ * returns super.isInTouchMode().
+ */
+ private boolean mListSelectionHidden;
+
+ /**
+ * True if this wrapper should fake focus.
+ */
+ private boolean mHijackFocus;
+
+ /** Whether to force drawing of the pressed state selector. */
+ private boolean mDrawsInPressedState;
+
+ /** Current drag-to-open click animation, if any. */
+ private ViewPropertyAnimatorCompat mClickAnimation;
+
+ /** Helper for drag-to-open auto scrolling. */
+ private ListViewAutoScrollHelper mScrollHelper;
+
+ /**
+ * <p>Creates a new list view wrapper.</p>
+ *
+ * @param context this view's context
+ */
+ public DropDownListView(Context context, boolean hijackFocus) {
+ super(context, null, R.attr.dropDownListViewStyle);
+ mHijackFocus = hijackFocus;
+ setCacheColorHint(0); // Transparent, since the background drawable could be anything.
+ }
+
+ /**
+ * Handles forwarded events.
+ *
+ * @param activePointerId id of the pointer that activated forwarding
+ * @return whether the event was handled
+ */
+ public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
+ boolean handledEvent = true;
+ boolean clearPressedItem = false;
+
+ final int actionMasked = MotionEventCompat.getActionMasked(event);
+ switch (actionMasked) {
+ case MotionEvent.ACTION_CANCEL:
+ handledEvent = false;
+ break;
+ case MotionEvent.ACTION_UP:
+ handledEvent = false;
+ // $FALL-THROUGH$
+ case MotionEvent.ACTION_MOVE:
+ final int activeIndex = event.findPointerIndex(activePointerId);
+ if (activeIndex < 0) {
+ handledEvent = false;
+ break;
+ }
+
+ final int x = (int) event.getX(activeIndex);
+ final int y = (int) event.getY(activeIndex);
+ final int position = pointToPosition(x, y);
+ if (position == INVALID_POSITION) {
+ clearPressedItem = true;
+ break;
+ }
+
+ final View child = getChildAt(position - getFirstVisiblePosition());
+ setPressedItem(child, position, x, y);
+ handledEvent = true;
+
+ if (actionMasked == MotionEvent.ACTION_UP) {
+ clickPressedItem(child, position);
+ }
+ break;
+ }
+
+ // Failure to handle the event cancels forwarding.
+ if (!handledEvent || clearPressedItem) {
+ clearPressedItem();
+ }
+
+ // Manage automatic scrolling.
+ if (handledEvent) {
+ if (mScrollHelper == null) {
+ mScrollHelper = new ListViewAutoScrollHelper(this);
+ }
+ mScrollHelper.setEnabled(true);
+ mScrollHelper.onTouch(this, event);
+ } else if (mScrollHelper != null) {
+ mScrollHelper.setEnabled(false);
+ }
+
+ return handledEvent;
+ }
+
+ /**
+ * Starts an alpha animation on the selector. When the animation ends,
+ * the list performs a click on the item.
+ */
+ private void clickPressedItem(final View child, final int position) {
+ final long id = getItemIdAtPosition(position);
+ performItemClick(child, position, id);
+ }
+
+ /**
+ * Sets whether the list selection is hidden, as part of a workaround for a
+ * touch mode issue (see the declaration for mListSelectionHidden).
+ *
+ * @param hideListSelection {@code true} to hide list selection,
+ * {@code false} to show
+ */
+ void setListSelectionHidden(boolean hideListSelection) {
+ mListSelectionHidden = hideListSelection;
+ }
+
+ private void clearPressedItem() {
+ mDrawsInPressedState = false;
+ setPressed(false);
+ // This will call through to updateSelectorState()
+ drawableStateChanged();
+
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null) {
+ motionView.setPressed(false);
+ }
+
+ if (mClickAnimation != null) {
+ mClickAnimation.cancel();
+ mClickAnimation = null;
+ }
+ }
+
+ private void setPressedItem(View child, int position, float x, float y) {
+ mDrawsInPressedState = true;
+
+ // Ordering is essential. First, update the container's pressed state.
+ if (Build.VERSION.SDK_INT >= 21) {
+ drawableHotspotChanged(x, y);
+ }
+ if (!isPressed()) {
+ setPressed(true);
+ }
+
+ // Next, run layout to stabilize child positions.
+ layoutChildren();
+
+ // Manage the pressed view based on motion position. This allows us to
+ // play nicely with actual touch and scroll events.
+ if (mMotionPosition != INVALID_POSITION) {
+ final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
+ if (motionView != null && motionView != child && motionView.isPressed()) {
+ motionView.setPressed(false);
+ }
+ }
+ mMotionPosition = position;
+
+ // Offset for child coordinates.
+ final float childX = x - child.getLeft();
+ final float childY = y - child.getTop();
+ if (Build.VERSION.SDK_INT >= 21) {
+ child.drawableHotspotChanged(childX, childY);
+ }
+ if (!child.isPressed()) {
+ child.setPressed(true);
+ }
+
+ // Ensure that keyboard focus starts from the last touched position.
+ positionSelectorLikeTouchCompat(position, child, x, y);
+
+ // This needs some explanation. We need to disable the selector for this next call
+ // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
+ // will draw the selector and bad things happen.
+ setSelectorEnabled(false);
+
+ // Refresh the drawable state to reflect the new pressed state,
+ // which will also update the selector state.
+ refreshDrawableState();
+ }
+
+ @Override
+ protected boolean touchModeDrawsInPressedStateCompat() {
+ return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
+ }
+
+ @Override
+ public boolean isInTouchMode() {
+ // WARNING: Please read the comment where mListSelectionHidden is declared
+ return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasWindowFocus() {
+ return mHijackFocus || super.hasWindowFocus();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean isFocused() {
+ return mHijackFocus || super.isFocused();
+ }
+
+ /**
+ * <p>Returns the focus state in the drop down.</p>
+ *
+ * @return true always if hijacking focus
+ */
+ @Override
+ public boolean hasFocus() {
+ return mHijackFocus || super.hasFocus();
+ }
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
new file mode 100644
index 0000000..1ad9218
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ForwardingListener.java
@@ -0,0 +1,306 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import android.os.SystemClock;
+import android.support.v4.view.MotionEventCompat;
+import android.support.v7.view.menu.ShowableListMenu;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+import android.view.ViewParent;
+
+
+/**
+ * Abstract class that forwards touch events to a {@link ShowableListMenu}.
+ *
+ * @hide
+ */
+public abstract class ForwardingListener implements View.OnTouchListener {
+ /** Scaled touch slop, used for detecting movement outside bounds. */
+ private final float mScaledTouchSlop;
+
+ /** Timeout before disallowing intercept on the source's parent. */
+ private final int mTapTimeout;
+
+ /** Timeout before accepting a long-press to start forwarding. */
+ private final int mLongPressTimeout;
+
+ /** Source view from which events are forwarded. */
+ private final View mSrc;
+
+ /** Runnable used to prevent conflicts with scrolling parents. */
+ private Runnable mDisallowIntercept;
+
+ /** Runnable used to trigger forwarding on long-press. */
+ private Runnable mTriggerLongPress;
+
+ /** Whether this listener is currently forwarding touch events. */
+ private boolean mForwarding;
+
+ /** The id of the first pointer down in the current event stream. */
+ private int mActivePointerId;
+
+ /**
+ * Temporary Matrix instance
+ */
+ private final int[] mTmpLocation = new int[2];
+
+ public ForwardingListener(View src) {
+ mSrc = src;
+ mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
+ mTapTimeout = ViewConfiguration.getTapTimeout();
+ // Use a medium-press timeout. Halfway between tap and long-press.
+ mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
+ }
+
+ /**
+ * Returns the popup to which this listener is forwarding events.
+ * <p>
+ * Override this to return the correct popup. If the popup is displayed
+ * asynchronously, you may also need to override
+ * {@link #onForwardingStopped} to prevent premature cancelation of
+ * forwarding.
+ *
+ * @return the popup to which this listener is forwarding events
+ */
+ public abstract ShowableListMenu getPopup();
+
+ @Override
+ public boolean onTouch(View v, MotionEvent event) {
+ final boolean wasForwarding = mForwarding;
+ final boolean forwarding;
+ if (wasForwarding) {
+ forwarding = onTouchForwarded(event) || !onForwardingStopped();
+ } else {
+ forwarding = onTouchObserved(event) && onForwardingStarted();
+
+ if (forwarding) {
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
+ 0.0f, 0.0f, 0);
+ mSrc.onTouchEvent(e);
+ e.recycle();
+ }
+ }
+
+ mForwarding = forwarding;
+ return forwarding || wasForwarding;
+ }
+
+ /**
+ * Called when forwarding would like to start.
+ * <p>
+ * By default, this will show the popup returned by {@link #getPopup()}.
+ * It may be overridden to perform another action, like clicking the
+ * source view or preparing the popup before showing it.
+ *
+ * @return true to start forwarding, false otherwise
+ */
+ protected boolean onForwardingStarted() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && !popup.isShowing()) {
+ popup.show();
+ }
+ return true;
+ }
+
+ /**
+ * Called when forwarding would like to stop.
+ * <p>
+ * By default, this will dismiss the popup returned by
+ * {@link #getPopup()}. It may be overridden to perform some other
+ * action.
+ *
+ * @return true to stop forwarding, false otherwise
+ */
+ protected boolean onForwardingStopped() {
+ final ShowableListMenu popup = getPopup();
+ if (popup != null && popup.isShowing()) {
+ popup.dismiss();
+ }
+ return true;
+ }
+
+ /**
+ * Observes motion events and determines when to start forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to start forwarding motion events, false otherwise
+ */
+ private boolean onTouchObserved(MotionEvent srcEvent) {
+ final View src = mSrc;
+ if (!src.isEnabled()) {
+ return false;
+ }
+
+ final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
+ switch (actionMasked) {
+ case MotionEvent.ACTION_DOWN:
+ mActivePointerId = srcEvent.getPointerId(0);
+
+ if (mDisallowIntercept == null) {
+ mDisallowIntercept = new DisallowIntercept();
+ }
+ src.postDelayed(mDisallowIntercept, mTapTimeout);
+
+ if (mTriggerLongPress == null) {
+ mTriggerLongPress = new TriggerLongPress();
+ }
+ src.postDelayed(mTriggerLongPress, mLongPressTimeout);
+ break;
+ case MotionEvent.ACTION_MOVE:
+ final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
+ if (activePointerIndex >= 0) {
+ final float x = srcEvent.getX(activePointerIndex);
+ final float y = srcEvent.getY(activePointerIndex);
+
+ // Has the pointer moved outside of the view?
+ if (!pointInView(src, x, y, mScaledTouchSlop)) {
+ clearCallbacks();
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+ return true;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ case MotionEvent.ACTION_UP:
+ clearCallbacks();
+ break;
+ }
+
+ return false;
+ }
+
+ private void clearCallbacks() {
+ if (mTriggerLongPress != null) {
+ mSrc.removeCallbacks(mTriggerLongPress);
+ }
+
+ if (mDisallowIntercept != null) {
+ mSrc.removeCallbacks(mDisallowIntercept);
+ }
+ }
+
+ private void onLongPress() {
+ clearCallbacks();
+
+ final View src = mSrc;
+ if (!src.isEnabled() || src.isLongClickable()) {
+ // Ignore long-press if the view is disabled or has its own
+ // handler.
+ return;
+ }
+
+ if (!onForwardingStarted()) {
+ return;
+ }
+
+ // Don't let the parent intercept our events.
+ src.getParent().requestDisallowInterceptTouchEvent(true);
+
+ // Make sure we cancel any ongoing source event stream.
+ final long now = SystemClock.uptimeMillis();
+ final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
+ src.onTouchEvent(e);
+ e.recycle();
+
+ mForwarding = true;
+ }
+
+ /**
+ * Handles forwarded motion events and determines when to stop
+ * forwarding.
+ *
+ * @param srcEvent motion event in source view coordinates
+ * @return true to continue forwarding motion events, false to cancel
+ */
+ private boolean onTouchForwarded(MotionEvent srcEvent) {
+ final View src = mSrc;
+ final ShowableListMenu popup = getPopup();
+ if (popup == null || !popup.isShowing()) {
+ return false;
+ }
+
+ final DropDownListView dst = (DropDownListView) popup.getListView();
+ if (dst == null || !dst.isShown()) {
+ return false;
+ }
+
+ // Convert event to destination-local coordinates.
+ final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
+ toGlobalMotionEvent(src, dstEvent);
+ toLocalMotionEvent(dst, dstEvent);
+
+ // Forward converted event to destination view, then recycle it.
+ final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
+ dstEvent.recycle();
+
+ // Always cancel forwarding when the touch stream ends.
+ final int action = MotionEventCompat.getActionMasked(srcEvent);
+ final boolean keepForwarding = action != MotionEvent.ACTION_UP
+ && action != MotionEvent.ACTION_CANCEL;
+
+ return handled && keepForwarding;
+ }
+
+ private static boolean pointInView(View view, float localX, float localY, float slop) {
+ return localX >= -slop && localY >= -slop &&
+ localX < ((view.getRight() - view.getLeft()) + slop) &&
+ localY < ((view.getBottom() - view.getTop()) + slop);
+ }
+
+ /**
+ * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
+ * (scaleX, scaleY, etc).
+ */
+ private boolean toLocalMotionEvent(View view, MotionEvent event) {
+ final int[] loc = mTmpLocation;
+ view.getLocationOnScreen(loc);
+ event.offsetLocation(-loc[0], -loc[1]);
+ return true;
+ }
+
+ /**
+ * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
+ * (scaleX, scaleY, etc).
+ */
+ private boolean toGlobalMotionEvent(View view, MotionEvent event) {
+ final int[] loc = mTmpLocation;
+ view.getLocationOnScreen(loc);
+ event.offsetLocation(loc[0], loc[1]);
+ return true;
+ }
+
+ private class DisallowIntercept implements Runnable {
+ @Override
+ public void run() {
+ final ViewParent parent = mSrc.getParent();
+ parent.requestDisallowInterceptTouchEvent(true);
+ }
+ }
+
+ private class TriggerLongPress implements Runnable {
+ @Override
+ public void run() {
+ onLongPress();
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
index 5779599..3a6a4e6 100644
--- a/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
+++ b/v7/appcompat/src/android/support/v7/widget/ListPopupWindow.java
@@ -23,14 +23,15 @@
import android.graphics.drawable.Drawable;
import android.os.Build;
import android.os.Handler;
-import android.os.SystemClock;
-import android.support.v4.text.TextUtilsCompat;
-import android.support.v4.view.MotionEventCompat;
+import android.support.annotation.AttrRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
+import android.support.v4.os.BuildCompat;
import android.support.v4.view.ViewCompat;
-import android.support.v4.view.ViewPropertyAnimatorCompat;
-import android.support.v4.widget.ListViewAutoScrollHelper;
import android.support.v4.widget.PopupWindowCompat;
import android.support.v7.appcompat.R;
+import android.support.v7.view.menu.ShowableListMenu;
import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
@@ -39,19 +40,18 @@
import android.view.View;
import android.view.View.MeasureSpec;
import android.view.View.OnTouchListener;
-import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.WindowManager;
import android.widget.AbsListView;
import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
import android.widget.LinearLayout;
import android.widget.ListAdapter;
import android.widget.ListView;
import android.widget.PopupWindow;
import java.lang.reflect.Method;
-import java.util.Locale;
/**
* Static library support version of the framework's {@link android.widget.ListPopupWindow}.
@@ -62,7 +62,7 @@
*
* @see android.widget.ListPopupWindow
*/
-public class ListPopupWindow {
+public class ListPopupWindow implements ShowableListMenu {
private static final String TAG = "ListPopupWindow";
private static final boolean DEBUG = false;
@@ -75,6 +75,7 @@
private static Method sClipToWindowEnabledMethod;
private static Method sGetMaxAvailableHeightMethod;
+ private static Method sSetEpicenterBoundsMethod;
static {
try {
@@ -90,10 +91,15 @@
Log.i(TAG, "Could not find method getMaxAvailableHeight(View, int, boolean)"
+ " on PopupWindow. Oh well.");
}
+ try {
+ sSetEpicenterBoundsMethod = PopupWindow.class.getDeclaredMethod(
+ "setEpicenterBounds", Rect.class);
+ } catch (NoSuchMethodException e) {
+ Log.i(TAG, "Could not find method setEpicenterBounds(Rect) on PopupWindow. Oh well.");
+ }
}
private Context mContext;
- private PopupWindow mPopup;
private ListAdapter mAdapter;
private DropDownListView mDropDownList;
@@ -103,6 +109,7 @@
private int mDropDownVerticalOffset;
private int mDropDownWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
private boolean mDropDownVerticalOffsetSet;
+ private boolean mIsAnimatedFromAnchor = true;
private int mDropDownGravity = Gravity.NO_GRAVITY;
@@ -120,7 +127,7 @@
private Drawable mDropDownListHighlight;
private AdapterView.OnItemClickListener mItemClickListener;
- private AdapterView.OnItemSelectedListener mItemSelectedListener;
+ private OnItemSelectedListener mItemSelectedListener;
private final ResizePopupRunnable mResizePopupRunnable = new ResizePopupRunnable();
private final PopupTouchInterceptor mTouchInterceptor = new PopupTouchInterceptor();
@@ -130,11 +137,17 @@
private final Handler mHandler;
- private Rect mTempRect = new Rect();
+ private final Rect mTempRect = new Rect();
+
+ /**
+ * Optional anchor-relative bounds to be used as the transition epicenter.
+ * When {@code null}, the anchor bounds are used as the epicenter.
+ */
+ private Rect mEpicenterBounds;
private boolean mModal;
- private int mLayoutDirection;
+ PopupWindow mPopup;
/**
* The provided prompt view should appear above list content.
@@ -197,7 +210,7 @@
*
* @param context Context used for contained views.
*/
- public ListPopupWindow(Context context) {
+ public ListPopupWindow(@NonNull Context context) {
this(context, null, R.attr.listPopupWindowStyle);
}
@@ -208,7 +221,7 @@
* @param context Context used for contained views.
* @param attrs Attributes from inflating parent views used to style the popup.
*/
- public ListPopupWindow(Context context, AttributeSet attrs) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs) {
this(context, attrs, R.attr.listPopupWindowStyle);
}
@@ -220,7 +233,8 @@
* @param attrs Attributes from inflating parent views used to style the popup.
* @param defStyleAttr Default style attribute to use for popup content.
*/
- public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr) {
this(context, attrs, defStyleAttr, 0);
}
@@ -233,7 +247,8 @@
* @param defStyleAttr Style attribute to read for default styling of popup content.
* @param defStyleRes Style resource ID to use for default styling of popup content.
*/
- public ListPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ public ListPopupWindow(@NonNull Context context, @Nullable AttributeSet attrs,
+ @AttrRes int defStyleAttr, @StyleRes int defStyleRes) {
mContext = context;
mHandler = new Handler(context.getMainLooper());
@@ -248,12 +263,12 @@
}
a.recycle();
- mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
+ if (Build.VERSION.SDK_INT >= 11) {
+ mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr, defStyleRes);
+ } else {
+ mPopup = new AppCompatPopupWindow(context, attrs, defStyleAttr);
+ }
mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED);
-
- // Set the default layout direction to match the default locale one
- final Locale locale = mContext.getResources().getConfiguration().locale;
- mLayoutDirection = TextUtilsCompat.getLayoutDirectionFromLocale(locale);
}
/**
@@ -262,7 +277,7 @@
*
* @param adapter The adapter to use to create this window's content.
*/
- public void setAdapter(ListAdapter adapter) {
+ public void setAdapter(@Nullable ListAdapter adapter) {
if (mObserver == null) {
mObserver = new PopupDataSetObserver();
} else if (mAdapter != null) {
@@ -395,7 +410,7 @@
/**
* @return The background drawable for the popup window.
*/
- public Drawable getBackground() {
+ public @Nullable Drawable getBackground() {
return mPopup.getBackground();
}
@@ -404,7 +419,7 @@
*
* @param d A drawable to set as the background.
*/
- public void setBackgroundDrawable(Drawable d) {
+ public void setBackgroundDrawable(@Nullable Drawable d) {
mPopup.setBackgroundDrawable(d);
}
@@ -413,16 +428,17 @@
*
* @param animationStyle Animation style to use.
*/
- public void setAnimationStyle(int animationStyle) {
+ public void setAnimationStyle(@StyleRes int animationStyle) {
mPopup.setAnimationStyle(animationStyle);
}
/**
- * Returns the animation style that will be used when the popup window is shown or dismissed.
+ * Returns the animation style that will be used when the popup window is
+ * shown or dismissed.
*
* @return Animation style that will be used.
*/
- public int getAnimationStyle() {
+ public @StyleRes int getAnimationStyle() {
return mPopup.getAnimationStyle();
}
@@ -431,17 +447,17 @@
*
* @return The popup's anchor view
*/
- public View getAnchorView() {
+ public @Nullable View getAnchorView() {
return mDropDownAnchorView;
}
/**
- * Sets the popup's anchor view. This popup will always be positioned relative to the anchor
- * view when shown.
+ * Sets the popup's anchor view. This popup will always be positioned relative to
+ * the anchor view when shown.
*
* @param anchor The view to use as an anchor.
*/
- public void setAnchorView(View anchor) {
+ public void setAnchorView(@Nullable View anchor) {
mDropDownAnchorView = anchor;
}
@@ -482,6 +498,17 @@
}
/**
+ * Specifies the anchor-relative bounds of the popup's transition
+ * epicenter.
+ *
+ * @param bounds anchor-relative bounds
+ * @hide
+ */
+ public void setEpicenterBounds(Rect bounds) {
+ mEpicenterBounds = bounds;
+ }
+
+ /**
* Set the gravity of the dropdown list. This is commonly used to
* set gravity to START or END for alignment with the anchor.
*
@@ -560,7 +587,7 @@
*
* @see ListView#setOnItemClickListener(android.widget.AdapterView.OnItemClickListener)
*/
- public void setOnItemClickListener(AdapterView.OnItemClickListener clickListener) {
+ public void setOnItemClickListener(@Nullable AdapterView.OnItemClickListener clickListener) {
mItemClickListener = clickListener;
}
@@ -569,9 +596,9 @@
*
* @param selectedListener Listener to register.
*
- * @see ListView#setOnItemSelectedListener(android.widget.AdapterView.OnItemSelectedListener)
+ * @see ListView#setOnItemSelectedListener(OnItemSelectedListener)
*/
- public void setOnItemSelectedListener(AdapterView.OnItemSelectedListener selectedListener) {
+ public void setOnItemSelectedListener(@Nullable OnItemSelectedListener selectedListener) {
mItemSelectedListener = selectedListener;
}
@@ -581,7 +608,7 @@
*
* @param prompt View to use as an informational prompt.
*/
- public void setPromptView(View prompt) {
+ public void setPromptView(@Nullable View prompt) {
boolean showing = isShowing();
if (showing) {
removePromptView();
@@ -603,10 +630,11 @@
* Show the popup list. If the list is already showing, this method
* will recalculate the popup's size and position.
*/
+ @Override
public void show() {
int height = buildDropDown();
- boolean noInputMethod = isInputMethodNotNeeded();
+ final boolean noInputMethod = isInputMethodNotNeeded();
PopupWindowCompat.setWindowLayoutType(mPopup, mDropDownWindowLayoutType);
if (mPopup.isShowing()) {
@@ -677,6 +705,13 @@
// only set this if the dropdown is not always visible
mPopup.setOutsideTouchable(!mForceIgnoreOutsideTouch && !mDropDownAlwaysVisible);
mPopup.setTouchInterceptor(mTouchInterceptor);
+ if (sSetEpicenterBoundsMethod != null) {
+ try {
+ sSetEpicenterBoundsMethod.invoke(mPopup, mEpicenterBounds);
+ } catch (Exception e) {
+ Log.e(TAG, "Could not invoke setEpicenterBounds on PopupWindow", e);
+ }
+ }
PopupWindowCompat.showAsDropDown(mPopup, getAnchorView(), mDropDownHorizontalOffset,
mDropDownVerticalOffset, mDropDownGravity);
mDropDownList.setSelection(ListView.INVALID_POSITION);
@@ -693,6 +728,7 @@
/**
* Dismiss the popup window.
*/
+ @Override
public void dismiss() {
mPopup.dismiss();
removePromptView();
@@ -706,7 +742,7 @@
*
* @param listener Listener that will be notified when the popup is dismissed.
*/
- public void setOnDismissListener(PopupWindow.OnDismissListener listener) {
+ public void setOnDismissListener(@Nullable PopupWindow.OnDismissListener listener) {
mPopup.setOnDismissListener(listener);
}
@@ -754,7 +790,7 @@
public void setSelection(int position) {
DropDownListView list = mDropDownList;
if (isShowing() && list != null) {
- list.mListSelectionHidden = false;
+ list.setListSelectionHidden(false);
list.setSelection(position);
if (Build.VERSION.SDK_INT >= 11) {
@@ -773,7 +809,7 @@
final DropDownListView list = mDropDownList;
if (list != null) {
// WARNING: Please read the comment where mListSelectionHidden is declared
- list.mListSelectionHidden = true;
+ list.setListSelectionHidden(true);
//list.hideSelector();
list.requestLayout();
}
@@ -782,6 +818,7 @@
/**
* @return {@code true} if the popup is currently showing, {@code false} otherwise.
*/
+ @Override
public boolean isShowing() {
return mPopup.isShowing();
}
@@ -817,7 +854,7 @@
/**
* @return The currently selected item or null if the popup is not showing.
*/
- public Object getSelectedItem() {
+ public @Nullable Object getSelectedItem() {
if (!isShowing()) {
return null;
}
@@ -856,7 +893,7 @@
*
* @see ListView#getSelectedView()
*/
- public View getSelectedView() {
+ public @Nullable View getSelectedView() {
if (!isShowing()) {
return null;
}
@@ -867,10 +904,15 @@
* @return The {@link ListView} displayed within the popup window.
* Only valid when {@link #isShowing()} == {@code true}.
*/
- public ListView getListView() {
+ @Override
+ public @Nullable ListView getListView() {
return mDropDownList;
}
+ @NonNull DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+ return new DropDownListView(context, hijackFocus);
+ }
+
/**
* The maximum number of list items that can be visible and still have
* the list expand when touched.
@@ -891,7 +933,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyDown(int keyCode, KeyEvent event) {
+ public boolean onKeyDown(int keyCode, @NonNull KeyEvent event) {
// when the drop down is shown, we drive it directly
if (isShowing()) {
// the key events are forwarded to the list in the drop down view
@@ -932,7 +974,7 @@
} else {
// WARNING: Please read the comment where mListSelectionHidden
// is declared
- mDropDownList.mListSelectionHidden = false;
+ mDropDownList.setListSelectionHidden(false);
}
consumed = mDropDownList.onKeyDown(keyCode, event);
@@ -986,7 +1028,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyUp(int keyCode, KeyEvent event) {
+ public boolean onKeyUp(int keyCode, @NonNull KeyEvent event) {
if (isShowing() && mDropDownList.getSelectedItemPosition() >= 0) {
boolean consumed = mDropDownList.onKeyUp(keyCode, event);
if (consumed && isConfirmKey(keyCode)) {
@@ -1010,7 +1052,7 @@
*
* @see #setModal(boolean)
*/
- public boolean onKeyPreIme(int keyCode, KeyEvent event) {
+ public boolean onKeyPreIme(int keyCode, @NonNull KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK && isShowing()) {
// special case for the back key, we do not even try to send it
// to the drop down list but instead, consume it immediately
@@ -1093,7 +1135,7 @@
}
};
- mDropDownList = new DropDownListView(context, !mModal);
+ mDropDownList = createDropDownListView(context, !mModal);
if (mDropDownListHighlight != null) {
mDropDownList.setSelector(mDropDownListHighlight);
}
@@ -1101,7 +1143,7 @@
mDropDownList.setOnItemClickListener(mItemClickListener);
mDropDownList.setFocusable(true);
mDropDownList.setFocusableInTouchMode(true);
- mDropDownList.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
+ mDropDownList.setOnItemSelectedListener(new OnItemSelectedListener() {
public void onItemSelected(AdapterView<?> parent, View view,
int position, long id) {
@@ -1109,7 +1151,7 @@
DropDownListView dropDownList = mDropDownList;
if (dropDownList != null) {
- dropDownList.mListSelectionHidden = false;
+ dropDownList.setListSelectionHidden(false);
}
}
}
@@ -1187,20 +1229,21 @@
}
// getMaxAvailableHeight() subtracts the padding, so we put it back
- // to get the available height for the whole window
- int padding = 0;
- Drawable background = mPopup.getBackground();
+ // to get the available height for the whole window.
+ final int padding;
+ final Drawable background = mPopup.getBackground();
if (background != null) {
background.getPadding(mTempRect);
padding = mTempRect.top + mTempRect.bottom;
- // If we don't have an explicit vertical offset, determine one from the window
- // background so that content will line up.
+ // If we don't have an explicit vertical offset, determine one from
+ // the window background so that content will line up.
if (!mDropDownVerticalOffsetSet) {
mDropDownVerticalOffset = -mTempRect.top;
}
} else {
mTempRect.setEmpty();
+ padding = 0;
}
// Max height available on the screen for a popup.
@@ -1216,14 +1259,14 @@
switch (mDropDownWidth) {
case ViewGroup.LayoutParams.WRAP_CONTENT:
childWidthSpec = MeasureSpec.makeMeasureSpec(
- mContext.getResources().getDisplayMetrics().widthPixels -
- (mTempRect.left + mTempRect.right),
+ mContext.getResources().getDisplayMetrics().widthPixels
+ - (mTempRect.left + mTempRect.right),
MeasureSpec.AT_MOST);
break;
case ViewGroup.LayoutParams.MATCH_PARENT:
childWidthSpec = MeasureSpec.makeMeasureSpec(
- mContext.getResources().getDisplayMetrics().widthPixels -
- (mTempRect.left + mTempRect.right),
+ mContext.getResources().getDisplayMetrics().widthPixels
+ - (mTempRect.left + mTempRect.right),
MeasureSpec.EXACTLY);
break;
default:
@@ -1231,539 +1274,19 @@
break;
}
+ // Add padding only if the list has items in it, that way we don't show
+ // the popup if it is not needed.
final int listContent = mDropDownList.measureHeightOfChildrenCompat(childWidthSpec,
0, DropDownListView.NO_POSITION, maxHeight - otherHeights, -1);
- // add padding only if the list has items in it, that way we don't show
- // the popup if it is not needed
- if (listContent > 0) otherHeights += padding;
+ if (listContent > 0) {
+ final int listPadding = mDropDownList.getPaddingTop()
+ + mDropDownList.getPaddingBottom();
+ otherHeights += padding + listPadding;
+ }
return listContent + otherHeights;
}
- /**
- * Abstract class that forwards touch events to a {@link ListPopupWindow}.
- *
- * @hide
- */
- public static abstract class ForwardingListener implements View.OnTouchListener {
- /** Scaled touch slop, used for detecting movement outside bounds. */
- private final float mScaledTouchSlop;
-
- /** Timeout before disallowing intercept on the source's parent. */
- private final int mTapTimeout;
- /** Timeout before accepting a long-press to start forwarding. */
- private final int mLongPressTimeout;
-
- /** Source view from which events are forwarded. */
- private final View mSrc;
-
- /** Runnable used to prevent conflicts with scrolling parents. */
- private Runnable mDisallowIntercept;
- /** Runnable used to trigger forwarding on long-press. */
- private Runnable mTriggerLongPress;
-
- /** Whether this listener is currently forwarding touch events. */
- private boolean mForwarding;
- /**
- * Whether forwarding was initiated by a long-press. If so, we won't
- * force the window to dismiss when the touch stream ends.
- */
- private boolean mWasLongPress;
-
- /** The id of the first pointer down in the current event stream. */
- private int mActivePointerId;
-
- /**
- * Temporary Matrix instance
- */
- private final int[] mTmpLocation = new int[2];
-
- public ForwardingListener(View src) {
- mSrc = src;
- mScaledTouchSlop = ViewConfiguration.get(src.getContext()).getScaledTouchSlop();
- mTapTimeout = ViewConfiguration.getTapTimeout();
- // Use a medium-press timeout. Halfway between tap and long-press.
- mLongPressTimeout = (mTapTimeout + ViewConfiguration.getLongPressTimeout()) / 2;
- }
-
- /**
- * Returns the popup to which this listener is forwarding events.
- * <p>
- * Override this to return the correct popup. If the popup is displayed
- * asynchronously, you may also need to override
- * {@link #onForwardingStopped} to prevent premature cancelation of
- * forwarding.
- *
- * @return the popup to which this listener is forwarding events
- */
- public abstract ListPopupWindow getPopup();
-
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- final boolean wasForwarding = mForwarding;
- final boolean forwarding;
- if (wasForwarding) {
- if (mWasLongPress) {
- // If we started forwarding as a result of a long-press,
- // just silently stop forwarding events so that the window
- // stays open.
- forwarding = onTouchForwarded(event);
- } else {
- forwarding = onTouchForwarded(event) || !onForwardingStopped();
- }
- } else {
- forwarding = onTouchObserved(event) && onForwardingStarted();
-
- if (forwarding) {
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL,
- 0.0f, 0.0f, 0);
- mSrc.onTouchEvent(e);
- e.recycle();
- }
- }
-
- mForwarding = forwarding;
- return forwarding || wasForwarding;
- }
-
- /**
- * Called when forwarding would like to start. <p> By default, this will show the popup
- * returned by {@link #getPopup()}. It may be overridden to perform another action, like
- * clicking the source view or preparing the popup before showing it.
- *
- * @return true to start forwarding, false otherwise
- */
- protected boolean onForwardingStarted() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && !popup.isShowing()) {
- popup.show();
- }
- return true;
- }
-
- /**
- * Called when forwarding would like to stop. <p> By default, this will dismiss the popup
- * returned by {@link #getPopup()}. It may be overridden to perform some other action.
- *
- * @return true to stop forwarding, false otherwise
- */
- protected boolean onForwardingStopped() {
- final ListPopupWindow popup = getPopup();
- if (popup != null && popup.isShowing()) {
- popup.dismiss();
- }
- return true;
- }
-
- /**
- * Observes motion events and determines when to start forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to start forwarding motion events, false otherwise
- */
- private boolean onTouchObserved(MotionEvent srcEvent) {
- final View src = mSrc;
- if (!src.isEnabled()) {
- return false;
- }
-
- final int actionMasked = MotionEventCompat.getActionMasked(srcEvent);
- switch (actionMasked) {
- case MotionEvent.ACTION_DOWN:
- mActivePointerId = srcEvent.getPointerId(0);
- mWasLongPress = false;
-
- if (mDisallowIntercept == null) {
- mDisallowIntercept = new DisallowIntercept();
- }
- src.postDelayed(mDisallowIntercept, mTapTimeout);
- if (mTriggerLongPress == null) {
- mTriggerLongPress = new TriggerLongPress();
- }
- src.postDelayed(mTriggerLongPress, mLongPressTimeout);
- break;
- case MotionEvent.ACTION_MOVE:
- final int activePointerIndex = srcEvent.findPointerIndex(mActivePointerId);
- if (activePointerIndex >= 0) {
- final float x = srcEvent.getX(activePointerIndex);
- final float y = srcEvent.getY(activePointerIndex);
- if (!pointInView(src, x, y, mScaledTouchSlop)) {
- clearCallbacks();
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
- return true;
- }
- }
- break;
- case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
- clearCallbacks();
- break;
- }
-
- return false;
- }
-
- private void clearCallbacks() {
- if (mTriggerLongPress != null) {
- mSrc.removeCallbacks(mTriggerLongPress);
- }
-
- if (mDisallowIntercept != null) {
- mSrc.removeCallbacks(mDisallowIntercept);
- }
- }
-
- private void onLongPress() {
- clearCallbacks();
-
- final View src = mSrc;
- if (!src.isEnabled() || src.isLongClickable()) {
- // Ignore long-press if the view is disabled or has its own
- // handler.
- return;
- }
-
- if (!onForwardingStarted()) {
- return;
- }
-
- // Don't let the parent intercept our events.
- src.getParent().requestDisallowInterceptTouchEvent(true);
-
- // Make sure we cancel any ongoing source event stream.
- final long now = SystemClock.uptimeMillis();
- final MotionEvent e = MotionEvent.obtain(now, now, MotionEvent.ACTION_CANCEL, 0, 0, 0);
- src.onTouchEvent(e);
- e.recycle();
-
- mForwarding = true;
- mWasLongPress = true;
- }
-
- /**
- * Handled forwarded motion events and determines when to stop forwarding.
- *
- * @param srcEvent motion event in source view coordinates
- * @return true to continue forwarding motion events, false to cancel
- */
- private boolean onTouchForwarded(MotionEvent srcEvent) {
- final View src = mSrc;
- final ListPopupWindow popup = getPopup();
- if (popup == null || !popup.isShowing()) {
- return false;
- }
-
- final DropDownListView dst = popup.mDropDownList;
- if (dst == null || !dst.isShown()) {
- return false;
- }
-
- // Convert event to destination-local coordinates.
- final MotionEvent dstEvent = MotionEvent.obtainNoHistory(srcEvent);
- toGlobalMotionEvent(src, dstEvent);
- toLocalMotionEvent(dst, dstEvent);
-
- // Forward converted event to destination view, then recycle it.
- final boolean handled = dst.onForwardedEvent(dstEvent, mActivePointerId);
- dstEvent.recycle();
-
- // Always cancel forwarding when the touch stream ends.
- final int action = MotionEventCompat.getActionMasked(srcEvent);
- final boolean keepForwarding = action != MotionEvent.ACTION_UP
- && action != MotionEvent.ACTION_CANCEL;
-
- return handled && keepForwarding;
- }
-
- private static boolean pointInView(View view, float localX, float localY, float slop) {
- return localX >= -slop && localY >= -slop &&
- localX < ((view.getRight() - view.getLeft()) + slop) &&
- localY < ((view.getBottom() - view.getTop()) + slop);
- }
-
- /**
- * Emulates View.toLocalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private boolean toLocalMotionEvent(View view, MotionEvent event) {
- final int[] loc = mTmpLocation;
- view.getLocationOnScreen(loc);
- event.offsetLocation(-loc[0], -loc[1]);
- return true;
- }
-
- /**
- * Emulates View.toGlobalMotionEvent(). This implementation does not handle transformations
- * (scaleX, scaleY, etc).
- */
- private boolean toGlobalMotionEvent(View view, MotionEvent event) {
- final int[] loc = mTmpLocation;
- view.getLocationOnScreen(loc);
- event.offsetLocation(loc[0], loc[1]);
- return true;
- }
-
- private class DisallowIntercept implements Runnable {
- @Override
- public void run() {
- final ViewParent parent = mSrc.getParent();
- parent.requestDisallowInterceptTouchEvent(true);
- }
- }
-
- private class TriggerLongPress implements Runnable {
- @Override
- public void run() {
- onLongPress();
- }
- }
- }
-
- /**
- * <p>Wrapper class for a ListView. This wrapper can hijack the focus to
- * make sure the list uses the appropriate drawables and states when
- * displayed on screen within a drop down. The focus is never actually
- * passed to the drop down in this mode; the list only looks focused.</p>
- */
- private static class DropDownListView extends ListViewCompat {
-
- /*
- * WARNING: This is a workaround for a touch mode issue.
- *
- * Touch mode is propagated lazily to windows. This causes problems in
- * the following scenario:
- * - Type something in the AutoCompleteTextView and get some results
- * - Move down with the d-pad to select an item in the list
- * - Move up with the d-pad until the selection disappears
- * - Type more text in the AutoCompleteTextView *using the soft keyboard*
- * and get new results; you are now in touch mode
- * - The selection comes back on the first item in the list, even though
- * the list is supposed to be in touch mode
- *
- * Using the soft keyboard triggers the touch mode change but that change
- * is propagated to our window only after the first list layout, therefore
- * after the list attempts to resurrect the selection.
- *
- * The trick to work around this issue is to pretend the list is in touch
- * mode when we know that the selection should not appear, that is when
- * we know the user moved the selection away from the list.
- *
- * This boolean is set to true whenever we explicitly hide the list's
- * selection and reset to false whenever we know the user moved the
- * selection back to the list.
- *
- * When this boolean is true, isInTouchMode() returns true, otherwise it
- * returns super.isInTouchMode().
- */
- private boolean mListSelectionHidden;
-
- /**
- * True if this wrapper should fake focus.
- */
- private boolean mHijackFocus;
-
- /** Whether to force drawing of the pressed state selector. */
- private boolean mDrawsInPressedState;
-
- /** Current drag-to-open click animation, if any. */
- private ViewPropertyAnimatorCompat mClickAnimation;
-
- /** Helper for drag-to-open auto scrolling. */
- private ListViewAutoScrollHelper mScrollHelper;
-
- /**
- * <p>Creates a new list view wrapper.</p>
- *
- * @param context this view's context
- */
- public DropDownListView(Context context, boolean hijackFocus) {
- super(context, null, R.attr.dropDownListViewStyle);
- mHijackFocus = hijackFocus;
- setCacheColorHint(0); // Transparent, since the background drawable could be anything.
- }
-
- /**
- * Handles forwarded events.
- *
- * @param activePointerId id of the pointer that activated forwarding
- * @return whether the event was handled
- */
- public boolean onForwardedEvent(MotionEvent event, int activePointerId) {
- boolean handledEvent = true;
- boolean clearPressedItem = false;
-
- final int actionMasked = MotionEventCompat.getActionMasked(event);
- switch (actionMasked) {
- case MotionEvent.ACTION_CANCEL:
- handledEvent = false;
- break;
- case MotionEvent.ACTION_UP:
- handledEvent = false;
- // $FALL-THROUGH$
- case MotionEvent.ACTION_MOVE:
- final int activeIndex = event.findPointerIndex(activePointerId);
- if (activeIndex < 0) {
- handledEvent = false;
- break;
- }
-
- final int x = (int) event.getX(activeIndex);
- final int y = (int) event.getY(activeIndex);
- final int position = pointToPosition(x, y);
- if (position == INVALID_POSITION) {
- clearPressedItem = true;
- break;
- }
-
- final View child = getChildAt(position - getFirstVisiblePosition());
- setPressedItem(child, position, x, y);
- handledEvent = true;
-
- if (actionMasked == MotionEvent.ACTION_UP) {
- clickPressedItem(child, position);
- }
- break;
- }
-
- // Failure to handle the event cancels forwarding.
- if (!handledEvent || clearPressedItem) {
- clearPressedItem();
- }
-
- // Manage automatic scrolling.
- if (handledEvent) {
- if (mScrollHelper == null) {
- mScrollHelper = new ListViewAutoScrollHelper(this);
- }
- mScrollHelper.setEnabled(true);
- mScrollHelper.onTouch(this, event);
- } else if (mScrollHelper != null) {
- mScrollHelper.setEnabled(false);
- }
-
- return handledEvent;
- }
-
- /**
- * Starts an alpha animation on the selector. When the animation ends,
- * the list performs a click on the item.
- */
- private void clickPressedItem(final View child, final int position) {
- final long id = getItemIdAtPosition(position);
- performItemClick(child, position, id);
- }
-
- private void clearPressedItem() {
- mDrawsInPressedState = false;
- setPressed(false);
- // This will call through to updateSelectorState()
- drawableStateChanged();
-
- final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
- if (motionView != null) {
- motionView.setPressed(false);
- }
-
- if (mClickAnimation != null) {
- mClickAnimation.cancel();
- mClickAnimation = null;
- }
- }
-
- private void setPressedItem(View child, int position, float x, float y) {
- mDrawsInPressedState = true;
-
- // Ordering is essential. First, update the container's pressed state.
- if (Build.VERSION.SDK_INT >= 21) {
- drawableHotspotChanged(x, y);
- }
- if (!isPressed()) {
- setPressed(true);
- }
-
- // Next, run layout to stabilize child positions.
- layoutChildren();
-
- // Manage the pressed view based on motion position. This allows us to
- // play nicely with actual touch and scroll events.
- if (mMotionPosition != INVALID_POSITION) {
- final View motionView = getChildAt(mMotionPosition - getFirstVisiblePosition());
- if (motionView != null && motionView != child && motionView.isPressed()) {
- motionView.setPressed(false);
- }
- }
- mMotionPosition = position;
-
- // Offset for child coordinates.
- final float childX = x - child.getLeft();
- final float childY = y - child.getTop();
- if (Build.VERSION.SDK_INT >= 21) {
- child.drawableHotspotChanged(childX, childY);
- }
- if (!child.isPressed()) {
- child.setPressed(true);
- }
-
- // Ensure that keyboard focus starts from the last touched position.
- setSelection(position);
- positionSelectorLikeTouchCompat(position, child, x, y);
-
- // This needs some explanation. We need to disable the selector for this next call
- // due to the way that ListViewCompat works. Otherwise both ListView and ListViewCompat
- // will draw the selector and bad things happen.
- setSelectorEnabled(false);
-
- // Refresh the drawable state to reflect the new pressed state,
- // which will also update the selector state.
- refreshDrawableState();
- }
-
- @Override
- protected boolean touchModeDrawsInPressedStateCompat() {
- return mDrawsInPressedState || super.touchModeDrawsInPressedStateCompat();
- }
-
- @Override
- public boolean isInTouchMode() {
- // WARNING: Please read the comment where mListSelectionHidden is declared
- return (mHijackFocus && mListSelectionHidden) || super.isInTouchMode();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasWindowFocus() {
- return mHijackFocus || super.hasWindowFocus();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean isFocused() {
- return mHijackFocus || super.isFocused();
- }
-
- /**
- * <p>Returns the focus state in the drop down.</p>
- *
- * @return true always if hijacking focus
- */
- @Override
- public boolean hasFocus() {
- return mHijackFocus || super.hasFocus();
- }
- }
-
private class PopupDataSetObserver extends DataSetObserver {
@Override
public void onChanged() {
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
new file mode 100644
index 0000000..a1e5645
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuItemHoverListener.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v7.widget;
+
+import android.support.annotation.NonNull;
+import android.support.v7.view.menu.MenuBuilder;
+import android.view.MenuItem;
+
+/**
+ * An interface notified when a menu item is hovered. Useful for cases when hover should trigger
+ * some behavior at a higher level, like managing the opening and closing of submenus.
+ *
+ * @hide
+ */
+public interface MenuItemHoverListener {
+ /**
+ * Called when hover exits a menu item.
+ * <p>
+ * If hover is moving to another item, this method will be called before
+ * {@link #onItemHoverEnter(MenuBuilder, MenuItem)} for the newly-hovered item.
+ *
+ * @param menu the item's parent menu
+ * @param item the hovered menu item
+ */
+ void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+
+ /**
+ * Called when hover enters a menu item.
+ *
+ * @param menu the item's parent menu
+ * @param item the hovered menu item
+ */
+ void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item);
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
new file mode 100644
index 0000000..7d3f77c
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/MenuPopupWindow.java
@@ -0,0 +1,225 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.PopupWindowCompat;
+import android.support.v7.view.menu.ListMenuItemView;
+import android.support.v7.view.menu.MenuAdapter;
+import android.support.v7.view.menu.MenuBuilder;
+import android.transition.Transition;
+import android.util.AttributeSet;
+import android.util.Log;
+import android.view.KeyEvent;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.widget.HeaderViewListAdapter;
+import android.widget.ListAdapter;
+import android.widget.PopupWindow;
+
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+
+/**
+ * A MenuPopupWindow represents the popup window for menu.
+ *
+ * MenuPopupWindow is mostly same as ListPopupWindow, but it has customized
+ * behaviors specific to menus,
+ *
+ * @hide
+ */
+public class MenuPopupWindow extends ListPopupWindow implements MenuItemHoverListener {
+ private static final String TAG = "MenuPopupWindow";
+
+ private static Method sSetTouchModalMethod;
+
+ static {
+ try {
+ sSetTouchModalMethod = PopupWindow.class.getDeclaredMethod(
+ "setTouchModal", boolean.class);
+ } catch (NoSuchMethodException e) {
+ Log.i(TAG, "Could not find method setTouchModal() on PopupWindow. Oh well.");
+ }
+ }
+
+ private MenuItemHoverListener mHoverListener;
+
+ public MenuPopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ DropDownListView createDropDownListView(Context context, boolean hijackFocus) {
+ MenuDropDownListView view = new MenuDropDownListView(context, hijackFocus);
+ view.setHoverListener(this);
+ return view;
+ }
+
+ public void setEnterTransition(Object enterTransition) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ mPopup.setEnterTransition((Transition) enterTransition);
+ }
+ }
+
+ public void setExitTransition(Object exitTransition) {
+ if (Build.VERSION.SDK_INT >= 23) {
+ mPopup.setExitTransition((Transition) exitTransition);
+ }
+ }
+
+ public void setHoverListener(MenuItemHoverListener hoverListener) {
+ mHoverListener = hoverListener;
+ }
+
+ /**
+ * Set whether this window is touch modal or if outside touches will be sent to
+ * other windows behind it.
+ */
+ public void setTouchModal(final boolean touchModal) {
+ if (sSetTouchModalMethod != null) {
+ try {
+ sSetTouchModalMethod.invoke(mPopup, touchModal);
+ } catch (Exception e) {
+ Log.i(TAG, "Could not invoke setTouchModal() on PopupWindow. Oh well.");
+ }
+ }
+ }
+
+ @Override
+ public void onItemHoverEnter(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // Forward up the chain
+ if (mHoverListener != null) {
+ mHoverListener.onItemHoverEnter(menu, item);
+ }
+ }
+
+ @Override
+ public void onItemHoverExit(@NonNull MenuBuilder menu, @NonNull MenuItem item) {
+ // Forward up the chain
+ if (mHoverListener != null) {
+ mHoverListener.onItemHoverExit(menu, item);
+ }
+ }
+
+ /**
+ * @hide
+ */
+ public static class MenuDropDownListView extends DropDownListView {
+ final int mAdvanceKey;
+ final int mRetreatKey;
+
+ private MenuItemHoverListener mHoverListener;
+ private MenuItem mHoveredMenuItem;
+
+ public MenuDropDownListView(Context context, boolean hijackFocus) {
+ super(context, hijackFocus);
+
+ final Resources res = context.getResources();
+ final Configuration config = res.getConfiguration();
+ if (Build.VERSION.SDK_INT >= 17
+ && ViewCompat.LAYOUT_DIRECTION_RTL == config.getLayoutDirection()) {
+ mAdvanceKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ mRetreatKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ } else {
+ mAdvanceKey = KeyEvent.KEYCODE_DPAD_RIGHT;
+ mRetreatKey = KeyEvent.KEYCODE_DPAD_LEFT;
+ }
+ }
+
+ public void setHoverListener(MenuItemHoverListener hoverListener) {
+ mHoverListener = hoverListener;
+ }
+
+ public void clearSelection() {
+ setSelection(INVALID_POSITION);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ ListMenuItemView selectedItem = (ListMenuItemView) getSelectedView();
+ if (selectedItem != null && keyCode == mAdvanceKey) {
+ if (selectedItem.isEnabled() && selectedItem.getItemData().hasSubMenu()) {
+ performItemClick(
+ selectedItem,
+ getSelectedItemPosition(),
+ getSelectedItemId());
+ }
+ return true;
+ } else if (selectedItem != null && keyCode == mRetreatKey) {
+ setSelection(INVALID_POSITION);
+
+ // Close only the top-level menu.
+ ((MenuAdapter) getAdapter()).getAdapterMenu().close(false /* closeAllMenus */);
+ return true;
+ }
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onHoverEvent(MotionEvent ev) {
+ // Dispatch any changes in hovered item index to the listener.
+ if (mHoverListener != null) {
+ // The adapter may be wrapped. Adjust the index if necessary.
+ final int headersCount;
+ final MenuAdapter menuAdapter;
+ final ListAdapter adapter = getAdapter();
+ if (adapter instanceof HeaderViewListAdapter) {
+ final HeaderViewListAdapter headerAdapter = (HeaderViewListAdapter) adapter;
+ headersCount = headerAdapter.getHeadersCount();
+ menuAdapter = (MenuAdapter) headerAdapter.getWrappedAdapter();
+ } else {
+ headersCount = 0;
+ menuAdapter = (MenuAdapter) adapter;
+ }
+
+ // Find the menu item for the view at the event coordinates.
+ MenuItem menuItem = null;
+ if (ev.getAction() != MotionEvent.ACTION_HOVER_EXIT) {
+ final int position = pointToPosition((int) ev.getX(), (int) ev.getY());
+ if (position != INVALID_POSITION) {
+ final int itemPosition = position - headersCount;
+ if (itemPosition >= 0 && itemPosition < menuAdapter.getCount()) {
+ menuItem = menuAdapter.getItem(itemPosition);
+ }
+ }
+ }
+
+ final MenuItem oldMenuItem = mHoveredMenuItem;
+ if (oldMenuItem != menuItem) {
+ final MenuBuilder menu = menuAdapter.getAdapterMenu();
+ if (oldMenuItem != null) {
+ mHoverListener.onItemHoverExit(menu, oldMenuItem);
+ }
+
+ mHoveredMenuItem = menuItem;
+
+ if (menuItem != null) {
+ mHoverListener.onItemHoverEnter(menu, menuItem);
+ }
+ }
+ }
+
+ return super.onHoverEvent(ev);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
index 7f097c7..066c7e0 100644
--- a/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
+++ b/v7/appcompat/src/android/support/v7/widget/PopupMenu.java
@@ -16,20 +16,23 @@
package android.support.v7.widget;
-
import android.content.Context;
+import android.support.annotation.AttrRes;
import android.support.annotation.MenuRes;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.StyleRes;
import android.support.v7.appcompat.R;
import android.support.v7.view.SupportMenuInflater;
import android.support.v7.view.menu.MenuBuilder;
import android.support.v7.view.menu.MenuPopupHelper;
-import android.support.v7.view.menu.MenuPresenter;
-import android.support.v7.view.menu.SubMenuBuilder;
+import android.support.v7.view.menu.ShowableListMenu;
import android.view.Gravity;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
+import android.widget.PopupWindow;
/**
* Static library support version of the framework's {@link android.widget.PopupMenu}.
@@ -38,35 +41,25 @@
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*/
-public class PopupMenu implements MenuBuilder.Callback, MenuPresenter.Callback {
- private Context mContext;
- private MenuBuilder mMenu;
- private View mAnchor;
- private MenuPopupHelper mPopup;
+public class PopupMenu {
+ private final Context mContext;
+ private final MenuBuilder mMenu;
+ private final View mAnchor;
+ private final MenuPopupHelper mPopup;
+
private OnMenuItemClickListener mMenuItemClickListener;
- private OnDismissListener mDismissListener;
+ private OnDismissListener mOnDismissListener;
private View.OnTouchListener mDragListener;
/**
- * Callback interface used to notify the application that the menu has closed.
- */
- public interface OnDismissListener {
- /**
- * Called when the associated menu has been dismissed.
+ * Constructor to create a new popup menu with an anchor view.
*
- * @param menu The PopupMenu that was dismissed.
- */
- public void onDismiss(PopupMenu menu);
- }
-
- /**
- * Construct a new PopupMenu.
- *
- * @param context Context for the PopupMenu.
- * @param anchor Anchor view for this popup. The popup will appear below the anchor if there
- * is room, or above it if there is not.
+ * @param context Context the popup menu is running in, through which it
+ * can access the current theme, resources, etc.
+ * @param anchor Anchor view for this popup. The popup will appear below
+ * the anchor if there is room, or above it if there is not.
*/
- public PopupMenu(Context context, View anchor) {
+ public PopupMenu(@NonNull Context context, @NonNull View anchor) {
this(context, anchor, Gravity.NO_GRAVITY);
}
@@ -81,7 +74,7 @@
* @param gravity The {@link Gravity} value for aligning the popup with its
* anchor.
*/
- public PopupMenu(Context context, View anchor, int gravity) {
+ public PopupMenu(@NonNull Context context, @NonNull View anchor, int gravity) {
this(context, anchor, gravity, R.attr.popupMenuStyle, 0);
}
@@ -102,15 +95,36 @@
* popupStyleAttr is 0 or can not be found in the theme. Can be 0
* to not look for defaults.
*/
- public PopupMenu(Context context, View anchor, int gravity, int popupStyleAttr,
- int popupStyleRes) {
+ public PopupMenu(@NonNull Context context, @NonNull View anchor, int gravity,
+ @AttrRes int popupStyleAttr, @StyleRes int popupStyleRes) {
mContext = context;
- mMenu = new MenuBuilder(context);
- mMenu.setCallback(this);
mAnchor = anchor;
+
+ mMenu = new MenuBuilder(context);
+ mMenu.setCallback(new MenuBuilder.Callback() {
+ @Override
+ public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
+ if (mMenuItemClickListener != null) {
+ return mMenuItemClickListener.onMenuItemClick(item);
+ }
+ return false;
+ }
+
+ @Override
+ public void onMenuModeChange(MenuBuilder menu) {
+ }
+ });
+
mPopup = new MenuPopupHelper(context, mMenu, anchor, false, popupStyleAttr, popupStyleRes);
mPopup.setGravity(gravity);
- mPopup.setCallback(this);
+ mPopup.setOnDismissListener(new PopupWindow.OnDismissListener() {
+ @Override
+ public void onDismiss() {
+ if (mOnDismissListener != null) {
+ mOnDismissListener.onDismiss(PopupMenu.this);
+ }
+ }
+ });
}
/**
@@ -120,7 +134,6 @@
* the next time the popup is shown.
*
* @param gravity the gravity used to align the popup window
- *
* @see #getGravity()
*/
public void setGravity(int gravity) {
@@ -129,7 +142,6 @@
/**
* @return the gravity used to align the popup window to its anchor view
- *
* @see #setGravity(int)
*/
public int getGravity() {
@@ -137,12 +149,12 @@
}
/**
- * Returns an {@link android.view.View.OnTouchListener} that can be added to the anchor view
+ * Returns an {@link View.OnTouchListener} that can be added to the anchor view
* to implement drag-to-open behavior.
* <p>
* When the listener is set on a view, touching that view and dragging
- * outside of its bounds will open the popup window. Lifting will select the
- * currently touched list item.
+ * outside of its bounds will open the popup window. Lifting will select
+ * the currently touched list item.
* <p>
* Example usage:
* <pre>
@@ -152,9 +164,10 @@
*
* @return a touch listener that controls drag-to-open behavior
*/
+ @NonNull
public View.OnTouchListener getDragToOpenListener() {
if (mDragListener == null) {
- mDragListener = new ListPopupWindow.ForwardingListener(mAnchor) {
+ mDragListener = new ForwardingListener(mAnchor) {
@Override
protected boolean onForwardingStarted() {
show();
@@ -168,7 +181,7 @@
}
@Override
- public ListPopupWindow getPopup() {
+ public ShowableListMenu getPopup() {
// This will be null until show() is called.
return mPopup.getPopup();
}
@@ -179,29 +192,32 @@
}
/**
- * @return the {@link Menu} associated with this popup. Populate the returned Menu with
- * items before calling {@link #show()}.
+ * Returns the {@link Menu} associated with this popup. Populate the
+ * returned Menu with items before calling {@link #show()}.
*
+ * @return the {@link Menu} associated with this popup
* @see #show()
* @see #getMenuInflater()
*/
+ @NonNull
public Menu getMenu() {
return mMenu;
}
/**
- * @return a {@link MenuInflater} that can be used to inflate menu items from XML into the
- * menu returned by {@link #getMenu()}.
- *
+ * @return a {@link MenuInflater} that can be used to inflate menu items
+ * from XML into the menu returned by {@link #getMenu()}
* @see #getMenu()
*/
+ @NonNull
public MenuInflater getMenuInflater() {
return new SupportMenuInflater(mContext);
}
/**
- * Inflate a menu resource into this PopupMenu. This is equivalent to calling
- * popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu()).
+ * Inflate a menu resource into this PopupMenu. This is equivalent to
+ * calling {@code popupMenu.getMenuInflater().inflate(menuRes, popupMenu.getMenu())}.
+ *
* @param menuRes Menu resource to inflate
*/
public void inflate(@MenuRes int menuRes) {
@@ -210,6 +226,7 @@
/**
* Show the menu popup anchored to the view specified during construction.
+ *
* @see #dismiss()
*/
public void show() {
@@ -218,6 +235,7 @@
/**
* Dismiss the menu popup.
+ *
* @see #show()
*/
public void dismiss() {
@@ -225,81 +243,49 @@
}
/**
- * Set a listener that will be notified when the user selects an item from the menu.
+ * Sets a listener that will be notified when the user selects an item from
+ * the menu.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
- public void setOnMenuItemClickListener(OnMenuItemClickListener listener) {
+ public void setOnMenuItemClickListener(@Nullable OnMenuItemClickListener listener) {
mMenuItemClickListener = listener;
}
/**
- * Set a listener that will be notified when this menu is dismissed.
+ * Sets a listener that will be notified when this menu is dismissed.
*
- * @param listener Listener to notify
+ * @param listener the listener to notify
*/
- public void setOnDismissListener(OnDismissListener listener) {
- mDismissListener = listener;
+ public void setOnDismissListener(@Nullable OnDismissListener listener) {
+ mOnDismissListener = listener;
}
/**
- * @hide
- */
- public boolean onMenuItemSelected(MenuBuilder menu, MenuItem item) {
- if (mMenuItemClickListener != null) {
- return mMenuItemClickListener.onMenuItemClick(item);
- }
- return false;
- }
-
- /**
- * @hide
- */
- public void onCloseMenu(MenuBuilder menu, boolean allMenusAreClosing) {
- if (mDismissListener != null) {
- mDismissListener.onDismiss(this);
- }
- }
-
- /**
- * @hide
- */
- public boolean onOpenSubMenu(MenuBuilder subMenu) {
- if (subMenu == null) return false;
-
- if (!subMenu.hasVisibleItems()) {
- return true;
- }
-
- // Current menu will be dismissed by the normal helper, submenu will be shown in its place.
- new MenuPopupHelper(mContext, subMenu, mAnchor).show();
- return true;
- }
-
- /**
- * @hide
- */
- public void onCloseSubMenu(SubMenuBuilder menu) {
- }
-
- /**
- * @hide
- */
- public void onMenuModeChange(MenuBuilder menu) {
- }
-
- /**
- * Interface responsible for receiving menu item click events if the items themselves
- * do not have individual item click listeners.
+ * Interface responsible for receiving menu item click events if the items
+ * themselves do not have individual item click listeners.
*/
public interface OnMenuItemClickListener {
/**
- * This method will be invoked when a menu item is clicked if the item itself did
- * not already handle the event.
+ * This method will be invoked when a menu item is clicked if the item
+ * itself did not already handle the event.
*
- * @param item {@link MenuItem} that was clicked
- * @return <code>true</code> if the event was handled, <code>false</code> otherwise.
+ * @param item the menu item that was clicked
+ * @return {@code true} if the event was handled, {@code false}
+ * otherwise
*/
- public boolean onMenuItemClick(MenuItem item);
+ boolean onMenuItemClick(MenuItem item);
+ }
+
+ /**
+ * Callback interface used to notify the application that the menu has closed.
+ */
+ public interface OnDismissListener {
+ /**
+ * Called when the associated menu has been dismissed.
+ *
+ * @param menu the popup menu that was dismissed
+ */
+ void onDismiss(PopupMenu menu);
}
}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/SearchView.java b/v7/appcompat/src/android/support/v7/widget/SearchView.java
index e4644d0..7ae8827 100644
--- a/v7/appcompat/src/android/support/v7/widget/SearchView.java
+++ b/v7/appcompat/src/android/support/v7/widget/SearchView.java
@@ -16,6 +16,8 @@
package android.support.v7.widget;
+import static android.support.v7.widget.SuggestionsAdapter.getColumnString;
+
import android.annotation.TargetApi;
import android.app.PendingIntent;
import android.app.SearchManager;
@@ -34,8 +36,15 @@
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
import android.os.ResultReceiver;
import android.speech.RecognizerIntent;
+import android.support.annotation.Nullable;
+import android.support.v4.content.res.ConfigurationHelper;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.KeyEventCompat;
import android.support.v4.widget.CursorAdapter;
import android.support.v7.appcompat.R;
@@ -48,10 +57,15 @@
import android.text.TextWatcher;
import android.text.style.ImageSpan;
import android.util.AttributeSet;
+import android.util.DisplayMetrics;
import android.util.Log;
+import android.util.TypedValue;
import android.view.KeyEvent;
import android.view.LayoutInflater;
+import android.view.MotionEvent;
+import android.view.TouchDelegate;
import android.view.View;
+import android.view.ViewConfiguration;
import android.view.ViewTreeObserver;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
@@ -67,8 +81,6 @@
import java.lang.reflect.Method;
import java.util.WeakHashMap;
-import static android.support.v7.widget.SuggestionsAdapter.getColumnString;
-
/**
* A widget that provides a user interface for the user to enter a search query and submit a request
* to a search provider. Shows a list of query suggestions or results, if available, and allows the
@@ -120,6 +132,12 @@
private final ImageView mVoiceButton;
private final View mDropDownAnchor;
+ private UpdatableTouchDelegate mTouchDelegate;
+ private Rect mSearchSrcTextViewBounds = new Rect();
+ private Rect mSearchSrtTextViewBoundsExpanded = new Rect();
+ private int[] mTemp = new int[2];
+ private int[] mTemp2 = new int[2];
+
/** Icon optionally displayed when the SearchView is collapsed. */
private final ImageView mCollapsedIcon;
@@ -452,6 +470,8 @@
*
* @see TextView#setImeOptions(int)
* @param imeOptions the options to set on the query text field
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_imeOptions
*/
public void setImeOptions(int imeOptions) {
mSearchSrcTextView.setImeOptions(imeOptions);
@@ -461,6 +481,8 @@
* Returns the IME options set on the query text field.
* @return the ime options
* @see TextView#setImeOptions(int)
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_imeOptions
*/
public int getImeOptions() {
return mSearchSrcTextView.getImeOptions();
@@ -471,6 +493,8 @@
*
* @see TextView#setInputType(int)
* @param inputType the input type to set on the query text field
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_inputType
*/
public void setInputType(int inputType) {
mSearchSrcTextView.setInputType(inputType);
@@ -479,6 +503,8 @@
/**
* Returns the input type set on the query text field.
* @return the input type
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_inputType
*/
public int getInputType() {
return mSearchSrcTextView.getInputType();
@@ -600,8 +626,9 @@
* from being displayed.
*
* @param hint the hint text to display or {@code null} to clear
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_queryHint
*/
- public void setQueryHint(CharSequence hint) {
+ public void setQueryHint(@Nullable CharSequence hint) {
mQueryHint = hint;
updateQueryHint();
}
@@ -619,8 +646,12 @@
* inflated
* </ol>
*
+ *
+ *
* @return the displayed query hint text, or {@code null} if none set
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_queryHint
*/
+ @Nullable
public CharSequence getQueryHint() {
final CharSequence hint;
if (mQueryHint != null) {
@@ -642,6 +673,8 @@
* <p>The default value is true.</p>
*
* @param iconified whether the search field should be iconified by default
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_iconifiedByDefault
*/
public void setIconifiedByDefault(boolean iconified) {
if (mIconifiedByDefault == iconified) return;
@@ -653,6 +686,8 @@
/**
* Returns the default iconified state of the search field.
* @return
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_iconifiedByDefault
*/
public boolean isIconfiedByDefault() {
return mIconifiedByDefault;
@@ -760,6 +795,8 @@
/**
* Makes the view at most this many pixels wide
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_maxWidth
*/
public void setMaxWidth(int maxpixels) {
mMaxWidth = maxpixels;
@@ -771,6 +808,8 @@
* Gets the specified maximum width in pixels, if set. Returns zero if
* no maximum width was specified.
* @return the maximum width of the view
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SearchView_android_maxWidth
*/
public int getMaxWidth() {
return mMaxWidth;
@@ -808,7 +847,48 @@
break;
}
widthMode = MeasureSpec.EXACTLY;
- super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode), heightMeasureSpec);
+
+ int heightMode = MeasureSpec.getMode(heightMeasureSpec);
+ int height = MeasureSpec.getSize(heightMeasureSpec);
+
+ switch (heightMode) {
+ case MeasureSpec.AT_MOST:
+ case MeasureSpec.UNSPECIFIED:
+ height = Math.min(getPreferredHeight(), height);
+ break;
+ }
+ heightMode = MeasureSpec.EXACTLY;
+
+ super.onMeasure(MeasureSpec.makeMeasureSpec(width, widthMode),
+ MeasureSpec.makeMeasureSpec(height, heightMode));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+
+ if (changed) {
+ // Expand mSearchSrcTextView touch target to be the height of the parent in order to
+ // allow it to be up to 48dp.
+ getChildBoundsWithinSearchView(mSearchSrcTextView, mSearchSrcTextViewBounds);
+ mSearchSrtTextViewBoundsExpanded.set(
+ mSearchSrcTextViewBounds.left, 0, mSearchSrcTextViewBounds.right, bottom - top);
+ if (mTouchDelegate == null) {
+ mTouchDelegate = new UpdatableTouchDelegate(mSearchSrtTextViewBoundsExpanded,
+ mSearchSrcTextViewBounds, mSearchSrcTextView);
+ setTouchDelegate(mTouchDelegate);
+ } else {
+ mTouchDelegate.setBounds(mSearchSrtTextViewBoundsExpanded, mSearchSrcTextViewBounds);
+ }
+ }
+ }
+
+ private void getChildBoundsWithinSearchView(View view, Rect rect) {
+ view.getLocationInWindow(mTemp);
+ getLocationInWindow(mTemp2);
+ final int top = mTemp[1] - mTemp2[1];
+ final int left = mTemp[0] - mTemp2[0];
+ rect.set(left, top, left + view.getWidth(), top + view.getHeight());
}
private int getPreferredWidth() {
@@ -816,6 +896,11 @@
.getDimensionPixelSize(R.dimen.abc_search_view_preferred_width);
}
+ private int getPreferredHeight() {
+ return getContext().getResources()
+ .getDimensionPixelSize(R.dimen.abc_search_view_preferred_height);
+ }
+
private void updateViewsVisibility(final boolean collapsed) {
mIconified = collapsed;
// Visibility of views that are visible when collapsed
@@ -1264,6 +1349,64 @@
setIconified(false);
}
+ static class SavedState extends AbsSavedState {
+ boolean isIconified;
+
+ SavedState(Parcelable superState) {
+ super(superState);
+ }
+
+ public SavedState(Parcel source, ClassLoader loader) {
+ super(source, loader);
+ isIconified = (Boolean) source.readValue(null);
+ }
+
+ @Override
+ public void writeToParcel(Parcel dest, int flags) {
+ super.writeToParcel(dest, flags);
+ dest.writeValue(isIconified);
+ }
+
+ @Override
+ public String toString() {
+ return "SearchView.SavedState{"
+ + Integer.toHexString(System.identityHashCode(this))
+ + " isIconified=" + isIconified + "}";
+ }
+
+ public static final Parcelable.Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
+
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
+ }
+
+ @Override
+ protected Parcelable onSaveInstanceState() {
+ Parcelable superState = super.onSaveInstanceState();
+ SavedState ss = new SavedState(superState);
+ ss.isIconified = isIconified();
+ return ss;
+ }
+
+ @Override
+ protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+ SavedState ss = (SavedState) state;
+ super.onRestoreInstanceState(ss.getSuperState());
+ updateViewsVisibility(ss.isIconified);
+ requestLayout();
+ }
private void adjustDropDownSizeAndPosition() {
if (mDropDownAnchor.getWidth() > 1) {
@@ -1626,6 +1769,101 @@
}
};
+ private static class UpdatableTouchDelegate extends TouchDelegate {
+ /**
+ * View that should receive forwarded touch events
+ */
+ private final View mDelegateView;
+
+ /**
+ * Bounds in local coordinates of the containing view that should be mapped to the delegate
+ * view. This rect is used for initial hit testing.
+ */
+ private final Rect mTargetBounds;
+
+ /**
+ * Bounds in local coordinates of the containing view that are actual bounds of the delegate
+ * view. This rect is used for event coordinate mapping.
+ */
+ private final Rect mActualBounds;
+
+ /**
+ * mTargetBounds inflated to include some slop. This rect is to track whether the motion events
+ * should be considered to be be within the delegate view.
+ */
+ private final Rect mSlopBounds;
+
+ private final int mSlop;
+
+ /**
+ * True if the delegate had been targeted on a down event (intersected mTargetBounds).
+ */
+ private boolean mDelegateTargeted;
+
+ public UpdatableTouchDelegate(Rect targetBounds, Rect actualBounds, View delegateView) {
+ super(targetBounds, delegateView);
+ mSlop = ViewConfiguration.get(delegateView.getContext()).getScaledTouchSlop();
+ mTargetBounds = new Rect();
+ mSlopBounds = new Rect();
+ mActualBounds = new Rect();
+ setBounds(targetBounds, actualBounds);
+ mDelegateView = delegateView;
+ }
+
+ public void setBounds(Rect desiredBounds, Rect actualBounds) {
+ mTargetBounds.set(desiredBounds);
+ mSlopBounds.set(desiredBounds);
+ mSlopBounds.inset(-mSlop, -mSlop);
+ mActualBounds.set(actualBounds);
+ }
+
+ @Override
+ public boolean onTouchEvent(MotionEvent event) {
+ final int x = (int) event.getX();
+ final int y = (int) event.getY();
+ boolean sendToDelegate = false;
+ boolean hit = true;
+ boolean handled = false;
+
+ switch (event.getAction()) {
+ case MotionEvent.ACTION_DOWN:
+ if (mTargetBounds.contains(x, y)) {
+ mDelegateTargeted = true;
+ sendToDelegate = true;
+ }
+ break;
+ case MotionEvent.ACTION_UP:
+ case MotionEvent.ACTION_MOVE:
+ sendToDelegate = mDelegateTargeted;
+ if (sendToDelegate) {
+ if (!mSlopBounds.contains(x, y)) {
+ hit = false;
+ }
+ }
+ break;
+ case MotionEvent.ACTION_CANCEL:
+ sendToDelegate = mDelegateTargeted;
+ mDelegateTargeted = false;
+ break;
+ }
+ if (sendToDelegate) {
+ if (hit && !mActualBounds.contains(x, y)) {
+ // Offset event coordinates to be in the center of the target view since we
+ // are within the targetBounds, but not inside the actual bounds of
+ // mDelegateView
+ event.setLocation(mDelegateView.getWidth() / 2,
+ mDelegateView.getHeight() / 2);
+ } else {
+ // Offset event coordinates to the target view coordinates.
+ event.setLocation(x - mActualBounds.left, y - mActualBounds.top);
+ }
+
+ handled = mDelegateView.dispatchTouchEvent(event);
+ }
+ return handled;
+ }
+ }
+
/**
* Local subclass for AutoCompleteTextView.
* @hide
@@ -1648,6 +1886,14 @@
mThreshold = getThreshold();
}
+ @Override
+ protected void onFinishInflate() {
+ super.onFinishInflate();
+ DisplayMetrics metrics = getResources().getDisplayMetrics();
+ setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP,
+ getSearchViewTextMinWidthDp(), metrics));
+ }
+
void setSearchView(SearchView searchView) {
mSearchView = searchView;
}
@@ -1743,6 +1989,23 @@
}
return super.onKeyPreIme(keyCode, event);
}
+
+ /**
+ * Get minimum width of the search view text entry area.
+ */
+ private int getSearchViewTextMinWidthDp() {
+ final Configuration config = getResources().getConfiguration();
+ final int widthDp = ConfigurationHelper.getScreenWidthDp(getResources());
+ final int heightDp = ConfigurationHelper.getScreenHeightDp(getResources());
+
+ if (widthDp >= 960 && heightDp >= 720
+ && config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+ return 256;
+ } else if (widthDp >= 600 || (widthDp >= 640 && heightDp >= 480)) {
+ return 192;
+ }
+ return 160;
+ }
}
private static class AutoCompleteTextViewReflector {
@@ -1821,4 +2084,4 @@
imm.showSoftInput(view, flags);
}
}
-}
\ No newline at end of file
+}
diff --git a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
index f87109b..74f4194 100644
--- a/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/SwitchCompat.java
@@ -23,11 +23,13 @@
import android.content.res.TypedArray;
import android.graphics.Canvas;
import android.graphics.Paint;
+import android.graphics.PorterDuff;
import android.graphics.Rect;
import android.graphics.Region;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.support.annotation.Nullable;
import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.ViewCompat;
@@ -64,7 +66,19 @@
* {@link #setTextAppearance(android.content.Context, int) textAppearance} and the related
* setTypeface() methods control the typeface and style of label text, whereas the
* {@link #setSwitchTextAppearance(android.content.Context, int) switchTextAppearance} and
- * the related seSwitchTypeface() methods control that of the thumb.
+ * the related setSwitchTypeface() methods control that of the thumb.
+ *
+ * <p>See the <a href="{@docRoot}guide/topics/ui/controls/togglebutton.html">Toggle Buttons</a>
+ * guide.</p>
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOn
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOff
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchMinWidth
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchPadding
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_thumb
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTextPadding
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_track
*/
public class SwitchCompat extends CompoundButton {
private static final int THUMB_ANIMATION_DURATION = 250;
@@ -83,7 +97,17 @@
private static final int MONOSPACE = 3;
private Drawable mThumbDrawable;
+ private ColorStateList mThumbTintList = null;
+ private PorterDuff.Mode mThumbTintMode = null;
+ private boolean mHasThumbTint = false;
+ private boolean mHasThumbTintMode = false;
+
private Drawable mTrackDrawable;
+ private ColorStateList mTrackTintList = null;
+ private PorterDuff.Mode mTrackTintMode = null;
+ private boolean mHasTrackTint = false;
+ private boolean mHasTrackTintMode = false;
+
private int mThumbTextPadding;
private int mSwitchMinWidth;
private int mSwitchPadding;
@@ -206,6 +230,36 @@
R.styleable.SwitchCompat_switchPadding, 0);
mSplitTrack = a.getBoolean(R.styleable.SwitchCompat_splitTrack, false);
+ ColorStateList thumbTintList = a.getColorStateList(R.styleable.SwitchCompat_thumbTint);
+ if (thumbTintList != null) {
+ mThumbTintList = thumbTintList;
+ mHasThumbTint = true;
+ }
+ PorterDuff.Mode thumbTintMode = DrawableUtils.parseTintMode(
+ a.getInt(R.styleable.SwitchCompat_thumbTintMode, -1), null);
+ if (mThumbTintMode != thumbTintMode) {
+ mThumbTintMode = thumbTintMode;
+ mHasThumbTintMode = true;
+ }
+ if (mHasThumbTint || mHasThumbTintMode) {
+ applyThumbTint();
+ }
+
+ ColorStateList trackTintList = a.getColorStateList(R.styleable.SwitchCompat_trackTint);
+ if (trackTintList != null) {
+ mTrackTintList = trackTintList;
+ mHasTrackTint = true;
+ }
+ PorterDuff.Mode trackTintMode = DrawableUtils.parseTintMode(
+ a.getInt(R.styleable.SwitchCompat_trackTintMode, -1), null);
+ if (mTrackTintMode != trackTintMode) {
+ mTrackTintMode = trackTintMode;
+ mHasTrackTintMode = true;
+ }
+ if (mHasTrackTint || mHasTrackTintMode) {
+ applyTrackTint();
+ }
+
final int appearance = a.getResourceId(
R.styleable.SwitchCompat_switchTextAppearance, 0);
if (appearance != 0) {
@@ -228,6 +282,8 @@
/**
* Sets the switch text color, size, style, hint color, and highlight color
* from the specified TextAppearance resource.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchTextAppearance
*/
public void setSwitchTextAppearance(Context context, int resid) {
TypedArray appearance = context.obtainStyledAttributes(resid, R.styleable.TextAppearance);
@@ -333,6 +389,8 @@
* Set the amount of horizontal padding between the switch and the associated text.
*
* @param pixels Amount of padding in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchPadding
*/
public void setSwitchPadding(int pixels) {
mSwitchPadding = pixels;
@@ -343,6 +401,8 @@
* Get the amount of horizontal padding between the switch and the associated text.
*
* @return Amount of padding in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchPadding
*/
public int getSwitchPadding() {
return mSwitchPadding;
@@ -353,6 +413,8 @@
* of this value and its measured width as determined by the switch drawables and text used.
*
* @param pixels Minimum width of the switch in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchMinWidth
*/
public void setSwitchMinWidth(int pixels) {
mSwitchMinWidth = pixels;
@@ -364,6 +426,8 @@
* of this value and its measured width as determined by the switch drawables and text used.
*
* @return Minimum width of the switch in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_switchMinWidth
*/
public int getSwitchMinWidth() {
return mSwitchMinWidth;
@@ -373,6 +437,8 @@
* Set the horizontal padding around the text drawn on the switch itself.
*
* @param pixels Horizontal padding for switch thumb text in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTextPadding
*/
public void setThumbTextPadding(int pixels) {
mThumbTextPadding = pixels;
@@ -383,6 +449,8 @@
* Get the horizontal padding around the text drawn on the switch itself.
*
* @return Horizontal padding for switch thumb text in pixels
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTextPadding
*/
public int getThumbTextPadding() {
return mThumbTextPadding;
@@ -392,9 +460,17 @@
* Set the drawable used for the track that the switch slides within.
*
* @param track Track drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_track
*/
public void setTrackDrawable(Drawable track) {
+ if (mTrackDrawable != null) {
+ mTrackDrawable.setCallback(null);
+ }
mTrackDrawable = track;
+ if (track != null) {
+ track.setCallback(this);
+ }
requestLayout();
}
@@ -402,6 +478,8 @@
* Set the drawable used for the track that the switch slides within.
*
* @param resId Resource ID of a track drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_track
*/
public void setTrackResource(int resId) {
setTrackDrawable(mDrawableManager.getDrawable(getContext(), resId));
@@ -411,19 +489,107 @@
* Get the drawable used for the track that the switch slides within.
*
* @return Track drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_track
*/
public Drawable getTrackDrawable() {
return mTrackDrawable;
}
/**
+ * Applies a tint to the track drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setTrackDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_trackTint
+ * @see #getTrackTintList()
+ */
+ public void setTrackTintList(@Nullable ColorStateList tint) {
+ mTrackTintList = tint;
+ mHasTrackTint = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the tint applied to the track drawable
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_trackTint
+ * @see #setTrackTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getTrackTintList() {
+ return mTrackTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setTrackTintList(ColorStateList)}} to the track drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_trackTintMode
+ * @see #getTrackTintMode()
+ */
+ public void setTrackTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mTrackTintMode = tintMode;
+ mHasTrackTintMode = true;
+
+ applyTrackTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the track
+ * drawable
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_trackTintMode
+ * @see #setTrackTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getTrackTintMode() {
+ return mTrackTintMode;
+ }
+
+ private void applyTrackTint() {
+ if (mTrackDrawable != null && (mHasTrackTint || mHasTrackTintMode)) {
+ mTrackDrawable = mTrackDrawable.mutate();
+
+ if (mHasTrackTint) {
+ DrawableCompat.setTintList(mTrackDrawable, mTrackTintList);
+ }
+
+ if (mHasTrackTintMode) {
+ DrawableCompat.setTintMode(mTrackDrawable, mTrackTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mTrackDrawable.isStateful()) {
+ mTrackDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Set the drawable used for the switch "thumb" - the piece that the user
* can physically touch and drag along the track.
*
* @param thumb Thumb drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_thumb
*/
public void setThumbDrawable(Drawable thumb) {
+ if (mThumbDrawable != null) {
+ mThumbDrawable.setCallback(null);
+ }
mThumbDrawable = thumb;
+ if (thumb != null) {
+ thumb.setCallback(this);
+ }
requestLayout();
}
@@ -432,6 +598,8 @@
* can physically touch and drag along the track.
*
* @param resId Resource ID of a thumb drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_thumb
*/
public void setThumbResource(int resId) {
setThumbDrawable(mDrawableManager.getDrawable(getContext(), resId));
@@ -442,17 +610,101 @@
* can physically touch and drag along the track.
*
* @return Thumb drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_thumb
*/
public Drawable getThumbDrawable() {
return mThumbDrawable;
}
/**
+ * Applies a tint to the thumb drawable. Does not modify the current
+ * tint mode, which is {@link PorterDuff.Mode#SRC_IN} by default.
+ * <p>
+ * Subsequent calls to {@link #setThumbDrawable(Drawable)} will
+ * automatically mutate the drawable and apply the specified tint and tint
+ * mode using {@link DrawableCompat#setTintList(Drawable, ColorStateList)}.
+ *
+ * @param tint the tint to apply, may be {@code null} to clear tint
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTint
+ * @see #getThumbTintList()
+ * @see Drawable#setTintList(ColorStateList)
+ */
+ public void setThumbTintList(@Nullable ColorStateList tint) {
+ mThumbTintList = tint;
+ mHasThumbTint = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the tint applied to the thumb drawable
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTint
+ * @see #setThumbTintList(ColorStateList)
+ */
+ @Nullable
+ public ColorStateList getThumbTintList() {
+ return mThumbTintList;
+ }
+
+ /**
+ * Specifies the blending mode used to apply the tint specified by
+ * {@link #setThumbTintList(ColorStateList)}} to the thumb drawable.
+ * The default mode is {@link PorterDuff.Mode#SRC_IN}.
+ *
+ * @param tintMode the blending mode used to apply the tint, may be
+ * {@code null} to clear tint
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTintMode
+ * @see #getThumbTintMode()
+ * @see Drawable#setTintMode(PorterDuff.Mode)
+ */
+ public void setThumbTintMode(@Nullable PorterDuff.Mode tintMode) {
+ mThumbTintMode = tintMode;
+ mHasThumbTintMode = true;
+
+ applyThumbTint();
+ }
+
+ /**
+ * @return the blending mode used to apply the tint to the thumb
+ * drawable
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_thumbTintMode
+ * @see #setThumbTintMode(PorterDuff.Mode)
+ */
+ @Nullable
+ public PorterDuff.Mode getThumbTintMode() {
+ return mThumbTintMode;
+ }
+
+ private void applyThumbTint() {
+ if (mThumbDrawable != null && (mHasThumbTint || mHasThumbTintMode)) {
+ mThumbDrawable = mThumbDrawable.mutate();
+
+ if (mHasThumbTint) {
+ DrawableCompat.setTintList(mThumbDrawable, mThumbTintList);
+ }
+
+ if (mHasThumbTintMode) {
+ DrawableCompat.setTintMode(mThumbDrawable, mThumbTintMode);
+ }
+
+ // The drawable (or one of its children) may not have been
+ // stateful before applying the tint, so let's try again.
+ if (mThumbDrawable.isStateful()) {
+ mThumbDrawable.setState(getDrawableState());
+ }
+ }
+ }
+
+ /**
* Specifies whether the track should be split by the thumb. When true,
* the thumb's optical bounds will be clipped out of the track drawable,
* then the thumb will be drawn into the resulting gap.
*
* @param splitTrack Whether the track should be split by the thumb
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_splitTrack
*/
public void setSplitTrack(boolean splitTrack) {
mSplitTrack = splitTrack;
@@ -461,6 +713,8 @@
/**
* Returns whether the track should be split by the thumb.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_splitTrack
*/
public boolean getSplitTrack() {
return mSplitTrack;
@@ -468,6 +722,8 @@
/**
* Returns the text displayed when the button is in the checked state.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOn
*/
public CharSequence getTextOn() {
return mTextOn;
@@ -475,6 +731,8 @@
/**
* Sets the text displayed when the button is in the checked state.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOn
*/
public void setTextOn(CharSequence textOn) {
mTextOn = textOn;
@@ -483,6 +741,8 @@
/**
* Returns the text displayed when the button is not in the checked state.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOff
*/
public CharSequence getTextOff() {
return mTextOff;
@@ -490,6 +750,8 @@
/**
* Sets the text displayed when the button is not in the checked state.
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_android_textOff
*/
public void setTextOff(CharSequence textOff) {
mTextOff = textOff;
@@ -500,6 +762,7 @@
* Sets whether the on/off text should be displayed.
*
* @param showText {@code true} to display on/off text
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_showText
*/
public void setShowText(boolean showText) {
if (mShowText != showText) {
@@ -510,6 +773,7 @@
/**
* @return whether the on/off text should be displayed
+ * @attr ref android.support.v7.appcompat.R.styleable#SwitchCompat_showText
*/
public boolean getShowText() {
return mShowText;
@@ -1072,17 +1336,22 @@
protected void drawableStateChanged() {
super.drawableStateChanged();
- final int[] myDrawableState = getDrawableState();
+ final int[] state = getDrawableState();
+ boolean changed = false;
- if (mThumbDrawable != null) {
- mThumbDrawable.setState(myDrawableState);
+ final Drawable thumbDrawable = mThumbDrawable;
+ if (thumbDrawable != null && thumbDrawable.isStateful()) {
+ changed |= thumbDrawable.setState(state);
}
- if (mTrackDrawable != null) {
- mTrackDrawable.setState(myDrawableState);
+ final Drawable trackDrawable = mTrackDrawable;
+ if (trackDrawable != null && trackDrawable.isStateful()) {
+ changed |= trackDrawable.setState(state);
}
- invalidate();
+ if (changed) {
+ invalidate();
+ }
}
@Override
@@ -1119,6 +1388,7 @@
}
cancelPositionAnimator();
+ setThumbPosition(isChecked() ? 1 : 0);
}
}
diff --git a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
index 1c0151b..02ee49a 100644
--- a/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
+++ b/v7/appcompat/src/android/support/v7/widget/ThemeUtils.java
@@ -18,7 +18,6 @@
import android.content.Context;
import android.content.res.ColorStateList;
-import android.content.res.TypedArray;
import android.graphics.Color;
import android.support.v4.graphics.ColorUtils;
import android.util.TypedValue;
@@ -61,7 +60,7 @@
public static int getThemeAttrColor(Context context, int attr) {
TEMP_ARRAY[0] = attr;
- TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
try {
return a.getColor(0, 0);
} finally {
@@ -71,7 +70,7 @@
public static ColorStateList getThemeAttrColorStateList(Context context, int attr) {
TEMP_ARRAY[0] = attr;
- TypedArray a = context.obtainStyledAttributes(null, TEMP_ARRAY);
+ TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, null, TEMP_ARRAY);
try {
return a.getColorStateList(0);
} finally {
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index ef12e06..3d2a29b 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -19,56 +19,99 @@
import android.content.Context;
import android.content.ContextWrapper;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.annotation.NonNull;
+import android.support.v7.app.AppCompatDelegate;
+import android.support.v7.widget.VectorEnabledTintResources;
+
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
/**
* A {@link android.content.ContextWrapper} which returns a tint-aware
* {@link android.content.res.Resources} instance from {@link #getResources()}.
+ *
+ * @hide
*/
-class TintContextWrapper extends ContextWrapper {
+public class TintContextWrapper extends ContextWrapper {
- public static Context wrap(Context context) {
- if (!(context instanceof TintContextWrapper)) {
- context = new TintContextWrapper(context);
+ private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
+
+ public static Context wrap(@NonNull final Context context) {
+ if (shouldWrap(context)) {
+ // First check our instance cache
+ for (int i = 0, count = sCache.size(); i < count; i++) {
+ final WeakReference<TintContextWrapper> ref = sCache.get(i);
+ final TintContextWrapper wrapper = ref != null ? ref.get() : null;
+ if (wrapper != null && wrapper.getBaseContext() == context) {
+ return wrapper;
+ }
+ }
+ // If we reach here then the cache didn't have a hit, so create a new instance
+ // and add it to the cache
+ final TintContextWrapper wrapper = new TintContextWrapper(context);
+ sCache.add(new WeakReference<>(wrapper));
+ return wrapper;
}
+
return context;
}
- private Resources mResources;
+ private static boolean shouldWrap(@NonNull final Context context) {
+ if (context instanceof TintContextWrapper
+ || context.getResources() instanceof TintResources
+ || context.getResources() instanceof VectorEnabledTintResources) {
+ // If the Context already has a TintResources[Experimental] impl, no need to wrap again
+ // If the Context is already a TintContextWrapper, no need to wrap again
+ return false;
+ }
+ if (AppCompatDelegate.isCompatVectorFromResourcesEnabled()
+ && Build.VERSION.SDK_INT > VectorEnabledTintResources.MAX_SDK_WHERE_REQUIRED) {
+ // If we're running on API 21+ and have the vector resources enabled, there's
+ // no need to wrap
+ return false;
+ }
+ // Else, we should wrap
+ return true;
+ }
- private TintContextWrapper(Context base) {
+ private Resources mResources;
+ private final Resources.Theme mTheme;
+
+ private TintContextWrapper(@NonNull final Context base) {
super(base);
+
+ if (VectorEnabledTintResources.shouldBeUsed()) {
+ // We need to create a copy of the Theme so that the Theme references our Resources
+ // instance
+ mTheme = getResources().newTheme();
+ mTheme.setTo(base.getTheme());
+ } else {
+ mTheme = null;
+ }
+ }
+
+ @Override
+ public Resources.Theme getTheme() {
+ return mTheme == null ? super.getTheme() : mTheme;
+ }
+
+ @Override
+ public void setTheme(int resid) {
+ if (mTheme == null) {
+ super.setTheme(resid);
+ } else {
+ mTheme.applyStyle(resid, true);
+ }
}
@Override
public Resources getResources() {
if (mResources == null) {
- mResources = new TintResources(super.getResources());
+ mResources = (mTheme == null)
+ ? new TintResources(this, super.getResources())
+ : new VectorEnabledTintResources(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/TintInfo.java b/v7/appcompat/src/android/support/v7/widget/TintInfo.java
index 547220c..0e7a667 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintInfo.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintInfo.java
@@ -24,4 +24,11 @@
public PorterDuff.Mode mTintMode;
public boolean mHasTintMode;
public boolean mHasTintList;
+
+ void clear() {
+ mTintList = null;
+ mHasTintList = false;
+ mTintMode = null;
+ mHasTintMode = false;
+ }
}
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..39e03a5
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/TintResources.java
@@ -0,0 +1,52 @@
+/*
+ * 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;
+
+import java.lang.ref.WeakReference;
+
+/**
+ * This class allows us to intercept calls so that we can tint resources (if applicable).
+ */
+class TintResources extends ResourcesWrapper {
+
+ private final WeakReference<Context> mContextRef;
+
+ public TintResources(@NonNull Context context, @NonNull final Resources res) {
+ super(res);
+ mContextRef = new WeakReference<>(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 {
+ Drawable d = super.getDrawable(id);
+ Context context = mContextRef.get();
+ if (d != null && context != null) {
+ AppCompatDrawableManager.get().tintDrawableUsingColorFilter(context, id, d);
+ }
+ return d;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
index 50e1046..1c92397 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintTypedArray.java
@@ -21,6 +21,8 @@
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
+import android.os.Build;
+import android.support.v7.content.res.AppCompatResources;
import android.util.AttributeSet;
import android.util.TypedValue;
@@ -36,16 +38,21 @@
private final Context mContext;
private final TypedArray mWrapped;
+ private TypedValue mTypedValue;
+
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs) {
- TypedArray array = context.obtainStyledAttributes(set, attrs);
- return new TintTypedArray(context, array);
+ return new TintTypedArray(context, context.obtainStyledAttributes(set, attrs));
}
public static TintTypedArray obtainStyledAttributes(Context context, AttributeSet set,
int[] attrs, int defStyleAttr, int defStyleRes) {
- TypedArray array = context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes);
- return new TintTypedArray(context, array);
+ return new TintTypedArray(context,
+ context.obtainStyledAttributes(set, attrs, defStyleAttr, defStyleRes));
+ }
+
+ public static TintTypedArray obtainStyledAttributes(Context context, int resid, int[] attrs) {
+ return new TintTypedArray(context, context.obtainStyledAttributes(resid, attrs));
}
private TintTypedArray(Context context, TypedArray array) {
@@ -118,6 +125,16 @@
}
public ColorStateList getColorStateList(int index) {
+ if (mWrapped.hasValue(index)) {
+ final int resourceId = mWrapped.getResourceId(index, 0);
+ if (resourceId != 0) {
+ final ColorStateList value =
+ AppCompatResources.getColorStateList(mContext, resourceId);
+ if (value != null) {
+ return value;
+ }
+ }
+ }
return mWrapped.getColorStateList(index);
}
@@ -162,7 +179,15 @@
}
public int getType(int index) {
- return mWrapped.getType(index);
+ if (Build.VERSION.SDK_INT >= 21) {
+ return mWrapped.getType(index);
+ } else {
+ if (mTypedValue == null) {
+ mTypedValue = new TypedValue();
+ }
+ mWrapped.getValue(index, mTypedValue);
+ return mTypedValue.type;
+ }
}
public boolean hasValue(int index) {
diff --git a/v7/appcompat/src/android/support/v7/widget/Toolbar.java b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
index ca4d199..9a8e28e 100644
--- a/v7/appcompat/src/android/support/v7/widget/Toolbar.java
+++ b/v7/appcompat/src/android/support/v7/widget/Toolbar.java
@@ -28,6 +28,9 @@
import android.support.annotation.Nullable;
import android.support.annotation.StringRes;
import android.support.annotation.StyleRes;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.MarginLayoutParamsCompat;
import android.support.v4.view.MenuItemCompat;
@@ -105,6 +108,34 @@
* <p>In modern Android UIs developers should lean more on a visually distinct color scheme for
* toolbars than on their application icon. The use of application icon plus title as a standard
* layout is discouraged on API 21 devices and newer.</p>
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_buttonGravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_collapseIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetLeft
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetRight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStartWithNavigation
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEndWithActions
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_android_gravity
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logo
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_logoDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_maxButtonHeight
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_popupTheme
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitle
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_subtitleTextColor
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_title
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextAppearance
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleTextColor
*/
public class Toolbar extends ViewGroup {
private static final String TAG = "Toolbar";
@@ -139,6 +170,8 @@
private int mTitleMarginBottom;
private final RtlSpacingHelper mContentInsets = new RtlSpacingHelper();
+ private int mContentInsetStartWithNavigation;
+ private int mContentInsetEndWithActions;
private int mGravity = GravityCompat.START | Gravity.CENTER_VERTICAL;
@@ -206,9 +239,15 @@
mTitleTextAppearance = a.getResourceId(R.styleable.Toolbar_titleTextAppearance, 0);
mSubtitleTextAppearance = a.getResourceId(R.styleable.Toolbar_subtitleTextAppearance, 0);
mGravity = a.getInteger(R.styleable.Toolbar_android_gravity, mGravity);
- mButtonGravity = Gravity.TOP;
- mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom =
- a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, 0);
+ mButtonGravity = a.getInteger(R.styleable.Toolbar_buttonGravity, Gravity.TOP);
+
+ // First read the correct attribute
+ int titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargin, 0);
+ if (a.hasValue(R.styleable.Toolbar_titleMargins)) {
+ // Now read the deprecated attribute, if it has a value
+ titleMargin = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMargins, titleMargin);
+ }
+ mTitleMarginStart = mTitleMarginEnd = mTitleMarginTop = mTitleMarginBottom = titleMargin;
final int marginStart = a.getDimensionPixelOffset(R.styleable.Toolbar_titleMarginStart, -1);
if (marginStart >= 0) {
@@ -251,6 +290,11 @@
mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
}
+ mContentInsetStartWithNavigation = a.getDimensionPixelOffset(
+ R.styleable.Toolbar_contentInsetStartWithNavigation, RtlSpacingHelper.UNDEFINED);
+ mContentInsetEndWithActions = a.getDimensionPixelOffset(
+ R.styleable.Toolbar_contentInsetEndWithActions, RtlSpacingHelper.UNDEFINED);
+
mCollapseIcon = a.getDrawable(R.styleable.Toolbar_collapseIcon);
mCollapseDescription = a.getText(R.styleable.Toolbar_collapseContentDescription);
@@ -263,6 +307,7 @@
if (!TextUtils.isEmpty(subtitle)) {
setSubtitle(subtitle);
}
+
// Set the default context, since setPopupTheme() may be a no-op.
mPopupContext = getContext();
setPopupTheme(a.getResourceId(R.styleable.Toolbar_popupTheme, 0));
@@ -325,6 +370,116 @@
return mPopupTheme;
}
+ /**
+ * Sets the title margin.
+ *
+ * @param start the starting title margin in pixels
+ * @param top the top title margin in pixels
+ * @param end the ending title margin in pixels
+ * @param bottom the bottom title margin in pixels
+ * @see #getTitleMarginStart()
+ * @see #getTitleMarginTop()
+ * @see #getTitleMarginEnd()
+ * @see #getTitleMarginBottom()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMargin
+ */
+ public void setTitleMargin(int start, int top, int end, int bottom) {
+ mTitleMarginStart = start;
+ mTitleMarginTop = top;
+ mTitleMarginEnd = end;
+ mTitleMarginBottom = bottom;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the starting title margin in pixels
+ * @see #setTitleMarginStart(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ */
+ public int getTitleMarginStart() {
+ return mTitleMarginStart;
+ }
+
+ /**
+ * Sets the starting title margin in pixels.
+ *
+ * @param margin the starting title margin in pixels
+ * @see #getTitleMarginStart()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginStart
+ */
+ public void setTitleMarginStart(int margin) {
+ mTitleMarginStart = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the top title margin in pixels
+ * @see #setTitleMarginTop(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ */
+ public int getTitleMarginTop() {
+ return mTitleMarginTop;
+ }
+
+ /**
+ * Sets the top title margin in pixels.
+ *
+ * @param margin the top title margin in pixels
+ * @see #getTitleMarginTop()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginTop
+ */
+ public void setTitleMarginTop(int margin) {
+ mTitleMarginTop = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the ending title margin in pixels
+ * @see #setTitleMarginEnd(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ */
+ public int getTitleMarginEnd() {
+ return mTitleMarginEnd;
+ }
+
+ /**
+ * Sets the ending title margin in pixels.
+ *
+ * @param margin the ending title margin in pixels
+ * @see #getTitleMarginEnd()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginEnd
+ */
+ public void setTitleMarginEnd(int margin) {
+ mTitleMarginEnd = margin;
+
+ requestLayout();
+ }
+
+ /**
+ * @return the bottom title margin in pixels
+ * @see #setTitleMarginBottom(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ */
+ public int getTitleMarginBottom() {
+ return mTitleMarginBottom;
+ }
+
+ /**
+ * Sets the bottom title margin in pixels.
+ *
+ * @param margin the bottom title margin in pixels
+ * @see #getTitleMarginBottom()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_titleMarginBottom
+ */
+ public void setTitleMarginBottom(int margin) {
+ mTitleMarginBottom = margin;
+
+ requestLayout();
+ }
+
public void onRtlPropertiesChanged(int layoutDirection) {
if (Build.VERSION.SDK_INT >= 17) {
super.onRtlPropertiesChanged(layoutDirection);
@@ -721,6 +876,8 @@
* as screen readers or tooltips.
*
* @return The navigation button's content description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
@Nullable
public CharSequence getNavigationContentDescription() {
@@ -734,6 +891,8 @@
*
* @param resId Resource ID of a content description string to set, or 0 to
* clear the description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
public void setNavigationContentDescription(@StringRes int resId) {
setNavigationContentDescription(resId != 0 ? getContext().getText(resId) : null);
@@ -746,6 +905,8 @@
*
* @param description Content description to set, or <code>null</code> to
* clear the content description
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationContentDescription
*/
public void setNavigationContentDescription(@Nullable CharSequence description) {
if (!TextUtils.isEmpty(description)) {
@@ -767,6 +928,8 @@
* tooltips.</p>
*
* @param resId Resource ID of a drawable to set
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
public void setNavigationIcon(@DrawableRes int resId) {
setNavigationIcon(mDrawableManager.getDrawable(getContext(), resId));
@@ -783,6 +946,8 @@
* tooltips.</p>
*
* @param icon Drawable to set, may be null to clear the icon
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
public void setNavigationIcon(@Nullable Drawable icon) {
if (icon != null) {
@@ -803,6 +968,8 @@
* Return the current drawable used as the navigation icon.
*
* @return The navigation icon drawable
+ *
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_navigationIcon
*/
@Nullable
public Drawable getNavigationIcon() {
@@ -912,7 +1079,7 @@
}
/**
- * Set the content insets for this toolbar relative to layout direction.
+ * Sets the content insets for this toolbar relative to layout direction.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -926,13 +1093,15 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEnd
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStart
*/
public void setContentInsetsRelative(int contentInsetStart, int contentInsetEnd) {
mContentInsets.setRelative(contentInsetStart, contentInsetEnd);
}
/**
- * Get the starting content inset for this toolbar.
+ * Gets the starting content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -945,13 +1114,14 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStart
*/
public int getContentInsetStart() {
return mContentInsets.getStart();
}
/**
- * Get the ending content inset for this toolbar.
+ * Gets the ending content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -964,13 +1134,14 @@
* @see #getContentInsetStart()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEnd
*/
public int getContentInsetEnd() {
return mContentInsets.getEnd();
}
/**
- * Set the content insets for this toolbar.
+ * Sets the content insets for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -984,13 +1155,15 @@
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
* @see #getContentInsetRight()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetLeft
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetRight
*/
public void setContentInsetsAbsolute(int contentInsetLeft, int contentInsetRight) {
mContentInsets.setAbsolute(contentInsetLeft, contentInsetRight);
}
/**
- * Get the left content inset for this toolbar.
+ * Gets the left content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1003,13 +1176,14 @@
* @see #getContentInsetStart()
* @see #getContentInsetEnd()
* @see #getContentInsetRight()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetLeft
*/
public int getContentInsetLeft() {
return mContentInsets.getLeft();
}
/**
- * Get the right content inset for this toolbar.
+ * Gets the right content inset for this toolbar.
*
* <p>The content inset affects the valid area for Toolbar content other than
* the navigation button and menu. Insets define the minimum margin for these components
@@ -1022,11 +1196,160 @@
* @see #getContentInsetStart()
* @see #getContentInsetEnd()
* @see #getContentInsetLeft()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetRight
*/
public int getContentInsetRight() {
return mContentInsets.getRight();
}
+ /**
+ * Gets the start content inset to use when a navigation button is present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+ *
+ * @return the start content inset used when a navigation icon has been set in pixels
+ *
+ * @see #setContentInsetStartWithNavigation(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStartWithNavigation
+ */
+ public int getContentInsetStartWithNavigation() {
+ return mContentInsetStartWithNavigation != RtlSpacingHelper.UNDEFINED
+ ? mContentInsetStartWithNavigation
+ : getContentInsetStart();
+ }
+
+ /**
+ * Sets the start content inset to use when a navigation button is present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetStart()} and this value will be used during layout.</p>
+ *
+ * @param insetStartWithNavigation the inset to use when a navigation icon has been set
+ * in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetStartWithNavigation
+ */
+ public void setContentInsetStartWithNavigation(int insetStartWithNavigation) {
+ if (insetStartWithNavigation < 0) {
+ insetStartWithNavigation = RtlSpacingHelper.UNDEFINED;
+ }
+ if (insetStartWithNavigation != mContentInsetStartWithNavigation) {
+ mContentInsetStartWithNavigation = insetStartWithNavigation;
+ if (getNavigationIcon() != null) {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Gets the end content inset to use when action buttons are present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+ *
+ * @return the end content inset used when a menu has been set in pixels
+ *
+ * @see #setContentInsetEndWithActions(int)
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEndWithActions
+ */
+ public int getContentInsetEndWithActions() {
+ return mContentInsetEndWithActions != RtlSpacingHelper.UNDEFINED
+ ? mContentInsetEndWithActions
+ : getContentInsetEnd();
+ }
+
+ /**
+ * Sets the start content inset to use when action buttons are present.
+ *
+ * <p>Different content insets are often called for when additional buttons are present
+ * in the toolbar, as well as at different toolbar sizes. The larger value of
+ * {@link #getContentInsetEnd()} and this value will be used during layout.</p>
+ *
+ * @param insetEndWithActions the inset to use when a menu has been set in pixels
+ *
+ * @see #getContentInsetEndWithActions()
+ * @attr ref android.support.v7.appcompat.R.styleable#Toolbar_contentInsetEndWithActions
+ */
+ public void setContentInsetEndWithActions(int insetEndWithActions) {
+ if (insetEndWithActions < 0) {
+ insetEndWithActions = RtlSpacingHelper.UNDEFINED;
+ }
+ if (insetEndWithActions != mContentInsetEndWithActions) {
+ mContentInsetEndWithActions = insetEndWithActions;
+ if (getNavigationIcon() != null) {
+ requestLayout();
+ }
+ }
+ }
+
+ /**
+ * Gets the content inset that will be used on the starting side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset start in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ */
+ public int getCurrentContentInsetStart() {
+ return getNavigationIcon() != null
+ ? Math.max(getContentInsetStart(), Math.max(mContentInsetStartWithNavigation, 0))
+ : getContentInsetStart();
+ }
+
+ /**
+ * Gets the content inset that will be used on the ending side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset end in pixels
+ *
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetEnd() {
+ boolean hasActions = false;
+ if (mMenuView != null) {
+ final MenuBuilder mb = mMenuView.peekMenu();
+ hasActions = mb != null && mb.hasVisibleItems();
+ }
+ return hasActions
+ ? Math.max(getContentInsetEnd(), Math.max(mContentInsetEndWithActions, 0))
+ : getContentInsetEnd();
+ }
+
+ /**
+ * Gets the content inset that will be used on the left side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset left in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetLeft() {
+ return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
+ ? getCurrentContentInsetEnd()
+ : getCurrentContentInsetStart();
+ }
+
+ /**
+ * Gets the content inset that will be used on the right side of the bar in the current
+ * toolbar configuration.
+ *
+ * @return the current content inset right in pixels
+ *
+ * @see #getContentInsetStartWithNavigation()
+ * @see #getContentInsetEndWithActions()
+ */
+ public int getCurrentContentInsetRight() {
+ return ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_RTL
+ ? getCurrentContentInsetStart()
+ : getCurrentContentInsetEnd();
+ }
+
private void ensureNavButtonView() {
if (mNavButtonView == null) {
mNavButtonView = new ImageButton(getContext(), null,
@@ -1090,6 +1413,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
final SavedState ss = (SavedState) state;
super.onRestoreInstanceState(ss.getSuperState());
@@ -1272,7 +1600,7 @@
ViewCompat.getMeasuredState(mCollapseButtonView));
}
- final int contentInsetStart = getContentInsetStart();
+ final int contentInsetStart = getCurrentContentInsetStart();
width += Math.max(contentInsetStart, navWidth);
collapsingMargins[marginStartIndex] = Math.max(0, contentInsetStart - navWidth);
@@ -1287,7 +1615,7 @@
ViewCompat.getMeasuredState(mMenuView));
}
- final int contentInsetEnd = getContentInsetEnd();
+ final int contentInsetEnd = getCurrentContentInsetEnd();
width += Math.max(contentInsetEnd, menuWidth);
collapsingMargins[marginEndIndex] = Math.max(0, contentInsetEnd - menuWidth);
@@ -1415,10 +1743,12 @@
}
}
- collapsingMargins[0] = Math.max(0, getContentInsetLeft() - left);
- collapsingMargins[1] = Math.max(0, getContentInsetRight() - (width - paddingRight - right));
- left = Math.max(left, getContentInsetLeft());
- right = Math.min(right, width - paddingRight - getContentInsetRight());
+ final int contentInsetLeft = getCurrentContentInsetLeft();
+ final int contentInsetRight = getCurrentContentInsetRight();
+ collapsingMargins[0] = Math.max(0, contentInsetLeft - left);
+ collapsingMargins[1] = Math.max(0, contentInsetRight - (width - paddingRight - right));
+ left = Math.max(left, contentInsetLeft);
+ right = Math.min(right, width - paddingRight - contentInsetRight);
if (shouldLayout(mExpandedActionView)) {
if (isRtl) {
@@ -1820,6 +2150,9 @@
public void setMenuCallbacks(MenuPresenter.Callback pcb, MenuBuilder.Callback mcb) {
mActionMenuPresenterCallback = pcb;
mMenuBuilderCallback = mcb;
+ if (mMenuView != null) {
+ mMenuView.setMenuCallbacks(pcb, mcb);
+ }
}
/**
@@ -1900,12 +2233,16 @@
}
}
- public static class SavedState extends BaseSavedState {
+ public static class SavedState extends AbsSavedState {
int expandedMenuItemId;
boolean isOverflowOpen;
public SavedState(Parcel source) {
- super(source);
+ this(source, null);
+ }
+
+ public SavedState(Parcel source, ClassLoader loader) {
+ super(source, loader);
expandedMenuItemId = source.readInt();
isOverflowOpen = source.readInt() != 0;
}
@@ -1921,17 +2258,18 @@
out.writeInt(isOverflowOpen ? 1 : 0);
}
- public static final Creator<SavedState> CREATOR = new Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel source) {
- return new SavedState(source);
- }
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
}
private class ExpandedActionViewMenuPresenter implements MenuPresenter {
diff --git a/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java b/v7/appcompat/src/android/support/v7/widget/ToolbarWidgetWrapper.java
index 0576c153..29bf1a6 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,
@@ -99,11 +99,10 @@
mSubtitle = toolbar.getSubtitle();
mTitleSet = mTitle != null;
mNavIcon = toolbar.getNavigationIcon();
-
- if (style) {
- final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
+ final TintTypedArray a = TintTypedArray.obtainStyledAttributes(toolbar.getContext(),
null, R.styleable.ActionBar, R.attr.actionBarStyle, 0);
-
+ mDefaultNavigationIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
+ if (style) {
final CharSequence title = a.getText(R.styleable.ActionBar_title);
if (!TextUtils.isEmpty(title)) {
setTitle(title);
@@ -120,15 +119,12 @@
}
final Drawable icon = a.getDrawable(R.styleable.ActionBar_icon);
- if (mNavIcon == null && icon != null) {
+ if (icon != null) {
setIcon(icon);
}
-
- final Drawable navIcon = a.getDrawable(R.styleable.ActionBar_homeAsUpIndicator);
- if (navIcon != null) {
- setNavigationIcon(navIcon);
+ if (mNavIcon == null && mDefaultNavigationIcon != null) {
+ setNavigationIcon(mDefaultNavigationIcon);
}
-
setDisplayOptions(a.getInt(R.styleable.ActionBar_displayOptions, 0));
final int customNavId = a.getResourceId(
@@ -170,19 +166,16 @@
if (popupTheme != 0) {
mToolbar.setPopupTheme(popupTheme);
}
-
- a.recycle();
} else {
mDisplayOpts = detectDisplayOptions();
}
+ a.recycle();
mDrawableManager = AppCompatDrawableManager.get();
setDefaultNavigationContentDescription(defaultNavigationContentDescription);
mHomeDescription = mToolbar.getNavigationContentDescription();
- setDefaultNavigationIcon(mDrawableManager.getDrawable(getContext(), defaultNavigationIcon));
-
mToolbar.setNavigationOnClickListener(new View.OnClickListener() {
final ActionMenuItem mNavItem = new ActionMenuItem(mToolbar.getContext(),
0, android.R.id.home, 0, 0, mTitle);
@@ -195,15 +188,6 @@
});
}
- /**
- * Sets the default content description for the navigation button.
- * <p>
- * It changes the current content description if and only if the provided resource id is
- * different than the current default resource id and the current content description is empty.
- *
- * @param defaultNavigationContentDescription The resource id for the default content
- * description
- */
@Override
public void setDefaultNavigationContentDescription(int defaultNavigationContentDescription) {
if (defaultNavigationContentDescription == mDefaultNavigationContentDescription) {
@@ -215,19 +199,12 @@
}
}
- @Override
- public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
- if (mDefaultNavigationIcon != defaultNavigationIcon) {
- mDefaultNavigationIcon = defaultNavigationIcon;
- updateNavigationIcon();
- }
- }
-
private int detectDisplayOptions() {
int opts = ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_HOME |
ActionBar.DISPLAY_USE_LOGO;
if (mToolbar.getNavigationIcon() != null) {
opts |= ActionBar.DISPLAY_HOME_AS_UP;
+ mDefaultNavigationIcon = mToolbar.getNavigationIcon();
}
return opts;
}
@@ -408,11 +385,9 @@
if (changed != 0) {
if ((changed & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
if ((newOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- updateNavigationIcon();
updateHomeAccessibility();
- } else {
- mToolbar.setNavigationIcon(null);
}
+ updateNavigationIcon();
}
if ((changed & AFFECTS_LOGO_MASK) != 0) {
@@ -618,8 +593,23 @@
@Override
public void setNavigationIcon(int resId) {
- setNavigationIcon(resId != 0
- ? AppCompatDrawableManager.get().getDrawable(getContext(), resId) : null);
+ setNavigationIcon(resId != 0 ? mDrawableManager.getDrawable(getContext(), resId) : null);
+ }
+
+ @Override
+ public void setDefaultNavigationIcon(Drawable defaultNavigationIcon) {
+ if (mDefaultNavigationIcon != defaultNavigationIcon) {
+ mDefaultNavigationIcon = defaultNavigationIcon;
+ updateNavigationIcon();
+ }
+ }
+
+ private void updateNavigationIcon() {
+ if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
+ mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
+ } else {
+ mToolbar.setNavigationIcon(null);
+ }
}
@Override
@@ -643,12 +633,6 @@
}
}
- private void updateNavigationIcon() {
- if ((mDisplayOpts & ActionBar.DISPLAY_HOME_AS_UP) != 0) {
- mToolbar.setNavigationIcon(mNavIcon != null ? mNavIcon : mDefaultNavigationIcon);
- }
- }
-
@Override
public void saveHierarchyState(SparseArray<Parcelable> toolbarStates) {
mToolbar.saveHierarchyState(toolbarStates);
diff --git a/v7/appcompat/src/android/support/v7/widget/ViewStubCompat.java b/v7/appcompat/src/android/support/v7/widget/ViewStubCompat.java
index 11cd213..2b9c58e 100644
--- a/v7/appcompat/src/android/support/v7/widget/ViewStubCompat.java
+++ b/v7/appcompat/src/android/support/v7/widget/ViewStubCompat.java
@@ -71,7 +71,7 @@
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #setInflatedId(int)
- * @attr ref android.R.styleable#ViewStub_inflatedId
+ * @attr name android:inflatedId
*/
public int getInflatedId() {
return mInflatedId;
@@ -85,7 +85,7 @@
* {@link #NO_ID} if the inflated view should keep its id.
*
* @see #getInflatedId()
- * @attr ref android.R.styleable#ViewStub_inflatedId
+ * @attr name android:inflatedId
*/
public void setInflatedId(int inflatedId) {
mInflatedId = inflatedId;
@@ -101,7 +101,7 @@
* @see #setLayoutResource(int)
* @see #setVisibility(int)
* @see #inflate()
- * @attr ref android.R.styleable#ViewStub_layout
+ * @attr name android:layout
*/
public int getLayoutResource() {
return mLayoutResource;
@@ -117,7 +117,7 @@
* @see #getLayoutResource()
* @see #setVisibility(int)
* @see #inflate()
- * @attr ref android.R.styleable#ViewStub_layout
+ * @attr name android:layout
*/
public void setLayoutResource(int layoutResource) {
mLayoutResource = layoutResource;
diff --git a/v7/appcompat/tests/AndroidManifest.xml b/v7/appcompat/tests/AndroidManifest.xml
index f33f28b..539030d 100644
--- a/v7/appcompat/tests/AndroidManifest.xml
+++ b/v7/appcompat/tests/AndroidManifest.xml
@@ -1,37 +1,95 @@
<?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.
+ 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.v7.appcompat.test">
- <uses-sdk android:minSdkVersion="8"
- tools:overrideLibrary="android.support.test"/>
+ <uses-sdk
+ android:minSdkVersion="7"
+ android:targetSdkVersion="23"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule,
+ android.support.test.espresso, android.support.test.espresso.idling"/>
- <application android:theme="@style/Theme.AppCompat">
+ <application
+ android:theme="@style/Theme.AppCompat"
+ android:supportsRtl="true">
+
<uses-library android:name="android.test.runner"/>
<activity
android:name="android.support.v7.app.AppCompatActivity"/>
+
<activity
android:name="android.support.v7.app.WindowDecorActionBarActivity"/>
<activity
android:name="android.support.v7.app.ToolbarActionBarActivity"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
+ <activity
+ android:name="android.support.v7.app.DrawerLayoutActivity"
+ android:label="@string/drawer_layout_activity"
+ android:theme="@style/Theme.SampleDrawerLayout" />
+
+ <activity
+ android:name="android.support.v7.app.DrawerLayoutDoubleActivity"
+ android:label="@string/drawer_layout_activity"
+ android:theme="@style/Theme.SampleDrawerLayout" />
+
+ <activity
+ android:name="android.support.v7.app.DrawerDynamicLayoutActivity"
+ android:label="@string/drawer_layout_activity"
+ android:theme="@style/Theme.SampleDrawerLayout" />
+
+ <activity
+ android:name="android.support.v7.app.AlertDialogTestActivity"
+ android:label="@string/alert_dialog_activity"
+ android:theme="@style/Theme.AppCompat.Light" />
+
+ <activity
+ android:name="android.support.v7.widget.PopupTestActivity"
+ android:label="@string/popup_activity"
+ android:theme="@style/Theme.AppCompat.Light" />
+
+ <activity
+ android:name="android.support.v7.widget.AppCompatSpinnerActivity"
+ android:label="@string/app_compat_spinner_activity"
+ android:theme="@style/Theme.AppCompat.Light" />
+
+ <activity
+ android:name="android.support.v7.widget.AppCompatTextViewActivity"
+ android:label="@string/app_compat_text_view_activity"
+ android:theme="@style/Theme.TextColors" />
+
+ <activity
+ android:name="android.support.v7.widget.AppCompatImageViewActivity"
+ android:label="@string/app_compat_image_view_activity"
+ android:theme="@style/Theme.AppCompat.Light" />
+
+ <activity
+ android:name="android.support.v7.widget.AppCompatButtonActivity"
+ android:label="@string/app_compat_button_activity"
+ android:theme="@style/Theme.AppCompat.Light" />
+
+ <activity
+ android:name="android.support.v7.app.LayoutInflaterFactoryTestActivity"/>
+
+ <activity
+ android:name="android.support.v7.app.FragmentContentIdActivity"/>
+
</application>
<instrumentation
diff --git a/v7/appcompat/tests/NO_DOCS b/v7/appcompat/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v7/appcompat/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/v7/appcompat/tests/res/color/color_state_list_emerald_translucent.xml b/v7/appcompat/tests/res/color/color_state_list_emerald_translucent.xml
new file mode 100644
index 0000000..56fd16a
--- /dev/null
+++ b/v7/appcompat/tests/res/color/color_state_list_emerald_translucent.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/emerald_translucent_disabled" />
+ <item android:color="@color/emerald_translucent_default"/>
+</selector>
+
diff --git a/v7/appcompat/tests/res/color/color_state_list_lilac.xml b/v7/appcompat/tests/res/color/color_state_list_lilac.xml
new file mode 100644
index 0000000..f0b2791
--- /dev/null
+++ b/v7/appcompat/tests/res/color/color_state_list_lilac.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/lilac_disabled" />
+ <item android:color="@color/lilac_default"/>
+</selector>
+
diff --git a/v7/appcompat/tests/res/color/color_state_list_ocean.xml b/v7/appcompat/tests/res/color/color_state_list_ocean.xml
new file mode 100644
index 0000000..7f1c80d
--- /dev/null
+++ b/v7/appcompat/tests/res/color/color_state_list_ocean.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/ocean_disabled" />
+ <item android:color="@color/ocean_default"/>
+</selector>
+
diff --git a/v7/appcompat/tests/res/color/color_state_list_sand.xml b/v7/appcompat/tests/res/color/color_state_list_sand.xml
new file mode 100644
index 0000000..36229a4
--- /dev/null
+++ b/v7/appcompat/tests/res/color/color_state_list_sand.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_enabled="false" android:color="@color/sand_disabled" />
+ <item android:color="@color/sand_default"/>
+</selector>
+
diff --git a/v7/appcompat/tests/res/drawable-hdpi/drawer_shadow.9.png b/v7/appcompat/tests/res/drawable-hdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..e54a3a4
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable-hdpi/drawer_shadow.9.png
Binary files differ
diff --git a/v7/appcompat/tests/res/drawable-mdpi/drawer_shadow.9.png b/v7/appcompat/tests/res/drawable-mdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..2343e5c9
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable-mdpi/drawer_shadow.9.png
Binary files differ
diff --git a/v7/appcompat/tests/res/drawable-mdpi/test_drawable.png b/v7/appcompat/tests/res/drawable-mdpi/test_drawable.png
new file mode 100644
index 0000000..c66ad10
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable-mdpi/test_drawable.png
Binary files differ
diff --git a/v7/appcompat/tests/res/drawable-xhdpi/drawer_shadow.9.png b/v7/appcompat/tests/res/drawable-xhdpi/drawer_shadow.9.png
new file mode 100644
index 0000000..749823d
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable-xhdpi/drawer_shadow.9.png
Binary files differ
diff --git a/v7/appcompat/tests/res/drawable/test_background_blue.xml b/v7/appcompat/tests/res/drawable/test_background_blue.xml
new file mode 100644
index 0000000..fe4bca2
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_background_blue.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_background_green.xml b/v7/appcompat/tests/res/drawable/test_background_green.xml
new file mode 100644
index 0000000..b90d9bc
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_background_green.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_background_red.xml b/v7/appcompat/tests/res/drawable/test_background_red.xml
new file mode 100644
index 0000000..87d808b
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_background_red.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <solid
+ android:color="@color/test_red" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_drawable_blue.xml b/v7/appcompat/tests/res/drawable/test_drawable_blue.xml
new file mode 100644
index 0000000..5d05ef2
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_drawable_blue.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_large_size"
+ android:height="@dimen/drawable_small_size" />
+ <solid
+ android:color="@color/test_blue" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_drawable_green.xml b/v7/appcompat/tests/res/drawable/test_drawable_green.xml
new file mode 100644
index 0000000..9f33104
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_drawable_green.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_medium_size"
+ android:height="@dimen/drawable_large_size" />
+ <solid
+ android:color="@color/test_green" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_drawable_red.xml b/v7/appcompat/tests/res/drawable/test_drawable_red.xml
new file mode 100644
index 0000000..cd1af56
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_drawable_red.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.
+-->
+
+<shape
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:shape="rectangle">
+ <size
+ android:width="@dimen/drawable_small_size"
+ android:height="@dimen/drawable_medium_size" />
+ <solid
+ android:color="@color/test_red" />
+</shape>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_vector.xml b/v7/appcompat/tests/res/drawable/test_vector.xml
new file mode 100644
index 0000000..6319a70
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_vector.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_checked="true" android:drawable="@drawable/test_vector_on" />
+ <item android:drawable="@drawable/test_vector_off" />
+</selector>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/drawable/test_vector_off.xml b/v7/appcompat/tests/res/drawable/test_vector_off.xml
new file mode 100644
index 0000000..312dc3a
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_vector_off.xml
@@ -0,0 +1,46 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<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/tests/res/drawable/test_vector_on.xml b/v7/appcompat/tests/res/drawable/test_vector_on.xml
new file mode 100644
index 0000000..47717c2
--- /dev/null
+++ b/v7/appcompat/tests/res/drawable/test_vector_on.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<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/tests/res/layout/alert_dialog_activity.xml b/v7/appcompat/tests/res/layout/alert_dialog_activity.xml
new file mode 100644
index 0000000..c957d67
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/alert_dialog_activity.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/alert_dialog_show" />
+</FrameLayout>
+
diff --git a/v7/appcompat/tests/res/layout/alert_dialog_custom_title.xml b/v7/appcompat/tests/res/layout/alert_dialog_custom_title.xml
new file mode 100644
index 0000000..c68b02f
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/alert_dialog_custom_title.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="32sp"
+ android:textColor="@color/test_red"
+ android:text="@string/alert_dialog_custom_title"
+ android:singleLine="false" />
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/alert_dialog_custom_view.xml b/v7/appcompat/tests/res/layout/alert_dialog_custom_view.xml
new file mode 100644
index 0000000..3d92ec5
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/alert_dialog_custom_view.xml
@@ -0,0 +1,37 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/alert_dialog_custom_view"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="24sp"
+ android:textColor="@color/test_green"
+ android:text="@string/alert_dialog_custom_text1"
+ android:singleLine="false" />
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:textSize="20sp"
+ android:textColor="@color/test_blue"
+ android:text="@string/alert_dialog_custom_text2"
+ android:singleLine="false" />
+</LinearLayout>
diff --git a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
new file mode 100644
index 0000000..72d6500
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.AppCompatButton
+ style="@style/TextStyleAllCapsOn"
+ android:id="@+id/button_caps1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text1" />
+
+ <android.support.v7.widget.AppCompatButton
+ style="@style/TextStyleAllCapsOff"
+ android:id="@+id/button_caps2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2" />
+
+ <android.support.v7.widget.AppCompatButton
+ android:id="@+id/button_app_allcaps_false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ app:textAllCaps="false"/>
+
+ <android.support.v7.widget.AppCompatButton
+ android:id="@+id/view_tinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text1"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_tinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:background="@drawable/test_drawable"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatButton
+ android:id="@+id/view_untinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2" />
+
+ <android.support.v7.widget.AppCompatButton
+ android:id="@+id/view_untinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:background="@drawable/test_background_green" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml
new file mode 100644
index 0000000..c2aa5ed
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_imageview_activity.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.AppCompatImageView
+ android:id="@+id/view_tinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/test_drawable_blue"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatImageView
+ android:id="@+id/view_tinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/test_drawable_blue"
+ android:background="@drawable/test_drawable"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatImageView
+ android:id="@+id/view_untinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/test_drawable_blue" />
+
+ <android.support.v7.widget.AppCompatImageView
+ android:id="@+id/view_untinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:src="@drawable/test_drawable_blue"
+ android:background="@drawable/test_background_green" />
+
+ <android.support.v7.widget.AppCompatImageView
+ android:id="@+id/view_android_src_srccompat"
+ android:layout_width="30dp"
+ android:layout_height="30dp"
+ android:src="@drawable/test_drawable_blue"
+ app:srcCompat="@drawable/test_drawable_red" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml b/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml
new file mode 100644
index 0000000..1c0110e
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_spinner_activity.xml
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_tinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_tinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/test_drawable"
+ android:entries="@array/planets_array"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_untinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_untinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array"
+ android:background="@drawable/test_background_green" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_magenta_themed_popup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in"
+ app:popupTheme="@style/MagentaSpinnerPopupTheme" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_unthemed_popup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatSpinner
+ android:id="@+id/view_ocean_themed_popup"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:entries="@array/planets_array"
+ android:spinnerMode="dropdown"
+ app:popupTheme="@style/OceanSpinnerPopupTheme" />
+ </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
new file mode 100644
index 0000000..9fd3f29
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/appcompat_textview_activity.xml
@@ -0,0 +1,109 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<ScrollView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <LinearLayout
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:orientation="vertical">
+
+ <android.support.v7.widget.AppCompatTextView
+ style="@style/TextStyleAllCapsOn"
+ android:id="@+id/text_view_caps1"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text1" />
+
+ <android.support.v7.widget.AppCompatTextView
+ style="@style/TextStyleAllCapsOff"
+ android:id="@+id/text_view_caps2"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/text_view_app_allcaps_false"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ app:textAllCaps="false"/>
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_tinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text1"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_tinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:background="@drawable/test_drawable"
+ app:backgroundTint="@color/color_state_list_lilac"
+ app:backgroundTintMode="src_in" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_untinted_no_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_untinted_background"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:background="@drawable/test_background_green" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_text_color_hex"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:textColor="#FF0000" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_text_color_csl"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:textColor="@color/color_state_list_ocean" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_text_color_primary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text1"
+ android:textColor="?android:attr/textColorPrimary" />
+
+ <android.support.v7.widget.AppCompatTextView
+ android:id="@+id/view_text_color_secondary"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="@string/sample_text2"
+ android:textColor="?android:attr/textColorSecondary" />
+
+ </LinearLayout>
+
+</ScrollView>
diff --git a/v7/appcompat/tests/res/layout/drawer_double_layout.xml b/v7/appcompat/tests/res/layout/drawer_double_layout.xml
new file mode 100644
index 0000000..1af5be3
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_double_layout.xml
@@ -0,0 +1,81 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!--
+ A DrawerLayout is indended to be used as the top-level content view
+ using match_parent for both width and height to consume the full space available.
+-->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <LinearLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <!-- This will be set as the support action bar of the activity at runtime.
+ It needs to be a dynamic runtime call for correct vertical layering of
+ the drawer and the toolbar. -->
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbarStyle="outsideOverlay">
+ <TextView
+ android:id="@+id/content_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="@string/drawer_layout_summary"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:padding="16dp"/>
+ </ScrollView>
+ </LinearLayout>
+
+ <!-- android:layout_gravity="start" tells DrawerLayout to treat
+ this as a sliding drawer on the starting side, which is
+ left for left-to-right locales. The drawer is given a fixed
+ width in dp and extends the full height of the container. A
+ solid background is used for contrast with the content view.
+ android:fitsSystemWindows="true" tells the system to have
+ DrawerLayout span the full height of the screen, including the
+ system status bar on Lollipop+ versions of the plaform. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#ff333333"
+ android:fitsSystemWindows="true"/>
+
+ <!-- End drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/end_drawer"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#444" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end.xml
new file mode 100644
index 0000000..1c72ad5
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end.xml
@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with two end drawers -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/end_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#333" />
+
+ <!-- Another start drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/end_drawer2"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#444" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end_single_start.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end_single_start.xml
new file mode 100644
index 0000000..6838c3f3
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_end_single_start.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with two end drawers and one start drawer -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/end_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#333" />
+
+ <!-- Another start drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/end_drawer2"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#444" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start.xml
new file mode 100644
index 0000000..d087ad1b
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start.xml
@@ -0,0 +1,49 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with two start drawers -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true" />
+
+ <!-- Another start drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/start_drawer2"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#444"
+ android:fitsSystemWindows="true" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start_single_end.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start_single_end.xml
new file mode 100644
index 0000000..900fb50
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_double_start_single_end.xml
@@ -0,0 +1,57 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with two start drawers and one end drawer -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true" />
+
+ <!-- Another start drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/start_drawer2"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#444"
+ android:fitsSystemWindows="true" />
+
+ <!-- End drawer with fixed width. -->
+ <ListView
+ android:id="@+id/end_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#333" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_end.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_end.xml
new file mode 100644
index 0000000..158c7e5
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_end.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with a single end drawer -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- End drawer with fixed width. -->
+ <ListView
+ android:id="@+id/end_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#333" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_start.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_start.xml
new file mode 100644
index 0000000..5b0b99f
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_single_start.xml
@@ -0,0 +1,40 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with a single start drawer -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_content_start_end.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_content_start_end.xml
new file mode 100644
index 0000000..7ecd244
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_content_start_end.xml
@@ -0,0 +1,48 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- Drawer content with one start drawer and one end drawer -->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <FrameLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent" />
+
+ <!-- Start drawer with fixed width. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#333"
+ android:fitsSystemWindows="true" />
+
+ <!-- End drawer with fixed width. -->
+ <FrameLayout
+ android:id="@+id/end_drawer"
+ android:layout_width="250dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="end"
+ android:background="#444" />
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/drawer_dynamic_layout.xml b/v7/appcompat/tests/res/layout/drawer_dynamic_layout.xml
new file mode 100644
index 0000000..78dbb67
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_dynamic_layout.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<!-- ViewStub that will be inflated to a DrawerLayout at test runtime. -->
+<ViewStub
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_stub"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:inflatedId="@+id/drawer_layout"
+ android:fitsSystemWindows="true" />
diff --git a/v7/appcompat/tests/res/layout/drawer_layout.xml b/v7/appcompat/tests/res/layout/drawer_layout.xml
new file mode 100644
index 0000000..c65eb72
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/drawer_layout.xml
@@ -0,0 +1,73 @@
+<?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.
+-->
+
+<!--
+ A DrawerLayout is indended to be used as the top-level content view
+ using match_parent for both width and height to consume the full space available.
+-->
+<android.support.v7.custom.CustomDrawerLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/drawer_layout"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:fitsSystemWindows="true">
+ <!-- As the main content view, the view below consumes the entire
+ space available using match_parent in both dimensions. Note that
+ this child does not specify android:layout_gravity attribute. -->
+ <LinearLayout
+ android:id="@+id/content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical">
+ <!-- This will be set as the support action bar of the activity at runtime.
+ It needs to be a dynamic runtime call for correct vertical layering of
+ the drawer and the toolbar. -->
+ <android.support.v7.widget.Toolbar
+ android:id="@+id/toolbar"
+ android:layout_width="match_parent"
+ android:layout_height="?attr/actionBarSize" />
+
+ <ScrollView
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:scrollbarStyle="outsideOverlay">
+ <TextView
+ android:id="@+id/content_text"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:text="@string/drawer_layout_summary"
+ android:textAppearance="?android:attr/textAppearanceMedium"
+ android:padding="16dp"/>
+ </ScrollView>
+ </LinearLayout>
+
+ <!-- android:layout_gravity="start" tells DrawerLayout to treat
+ this as a sliding drawer on the starting side, which is
+ left for left-to-right locales. The drawer is given a fixed
+ width in dp and extends the full height of the container. A
+ solid background is used for contrast with the content view.
+ android:fitsSystemWindows="true" tells the system to have
+ DrawerLayout span the full height of the screen, including the
+ system status bar on Lollipop+ versions of the plaform. -->
+ <ListView
+ android:id="@+id/start_drawer"
+ android:layout_width="300dp"
+ android:layout_height="match_parent"
+ android:layout_gravity="start"
+ android:background="#ff333333"
+ android:fitsSystemWindows="true"/>
+</android.support.v7.custom.CustomDrawerLayout>
+
diff --git a/v7/appcompat/tests/res/layout/layout_android_theme.xml b/v7/appcompat/tests/res/layout/layout_android_theme.xml
index bcbcbb6..2fe31d9 100644
--- a/v7/appcompat/tests/res/layout/layout_android_theme.xml
+++ b/v7/appcompat/tests/res/layout/layout_android_theme.xml
@@ -17,4 +17,4 @@
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
- android:theme="@style/ThemeOverlay.AppCompat.Dark"/>
\ No newline at end of file
+ android:theme="@style/MagentaThemeOverlay"/>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/layout_android_theme_children.xml b/v7/appcompat/tests/res/layout/layout_android_theme_children.xml
new file mode 100644
index 0000000..be95143
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/layout_android_theme_children.xml
@@ -0,0 +1,39 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/MagentaThemeOverlay"
+ android:orientation="vertical">
+
+ <TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Test"/>
+
+ <TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Test"/>
+
+ <TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Test"/>
+
+ <TextView android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:text="Test"/>
+
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/layout_app_theme.xml b/v7/appcompat/tests/res/layout/layout_app_theme.xml
index 623d57ea..762abba 100644
--- a/v7/appcompat/tests/res/layout/layout_app_theme.xml
+++ b/v7/appcompat/tests/res/layout/layout_app_theme.xml
@@ -18,4 +18,4 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
- app:theme="@style/ThemeOverlay.AppCompat.Dark"/>
\ No newline at end of file
+ app:theme="@style/MagentaThemeOverlay"/>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/layout_button_themed_onclick.xml b/v7/appcompat/tests/res/layout/layout_button_themed_onclick.xml
new file mode 100644
index 0000000..a58dfce
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/layout_button_themed_onclick.xml
@@ -0,0 +1,21 @@
+<?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.
+-->
+
+<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:theme="@style/Theme.AppCompat.Light"
+ android:onClick="declarativeOnClick"/>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml b/v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml
new file mode 100644
index 0000000..cf7e11a
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/layout_radiobutton_vector.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<RadioButton xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:button="@drawable/test_vector"/>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/popup_test_activity.xml b/v7/appcompat/tests/res/layout/popup_test_activity.xml
new file mode 100644
index 0000000..9ea488f
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/popup_test_activity.xml
@@ -0,0 +1,29 @@
+<?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.
+-->
+
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/container"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <Button
+ android:id="@+id/test_button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content"
+ android:layout_gravity="center_horizontal"
+ android:text="@string/popup_show" />
+</FrameLayout>
+
diff --git a/v7/appcompat/tests/res/layout/popup_window_item.xml b/v7/appcompat/tests/res/layout/popup_window_item.xml
new file mode 100644
index 0000000..7726930
--- /dev/null
+++ b/v7/appcompat/tests/res/layout/popup_window_item.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<TextView
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:id="@+id/title"
+ android:layout_width="match_parent"
+ android:layout_height="@dimen/popup_row_height"
+ android:textAppearance="?attr/textAppearanceLargePopupMenu"
+ android:gravity="center_vertical" />
diff --git a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
index e2f6b3f..91d93ca 100644
--- a/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/toolbar_decor_content.xml
@@ -24,4 +24,16 @@
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"/>
+ <android.support.v7.custom.FitWindowsContentLayout
+ android:id="@+id/test_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <EditText
+ android:id="@+id/editText"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"/>
+
+ </android.support.v7.custom.FitWindowsContentLayout>
+
</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/layout/window_decor_content.xml b/v7/appcompat/tests/res/layout/window_decor_content.xml
index 657f51c..2d79660 100644
--- a/v7/appcompat/tests/res/layout/window_decor_content.xml
+++ b/v7/appcompat/tests/res/layout/window_decor_content.xml
@@ -19,4 +19,9 @@
android:layout_width="match_parent"
android:layout_height="match_parent">
+ <android.support.v7.custom.FitWindowsContentLayout
+ android:id="@+id/test_content"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"/>
+
</LinearLayout>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/menu/popup_menu.xml b/v7/appcompat/tests/res/menu/popup_menu.xml
new file mode 100644
index 0000000..f50efc5
--- /dev/null
+++ b/v7/appcompat/tests/res/menu/popup_menu.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 Google Inc.
+
+ 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.
+-->
+<menu xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:id="@+id/action_highlight"
+ android:title="@string/popup_menu_highlight" />
+ <item android:id="@+id/action_edit"
+ android:title="@string/popup_menu_edit" />
+ <item android:id="@+id/action_delete"
+ android:title="@string/popup_menu_delete" />
+ <item android:id="@+id/action_ignore"
+ android:title="@string/popup_menu_ignore" />
+ <item android:id="@+id/action_share"
+ android:title="@string/popup_menu_share">
+ <menu>
+ <item android:id="@+id/action_share_email"
+ android:title="@string/popup_menu_share_email" />
+ <item android:id="@+id/action_share_circles"
+ android:title="@string/popup_menu_share_circles" />
+ </menu>
+ </item>
+ <item android:id="@+id/action_print"
+ android:title="@string/popup_menu_print" />
+</menu>
diff --git a/v7/appcompat/tests/res/values/colors.xml b/v7/appcompat/tests/res/values/colors.xml
new file mode 100644
index 0000000..1f6fab2
--- /dev/null
+++ b/v7/appcompat/tests/res/values/colors.xml
@@ -0,0 +1,34 @@
+<?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>
+ <color name="drawer_sample_metal_blue">#FF505080</color>
+
+ <color name="test_red">#FF6030</color>
+ <color name="test_green">#50E080</color>
+ <color name="test_blue">#3050CF</color>
+ <color name="test_magenta">#F050F0</color>
+
+ <color name="lilac_default">#F080F0</color>
+ <color name="lilac_disabled">#F0A0FF</color>
+ <color name="sand_default">#F0B000</color>
+ <color name="sand_disabled">#FFC080</color>
+ <color name="ocean_default">#50C0FF</color>
+ <color name="ocean_disabled">#90F0FF</color>
+
+ <color name="emerald_translucent_default">#8020A060</color>
+ <color name="emerald_translucent_disabled">#8070C090</color>
+</resources>
diff --git a/v7/appcompat/tests/res/values/dimens.xml b/v7/appcompat/tests/res/values/dimens.xml
new file mode 100644
index 0000000..16a8d4c
--- /dev/null
+++ b/v7/appcompat/tests/res/values/dimens.xml
@@ -0,0 +1,22 @@
+<?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>
+ <dimen name="drawable_small_size">12dip</dimen>
+ <dimen name="drawable_medium_size">16dip</dimen>
+ <dimen name="drawable_large_size">20dip</dimen>
+
+ <dimen name="popup_row_height">48dip</dimen>
+</resources>
\ No newline at end of file
diff --git a/v7/appcompat/tests/res/values/ids.xml b/v7/appcompat/tests/res/values/ids.xml
new file mode 100644
index 0000000..ec1d9b5
--- /dev/null
+++ b/v7/appcompat/tests/res/values/ids.xml
@@ -0,0 +1,20 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <item name="fragment_a" type="id"/>
+ <item name="fragment_b" type="id"/>
+</resources>
diff --git a/v7/appcompat/tests/res/values/strings.xml b/v7/appcompat/tests/res/values/strings.xml
new file mode 100644
index 0000000..510f65a
--- /dev/null
+++ b/v7/appcompat/tests/res/values/strings.xml
@@ -0,0 +1,69 @@
+<?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>
+ <string name="drawer_layout_activity">Drawer layout</string>
+ <string name="drawer_layout_summary">This activity illustrates the use of sliding drawers. The drawer may be pulled out from the starting edge, which is left on left-to-right locales, with an edge swipe. You can tap the hamburger (three horizontal lines) icon at the starting side of the action bar to open the drawer as well.</string>
+ <string name="drawer_title">Navigation</string>
+ <string name="drawer_open">Open navigation drawer</string>
+ <string name="drawer_close">Close navigation drawer</string>
+
+ <string name="popup_activity">Popup activity</string>
+ <string name="popup_show">Show popup</string>
+ <string name="popup_menu_highlight">Highlight</string>
+ <string name="popup_menu_edit">Edit</string>
+ <string name="popup_menu_delete">Delete</string>
+ <string name="popup_menu_ignore">Ignore</string>
+ <string name="popup_menu_share">Share</string>
+ <string name="popup_menu_share_email">Via email</string>
+ <string name="popup_menu_share_circles">To my circles</string>
+ <string name="popup_menu_print">Print</string>
+
+ <string name="alert_dialog_activity">Alert dialog</string>
+ <string name="alert_dialog_show">Show alert dialog</string>
+ <string name="alert_dialog_title">Dialog title</string>
+ <string name="alert_dialog_content">Dialog content</string>
+ <string-array name="alert_dialog_items">
+ <item>Albania</item>
+ <item>Belize</item>
+ <item>Chad</item>
+ <item>Djibouti</item>
+ </string-array>
+ <string name="alert_dialog_positive_button">Forward</string>
+ <string name="alert_dialog_negative_button">Backward</string>
+ <string name="alert_dialog_neutral_button">Stall</string>
+ <string name="alert_dialog_custom_title">Dialog custom title</string>
+ <string name="alert_dialog_custom_text1">Dialog custom text</string>
+ <string name="alert_dialog_custom_text2">Dialog more custom text</string>
+
+ <string name="app_compat_spinner_activity">AppCompat spinner</string>
+ <string name="app_compat_text_view_activity">AppCompat text view</string>
+ <string name="sample_text1">Sample text 1</string>
+ <string name="sample_text2">Sample text 2</string>
+ <string name="app_compat_image_view_activity">AppCompat image view</string>
+ <string name="app_compat_button_activity">AppCompat button</string>
+ <string-array name="planets_array">
+ <item>Mercury</item>
+ <item>Venus</item>
+ <item>Earth</item>
+ <item>Mars</item>
+ <item>Jupiter</item>
+ <item>Saturn</item>
+ <item>Uranus</item>
+ <item>Neptune</item>
+ <item>Pluto</item>
+ </string-array>
+</resources>
diff --git a/v7/appcompat/tests/res/values/styles.xml b/v7/appcompat/tests/res/values/styles.xml
new file mode 100644
index 0000000..0232a2b
--- /dev/null
+++ b/v7/appcompat/tests/res/values/styles.xml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ Copyright (C) 2014 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <style name="Theme.SampleDrawerLayout" parent="@style/Theme.AppCompat.NoActionBar">
+ <!-- Tell SystemUI that our activity window will draw the background for the status bar. -->
+ <item name="android:windowDrawsSystemBarBackgrounds">true</item>
+ <!-- Set the status bar to be translucent black. -->
+ <item name="android:statusBarColor">#30000000</item>
+ <item name="windowActionModeOverlay">true</item>
+ <item name="android:windowContentOverlay">@null</item>
+ </style>
+
+ <style name="Theme.TextColors" parent="@style/Theme.AppCompat.Light">
+ <item name="android:textColorPrimary">#FF0000FF</item>
+ <item name="android:textColorSecondary">@color/color_state_list_sand</item>
+ </style>
+
+ <style name="TextStyleAllCapsOn" parent="@android:style/TextAppearance.Medium">
+ <item name="textAllCaps">true</item>
+ </style>
+
+ <style name="TextStyleAllCapsOff" parent="@android:style/TextAppearance.Medium">
+ <item name="textAllCaps">false</item>
+ </style>
+
+ <style name="MagentaThemeOverlay">
+ <item name="colorAccent">@color/test_magenta</item>
+ </style>
+
+ <style name="MagentaSpinnerPopupTheme">
+ <item name="android:background">@color/test_magenta</item>
+ </style>
+
+ <style name="OceanSpinnerPopupTheme">
+ <item name="android:background">@color/ocean_default</item>
+ </style>
+
+ <style name="PopupEmptyStyle" />
+</resources>
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..50346b9
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
@@ -0,0 +1,409 @@
+/*
+ * 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.InstrumentationRegistry;
+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 org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+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;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class AlertDialogCursorTest
+ extends BaseInstrumentationTestCase<AlertDialogTestActivity> {
+
+ private Button mButton;
+
+ 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);
+ }
+
+ @Before
+ public void 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 = mActivityTestRule.getActivity();
+ mButton = (Button) activity.findViewById(R.id.test_button);
+
+ File dbDir = activity.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") + ");");
+ }
+ }
+
+ @After
+ public void tearDown() {
+ if (mCursor != null) {
+ // 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.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mCursor.close();
+ mCursor = null;
+ }
+ });
+ }
+ if (mDatabase != null) {
+ mDatabase.close();
+ }
+ if (mDatabaseFile != null) {
+ mDatabaseFile.delete();
+ }
+ }
+
+ 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,
+ DialogInterface.OnClickListener onClickListener) {
+ 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()));
+ }
+
+ // Verify that our click listener hasn't been called yet
+ verify(onClickListener, never()).onClick(any(DialogInterface.class), any(int.class));
+ // Test that a click on an item invokes the registered listener
+ int indexToClick = expectedCount - 2;
+ DataInteraction interactionForClick = onData(allOf(
+ is(instanceOf(SQLiteCursor.class)),
+ TestUtilsMatchers.withCursorItemContent(
+ TEXT_COLUMN_NAME, expectedContent[indexToClick])));
+ interactionForClick.inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, indexToClick);
+ }
+
+ @Test
+ @SmallTest
+ public void testSimpleItemsFromCursor() {
+ mCursor = mDatabase.query("test", mProjectionWithoutChecked,
+ null, null, null, null, null);
+ assertNotNull(mCursor);
+
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setCursor(mCursor, mockClickListener, "text");
+ wireBuilder(builder);
+
+ verifySimpleItemsContent(mTextContent, mockClickListener);
+ }
+
+ /**
+ * 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);
+ }
+
+ @Test
+ @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(mActivityTestRule.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, DialogInterface.OnClickListener onClickListener) {
+ 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());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+ // Now click the same item again and test that the selection has not changed
+ interactionForClick.inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(2)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ 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());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+ }
+
+ @Test
+ @SmallTest
+ public void testSingleChoiceItemsFromCursor() {
+ mCursor = mDatabase.query("test", mProjectionWithoutChecked,
+ null, null, null, null, null);
+ assertNotNull(mCursor);
+
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setSingleChoiceItems(mCursor, 2, TEXT_COLUMN_NAME, mockClickListener);
+ wireBuilder(builder);
+
+ verifySingleChoiceItemsContent(mTextContent, 2, mockClickListener);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
new file mode 100644
index 0000000..6a9d8c1
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
@@ -0,0 +1,1344 @@
+/*
+ * 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.Context;
+import android.content.DialogInterface;
+import android.graphics.drawable.ColorDrawable;
+import android.os.Handler;
+import android.os.Message;
+import android.support.annotation.ColorInt;
+import android.support.annotation.StringRes;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.annotation.UiThreadTest;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.ViewInteraction;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtilsMatchers;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.text.TextUtils;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.widget.ArrayAdapter;
+import android.widget.Button;
+import android.widget.CheckedTextView;
+import android.widget.ImageView;
+import android.widget.ListAdapter;
+import android.widget.ListView;
+import org.hamcrest.Matcher;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+
+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.PositionAssertions.isBelow;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.LayoutMatchers.hasEllipsizedText;
+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.Matchers.not;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.hamcrest.core.Is.is;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+/**
+ * Tests in this class make a few assumptions about the underlying implementation of
+ * <code>AlertDialog</code>. While the assumptions don't go all the way down to individual
+ * <code>R.id</code> references or very specific layout arrangements, internal refactoring
+ * of <code>AlertDialog</code> might require corresponding restructuring of the matching
+ * tests. Specifically:
+ *
+ * <ul>
+ * <li>Testing <code>setIcon</code> API assumes that the icon is displayed by a separate
+ * <code>ImageView</code> which is a sibling of a title view.</li>
+ * <li>Testing <code>setMultiChoiceItems</code> API assumes that each item in the list
+ * is rendered by a single <code></code>CheckedTextView</code>.</li>
+ * <li>Testing <code>setSingleChoiceItems</code> API assumes that each item in the list
+ * is rendered by a single <code></code>CheckedTextView</code>.</li>
+ * </ul>
+ */
+public class AlertDialogTest extends BaseInstrumentationTestCase<AlertDialogTestActivity> {
+ private Button mButton;
+
+ private AlertDialog mAlertDialog;
+
+ public AlertDialogTest() {
+ super(AlertDialogTestActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ final AlertDialogTestActivity activity = mActivityTestRule.getActivity();
+ mButton = (Button) activity.findViewById(R.id.test_button);
+ }
+
+ private void wireBuilder(final AlertDialog.Builder builder) {
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.show();
+ }
+ });
+ }
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testBuilderTheme() {
+ final Context context = mActivityTestRule.getActivity();
+ final AlertDialog dialog = new AlertDialog.Builder(context, R.style.Theme_TextColors)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .create();
+
+ final TypedValue tv = new TypedValue();
+ dialog.getContext().getTheme().resolveAttribute(android.R.attr.textColorPrimary, tv, true);
+ assertEquals(0xFF0000FF, tv.data);
+ }
+
+ @Test
+ @SmallTest
+ public void testBasicContent() {
+ final Context context = mActivityTestRule.getActivity();
+ AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Test that we're showing a dialog with vertically stacked title and content
+ final String expectedTitle = context.getString(R.string.alert_dialog_title);
+ final String expectedMessage = context.getString(R.string.alert_dialog_content);
+ onView(withText(expectedTitle)).inRoot(isDialog()).check(matches(isDisplayed()));
+ onView(withText(expectedMessage)).inRoot(isDialog()).check(matches(isDisplayed()));
+ onView(withText(expectedMessage)).inRoot(isDialog()).check(
+ isBelow(withText(expectedTitle)));
+
+ assertNull("No list view", mAlertDialog.getListView());
+
+ assertEquals("Positive button not shown", View.GONE,
+ mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE).getVisibility());
+ assertEquals("Negative button not shown", View.GONE,
+ mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE).getVisibility());
+ assertEquals("Neutral button not shown", View.GONE,
+ mAlertDialog.getButton(AlertDialog.BUTTON_NEUTRAL).getVisibility());
+ }
+
+ // Tests for message logic
+
+ @Test
+ @SmallTest
+ public void testMessageString() {
+ final String dialogMessage = "Dialog message";
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(dialogMessage);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+ onView(withText(dialogMessage)).inRoot(isDialog()).check(matches(isDisplayed()));
+ }
+
+ @Test
+ @SmallTest
+ public void testMessageStringPostCreation() {
+ final String dialogInitialMessage = "Initial message";
+ final String dialogUpdatedMessage = "Updated message";
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(dialogInitialMessage);
+ wireBuilder(builder);
+
+ // Click the button to show the dialog and check that it shows the initial message
+ onView(withId(R.id.test_button)).perform(click());
+ onView(withText(dialogInitialMessage)).inRoot(isDialog()).check(matches(isDisplayed()));
+
+ // Update the dialog message
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setMessage(dialogUpdatedMessage);
+ }
+ });
+ // Check that the old message is not showing
+ onView(withText(dialogInitialMessage)).inRoot(isDialog()).check(doesNotExist());
+ // and that the new message is showing
+ onView(withText(dialogUpdatedMessage)).inRoot(isDialog()).check(matches(isDisplayed()));
+ }
+
+ // Tests for custom title logic
+
+ /**
+ * Helper method to verify that setting custom title hides the default title and shows
+ * the custom title above the dialog message.
+ */
+ private void verifyCustomTitle() {
+ final Context context = mActivityTestRule.getActivity();
+
+ // Test that we're showing a dialog with vertically stacked custom title and content
+ final String title = context.getString(R.string.alert_dialog_title);
+ final String expectedCustomTitle = context.getString(R.string.alert_dialog_custom_title);
+ final String expectedMessage = context.getString(R.string.alert_dialog_content);
+
+ // Check that the default title is not showing
+ onView(withText(title)).inRoot(isDialog()).check(doesNotExist());
+ // Check that the custom title is fully displayed with no text eliding and is
+ // stacked above the message
+ onView(withText(expectedCustomTitle)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(expectedCustomTitle)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+ onView(withText(expectedMessage)).inRoot(isDialog()).check(matches(isDisplayed()));
+ onView(withText(expectedMessage)).inRoot(isDialog()).check(
+ isBelow(withText(expectedCustomTitle)));
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomTitle() {
+ final Context context = mActivityTestRule.getActivity();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setCustomTitle(inflater.inflate(R.layout.alert_dialog_custom_title, null, false));
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ verifyCustomTitle();
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomTitlePostCreation() {
+ final Context context = mActivityTestRule.getActivity();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content);
+
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.create();
+
+ // Configure custom title
+ mAlertDialog.setCustomTitle(inflater.inflate(
+ R.layout.alert_dialog_custom_title, null, false));
+
+ mAlertDialog.show();
+ }
+ });
+
+ // Click the button to create the dialog, configure custom title and show the dialog
+ onView(withId(R.id.test_button)).perform(click());
+
+ verifyCustomTitle();
+ }
+
+ // Tests for custom view logic
+
+ /**
+ * Helper method to verify that setting custom view shows the content of that view.
+ */
+ private void verifyCustomView() {
+ final Context context = mActivityTestRule.getActivity();
+
+ // Test that we're showing a dialog with vertically stacked custom title and content
+ final String expectedCustomText1 = context.getString(R.string.alert_dialog_custom_text1);
+ final String expectedCustomText2 = context.getString(R.string.alert_dialog_custom_text2);
+
+ // Check that we're showing the content of our custom view
+ onView(withId(R.id.alert_dialog_custom_view)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(expectedCustomText1)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(expectedCustomText1)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+ onView(withText(expectedCustomText2)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(expectedCustomText2)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomView() {
+ final Context context = mActivityTestRule.getActivity();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setView(inflater.inflate(R.layout.alert_dialog_custom_view, null, false));
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ verifyCustomView();
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomViewById() {
+ final Context context = mActivityTestRule.getActivity();
+ AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setView(R.layout.alert_dialog_custom_view);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ verifyCustomView();
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomViewPostCreation() {
+ final Context context = mActivityTestRule.getActivity();
+ final LayoutInflater inflater = LayoutInflater.from(context);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content);
+
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.create();
+
+ // Configure custom view
+ mAlertDialog.setView(inflater.inflate(
+ R.layout.alert_dialog_custom_view, null, false));
+
+ mAlertDialog.show();
+ }
+ });
+
+ // Click the button to create the dialog, configure custom view and show the dialog
+ onView(withId(R.id.test_button)).perform(click());
+
+ verifyCustomView();
+ }
+
+ // Tests for cancel logic
+
+ @Test
+ @SmallTest
+ public void testCancelCancelableDialog() {
+ DialogInterface.OnCancelListener mockCancelListener =
+ mock(DialogInterface.OnCancelListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setCancelable(true)
+ .setOnCancelListener(mockCancelListener);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate a tap on the device BACK button
+ Espresso.pressBack();
+
+ // Since our dialog is cancelable, check that the cancel listener has been invoked
+ verify(mockCancelListener, times(1)).onCancel(mAlertDialog);
+ }
+
+ @Test
+ @SmallTest
+ public void testCancelNonCancelableDialog() {
+ DialogInterface.OnCancelListener mockCancelListener =
+ mock(DialogInterface.OnCancelListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setCancelable(false)
+ .setOnCancelListener(mockCancelListener);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate a tap on the device BACK button
+ Espresso.pressBack();
+
+ // Since our dialog is not cancelable, check that the cancel listener has not been invoked
+ verify(mockCancelListener, never()).onCancel(mAlertDialog);
+ }
+
+ // Tests for items content logic (simple, single-choice, multi-choice)
+
+ private void verifySimpleItemsContent(String[] expectedContent,
+ DialogInterface.OnClickListener onClickListener) {
+ 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());
+ for (int i = 0; i < expectedCount; i++) {
+ assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+ }
+
+ // Test that all items are showing
+ onView(withText("Dialog title")).inRoot(isDialog()).check(matches(isDisplayed()));
+ for (int i = 0; i < expectedCount; i++) {
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()).
+ check(matches(isDisplayed()));
+ }
+
+ // Verify that our click listener hasn't been called yet
+ verify(onClickListener, never()).onClick(any(DialogInterface.class), any(int.class));
+ // Test that a click on an item invokes the registered listener
+ int indexToClick = expectedCount - 2;
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[indexToClick]))).
+ inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, indexToClick);
+ }
+
+ @Test
+ @SmallTest
+ public void testCustomAdapter() {
+ final Context context = mActivityTestRule.getActivity();
+ final String[] content = context.getResources().getStringArray(R.array.alert_dialog_items);
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setAdapter(
+ new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, content),
+ mockClickListener);
+ wireBuilder(builder);
+
+ verifySimpleItemsContent(content, mockClickListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testSimpleItemsFromRuntimeArray() {
+ final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setItems(content, mockClickListener);
+ wireBuilder(builder);
+
+ verifySimpleItemsContent(content, mockClickListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testSimpleItemsFromResourcesArray() {
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setItems(R.array.alert_dialog_items, mockClickListener);
+ wireBuilder(builder);
+
+ verifySimpleItemsContent(mActivityTestRule.getActivity().getResources().getStringArray(
+ R.array.alert_dialog_items), mockClickListener);
+ }
+
+ /**
+ * 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++) {
+ assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+ }
+
+ 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.
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).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());
+ for (int i = 0; i < expectedCount; i++) {
+ assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+ }
+
+ // 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];
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))).
+ 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];
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[1]))).
+ 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];
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[expectedCount - 1]))).
+ inRoot(isDialog()).perform(click());
+ verifyMultiChoiceItemsState(expectedContent, expectedAfterClickLast);
+ }
+
+ @Test
+ @SmallTest
+ public void testMultiChoiceItemsFromRuntimeArray() {
+ final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+ final boolean[] checkedTracker = new boolean[] { false, true, false, false };
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMultiChoiceItems(
+ content, checkedTracker,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which,
+ boolean isChecked) {
+ checkedTracker[which] = isChecked;
+ }
+ });
+ wireBuilder(builder);
+
+ // Pass the same boolean[] array as used for initialization since our click listener
+ // will be updating its content.
+ verifyMultiChoiceItemsContent(content, checkedTracker);
+ }
+
+ @Test
+ @SmallTest
+ public void testMultiChoiceItemsFromResourcesArray() {
+ final boolean[] checkedTracker = new boolean[] { true, false, true, false };
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMultiChoiceItems(R.array.alert_dialog_items, checkedTracker,
+ new DialogInterface.OnMultiChoiceClickListener() {
+ @Override
+ public void onClick(DialogInterface dialog, int which,
+ boolean isChecked) {
+ checkedTracker[which] = isChecked;
+ }
+ });
+ wireBuilder(builder);
+
+ verifyMultiChoiceItemsContent(
+ mActivityTestRule.getActivity().getResources().getStringArray(
+ R.array.alert_dialog_items),
+ 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++) {
+ assertEquals("List entry #" + i, expectedContent[i], listAdapter.getItem(i));
+ }
+
+ 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.
+ onData(allOf(is(instanceOf(String.class)), is(expectedContent[i]))).inRoot(isDialog()).
+ check(matches(allOf(
+ isDisplayed(),
+ isAssignableFrom(CheckedTextView.class),
+ isDescendantOfA(isAssignableFrom(ListView.class)),
+ checkedStateMatcher)));
+ }
+ }
+
+ private void verifySingleChoiceItemsContent(String[] expectedContent,
+ int initialSelectionIndex, DialogInterface.OnClickListener onClickListener) {
+ 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;
+ onData(allOf(is(instanceOf(String.class)),
+ is(expectedContent[currentlyExpectedSelectionIndex]))).
+ inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+
+ // Now click the same item again and test that the selection has not changed
+ onData(allOf(is(instanceOf(String.class)),
+ is(expectedContent[currentlyExpectedSelectionIndex]))).
+ inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(2)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ 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;
+ onData(allOf(is(instanceOf(String.class)),
+ is(expectedContent[currentlyExpectedSelectionIndex]))).
+ inRoot(isDialog()).perform(click());
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
+ verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
+ }
+
+ @Test
+ @SmallTest
+ public void testSingleChoiceItemsFromRuntimeArray() {
+ final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setSingleChoiceItems(content, 2, mockClickListener);
+ wireBuilder(builder);
+
+ verifySingleChoiceItemsContent(content, 2, mockClickListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testSingleChoiceItemsFromResourcesArray() {
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setSingleChoiceItems(R.array.alert_dialog_items, 1, mockClickListener);
+ wireBuilder(builder);
+
+ verifySingleChoiceItemsContent(new String[] { "Albania", "Belize", "Chad", "Djibouti" }, 1,
+ mockClickListener);
+ }
+
+ // Tests for icon logic
+
+ @Test
+ @SmallTest
+ public void testIconResource() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(R.drawable.test_drawable_red);
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected red color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFFFF6030)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconResourceChangeAfterInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(R.drawable.test_drawable_red);
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background loading of the new icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(R.drawable.test_drawable_green);
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected (newly set) green color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF50E080)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconResourceChangeWithNoInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content);
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background loading of the new icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(R.drawable.test_drawable_green);
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected (newly set) green color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF50E080)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconResourceRemoveAfterInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(R.drawable.test_drawable_red);
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background resetting of the icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(0);
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that we couldn't find the title icon (since it's expected to be GONE)
+ titleIconInteraction.check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testIconDrawable() {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(new TestDrawable(0xFF807060, 40, 40));
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected red color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF807060)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconResourceDrawableAfterInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(new TestDrawable(0xFF807060, 40, 40));
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background loading of the new icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(new TestDrawable(0xFF503090, 40, 40));
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected (newly set) green color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF503090)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconDrawableChangeWithNoInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content);
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background loading of the new icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(new TestDrawable(0xFF503090, 40, 40));
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that it's the expected (newly set) green color
+ titleIconInteraction.check(matches(TestUtilsMatchers.drawable(0xFF503090)));
+ }
+
+ @Test
+ @SmallTest
+ public void testIconDrawableRemoveAfterInitialSetup() throws Throwable {
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setMessage(R.string.alert_dialog_content)
+ .setIcon(new TestDrawable(0xFF807060, 40, 40));
+
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Emulate background resetting of the icon
+ Thread.sleep(1000);
+
+ // Change the icon
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mAlertDialog.setIcon(null);
+ }
+ });
+
+ // Find the title icon as a visible view that is the sibling of our title
+ ViewInteraction titleIconInteraction = onView(allOf(
+ isAssignableFrom(ImageView.class),
+ isDisplayed(),
+ hasSibling(withText("Dialog title"))));
+ // And check that we couldn't find the title icon (since it's expected to be GONE)
+ titleIconInteraction.check(doesNotExist());
+ }
+
+ // Tests for buttons logic
+
+ /**
+ * Helper method to verify visibility and text content of dialog buttons. Gets expected texts
+ * for three buttons (positive, negative and neutral) and for each button verifies that:
+ *
+ * If the text is null or empty, that the button is GONE
+ * If the text is not empty, that the button is VISIBLE and shows the corresponding text
+ */
+ private void verifyButtonContent(String expectedPositiveButtonText,
+ String expectedNegativeButtonText, String expectedNeutralButtonText) {
+ assertTrue("Dialog is showing", mAlertDialog.isShowing());
+
+ final Button positiveButton = mAlertDialog.getButton(AlertDialog.BUTTON_POSITIVE);
+ final Button negativeButton = mAlertDialog.getButton(AlertDialog.BUTTON_NEGATIVE);
+ final Button neutralButton = mAlertDialog.getButton(AlertDialog.BUTTON_NEUTRAL);
+
+ if (TextUtils.isEmpty(expectedPositiveButtonText)) {
+ assertEquals("Positive button not shown", View.GONE, positiveButton.getVisibility());
+ } else {
+ assertEquals("Positive button shown", View.VISIBLE, positiveButton.getVisibility());
+ assertEquals("Positive button text", expectedPositiveButtonText,
+ positiveButton.getText());
+ }
+
+ if (TextUtils.isEmpty(expectedNegativeButtonText)) {
+ assertEquals("Negative button not shown", View.GONE, negativeButton.getVisibility());
+ } else {
+ assertEquals("Negative button shown", View.VISIBLE, negativeButton.getVisibility());
+ assertEquals("Negative button text", expectedNegativeButtonText,
+ negativeButton.getText());
+ }
+
+ if (TextUtils.isEmpty(expectedNeutralButtonText)) {
+ assertEquals("Neutral button not shown", View.GONE, neutralButton.getVisibility());
+ } else {
+ assertEquals("Neutral button shown", View.VISIBLE, neutralButton.getVisibility());
+ assertEquals("Neutral button text", expectedNeutralButtonText,
+ neutralButton.getText());
+ }
+ }
+
+ /**
+ * Helper method to verify dialog state after a button has been clicked.
+ */
+ private void verifyPostButtonClickState(int whichButtonClicked,
+ DialogInterface.OnDismissListener onDismissListener,
+ Handler messageHandler) {
+ // Verify that a Message with expected 'what' field has been posted on our mock handler
+ ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(messageHandler, times(1)).sendMessageDelayed(
+ messageArgumentCaptor.capture(), anyInt());
+ assertEquals("Button clicked", whichButtonClicked, messageArgumentCaptor.getValue().what);
+ // Verify that the dialog is no longer showing
+ assertFalse("Dialog is not showing", mAlertDialog.isShowing());
+ if (onDismissListener != null) {
+ // And that our mock listener has been called when the dialog was dismissed
+ verify(onDismissListener, times(1)).onDismiss(mAlertDialog);
+ }
+ }
+
+ /**
+ * Helper method to verify dialog state after a button has been clicked.
+ */
+ private void verifyPostButtonClickState(int whichButtonClicked,
+ DialogInterface.OnClickListener onClickListener,
+ DialogInterface.OnDismissListener onDismissListener) {
+ if (onClickListener != null) {
+ verify(onClickListener, times(1)).onClick(mAlertDialog, whichButtonClicked);
+ }
+ assertFalse("Dialog is not showing", mAlertDialog.isShowing());
+ if (onDismissListener != null) {
+ verify(onDismissListener, times(1)).onDismiss(mAlertDialog);
+ }
+ }
+
+ /**
+ * Helper method to verify button-related logic for setXXXButton on AlertDialog.Builder
+ * that gets CharSequence parameter. This method configures the dialog buttons based
+ * on the passed texts (some of which may be null or empty, in which case the corresponding
+ * button is not configured), tests the buttons visibility and texts, simulates a click
+ * on the specified button and then tests the post-click dialog state.
+ */
+ private void verifyDialogButtons(String positiveButtonText, String negativeButtonText,
+ String neutralButtonText, int whichButtonToClick) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title);
+ // Configure buttons with non-empty texts
+ DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ if (!TextUtils.isEmpty(positiveButtonText)) {
+ builder.setPositiveButton(positiveButtonText, mockClickListener);
+ }
+ if (!TextUtils.isEmpty(negativeButtonText)) {
+ builder.setNegativeButton(negativeButtonText, mockClickListener);
+ }
+ if (!TextUtils.isEmpty(neutralButtonText)) {
+ builder.setNeutralButton(neutralButtonText, mockClickListener);
+ }
+ // Set a dismiss listener to verify that the dialog is dismissed on clicking any button
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+
+ // Wire the builder to the button click and click that button to show the dialog
+ wireBuilder(builder);
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Check that the dialog is showing the configured buttons
+ verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText);
+
+ // Click the specified button and verify the post-click state
+ String textOfButtonToClick = null;
+ switch (whichButtonToClick) {
+ case DialogInterface.BUTTON_POSITIVE:
+ textOfButtonToClick = positiveButtonText;
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ textOfButtonToClick = negativeButtonText;
+ break;
+ case DialogInterface.BUTTON_NEUTRAL:
+ textOfButtonToClick = neutralButtonText;
+ break;
+ }
+ onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener);
+ }
+
+ /**
+ * Helper method to verify button-related logic for setXXXButton on AlertDialog.Builder
+ * that gets string resource ID parameter. This method configures the dialog buttons based
+ * on the passed texts (some of which may be null or empty, in which case the corresponding
+ * button is not configured), tests the buttons visibility and texts, simulates a click
+ * on the specified button and then tests the post-click dialog state.
+ */
+ private void verifyDialogButtons(@StringRes int positiveButtonTextResId,
+ @StringRes int negativeButtonTextResId,
+ @StringRes int neutralButtonTextResId, int whichButtonToClick) {
+ Context context = mActivityTestRule.getActivity();
+ String positiveButtonText = null;
+ String negativeButtonText = null;
+ String neutralButtonText = null;
+
+ final AlertDialog.Builder builder = new AlertDialog.Builder(context)
+ .setTitle(R.string.alert_dialog_title);
+ DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+ // Configure buttons with non-zero text resource IDs
+ if (positiveButtonTextResId != 0) {
+ positiveButtonText = context.getString(positiveButtonTextResId);
+ builder.setPositiveButton(positiveButtonTextResId, mockClickListener);
+ }
+ if (negativeButtonTextResId != 0) {
+ negativeButtonText = context.getString(negativeButtonTextResId);
+ builder.setNegativeButton(negativeButtonTextResId, mockClickListener);
+ }
+ if (neutralButtonTextResId != 0) {
+ neutralButtonText = context.getString(neutralButtonTextResId);
+ builder.setNeutralButton(neutralButtonTextResId, mockClickListener);
+ }
+ // Set a dismiss listener to verify that the dialog is dismissed on clicking any button
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+
+ // Wire the builder to the button click and click that button to show the dialog
+ wireBuilder(builder);
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Check that the dialog is showing the configured buttons
+ verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText);
+
+ // Click the specified button and verify the post-click state
+ String textOfButtonToClick = null;
+ switch (whichButtonToClick) {
+ case DialogInterface.BUTTON_POSITIVE:
+ textOfButtonToClick = positiveButtonText;
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ textOfButtonToClick = negativeButtonText;
+ break;
+ case DialogInterface.BUTTON_NEUTRAL:
+ textOfButtonToClick = neutralButtonText;
+ break;
+ }
+ onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener);
+ }
+
+ /**
+ * Helper method to verify button-related logic for setButton on AlertDialog after the
+ * dialog has been create()'d. This method configures the dialog buttons based
+ * on the passed texts (some of which may be null or empty, in which case the corresponding
+ * button is not configured), tests the buttons visibility and texts, simulates a click
+ * on the specified button and then tests the post-click dialog state.
+ */
+ private void verifyDialogButtonsPostCreation(final String positiveButtonText,
+ final String negativeButtonText, final String neutralButtonText,
+ int whichButtonToClick) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title);
+ // Set a dismiss listener to verify that the dialog is dismissed on clicking any button
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
+
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.create();
+ // Configure buttons with non-empty texts
+ if (!TextUtils.isEmpty(positiveButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText,
+ mockClickListener);
+ }
+ if (!TextUtils.isEmpty(negativeButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText,
+ mockClickListener);
+ }
+ if (!TextUtils.isEmpty(neutralButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText,
+ mockClickListener);
+ }
+
+ mAlertDialog.show();
+ }
+ });
+
+ // Click the button to create the dialog, configure the buttons and show the dialog
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Check that the dialog is showing the configured buttons
+ verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText);
+
+ // Click the specified button and verify the post-click state
+ String textOfButtonToClick = null;
+ switch (whichButtonToClick) {
+ case DialogInterface.BUTTON_POSITIVE:
+ textOfButtonToClick = positiveButtonText;
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ textOfButtonToClick = negativeButtonText;
+ break;
+ case DialogInterface.BUTTON_NEUTRAL:
+ textOfButtonToClick = neutralButtonText;
+ break;
+ }
+ onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, null);
+ }
+
+ /**
+ * Helper method to verify button-related logic for setButton on AlertDialog after the
+ * dialog has been create()'d. This method configures the dialog buttons based
+ * on the passed texts (some of which may be null or empty, in which case the corresponding
+ * button is not configured), tests the buttons visibility and texts, simulates a click
+ * on the specified button and then tests the post-click dialog state.
+ */
+ private void verifyDialogButtonsPostCreationMessage(final String positiveButtonText,
+ final String negativeButtonText, final String neutralButtonText,
+ int whichButtonToClick) {
+ final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title);
+ // Set a dismiss listener to verify that the dialog is dismissed on clicking any button
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+
+ final Handler mockMessageHandler = mock(Handler.class);
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mAlertDialog = builder.create();
+ // Configure buttons with non-empty texts
+ if (!TextUtils.isEmpty(positiveButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText,
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_POSITIVE));
+ }
+ if (!TextUtils.isEmpty(negativeButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText,
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEGATIVE));
+ }
+ if (!TextUtils.isEmpty(neutralButtonText)) {
+ mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText,
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEUTRAL));
+ }
+
+ mAlertDialog.show();
+ }
+ });
+
+ // Click the button to create the dialog, configure the buttons and show the dialog
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Check that the dialog is showing the configured buttons
+ verifyButtonContent(positiveButtonText, negativeButtonText, neutralButtonText);
+
+ // Click the specified button and verify the post-click state
+ String textOfButtonToClick = null;
+ switch (whichButtonToClick) {
+ case DialogInterface.BUTTON_POSITIVE:
+ textOfButtonToClick = positiveButtonText;
+ break;
+ case DialogInterface.BUTTON_NEGATIVE:
+ textOfButtonToClick = negativeButtonText;
+ break;
+ case DialogInterface.BUTTON_NEUTRAL:
+ textOfButtonToClick = neutralButtonText;
+ break;
+ }
+ onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
+ verifyPostButtonClickState(whichButtonToClick, mockDismissListener, mockMessageHandler);
+ }
+
+ @Test
+ @SmallTest
+ public void testButtonVisibility() {
+ final String positiveButtonText = "Positive button";
+ final String negativeButtonText = "Negative button";
+ final String neutralButtonText = "Neutral button";
+ AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
+ .setTitle(R.string.alert_dialog_title)
+ .setPositiveButton(positiveButtonText, null)
+ .setNegativeButton(negativeButtonText, null)
+ .setNeutralButton(neutralButtonText, null);
+ wireBuilder(builder);
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Positive button should be fully displayed with no text eliding
+ onView(withText(positiveButtonText)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(positiveButtonText)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+
+ // Negative button should be fully displayed with no text eliding
+ onView(withText(negativeButtonText)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(negativeButtonText)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+
+ // Neutral button should be fully displayed with no text eliding
+ onView(withText(neutralButtonText)).inRoot(isDialog()).check(
+ matches(isCompletelyDisplayed()));
+ onView(withText(neutralButtonText)).inRoot(isDialog()).check(
+ matches(not(hasEllipsizedText())));
+ }
+
+ @Test
+ @MediumTest
+ public void testButtons() {
+ // Positive-only button
+ verifyDialogButtons("Positive", null, null, AlertDialog.BUTTON_POSITIVE);
+ verifyDialogButtons(R.string.alert_dialog_positive_button, 0, 0,
+ AlertDialog.BUTTON_POSITIVE);
+ verifyDialogButtonsPostCreation("Post positive", null, null, AlertDialog.BUTTON_POSITIVE);
+ verifyDialogButtonsPostCreationMessage("Message positive", null, null,
+ AlertDialog.BUTTON_POSITIVE);
+
+ // Negative-only button
+ verifyDialogButtons(null, "Negative", null, AlertDialog.BUTTON_NEGATIVE);
+ verifyDialogButtons(0, R.string.alert_dialog_negative_button, 0,
+ AlertDialog.BUTTON_NEGATIVE);
+ verifyDialogButtonsPostCreation(null, "Post negative", null, AlertDialog.BUTTON_NEGATIVE);
+ verifyDialogButtonsPostCreationMessage(null, "Message negative", null,
+ AlertDialog.BUTTON_NEGATIVE);
+
+ // Neutral-only button
+ verifyDialogButtons(null, null, "Neutral", AlertDialog.BUTTON_NEUTRAL);
+ verifyDialogButtons(0, 0, R.string.alert_dialog_neutral_button, AlertDialog.BUTTON_NEUTRAL);
+ verifyDialogButtonsPostCreation(null, null, "Post neutral", AlertDialog.BUTTON_NEUTRAL);
+ verifyDialogButtonsPostCreationMessage(null, null, "Message neutral",
+ AlertDialog.BUTTON_NEUTRAL);
+
+ // Show positive and negative, click positive
+ verifyDialogButtons(R.string.alert_dialog_positive_button,
+ R.string.alert_dialog_negative_button, 0, AlertDialog.BUTTON_POSITIVE);
+
+ // Show positive and neutral, click neutral
+ verifyDialogButtons("Positive", null, "Neutral", AlertDialog.BUTTON_NEUTRAL);
+
+ // Show negative and neutral, click negative
+ verifyDialogButtonsPostCreationMessage(null, "Message negative",
+ "Message neutral", AlertDialog.BUTTON_NEGATIVE);
+
+ // Show all, click positive
+ verifyDialogButtonsPostCreation("Post positive", "Post negative", "Post neutral",
+ AlertDialog.BUTTON_POSITIVE);
+ }
+
+ private static class TestDrawable extends ColorDrawable {
+ private int mWidth;
+ private int mHeight;
+
+ public TestDrawable(@ColorInt int color, int width, int height) {
+ super(color);
+ mWidth = width;
+ mHeight = height;
+ }
+
+ @Override
+ public int getIntrinsicWidth() {
+ return mWidth;
+ }
+
+ @Override
+ public int getIntrinsicHeight() {
+ return mHeight;
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTestActivity.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTestActivity.java
new file mode 100644
index 0000000..7f20bc3
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTestActivity.java
@@ -0,0 +1,26 @@
+/*
+ * 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.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AlertDialogTestActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.alert_dialog_activity;
+ }
+}
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 88b984c..5230f0e 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseBasicsTestCase.java
@@ -16,8 +16,34 @@
package android.support.v7.app;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoMoreInteractions;
+import static org.mockito.Mockito.when;
+
+import android.os.Build;
+import android.support.test.annotation.UiThreadTest;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.custom.FitWindowsContentLayout;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.TestUtils;
+import android.support.v7.view.ActionMode;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.Menu;
+import android.view.View;
+import android.view.WindowInsets;
+
import org.junit.Test;
+import java.util.concurrent.atomic.AtomicBoolean;
+
public abstract class BaseBasicsTestCase<A extends BaseTestActivity>
extends BaseInstrumentationTestCase<A> {
@@ -26,16 +52,19 @@
}
@Test
+ @SmallTest
public void testActionBarExists() {
assertNotNull("ActionBar is not null", getActivity().getSupportActionBar());
}
@Test
- public void testDefaultActionBarTitle() {
+ @SmallTest
+ public void testDefaultActionBarTitle() {
assertEquals(getActivity().getTitle(), getActivity().getSupportActionBar().getTitle());
}
@Test
+ @SmallTest
public void testSetActionBarTitle() throws Throwable {
final String newTitle = "hello";
runTestOnUiThread(new Runnable() {
@@ -47,4 +76,163 @@
}
});
}
+
+ @Test
+ @SmallTest
+ public void testMenuInvalidationAfterDestroy() throws Throwable {
+ final A activity = getActivity();
+ // Reset to make sure that we don't have a menu currently
+ activity.reset();
+ assertNull(activity.getMenu());
+
+ // Now destroy the Activity
+ activity.finish();
+ TestUtils.waitForActivityDestroyed(activity);
+
+ // Now dispatch a menu invalidation and wait for an idle sync
+ activity.supportInvalidateOptionsMenu();
+ getInstrumentation().waitForIdleSync();
+
+ // Make sure that we don't have a menu given to use after being destroyed
+ assertNull(activity.getMenu());
+ }
+
+ @Test
+ @SmallTest
+ public void testFitSystemWindowsReachesContent() {
+ final FitWindowsContentLayout content =
+ (FitWindowsContentLayout) getActivity().findViewById(R.id.test_content);
+ assertNotNull(content);
+ assertTrue(content.getFitsSystemWindowsCalled());
+ }
+
+ @Test
+ @SmallTest
+ public void testOnApplyWindowInsetsReachesContent() {
+ if (Build.VERSION.SDK_INT < 21) {
+ // OnApplyWindowInsetsListener is only available on API 21+
+ return;
+ }
+
+ final View content = getActivity().findViewById(R.id.test_content);
+ assertNotNull(content);
+
+ final AtomicBoolean applyWindowInsetsCalled = new AtomicBoolean();
+ content.setOnApplyWindowInsetsListener(new View.OnApplyWindowInsetsListener() {
+ @Override
+ public WindowInsets onApplyWindowInsets(View view, WindowInsets windowInsets) {
+ applyWindowInsetsCalled.set(true);
+ return windowInsets;
+ }
+ });
+ assertTrue(applyWindowInsetsCalled.get());
+ }
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testSupportActionModeCallbacks() {
+ final A activity = getActivity();
+
+ // Create a mock action mode callback which returns true from onCreateActionMode
+ final ActionMode.Callback callback = mock(ActionMode.Callback.class);
+ when(callback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).thenReturn(true);
+
+ // Start an action mode
+ final ActionMode actionMode = activity.startSupportActionMode(callback);
+ assertNotNull(actionMode);
+
+ // Now verify that onCreateActionMode and onPrepareActionMode are called once
+ verify(callback).onCreateActionMode(any(ActionMode.class), any(Menu.class));
+ verify(callback).onPrepareActionMode(any(ActionMode.class), any(Menu.class));
+
+ // Now finish and verify that onDestroyActionMode is called once, and there are no more
+ // interactions
+ actionMode.finish();
+ verify(callback).onDestroyActionMode(any(ActionMode.class));
+ verifyNoMoreInteractions(callback);
+ }
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testSupportActionModeCallbacksInvalidate() {
+ final A activity = getActivity();
+
+ // Create a mock action mode callback which returns true from onCreateActionMode
+ final ActionMode.Callback callback = mock(ActionMode.Callback.class);
+ when(callback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).thenReturn(true);
+
+ // Start an action mode
+ final ActionMode actionMode = activity.startSupportActionMode(callback);
+ // Assert that one was created
+ assertNotNull(actionMode);
+ // Reset the mock so that any callback counts from the create are reset
+ reset(callback);
+
+ // Now invalidate the action mode
+ actionMode.invalidate();
+
+ // Now verify that onCreateActionMode is not called, and onPrepareActionMode is called once
+ verify(callback, never()).onCreateActionMode(any(ActionMode.class), any(Menu.class));
+ verify(callback).onPrepareActionMode(any(ActionMode.class), any(Menu.class));
+ }
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testSupportActionModeCallbacksWithFalseOnCreate() {
+ final A activity = getActivity();
+
+ // Create a mock action mode callback which returns true from onCreateActionMode
+ final ActionMode.Callback callback = mock(ActionMode.Callback.class);
+ when(callback.onCreateActionMode(any(ActionMode.class), any(Menu.class))).thenReturn(false);
+
+ // Start an action mode
+ final ActionMode actionMode = activity.startSupportActionMode(callback);
+
+ // Now verify that onCreateActionMode is called once
+ verify(callback).onCreateActionMode(any(ActionMode.class), any(Menu.class));
+
+ // Now verify that onPrepareActionMode is not called (since onCreateActionMode
+ // returns false)
+ verify(callback, never()).onPrepareActionMode(any(ActionMode.class), any(Menu.class));
+
+ // Assert that an action mode was not created
+ assertNull(actionMode);
+ }
+
+ protected void testSupportActionModeAppCompatCallbacks(final boolean fromWindow) {
+ final A activity = getActivity();
+
+ // Create a mock action mode callback which returns true from onCreateActionMode
+ final ActionMode.Callback amCallback = mock(ActionMode.Callback.class);
+ when(amCallback.onCreateActionMode(any(ActionMode.class), any(Menu.class)))
+ .thenReturn(true);
+
+ // Create a mock AppCompatCallback, which returns null from
+ // onWindowStartingSupportActionMode, and set it on the Activity
+ final AppCompatCallback apCallback = mock(AppCompatCallback.class);
+ when(apCallback.onWindowStartingSupportActionMode(any(ActionMode.Callback.class)))
+ .thenReturn(null);
+ activity.setAppCompatCallback(apCallback);
+
+ // Start an action mode with the action mode callback
+ final ActionMode actionMode = activity.startSupportActionMode(amCallback);
+
+ if (fromWindow) {
+ // Verify that the callback's onWindowStartingSupportActionMode was called
+ verify(apCallback).onWindowStartingSupportActionMode(any(ActionMode.Callback.class));
+ }
+
+ // Now assert that an action mode was created
+ assertNotNull(actionMode);
+
+ // Now verify that onSupportActionModeStarted is called once
+ verify(apCallback).onSupportActionModeStarted(any(ActionMode.class));
+
+ // Now finish and verify that onDestroyActionMode is called once
+ actionMode.finish();
+ verify(apCallback).onSupportActionModeFinished(any(ActionMode.class));
+ }
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
index 6591ff79..76ca5dc 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseInstrumentationTestCase.java
@@ -16,28 +16,50 @@
package android.support.v7.app;
-import org.junit.Before;
+import org.junit.Rule;
import org.junit.runner.RunWith;
import android.app.Activity;
+import android.app.Instrumentation;
import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
import android.support.test.runner.AndroidJUnit4;
-import android.test.ActivityInstrumentationTestCase2;
@RunWith(AndroidJUnit4.class)
-public abstract class BaseInstrumentationTestCase<A extends Activity>
- extends ActivityInstrumentationTestCase2<A> {
+public abstract class BaseInstrumentationTestCase<A extends Activity> {
+
+ @Rule
+ public final ActivityTestRule<A> mActivityTestRule;
protected BaseInstrumentationTestCase(Class<A> activityClass) {
- super(activityClass);
+ mActivityTestRule = new ActivityTestRule<A>(activityClass);
}
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- injectInstrumentation(InstrumentationRegistry.getInstrumentation());
- getActivity();
+ @Deprecated
+ public A getActivity() {
+ return mActivityTestRule.getActivity();
+ }
+
+ @Deprecated
+ public Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ @Deprecated
+ public void runTestOnUiThread(final Runnable r) throws Throwable {
+ final Throwable[] exceptions = new Throwable[1];
+ getInstrumentation().runOnMainSync(new Runnable() {
+ public void run() {
+ try {
+ r.run();
+ } catch (Throwable throwable) {
+ exceptions[0] = throwable;
+ }
+ }
+ });
+ if (exceptions[0] != null) {
+ throw exceptions[0];
+ }
}
}
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 44f9207..debf278 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyEventsTestCase.java
@@ -19,13 +19,21 @@
import org.junit.Test;
import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
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;
import java.util.concurrent.atomic.AtomicBoolean;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
public abstract class BaseKeyEventsTestCase<A extends BaseTestActivity>
extends BaseInstrumentationTestCase<A> {
@@ -34,6 +42,7 @@
}
@Test
+ @SmallTest
public void testBackDismissesActionMode() {
final AtomicBoolean destroyed = new AtomicBoolean();
@@ -69,11 +78,12 @@
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
getInstrumentation().waitForIdleSync();
- assertFalse("Activity was not destroyed", getActivity().isDestroyed());
+ assertFalse("Activity was not finished", getActivity().isFinishing());
assertTrue("ActionMode was destroyed", destroyed.get());
}
@Test
+ @SmallTest
public void testBackCollapsesSearchView() throws InterruptedException {
// First expand the SearchView
getActivity().runOnUiThread(new Runnable() {
@@ -96,11 +106,12 @@
}
// Check that the Activity is still running and the SearchView is not expanded
- assertFalse("Activity was not destroyed", getActivity().isDestroyed());
+ assertFalse("Activity was not finished", getActivity().isFinishing());
assertFalse("SearchView was collapsed", getActivity().isSearchViewExpanded());
}
@Test
+ @SmallTest
public void testMenuPressInvokesPanelCallbacks() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
@@ -112,6 +123,7 @@
}
@Test
+ @SmallTest
public void testBackPressWithMenuInvokesOnPanelClosed() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
@@ -122,17 +134,19 @@
}
@Test
- public void testBackPressWithEmptyMenuDestroysActivity() throws InterruptedException {
+ @SmallTest
+ public void testBackPressWithEmptyMenuFinishesActivity() throws InterruptedException {
repopulateWithEmptyMenu();
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_BACK);
- waitAssertDestroyed();
+ assertTrue(getActivity().isFinishing());
}
@Test
+ @SmallTest
public void testDelKeyEventReachesActivity() {
// First send the event
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DEL);
@@ -148,6 +162,7 @@
}
@Test
+ @SmallTest
public void testMenuKeyEventReachesActivity() throws InterruptedException {
getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_MENU);
getInstrumentation().waitForIdleSync();
@@ -161,25 +176,13 @@
assertEquals("onKeyDown event matches", KeyEvent.KEYCODE_MENU, upEvent.getKeyCode());
}
- private void waitAssertDestroyed() throws InterruptedException {
- int count = 0;
- while (count++ < 10) {
- if (!getActivity().isDestroyed()) {
- Thread.sleep(50);
- } else {
- break;
- }
- }
- assertTrue("Activity destroyed", getActivity().isDestroyed());
- }
-
private void repopulateWithEmptyMenu() throws InterruptedException {
int count = 0;
getActivity().setShouldPopulateOptionsMenu(false);
while (count++ < 10) {
Menu menu = getActivity().getMenu();
if (menu == null || menu.size() != 0) {
- Thread.sleep(50);
+ Thread.sleep(100);
} else {
return;
}
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 bfe10c6..97383c3 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BaseKeyboardShortcutsTestCase.java
@@ -20,9 +20,17 @@
import android.os.SystemClock;
import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.test.suitebuilder.annotation.SmallTest;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.MenuItem;
+import android.view.View;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
public abstract class BaseKeyboardShortcutsTestCase<A extends BaseTestActivity>
extends BaseInstrumentationTestCase<A> {
@@ -32,6 +40,7 @@
}
@Test
+ @SmallTest
public void testAlphabeticCtrlShortcut() {
testKeyboardShortcut(KeyEvent.KEYCODE_A,
KeyEvent.META_CTRL_ON | KeyEvent.META_CTRL_LEFT_ON,
@@ -55,4 +64,65 @@
assertEquals("Correct options item selected", selectedItem.getItemId(), expectedId);
}
+ @Test
+ @SmallTest
+ public void testAccessActionBar() throws Throwable {
+ final BaseTestActivity activity = getActivity();
+
+ final View editText = activity.findViewById(R.id.editText);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ editText.requestFocus();
+ }
+ });
+
+ getInstrumentation().waitForIdleSync();
+ sendControlChar('<');
+ getInstrumentation().waitForIdleSync();
+
+ // Should jump to the action bar after control-<
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertFalse(editText.hasFocus());
+ final View toolbar = activity.findViewById(R.id.toolbar);
+ assertTrue(toolbar.hasFocus());
+ }
+ });
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_DOWN);
+ getInstrumentation().waitForIdleSync();
+
+ // Should jump to the first view again.
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(editText.hasFocus());
+ }
+ });
+ getInstrumentation().sendKeyDownUpSync(KeyEvent.KEYCODE_DPAD_UP);
+ getInstrumentation().waitForIdleSync();
+
+ // Now it shouldn't go up to action bar -- it doesn't allow taking focus once left
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertTrue(editText.hasFocus());
+ }
+ });
+ }
+
+ private void sendControlChar(char key) throws Throwable {
+ KeyEvent tempEvent = new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_A);
+ KeyCharacterMap map = tempEvent.getKeyCharacterMap();
+ KeyEvent[] events = map.getEvents(new char[] {key});
+ for (int i = 0; i < events.length; i++) {
+ long time = SystemClock.uptimeMillis();
+ KeyEvent event = events[i];
+ KeyEvent controlKey = new KeyEvent(time, time, event.getAction(), event.getKeyCode(),
+ event.getRepeatCount(), event.getMetaState() | KeyEvent.META_CTRL_ON);
+ getInstrumentation().sendKeySync(controlKey);
+ }
+ }
+
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java
deleted file mode 100644
index ebbd0d5..0000000
--- a/v7/appcompat/tests/src/android/support/v7/app/BaseTestActivity.java
+++ /dev/null
@@ -1,185 +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.app;
-
-import android.os.Bundle;
-import android.support.v4.view.MenuItemCompat;
-import android.support.v7.appcompat.test.R;
-import android.view.KeyEvent;
-import android.view.Menu;
-import android.view.MenuItem;
-import android.view.WindowManager;
-
-abstract class BaseTestActivity extends AppCompatActivity {
-
- private Menu mMenu;
-
- private KeyEvent mOnKeyDownEvent;
- private KeyEvent mOnKeyUpEvent;
- private KeyEvent mOnKeyShortcutEvent;
-
- private MenuItem mOptionsItemSelected;
-
- private boolean mOnMenuOpenedCalled;
- private boolean mOnPanelClosedCalled;
-
- private boolean mShouldPopulateOptionsMenu = true;
-
- private boolean mOnBackPressedCalled;
- 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
- public boolean onOptionsItemSelected(MenuItem item) {
- mOptionsItemSelected = item;
- return super.onOptionsItemSelected(item);
- }
-
- @Override
- public boolean onMenuOpened(int featureId, Menu menu) {
- mOnMenuOpenedCalled = true;
- return super.onMenuOpened(featureId, menu);
- }
-
- @Override
- public void onPanelClosed(int featureId, Menu menu) {
- mOnPanelClosedCalled = true;
- super.onPanelClosed(featureId, menu);
- }
-
- @Override
- public boolean onKeyDown(int keyCode, KeyEvent event) {
- mOnKeyDownEvent = event;
- return super.onKeyDown(keyCode, event);
- }
-
- @Override
- public boolean onKeyUp(int keyCode, KeyEvent event) {
- mOnKeyUpEvent = event;
- return super.onKeyUp(keyCode, event);
- }
-
- @Override
- public boolean onKeyShortcut(int keyCode, KeyEvent event) {
- mOnKeyShortcutEvent = event;
- return super.onKeyShortcut(keyCode, event);
- }
-
- public KeyEvent getInvokedKeyShortcutEvent() {
- return mOnKeyShortcutEvent;
- }
-
- public boolean wasOnMenuOpenedCalled() {
- return mOnMenuOpenedCalled;
- }
-
- public boolean wasOnPanelClosedCalled() {
- return mOnPanelClosedCalled;
- }
-
- public KeyEvent getInvokedKeyDownEvent() {
- return mOnKeyDownEvent;
- }
-
- public KeyEvent getInvokedKeyUpEvent() {
- return mOnKeyUpEvent;
- }
-
- @Override
- public boolean onCreateOptionsMenu(Menu menu) {
- mMenu = menu;
- if (mShouldPopulateOptionsMenu) {
- getMenuInflater().inflate(R.menu.sample_actions, menu);
- return true;
- } else {
- menu.clear();
- return super.onCreateOptionsMenu(menu);
- }
- }
-
- public boolean expandSearchView() {
- return MenuItemCompat.expandActionView(mMenu.findItem(R.id.action_search));
- }
-
- public boolean collapseSearchView() {
- return MenuItemCompat.collapseActionView(mMenu.findItem(R.id.action_search));
- }
-
- public boolean isSearchViewExpanded() {
- return MenuItemCompat.isActionViewExpanded(mMenu.findItem(R.id.action_search));
- }
-
- public MenuItem getOptionsItemSelected() {
- return mOptionsItemSelected;
- }
-
- public void reset() {
- mOnKeyUpEvent = null;
- mOnKeyDownEvent = null;
- mOnKeyShortcutEvent = null;
- mOnMenuOpenedCalled = false;
- mOnPanelClosedCalled = false;
- }
-
- public void setShouldPopulateOptionsMenu(boolean populate) {
- mShouldPopulateOptionsMenu = populate;
- if (mMenu != null) {
- supportInvalidateOptionsMenu();
- }
- }
-
- @Override
- protected void onDestroy() {
- super.onDestroy();
- mDestroyed = true;
- }
-
- @Override
- public void onBackPressed() {
- super.onBackPressed();
- mOnBackPressedCalled = true;
- }
-
- public boolean wasOnBackPressedCalled() {
- return mOnBackPressedCalled;
- }
-
- public Menu getMenu() {
- return mMenu;
- }
-
- @Override
- public boolean isDestroyed() {
- return mDestroyed;
- }
-}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithToolbar.java b/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithToolbar.java
index e01e247..f104f02 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithToolbar.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithToolbar.java
@@ -16,8 +16,21 @@
package android.support.v7.app;
+import android.support.test.annotation.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
public class BasicsTestCaseWithToolbar extends BaseBasicsTestCase<ToolbarActionBarActivity> {
public BasicsTestCaseWithToolbar() {
super(ToolbarActionBarActivity.class);
}
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testSupportActionModeAppCompatCallbacks() {
+ // Since we're using Toolbar, any action modes will be created from the window
+ testSupportActionModeAppCompatCallbacks(true);
+ }
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithWindowDecor.java b/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithWindowDecor.java
index ec4e6b0..27b5039 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithWindowDecor.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/BasicsTestCaseWithWindowDecor.java
@@ -16,8 +16,22 @@
package android.support.v7.app;
+import android.support.test.annotation.UiThreadTest;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+
public class BasicsTestCaseWithWindowDecor extends BaseBasicsTestCase<WindowDecorActionBarActivity> {
public BasicsTestCaseWithWindowDecor() {
super(WindowDecorActionBarActivity.class);
}
+
+ @Test
+ @SmallTest
+ @UiThreadTest
+ public void testSupportActionModeAppCompatCallbacks() {
+ // Since we're using the decor action bar, any action modes not will be created
+ // from the window
+ testSupportActionModeAppCompatCallbacks(false);
+ }
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java b/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
index 053e971..2381821 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DialogTestCase.java
@@ -21,6 +21,9 @@
import android.app.Dialog;
import android.os.Bundle;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
public class DialogTestCase extends BaseInstrumentationTestCase<WindowDecorActionBarActivity> {
public DialogTestCase() {
@@ -43,7 +46,7 @@
public static class TestDialogFragment extends AppCompatDialogFragment {
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- return new AlertDialog.Builder(getActivity())
+ return new AlertDialog.Builder(getContext())
.setTitle("Test")
.setMessage("Message")
.setPositiveButton("Button", null)
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutActivity.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutActivity.java
new file mode 100644
index 0000000..90a9b59
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutActivity.java
@@ -0,0 +1,31 @@
+/*
+ * 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.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.util.Log;
+
+/**
+ * Test activity for testing presence of single and multiple drawers in <code>DrawerLayout</code>.
+ */
+public class DrawerDynamicLayoutActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.drawer_dynamic_layout;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java
new file mode 100755
index 0000000..fb46ce9
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerDynamicLayoutTest.java
@@ -0,0 +1,181 @@
+/*
+ * 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.support.test.InstrumentationRegistry;
+import android.support.v4.widget.DrawerLayout;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.After;
+import org.junit.Test;
+
+import android.support.annotation.LayoutRes;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewStub;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.hamcrest.core.AllOf.allOf;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Test cases to verify that <code>DrawerLayout</code> only supports configurations
+ * with at most one drawer child along each vertical (left / right) edge.
+ */
+@SmallTest
+public class DrawerDynamicLayoutTest
+ extends BaseInstrumentationTestCase<DrawerDynamicLayoutActivity> {
+ public DrawerDynamicLayoutTest() {
+ super(DrawerDynamicLayoutActivity.class);
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ // Now that the test is done, replace the activity content view with ViewStub so
+ // that it's ready to be replaced for the next test.
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final DrawerDynamicLayoutActivity activity = mActivityTestRule.getActivity();
+ activity.setContentView(R.layout.drawer_dynamic_layout);
+ }
+ });
+ }
+
+ /**
+ * Matches views that have parents.
+ */
+ private Matcher<View> hasParent() {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has parent");
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ return view.getParent() != null;
+ }
+ };
+ }
+
+ /**
+ * Inflates the <code>ViewStub</code> with the passed layout resource.
+ */
+ private ViewAction inflateViewStub(final @LayoutRes int layoutResId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isAssignableFrom(ViewStub.class), hasParent());
+ }
+
+ @Override
+ public String getDescription() {
+ return "Inflates view stub";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewStub viewStub = (ViewStub) view;
+ viewStub.setLayoutResource(layoutResId);
+ viewStub.inflate();
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ @Test
+ public void testSingleStartDrawer() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_single_start));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoubleStartDrawers() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ // Note the expected exception in the @Test annotation, as we expect the DrawerLayout
+ // to throw exception during the measure pass as it detects two start drawers.
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_double_start));
+ }
+
+ @Test
+ public void testSingleEndDrawer() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_single_end));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoubleEndDrawers() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ // Note the expected exception in the @Test annotation, as we expect the DrawerLayout
+ // to throw exception during the measure pass as it detects two end drawers.
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_double_end));
+ }
+
+ @Test
+ public void testSingleStartDrawerSingleEndDrawer() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_start_end));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoubleStartDrawersSingleEndDrawer() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ // Note the expected exception in the @Test annotation, as we expect the DrawerLayout
+ // to throw exception during the measure pass as it detects two start drawers.
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_double_start_single_end));
+ }
+
+ @Test(expected=IllegalStateException.class)
+ public void testDoubleEndDrawersSingleStartDrawer() {
+ onView(withId(R.id.drawer_layout)).check(doesNotExist());
+ // Note the expected exception in the @Test annotation, as we expect the DrawerLayout
+ // to throw exception during the measure pass as it detects two start drawers.
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_double_end_single_start));
+ }
+
+ @Test
+ public void testRemoveUnregisteredListener() {
+ onView(withId(R.id.drawer_stub)).perform(
+ inflateViewStub(R.layout.drawer_dynamic_content_single_start));
+
+ // We do this test here and not in DrawerLayoutTest since we want to be sure that the
+ // call to DrawerLayout.removeDrawerLayout() didn't have any calls to addDrawerLayout()
+ // before it. DrawerLayoutTest and its DrawerLayoutActivity register listeners as part
+ // of their initial setup flow.
+ final DrawerLayout startDrawer =
+ (DrawerLayout) mActivityTestRule.getActivity().findViewById(R.id.drawer_layout);
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ startDrawer.removeDrawerListener(mockedListener);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActivity.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActivity.java
new file mode 100644
index 0000000..0dba273
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutActivity.java
@@ -0,0 +1,206 @@
+/*
+ * 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.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.os.Bundle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.Shakespeare;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Test activity for testing various APIs and interactions for DrawerLayout. It follows
+ * a common usage of the DrawerLayout widget combined with Toolbar in the Android support library
+ * that respect the
+ * <a href="https://www.google.com/design/spec/patterns/navigation-drawer.html">Material design
+ * guidelines</a> for the drawer component.
+ */
+public class DrawerLayoutActivity extends BaseTestActivity {
+ private DrawerLayout mDrawerLayout;
+ private ListView mDrawer;
+ private TextView mContent;
+
+ private ActionBarDrawerToggle mDrawerToggle;
+ private Toolbar mToolbar;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.drawer_layout;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ super.onContentViewSet();
+
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mDrawer = (ListView) findViewById(R.id.start_drawer);
+ mContent = (TextView) findViewById(R.id.content_text);
+
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+ // The drawer title must be set in order to announce state changes when
+ // accessibility is turned on. This is typically a simple description,
+ // e.g. "Navigation".
+ mDrawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.drawer_title));
+
+ mDrawer.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+ Shakespeare.TITLES));
+ mDrawer.setOnItemClickListener(new DrawerItemClickListener());
+
+ // Find the toolbar in our layout and set it as the support action bar on the activity.
+ // This is required to have the drawer slide "over" the toolbar.
+ mToolbar = (Toolbar) findViewById(R.id.toolbar);
+ mToolbar.setTitle(R.string.drawer_title);
+ setSupportActionBar(mToolbar);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(false);
+
+ // ActionBarDrawerToggle provides convenient helpers for tying together the
+ // prescribed interactions between a top-level sliding drawer and the action bar.
+ // Note that, as the Javadocs of ActionBarDrawerToggle constructors say, we are
+ // *not* using a constructor that gets a Toolbar since we're setting our toolbar
+ // dynamically at runtime. Furthermore, as the drawer is sliding over the toolbar,
+ // we are suppressing the morphing animation from hamburger to back arrow by
+ // calling super.onDrawerSlide with slideOffset=0.0f. In case your app only has
+ // top-level pages and doesn't need back arrow visuals at all, you can set up
+ // your activity theme to have attribute named "drawerArrowStyle" that points
+ // to an extension of Widget.AppCompat.DrawerArrowToggle that has its "spinBars"
+ // attribute set to false.
+ mDrawerToggle = new ActionBarDrawerToggle(this, mDrawerLayout,
+ R.string.drawer_open, R.string.drawer_close) {
+ @Override
+ public void onDrawerOpened(View drawerView) {
+ super.onDrawerOpened(drawerView);
+ super.onDrawerSlide(drawerView, 0.0f);
+ }
+
+ @Override
+ public void onDrawerSlide(View drawerView, float slideOffset) {
+ super.onDrawerSlide(drawerView, 0.0f);
+ }
+ };
+
+ mDrawerLayout.addDrawerListener(mDrawerToggle);
+
+ // Configure the background color fill of the system status bar (on supported platform
+ // versions) and the toolbar itself. We're using the same color, and android:statusBar
+ // from the theme makes the status bar slightly darker.
+ final int metalBlueColor = getResources().getColor(R.color.drawer_sample_metal_blue);
+ mDrawerLayout.setStatusBarBackgroundColor(metalBlueColor);
+ mToolbar.setBackgroundColor(metalBlueColor);
+
+ // Register a pre-draw listener to get the initial width of the DrawerLayout so
+ // that we can determine the width of the drawer based on the Material spec at
+ // https://www.google.com/design/spec/patterns/navigation-drawer.html#navigation-drawer-specs
+ mDrawerLayout.getViewTreeObserver().addOnPreDrawListener(
+ new ViewTreeObserver.OnPreDrawListener() {
+ @Override
+ public boolean onPreDraw() {
+ // What is the width of the entire DrawerLayout?
+ final int drawerLayoutWidth = mDrawerLayout.getWidth();
+
+ // What is the action bar size?
+ final Resources.Theme theme = mDrawerLayout.getContext().getTheme();
+ final TypedArray a = theme.obtainStyledAttributes(
+ new int[] { android.support.v7.appcompat.R.attr.actionBarSize });
+ final int actionBarSize = a.getDimensionPixelSize(0, 0);
+ if (a != null) {
+ a.recycle();
+ }
+
+ // Compute the width of the drawer and set it on the layout params.
+ final int idealDrawerWidth = 5 * actionBarSize;
+ final int maxDrawerWidth = Math.max(0, drawerLayoutWidth - actionBarSize);
+ final int drawerWidth = Math.min(idealDrawerWidth, maxDrawerWidth);
+
+ final DrawerLayout.LayoutParams drawerLp =
+ (DrawerLayout.LayoutParams) mDrawer.getLayoutParams();
+ drawerLp.width = drawerWidth;
+ mDrawer.setLayoutParams(drawerLp);
+
+ // Remove ourselves as the pre-draw listener since this is a one-time
+ // configuration.
+ mDrawerLayout.getViewTreeObserver().removeOnPreDrawListener(this);
+ return true;
+ }
+ });
+ }
+
+ @Override
+ protected void onPostCreate(Bundle savedInstanceState) {
+ super.onPostCreate(savedInstanceState);
+
+ // Sync the toggle state after onRestoreInstanceState has occurred.
+ mDrawerToggle.syncState();
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ /*
+ * The action bar home/up action should open or close the drawer.
+ * The drawer toggle will take care of this.
+ */
+ if (mDrawerToggle.onOptionsItemSelected(item)) {
+ return true;
+ }
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Is the drawer open?
+ if (mDrawerLayout.isDrawerOpen(mDrawer)) {
+ // Close the drawer and return.
+ mDrawerLayout.closeDrawer(mDrawer);
+ return;
+ }
+
+ super.onBackPressed();
+ }
+
+ @Override
+ public void onConfigurationChanged(Configuration newConfig) {
+ super.onConfigurationChanged(newConfig);
+ mDrawerToggle.onConfigurationChanged(newConfig);
+ }
+
+ /**
+ * This list item click listener implements very simple view switching by changing
+ * the primary content text. The drawer is closed when a selection is made.
+ */
+ private class DrawerItemClickListener implements ListView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mContent.setText(Shakespeare.DIALOGUE[position]);
+ mToolbar.setTitle(Shakespeare.TITLES[position]);
+ mDrawerLayout.closeDrawer(mDrawer);
+ }
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java
new file mode 100644
index 0000000..b2e0abc
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleActivity.java
@@ -0,0 +1,119 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.content.res.Configuration;
+import android.os.Bundle;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.Shakespeare;
+import android.support.v7.widget.Toolbar;
+import android.view.MenuItem;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+import android.widget.TextView;
+
+/**
+ * Test activity for testing various APIs and interactions for DrawerLayout with start and end
+ * drawers.
+ */
+public class DrawerLayoutDoubleActivity extends BaseTestActivity {
+ private DrawerLayout mDrawerLayout;
+ private ListView mStartDrawer;
+ private View mEndDrawer;
+ private TextView mContent;
+
+ private Toolbar mToolbar;
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.drawer_double_layout;
+ }
+
+ @Override
+ protected void onContentViewSet() {
+ super.onContentViewSet();
+
+ mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
+ mStartDrawer = (ListView) findViewById(R.id.start_drawer);
+ mEndDrawer = findViewById(R.id.end_drawer);
+ mContent = (TextView) findViewById(R.id.content_text);
+
+ mDrawerLayout.setDrawerShadow(R.drawable.drawer_shadow, GravityCompat.START);
+
+ // The drawer title must be set in order to announce state changes when
+ // accessibility is turned on. This is typically a simple description,
+ // e.g. "Navigation".
+ mDrawerLayout.setDrawerTitle(GravityCompat.START, getString(R.string.drawer_title));
+
+ mStartDrawer.setAdapter(new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1,
+ Shakespeare.TITLES));
+ mStartDrawer.setOnItemClickListener(new DrawerItemClickListener());
+
+ // Find the toolbar in our layout and set it as the support action bar on the activity.
+ // This is required to have the drawer slide "over" the toolbar.
+ mToolbar = (Toolbar) findViewById(R.id.toolbar);
+ mToolbar.setTitle(R.string.drawer_title);
+ setSupportActionBar(mToolbar);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+ getSupportActionBar().setDisplayShowHomeEnabled(false);
+
+ // Configure the background color fill of the system status bar (on supported platform
+ // versions) and the toolbar itself. We're using the same color, and android:statusBar
+ // from the theme makes the status bar slightly darker.
+ final int metalBlueColor = getResources().getColor(R.color.drawer_sample_metal_blue);
+ mDrawerLayout.setStatusBarBackgroundColor(metalBlueColor);
+ mToolbar.setBackgroundColor(metalBlueColor);
+ }
+
+ @Override
+ public void onBackPressed() {
+ // Is the start drawer open?
+ if (mDrawerLayout.isDrawerOpen(mStartDrawer)) {
+ // Close the drawer and return.
+ mDrawerLayout.closeDrawer(mStartDrawer);
+ return;
+ }
+
+ // Is the end drawer open?
+ if (mDrawerLayout.isDrawerOpen(mEndDrawer)) {
+ // Close the drawer and return.
+ mDrawerLayout.closeDrawer(mEndDrawer);
+ return;
+ }
+
+ super.onBackPressed();
+ }
+
+ /**
+ * This list item click listener implements very simple view switching by changing
+ * the primary content text. The drawer is closed when a selection is made.
+ */
+ private class DrawerItemClickListener implements ListView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
+ mContent.setText(Shakespeare.DIALOGUE[position]);
+ mToolbar.setTitle(Shakespeare.TITLES[position]);
+ mDrawerLayout.closeDrawer(mStartDrawer);
+ }
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java
new file mode 100755
index 0000000..1ca2095
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutDoubleTest.java
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.app;
+
+import android.support.v4.view.ViewCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.custom.CustomDrawerLayout;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v7.testutils.DrawerLayoutActions.closeDrawer;
+import static android.support.v7.testutils.DrawerLayoutActions.openDrawer;
+import static android.support.v7.testutils.DrawerLayoutActions.setDrawerLockMode;
+import static android.support.v7.testutils.TestUtilsActions.setLayoutDirection;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+public class DrawerLayoutDoubleTest
+ extends BaseInstrumentationTestCase<DrawerLayoutDoubleActivity> {
+ private CustomDrawerLayout mDrawerLayout;
+
+ private View mStartDrawer;
+
+ private View mEndDrawer;
+
+ private View mContentView;
+
+ public DrawerLayoutDoubleTest() {
+ super(DrawerLayoutDoubleActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ final DrawerLayoutDoubleActivity activity = mActivityTestRule.getActivity();
+ mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
+ mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
+ mEndDrawer = mDrawerLayout.findViewById(R.id.end_drawer);
+ mContentView = mDrawerLayout.findViewById(R.id.content);
+
+ // Close the drawers to reset the state for the next test
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ @SmallTest
+ public void testQueryOpenStateOfNonExistentDrawer() {
+ // Note that we're expecting the isDrawerOpen API call to result in an exception being
+ // thrown since mContentView is not a drawer.
+ assertFalse("Querying open state of a view that is not a drawer",
+ mDrawerLayout.isDrawerOpen(mContentView));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ @SmallTest
+ public void testQueryVisibleStateOfNonExistentDrawer() {
+ // Note that we're expecting the isDrawerVisible API call to result in an exception being
+ // thrown since mContentView is not a drawer.
+ assertFalse("Querying visible state of a view that is not a drawer",
+ mDrawerLayout.isDrawerVisible(mContentView));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ @SmallTest
+ public void testOpenNonExistentDrawer() {
+ // Note that we're expecting the openDrawer action to result in an exception being
+ // thrown since mContentView is not a drawer.
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(mContentView));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ @SmallTest
+ public void testCloseNonExistentDrawer() {
+ // Note that we're expecting the closeDrawer action to result in an exception being
+ // thrown since mContentView is not a drawer.
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mContentView));
+ }
+
+ @Test(expected=IllegalArgumentException.class)
+ @SmallTest
+ public void testLockNonExistentDrawer() {
+ // Note that we're expecting the setDrawerLockMode action to result in an exception being
+ // thrown since mContentView is not a drawer.
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mContentView));
+ }
+
+ private void verifyDrawerOpenClose() {
+ assertFalse("Start drawer is closed in initial state",
+ mDrawerLayout.isDrawerOpen(mStartDrawer));
+ assertFalse("Start drawer is not visible in initial state",
+ mDrawerLayout.isDrawerVisible(mStartDrawer));
+ assertFalse("End drawer is closed in initial state",
+ mDrawerLayout.isDrawerOpen(mEndDrawer));
+ assertFalse("End drawer is not visible in initial state",
+ mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+ // Open the start drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(mStartDrawer));
+ // And check that it's open (with the end drawer closed)
+ assertTrue("Start drawer is now open", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ assertTrue("Start drawer is now visible", mDrawerLayout.isDrawerVisible(mStartDrawer));
+ assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+ assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+ // Close the start drawer
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+ // And check that both drawers are closed
+ assertFalse("Start drawer is now closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ assertFalse("Start drawer is now not visible", mDrawerLayout.isDrawerVisible(mStartDrawer));
+ assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+ assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+ // Open the end drawer
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(mEndDrawer));
+ // And check that it's open (with the start drawer closed)
+ assertFalse("Start drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ assertFalse("Start drawer is still not visible",
+ mDrawerLayout.isDrawerVisible(mStartDrawer));
+ assertTrue("End drawer is now open", mDrawerLayout.isDrawerOpen(mEndDrawer));
+ assertTrue("End drawer is now visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+
+ // Close the end drawer
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+ // And check that both drawers are closed
+ assertFalse("Start drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ assertFalse("Start drawer is still not visible",
+ mDrawerLayout.isDrawerVisible(mStartDrawer));
+ assertFalse("End drawer is still closed", mDrawerLayout.isDrawerOpen(mEndDrawer));
+ assertFalse("End drawer is still not visible", mDrawerLayout.isDrawerVisible(mEndDrawer));
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerOpenCloseLtr() {
+ onView(withId(R.id.drawer_layout)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_LTR));
+
+ verifyDrawerOpenClose();
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerOpenCloseRtl() {
+ onView(withId(R.id.drawer_layout)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ verifyDrawerOpenClose();
+ }
+
+ private void verifyDrawerLockUnlock() {
+ assertEquals("Start drawer is unlocked in initial state",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ assertEquals("End drawer is unlocked in initial state",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+ // Lock the start drawer open
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mStartDrawer));
+ // And check that it's locked open (with the end drawer unlocked)
+ assertEquals("Start drawer is now locked open",
+ DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ assertEquals("End drawer is still unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+ // Unlock the start drawer and close it
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+ // And check that both drawers are unlocked
+ assertEquals("Start drawer is now unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ assertEquals("End drawer is now unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+ // Lock the end drawer open
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, mEndDrawer));
+ // And check that it's locked open (with the start drawer unlocked)
+ assertEquals("Start drawer is still unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ assertEquals("End drawer is now locked open",
+ DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+
+ // Unlock the end drawer and close it
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mEndDrawer));
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mEndDrawer));
+ // And check that both drawers are unlocked
+ assertEquals("Start drawer is now unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ assertEquals("End drawer is now unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mEndDrawer));
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerLockUnlockLtr() {
+ onView(withId(R.id.drawer_layout)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_LTR));
+
+ verifyDrawerLockUnlock();
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerLockUnlockRtl() {
+ onView(withId(R.id.drawer_layout)).perform(
+ setLayoutDirection(ViewCompat.LAYOUT_DIRECTION_RTL));
+
+ verifyDrawerLockUnlock();
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
new file mode 100755
index 0000000..ff63834
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -0,0 +1,515 @@
+/*
+ * 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.os.Build;
+import android.support.test.espresso.action.GeneralLocation;
+import android.support.test.espresso.action.GeneralSwipeAction;
+import android.support.test.espresso.action.Press;
+import android.support.test.espresso.action.Swipe;
+import android.support.v4.view.GravityCompat;
+import android.support.v4.widget.DrawerLayout;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.custom.CustomDrawerLayout;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.InOrder;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v7.testutils.DrawerLayoutActions.*;
+import static android.support.v7.testutils.TestUtilsMatchers.*;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+
+public class DrawerLayoutTest extends BaseInstrumentationTestCase<DrawerLayoutActivity> {
+ private CustomDrawerLayout mDrawerLayout;
+
+ private View mStartDrawer;
+
+ private View mContentView;
+
+ public DrawerLayoutTest() {
+ super(DrawerLayoutActivity.class);
+ }
+
+ @Before
+ public void setUp() {
+ final DrawerLayoutActivity activity = mActivityTestRule.getActivity();
+ mDrawerLayout = (CustomDrawerLayout) activity.findViewById(R.id.drawer_layout);
+ mStartDrawer = mDrawerLayout.findViewById(R.id.start_drawer);
+ mContentView = mDrawerLayout.findViewById(R.id.content);
+
+ // Close the drawer to reset the state for the next test
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+ }
+
+ // Tests for opening and closing the drawer and checking the open state
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseViaAPI() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseNoAnimationViaAPI() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseWithRedundancyViaAPI() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try opening the drawer when it's already opened
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+ assertTrue("Opened drawer is still opened #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try closing the drawer when it's already closed
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+ assertFalse("Closed drawer is still closed #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseNoAnimationWithRedundancyViaAPI() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try opening the drawer when it's already opened
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+ assertTrue("Opened drawer is still opened #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try closing the drawer when it's already closed
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+ assertFalse("Closed drawer is still closed #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseViaSwipes() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
+ // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
+ // detection of swiping the drawers open in DrawerLayout.
+ // It's critically important to wrap the GeneralSwipeAction to "wait" until the
+ // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
+ // open / close state. This is done in DrawerLayoutActions.wrap method.
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
+ GeneralLocation.CENTER_RIGHT, Press.FINGER)));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
+ GeneralLocation.CENTER_LEFT, Press.FINGER)));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @MediumTest
+ public void testDrawerOpenCloseWithRedundancyViaSwipes() {
+ assertFalse("Initial state", mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
+ // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
+ // detection of swiping the drawers open in DrawerLayout.
+ // It's critically important to wrap the GeneralSwipeAction to "wait" until the
+ // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
+ // open / close state. This is done in DrawerLayoutActions.wrap method.
+ for (int i = 0; i < 5; i++) {
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
+ GeneralLocation.CENTER_RIGHT, Press.FINGER)));
+ assertTrue("Opened drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try opening the drawer when it's already opened
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
+ GeneralLocation.CENTER_RIGHT, Press.FINGER)));
+ assertTrue("Opened drawer is still opened #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
+ GeneralLocation.CENTER_LEFT, Press.FINGER)));
+ assertFalse("Closed drawer #" + i, mDrawerLayout.isDrawerOpen(GravityCompat.START));
+
+ // Try closing the drawer when it's already closed
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
+ GeneralLocation.CENTER_LEFT, Press.FINGER)));
+ assertFalse("Closed drawer is still closed #" + i,
+ mDrawerLayout.isDrawerOpen(GravityCompat.START));
+ }
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerHeight() {
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ final int drawerLayoutHeight = mDrawerLayout.getHeight();
+ final int startDrawerHeight = mStartDrawer.getHeight();
+ final int contentHeight = mContentView.getHeight();
+
+ // On all devices the height of the drawer layout and the drawer should be identical.
+ assertEquals("Drawer layout and drawer heights", drawerLayoutHeight, startDrawerHeight);
+
+ if (Build.VERSION.SDK_INT < 21) {
+ // On pre-L devices the content height should be the same as the drawer layout height.
+ assertEquals("Drawer layout and content heights on pre-L",
+ drawerLayoutHeight, contentHeight);
+ } else {
+ // Our drawer layout is configured with android:fitsSystemWindows="true" which should be
+ // respected on L+ devices to extend the drawer layout into the system status bar.
+ // The start drawer is also configured with the same attribute so it should have the
+ // same height as the drawer layout. The main content does not have that attribute
+ // specified, so it should have its height reduced by the height of the system status
+ // bar.
+
+ // Get the system window top inset that was propagated to the top-level DrawerLayout
+ // during its layout.
+ int drawerTopInset = mDrawerLayout.getSystemWindowInsetTop();
+ assertTrue("Drawer top inset is positive on L+", drawerTopInset > 0);
+ assertEquals("Drawer layout and drawer heights on L+",
+ drawerLayoutHeight - drawerTopInset, contentHeight);
+ }
+ }
+
+ // Tests for listener(s) being notified of various events
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnOpeningViaAPI() {
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // We expect that our listener has been notified that the drawer has been opened
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+ // We expect that our listener has not been notified that the drawer has been closed
+ verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+ // We expect that our listener has been notified at least once on the drawer slide
+ // event. We expect that all such callbacks pass the reference to our drawer as the first
+ // parameter, and we capture the float slide values for further analysis
+ ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
+ verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
+ floatSlideCaptor.capture());
+ // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
+ // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
+ // is called since that depends on the hardware capabilities of the device and the current
+ // load on the CPU / GPU.
+ assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
+ assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
+
+ // We expect that our listener will be called with specific state changes
+ InOrder inOrder = inOrder(mockedListener);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnOpeningNoAnimationViaAPI() {
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+ // We expect that our listener has been notified that the drawer has been opened
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+ // We expect that our listener has not been notified that the drawer has been closed
+ verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+ verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(1f));
+
+ // Request to open the drawer again
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+ // We expect that our listener has not been notified again that the drawer has been opened
+ verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+ // We expect that our listener has not been notified that the drawer has been closed
+ verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnClosingViaAPI() {
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Close the drawer
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START));
+
+ // We expect that our listener has not been notified that the drawer has been opened
+ verify(mockedListener, never()).onDrawerOpened(any(View.class));
+ // We expect that our listener has been notified that the drawer has been closed
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+ // We expect that our listener has been notified at least once on the drawer slide
+ // event. We expect that all such callbacks pass the reference to our drawer as the first
+ // parameter, and we capture the float slide values for further analysis
+ ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
+ verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
+ floatSlideCaptor.capture());
+ // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
+ // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
+ // is called since that depends on the hardware capabilities of the device and the current
+ // load on the CPU / GPU.
+ assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
+ assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
+
+ // We expect that our listener will be called with specific state changes
+ InOrder inOrder = inOrder(mockedListener);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_SETTLING);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnClosingNoAnimationViaAPI() {
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START, false));
+
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Close the drawer
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+
+ // We expect that our listener has not been notified that the drawer has been opened
+ verify(mockedListener, never()).onDrawerOpened(any(View.class));
+ // We expect that our listener has been notified that the drawer has been closed
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+ verify(mockedListener, times(1)).onDrawerSlide(any(View.class), eq(0f));
+
+ // Attempt to close the drawer again.
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(GravityCompat.START, false));
+
+ // We expect that our listener has not been notified that the drawer has been opened
+ verify(mockedListener, never()).onDrawerOpened(any(View.class));
+ // We expect that our listener has not been notified again that the drawer has been closed
+ verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnOpeningViaSwipes() {
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Open the drawer so it becomes visible
+ // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
+ // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
+ // detection of swiping the drawers open in DrawerLayout.
+ // It's critically important to wrap the GeneralSwipeAction to "wait" until the
+ // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
+ // open / close state. This is done in DrawerLayoutActions.wrap method.
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_LEFT,
+ GeneralLocation.CENTER_RIGHT, Press.FINGER)));
+
+ // We expect that our listener has been notified that the drawer has been opened
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerOpened(mStartDrawer);
+ // We expect that our listener has not been notified that the drawer has been closed
+ verify(mockedListener, never()).onDrawerClosed(any(View.class));
+
+ // We expect that our listener has been notified at least once on the drawer slide
+ // event. We expect that all such callbacks pass the reference to our drawer as the first
+ // parameter, and we capture the float slide values for further analysis
+ ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
+ verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
+ floatSlideCaptor.capture());
+ // Now we verify that calls to onDrawerSlide "gave" us an increasing sequence of values
+ // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
+ // is called since that depends on the hardware capabilities of the device and the current
+ // load on the CPU / GPU.
+ assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
+ assertThat(floatSlideCaptor.getAllValues(), inAscendingOrder());
+
+ // We expect that our listener will be called with specific state changes
+ InOrder inOrder = inOrder(mockedListener);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerListenerCallbacksOnClosingViaSwipes() {
+ // Open the drawer so it becomes visible
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(GravityCompat.START));
+
+ // Register a mock listener
+ DrawerLayout.DrawerListener mockedListener = mock(DrawerLayout.DrawerListener.class);
+ mDrawerLayout.addDrawerListener(mockedListener);
+
+ // Close the drawer
+ // Note that we're using GeneralSwipeAction instead of swipeLeft() / swipeRight().
+ // Those Espresso actions use edge fuzzying which doesn't work well with edge-based
+ // detection of swiping the drawers open in DrawerLayout.
+ // It's critically important to wrap the GeneralSwipeAction to "wait" until the
+ // DrawerLayout has settled to STATE_IDLE state before continuing to query the drawer
+ // open / close state. This is done in DrawerLayoutActions.wrap method.
+ onView(withId(R.id.drawer_layout)).perform(
+ wrap(new GeneralSwipeAction(Swipe.FAST, GeneralLocation.CENTER_RIGHT,
+ GeneralLocation.CENTER_LEFT, Press.FINGER)));
+
+ // We expect that our listener has not been notified that the drawer has been opened
+ verify(mockedListener, never()).onDrawerOpened(any(View.class));
+ // We expect that our listener has been notified that the drawer has been closed
+ // with the reference to our drawer
+ verify(mockedListener, times(1)).onDrawerClosed(mStartDrawer);
+
+ // We expect that our listener has been notified at least once on the drawer slide
+ // event. We expect that all such callbacks pass the reference to our drawer as the first
+ // parameter, and we capture the float slide values for further analysis
+ ArgumentCaptor<Float> floatSlideCaptor = ArgumentCaptor.forClass(float.class);
+ verify(mockedListener, atLeastOnce()).onDrawerSlide(eq(mStartDrawer),
+ floatSlideCaptor.capture());
+ // Now we verify that calls to onDrawerSlide "gave" us a decreasing sequence of values
+ // in [0..1] range. Note that we don't have any expectation on how many times onDrawerSlide
+ // is called since that depends on the hardware capabilities of the device and the current
+ // load on the CPU / GPU.
+ assertThat(floatSlideCaptor.getAllValues(), inRange(0.0f, 1.0f));
+ assertThat(floatSlideCaptor.getAllValues(), inDescendingOrder());
+
+ // We expect that our listener will be called with specific state changes
+ InOrder inOrder = inOrder(mockedListener);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_DRAGGING);
+ inOrder.verify(mockedListener).onDrawerStateChanged(DrawerLayout.STATE_IDLE);
+
+ mDrawerLayout.removeDrawerListener(mockedListener);
+ }
+
+ @Test
+ @SmallTest
+ public void testDrawerLockUnlock() {
+ assertEquals("Drawer is unlocked in initial state",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+
+ // Lock the drawer open
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_OPEN, GravityCompat.START));
+ // Check that it's locked open
+ assertEquals("Drawer is now locked open",
+ DrawerLayout.LOCK_MODE_LOCKED_OPEN, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ // and also opened
+ assertTrue("Drawer is also opened", mDrawerLayout.isDrawerOpen(mStartDrawer));
+
+ // Unlock the drawer
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
+ // Check that it's still opened
+ assertTrue("Drawer is still opened", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ // Close the drawer
+ onView(withId(R.id.drawer_layout)).perform(closeDrawer(mStartDrawer));
+ // Check that the drawer is unlocked
+ assertEquals("Start drawer is now unlocked",
+ DrawerLayout.LOCK_MODE_UNLOCKED, mDrawerLayout.getDrawerLockMode(mStartDrawer));
+
+ // Open the drawer and then clock it closed
+ onView(withId(R.id.drawer_layout)).perform(openDrawer(mStartDrawer));
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_LOCKED_CLOSED, GravityCompat.START));
+ // Check that the drawer is locked close
+ assertEquals("Drawer is now locked close",
+ DrawerLayout.LOCK_MODE_LOCKED_CLOSED,
+ mDrawerLayout.getDrawerLockMode(mStartDrawer));
+ // and also closed
+ assertFalse("Drawer is also closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+
+ // Unlock the drawer
+ onView(withId(R.id.drawer_layout)).perform(
+ setDrawerLockMode(DrawerLayout.LOCK_MODE_UNLOCKED, mStartDrawer));
+ // Check that it's still closed
+ assertFalse("Drawer is still closed", mDrawerLayout.isDrawerOpen(mStartDrawer));
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdActivity.java b/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdActivity.java
new file mode 100644
index 0000000..0c9c628
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdActivity.java
@@ -0,0 +1,75 @@
+package android.support.v7.app;
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.os.Bundle;
+import android.support.annotation.Nullable;
+import android.support.v4.app.Fragment;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+
+
+public class FragmentContentIdActivity extends BaseTestActivity {
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ getSupportFragmentManager().beginTransaction()
+ .add(android.R.id.content, new FragmentA())
+ .commit();
+ }
+
+ @Override
+ protected int getContentViewLayoutResId() {
+ // We don't want to set a layout
+ return 0;
+ }
+
+ public void replaceWithFragmentB() {
+ getSupportFragmentManager().beginTransaction()
+ .replace(android.R.id.content, new FragmentB())
+ .commit();
+ }
+
+ public static class FragmentA extends Fragment {
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = new View(getContext());
+ view.setId(R.id.fragment_a);
+ return view;
+ }
+ }
+
+ public static class FragmentB extends Fragment {
+ @Nullable
+ @Override
+ public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
+ @Nullable Bundle savedInstanceState) {
+ View view = new View(getContext());
+ view.setId(R.id.fragment_b);
+ return view;
+ }
+ }
+
+}
+
+
+
diff --git a/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdTest.java b/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdTest.java
new file mode 100755
index 0000000..32a78cf
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/FragmentContentIdTest.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.assertion.ViewAssertions.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import org.junit.Test;
+
+public class FragmentContentIdTest extends BaseInstrumentationTestCase<FragmentContentIdActivity> {
+
+ public FragmentContentIdTest() {
+ super(FragmentContentIdActivity.class);
+ }
+
+ @SmallTest
+ @Test
+ public void testFragmentAddedToAndroidContentIdCanBeRemoved() {
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().replaceWithFragmentB();
+ }
+ });
+
+ // Ensure that fragment_a has been removed from the view hierarchy
+ onView(withId(R.id.fragment_a)).check(doesNotExist());
+ // And that fragment_b is displayed
+ onView(withId(R.id.fragment_b)).check(matches(isDisplayed()));
+ }
+
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestActivity.java b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestActivity.java
new file mode 100644
index 0000000..dd152c6
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestActivity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.view.View;
+
+public class LayoutInflaterFactoryTestActivity extends AppCompatActivity {
+
+ private boolean mDeclarativeOnClickCalled;
+
+ public void declarativeOnClick(View view) {
+ mDeclarativeOnClickCalled = true;
+ }
+
+ public boolean wasDeclarativeOnClickCalled() {
+ return mDeclarativeOnClickCalled;
+ }
+
+}
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..0c098ae 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/LayoutInflaterFactoryTestCase.java
@@ -18,6 +18,9 @@
import org.junit.Test;
+import android.content.Context;
+import android.content.res.Resources;
+import android.graphics.Color;
import android.os.Build;
import android.support.v7.appcompat.test.R;
import android.support.v7.widget.AppCompatAutoCompleteTextView;
@@ -28,88 +31,142 @@
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.util.TypedValue;
import android.view.LayoutInflater;
import android.view.View;
+import android.view.ViewGroup;
-public class LayoutInflaterFactoryTestCase extends BaseInstrumentationTestCase<AppCompatActivity> {
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+public class LayoutInflaterFactoryTestCase
+ extends BaseInstrumentationTestCase<LayoutInflaterFactoryTestActivity> {
public LayoutInflaterFactoryTestCase() {
- super(AppCompatActivity.class);
+ super(LayoutInflaterFactoryTestActivity.class);
}
@Test
+ @SmallTest
public void testAndroidThemeInflation() throws Throwable {
- if (Build.VERSION.SDK_INT < 10) {
- // Ignore this test if running on Gingerbread or below
- return;
- }
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- LayoutInflater inflater = LayoutInflater.from(getActivity());
- View view = inflater.inflate(R.layout.layout_android_theme, null);
- assertTrue("View has themed Context", view.getContext() != getActivity());
+ final LayoutInflater inflater = LayoutInflater.from(getActivity());
+ assertThemedContext(inflater.inflate(R.layout.layout_android_theme, null));
}
});
}
@Test
+ @SmallTest
public void testAppThemeInflation() throws Throwable {
- if (Build.VERSION.SDK_INT < 10) {
- // Ignore this test if running on Gingerbread or below
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final LayoutInflater inflater = LayoutInflater.from(getActivity());
+ assertThemedContext(inflater.inflate(R.layout.layout_app_theme, null));
+ }
+ });
+ }
+
+ @Test
+ @SmallTest
+ public void testAndroidThemeWithChildrenInflation() throws Throwable {
+ if (Build.VERSION.SDK_INT < 11) {
+ // Propagation of themed context to children only works on API 11+. Ignoring test.
return;
}
runTestOnUiThread(new Runnable() {
@Override
public void run() {
LayoutInflater inflater = LayoutInflater.from(getActivity());
- View view = inflater.inflate(R.layout.layout_app_theme, null);
- assertTrue("View has themed Context", view.getContext() != getActivity());
+ final ViewGroup root = (ViewGroup) inflater.inflate(
+ R.layout.layout_android_theme_children, null);
+
+ assertThemedContext(root);
+
+ for (int i = 0; i < root.getChildCount(); i++) {
+ final View child = root.getChildAt(i);
+ assertThemedContext(child);
+ }
}
});
}
@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 testRadioButtonInflationWithVectorButton() throws Throwable {
+ testAppCompatWidgetInflation(R.layout.layout_radiobutton_vector,
+ 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);
}
+ @Test
+ @SmallTest
+ public void testDeclarativeOnClickWithContextWrapper() throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ LayoutInflater inflater = LayoutInflater.from(getActivity());
+ View view = inflater.inflate(R.layout.layout_button_themed_onclick, null);
+
+ assertTrue(view.performClick());
+ assertTrue(getActivity().wasDeclarativeOnClickCalled());
+ }
+ });
+ }
+
private void testAppCompatWidgetInflation(final int layout, final Class<?> expectedClass)
throws Throwable {
runTestOnUiThread(new Runnable() {
@@ -117,9 +174,25 @@
public void run() {
LayoutInflater inflater = LayoutInflater.from(getActivity());
View view = inflater.inflate(layout, null);
- assertEquals("View is " + expectedClass.getSimpleName(), expectedClass,
+ assertSame("View is " + expectedClass.getSimpleName(), expectedClass,
view.getClass());
}
});
}
+
+ private static void assertThemedContext(View view) {
+ final Context viewContext = view.getContext();
+
+ final TypedValue colorAccentValue = getColorAccentValue(viewContext.getTheme());
+ assertTrue(colorAccentValue.type >= TypedValue.TYPE_FIRST_COLOR_INT
+ && colorAccentValue.type <= TypedValue.TYPE_LAST_COLOR_INT);
+ assertEquals("View does not have ContextThemeWrapper context",
+ Color.MAGENTA, colorAccentValue.data);
+ }
+
+ private static TypedValue getColorAccentValue(final Resources.Theme theme) {
+ final TypedValue typedValue = new TypedValue();
+ theme.resolveAttribute(R.attr.colorAccent, typedValue, true);
+ return typedValue;
+ }
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/ToolbarActionBarActivity.java b/v7/appcompat/tests/src/android/support/v7/app/ToolbarActionBarActivity.java
index 1df19c0..4b07a2e 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/ToolbarActionBarActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/ToolbarActionBarActivity.java
@@ -17,6 +17,7 @@
package android.support.v7.app;
import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
import android.support.v7.widget.Toolbar;
public class ToolbarActionBarActivity extends BaseTestActivity {
diff --git a/v7/appcompat/tests/src/android/support/v7/app/WindowDecorActionBarActivity.java b/v7/appcompat/tests/src/android/support/v7/app/WindowDecorActionBarActivity.java
index c7074e9..90366aa 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/WindowDecorActionBarActivity.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/WindowDecorActionBarActivity.java
@@ -17,6 +17,7 @@
package android.support.v7.app;
import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
public class WindowDecorActionBarActivity extends BaseTestActivity {
diff --git a/v7/appcompat/tests/src/android/support/v7/custom/CustomDrawerLayout.java b/v7/appcompat/tests/src/android/support/v7/custom/CustomDrawerLayout.java
new file mode 100644
index 0000000..217ccbc
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/custom/CustomDrawerLayout.java
@@ -0,0 +1,49 @@
+/*
+ * 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.custom;
+
+import android.content.Context;
+import android.os.Build;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.WindowInsets;
+
+import android.support.v4.widget.DrawerLayout;
+
+public class CustomDrawerLayout extends DrawerLayout {
+ private int mSystemWindowInsetTop;
+
+ public CustomDrawerLayout(Context context) {
+ super(context);
+ }
+
+ public CustomDrawerLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public CustomDrawerLayout(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ public WindowInsets dispatchApplyWindowInsets(WindowInsets insets) {
+ mSystemWindowInsetTop = insets.getSystemWindowInsetTop();
+ return super.dispatchApplyWindowInsets(insets);
+ }
+
+ public int getSystemWindowInsetTop() {
+ return mSystemWindowInsetTop;
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.java b/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.java
new file mode 100644
index 0000000..dfad1c8
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/custom/FitWindowsContentLayout.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.custom;
+
+import android.content.Context;
+import android.graphics.Rect;
+import android.util.AttributeSet;
+import android.widget.FrameLayout;
+
+public class FitWindowsContentLayout extends FrameLayout {
+
+ private final Rect mInsets = new Rect();
+ private boolean mFitSystemWindowsCalled = false;
+
+ public FitWindowsContentLayout(Context context) {
+ super(context);
+ }
+
+ public FitWindowsContentLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public FitWindowsContentLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected boolean fitSystemWindows(Rect insets) {
+ mFitSystemWindowsCalled = true;
+ mInsets.set(insets);
+
+ return super.fitSystemWindows(insets);
+ }
+
+ public boolean getFitsSystemWindowsCalled() {
+ return mFitSystemWindowsCalled;
+ }
+
+ public Rect getFitSystemWindowsInsets() {
+ return mInsets;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTintableViewActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTintableViewActions.java
new file mode 100644
index 0000000..764e6db
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/AppCompatTintableViewActions.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.testutils;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.DrawableRes;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.TintableBackgroundView;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.widget.AppCompatTextView;
+import android.view.View;
+import org.hamcrest.Matcher;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+import static org.hamcrest.core.AllOf.allOf;
+
+public class AppCompatTintableViewActions {
+ /**
+ * Sets enabled state on a <code>View</code> that implements the
+ * <code>TintableBackgroundView</code> interface.
+ */
+ public static ViewAction setEnabled(final boolean enabled) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isDisplayingAtLeast(90), TestUtilsMatchers.isTintableBackgroundView());
+ }
+
+ @Override
+ public String getDescription() {
+ return "set enabled";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ view.setEnabled(enabled);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the passed color state list as the background layer on a {@link View} that
+ * implements the {@link TintableBackgroundView} interface.
+ */
+ public static ViewAction setBackgroundTintList(final ColorStateList colorStateList) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isDisplayed(), TestUtilsMatchers.isTintableBackgroundView());
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background tint list";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TintableBackgroundView tintableBackgroundView = (TintableBackgroundView) view;
+ tintableBackgroundView.setSupportBackgroundTintList(colorStateList);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the passed mode as the background tint mode on a <code>View</code> that
+ * implements the <code>TintableBackgroundView</code> interface.
+ */
+ public static ViewAction setBackgroundTintMode(final PorterDuff.Mode tintMode) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isDisplayed(), TestUtilsMatchers.isTintableBackgroundView());
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background tint mode";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TintableBackgroundView tintableBackgroundView = (TintableBackgroundView) view;
+ tintableBackgroundView.setSupportBackgroundTintMode(tintMode);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets background drawable on a <code>View</code> that implements the
+ * <code>TintableBackgroundView</code> interface.
+ */
+ public static ViewAction setBackgroundDrawable(final Drawable background) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(TestUtilsMatchers.isTintableBackgroundView());
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background drawable";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ view.setBackgroundDrawable(background);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets background resource on a <code>View</code> that implements the
+ * <code>TintableBackgroundView</code> interface.
+ */
+ public static ViewAction setBackgroundResource(final @DrawableRes int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(TestUtilsMatchers.isTintableBackgroundView());
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background resource";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ view.setBackgroundResource(resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
new file mode 100644
index 0000000..b885ded
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/BaseTestActivity.java
@@ -0,0 +1,228 @@
+/*
+ * 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.testutils;
+
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v4.view.MenuItemCompat;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.AppCompatCallback;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.view.ActionMode;
+import android.view.KeyEvent;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.WindowManager;
+
+public abstract class BaseTestActivity extends AppCompatActivity {
+
+ private Menu mMenu;
+
+ private KeyEvent mOnKeyDownEvent;
+ private KeyEvent mOnKeyUpEvent;
+ private KeyEvent mOnKeyShortcutEvent;
+
+ private MenuItem mOptionsItemSelected;
+
+ private boolean mOnMenuOpenedCalled;
+ private boolean mOnPanelClosedCalled;
+
+ private boolean mShouldPopulateOptionsMenu = true;
+
+ private boolean mOnBackPressedCalled;
+ private boolean mDestroyed;
+
+ private AppCompatCallback mAppCompatCallback;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ 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);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+
+ protected abstract int getContentViewLayoutResId();
+
+ protected void onContentViewSet() {
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ mOptionsItemSelected = item;
+ return super.onOptionsItemSelected(item);
+ }
+
+ @Override
+ public boolean onMenuOpened(int featureId, Menu menu) {
+ mOnMenuOpenedCalled = true;
+ return super.onMenuOpened(featureId, menu);
+ }
+
+ @Override
+ public void onPanelClosed(int featureId, Menu menu) {
+ mOnPanelClosedCalled = true;
+ super.onPanelClosed(featureId, menu);
+ }
+
+ @Override
+ public boolean onKeyDown(int keyCode, KeyEvent event) {
+ mOnKeyDownEvent = event;
+ return super.onKeyDown(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyUp(int keyCode, KeyEvent event) {
+ mOnKeyUpEvent = event;
+ return super.onKeyUp(keyCode, event);
+ }
+
+ @Override
+ public boolean onKeyShortcut(int keyCode, KeyEvent event) {
+ mOnKeyShortcutEvent = event;
+ return super.onKeyShortcut(keyCode, event);
+ }
+
+ public KeyEvent getInvokedKeyShortcutEvent() {
+ return mOnKeyShortcutEvent;
+ }
+
+ public boolean wasOnMenuOpenedCalled() {
+ return mOnMenuOpenedCalled;
+ }
+
+ public boolean wasOnPanelClosedCalled() {
+ return mOnPanelClosedCalled;
+ }
+
+ public KeyEvent getInvokedKeyDownEvent() {
+ return mOnKeyDownEvent;
+ }
+
+ public KeyEvent getInvokedKeyUpEvent() {
+ return mOnKeyUpEvent;
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ mMenu = menu;
+ if (mShouldPopulateOptionsMenu) {
+ getMenuInflater().inflate(R.menu.sample_actions, menu);
+ return true;
+ } else {
+ menu.clear();
+ return super.onCreateOptionsMenu(menu);
+ }
+ }
+
+ public boolean expandSearchView() {
+ return MenuItemCompat.expandActionView(mMenu.findItem(R.id.action_search));
+ }
+
+ public boolean collapseSearchView() {
+ return MenuItemCompat.collapseActionView(mMenu.findItem(R.id.action_search));
+ }
+
+ public boolean isSearchViewExpanded() {
+ return MenuItemCompat.isActionViewExpanded(mMenu.findItem(R.id.action_search));
+ }
+
+ public MenuItem getOptionsItemSelected() {
+ return mOptionsItemSelected;
+ }
+
+ public void reset() {
+ mOnKeyUpEvent = null;
+ mOnKeyDownEvent = null;
+ mOnKeyShortcutEvent = null;
+ mOnMenuOpenedCalled = false;
+ mOnPanelClosedCalled = false;
+ mMenu = null;
+ mOptionsItemSelected = null;
+ }
+
+ public void setShouldPopulateOptionsMenu(boolean populate) {
+ mShouldPopulateOptionsMenu = populate;
+ if (mMenu != null) {
+ supportInvalidateOptionsMenu();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ super.onDestroy();
+ mDestroyed = true;
+ }
+
+ @Override
+ public void onBackPressed() {
+ super.onBackPressed();
+ mOnBackPressedCalled = true;
+ }
+
+ public boolean wasOnBackPressedCalled() {
+ return mOnBackPressedCalled;
+ }
+
+ public Menu getMenu() {
+ return mMenu;
+ }
+
+ @Override
+ public boolean isDestroyed() {
+ return mDestroyed;
+ }
+
+ @Override
+ public void onSupportActionModeStarted(@NonNull ActionMode mode) {
+ if (mAppCompatCallback != null) {
+ mAppCompatCallback.onSupportActionModeStarted(mode);
+ }
+ }
+
+ @Override
+ public void onSupportActionModeFinished(@NonNull ActionMode mode) {
+ if (mAppCompatCallback != null) {
+ mAppCompatCallback.onSupportActionModeFinished(mode);
+ }
+ }
+
+ @Nullable
+ @Override
+ public ActionMode onWindowStartingSupportActionMode(@NonNull ActionMode.Callback callback) {
+ if (mAppCompatCallback != null) {
+ return mAppCompatCallback.onWindowStartingSupportActionMode(callback);
+ }
+ return super.onWindowStartingSupportActionMode(callback);
+ }
+
+ public void setAppCompatCallback(AppCompatCallback callback) {
+ mAppCompatCallback = callback;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
new file mode 100755
index 0000000..8e839b9
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
@@ -0,0 +1,347 @@
+/*
+ * 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.testutils;
+
+import static android.support.test.espresso.matcher.ViewMatchers.isAssignableFrom;
+
+import android.support.annotation.Nullable;
+import android.support.test.espresso.Espresso;
+import android.support.test.espresso.IdlingResource;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.widget.DrawerLayout;
+import android.view.View;
+
+import org.hamcrest.Matcher;
+
+public class DrawerLayoutActions {
+ /**
+ * Drawer listener that serves as Espresso's {@link IdlingResource} and notifies the registered
+ * callback when the drawer gets to STATE_IDLE state.
+ */
+ private static class CustomDrawerListener
+ implements DrawerLayout.DrawerListener, IdlingResource {
+ private int mCurrState = DrawerLayout.STATE_IDLE;
+
+ @Nullable private IdlingResource.ResourceCallback mCallback;
+
+ private boolean mNeedsIdle = false;
+
+ @Override
+ public void registerIdleTransitionCallback(ResourceCallback resourceCallback) {
+ mCallback = resourceCallback;
+ }
+
+ @Override
+ public String getName() {
+ return "Drawer listener";
+ }
+
+ @Override
+ public boolean isIdleNow() {
+ if (!mNeedsIdle) {
+ return true;
+ } else {
+ return mCurrState == DrawerLayout.STATE_IDLE;
+ }
+ }
+
+ @Override
+ public void onDrawerClosed(View drawer) {
+ if (mCurrState == DrawerLayout.STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ @Override
+ public void onDrawerOpened(View drawer) {
+ if (mCurrState == DrawerLayout.STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+
+ @Override
+ public void onDrawerSlide(View drawer, float slideOffset) {
+ }
+
+ @Override
+ public void onDrawerStateChanged(int state) {
+ mCurrState = state;
+ if (state == DrawerLayout.STATE_IDLE) {
+ if (mCallback != null) {
+ mCallback.onTransitionToIdle();
+ }
+ }
+ }
+ }
+
+ private abstract static class WrappedViewAction implements ViewAction {
+ }
+
+ public static ViewAction wrap(final ViewAction baseAction) {
+ if (baseAction instanceof WrappedViewAction) {
+ throw new IllegalArgumentException("Don't wrap and already wrapped action");
+ }
+
+ return new WrappedViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return baseAction.getConstraints();
+ }
+
+ @Override
+ public String getDescription() {
+ return baseAction.getDescription();
+ }
+
+ @Override
+ public final void perform(UiController uiController, View view) {
+ final DrawerLayout drawer = (DrawerLayout) view;
+ // Add a custom tracker listener
+ final CustomDrawerListener customListener = new CustomDrawerListener();
+ drawer.addDrawerListener(customListener);
+
+ // Note that we're running the following block in a try-finally construct. This
+ // is needed since some of the wrapped actions are going to throw (expected)
+ // exceptions. If that happens, we still need to clean up after ourselves to
+ // leave the system (Espesso) in a good state.
+ try {
+ // Register our listener as idling resource so that Espresso waits until the
+ // wrapped action results in the drawer getting to the STATE_IDLE state
+ Espresso.registerIdlingResources(customListener);
+ baseAction.perform(uiController, view);
+ customListener.mNeedsIdle = true;
+ uiController.loopMainThreadUntilIdle();
+ customListener.mNeedsIdle = false;
+ } finally {
+ // Unregister our idling resource
+ Espresso.unregisterIdlingResources(customListener);
+ // And remove our tracker listener from DrawerLayout
+ drawer.removeDrawerListener(customListener);
+ }
+ }
+ };
+ }
+
+ /**
+ * Opens the drawer at the specified edge gravity.
+ */
+ public static ViewAction openDrawer(final int drawerEdgeGravity, final boolean animate) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Opens the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.openDrawer(drawerEdgeGravity, animate);
+ }
+ });
+ }
+
+ /**
+ * Opens the drawer at the specified edge gravity.
+ */
+ public static ViewAction openDrawer(final int drawerEdgeGravity) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Opens the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.openDrawer(drawerEdgeGravity);
+ }
+ });
+ }
+
+ /**
+ * Opens the drawer.
+ */
+ public static ViewAction openDrawer(final View drawerView) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Opens the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.openDrawer(drawerView);
+ }
+ });
+ }
+
+ /**
+ * Closes the drawer at the specified edge gravity.
+ */
+ public static ViewAction closeDrawer(final int drawerEdgeGravity) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Closes the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.closeDrawer(drawerEdgeGravity);
+ }
+ });
+ }
+
+ /**
+ * Closes the drawer at the specified edge gravity.
+ */
+ public static ViewAction closeDrawer(final int drawerEdgeGravity, final boolean animate) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Closes the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.closeDrawer(drawerEdgeGravity, animate);
+ }
+ });
+ }
+
+ /**
+ * Closes the drawer.
+ */
+ public static ViewAction closeDrawer(final View drawerView) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Closes the drawer";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.closeDrawer(drawerView);
+ }
+ });
+ }
+
+ /**
+ * Sets the lock mode for the drawer at the specified edge gravity.
+ */
+ public static ViewAction setDrawerLockMode(final int lockMode, final int drawerEdgeGravity) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets drawer lock mode";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.setDrawerLockMode(lockMode, drawerEdgeGravity);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+
+ /**
+ * Sets the lock mode for the drawer.
+ */
+ public static ViewAction setDrawerLockMode(final int lockMode, final View drawerView) {
+ return wrap(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isAssignableFrom(DrawerLayout.class);
+ }
+
+ @Override
+ public String getDescription() {
+ return "Sets drawer lock mode";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ DrawerLayout drawerLayout = (DrawerLayout) view;
+ drawerLayout.setDrawerLockMode(lockMode, drawerView);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ });
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/Shakespeare.java b/v7/appcompat/tests/src/android/support/v7/testutils/Shakespeare.java
new file mode 100644
index 0000000..78d5252
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/Shakespeare.java
@@ -0,0 +1,239 @@
+/*
+ * 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.testutils;
+
+public final class Shakespeare {
+ /**
+ * Our data, part 1.
+ */
+ public static final String[] TITLES =
+ {
+ "Henry IV (1)",
+ "Henry V",
+ "Henry VIII",
+ "Richard II",
+ "Richard III",
+ "Merchant of Venice",
+ "Othello",
+ "King Lear"
+ };
+
+ /**
+ * Our data, part 2.
+ */
+ public static final String[] DIALOGUE =
+ {
+ "So shaken as we are, so wan with care," +
+ "Find we a time for frighted peace to pant," +
+ "And breathe short-winded accents of new broils" +
+ "To be commenced in strands afar remote." +
+ "No more the thirsty entrance of this soil" +
+ "Shall daub her lips with her own children's blood;" +
+ "Nor more shall trenching war channel her fields," +
+ "Nor bruise her flowerets with the armed hoofs" +
+ "Of hostile paces: those opposed eyes," +
+ "Which, like the meteors of a troubled heaven," +
+ "All of one nature, of one substance bred," +
+ "Did lately meet in the intestine shock" +
+ "And furious close of civil butchery" +
+ "Shall now, in mutual well-beseeming ranks," +
+ "March all one way and be no more opposed" +
+ "Against acquaintance, kindred and allies:" +
+ "The edge of war, like an ill-sheathed knife," +
+ "No more shall cut his master. Therefore, friends," +
+ "As far as to the sepulchre of Christ," +
+ "Whose soldier now, under whose blessed cross" +
+ "We are impressed and engaged to fight," +
+ "Forthwith a power of English shall we levy;" +
+ "Whose arms were moulded in their mothers' womb" +
+ "To chase these pagans in those holy fields" +
+ "Over whose acres walk'd those blessed feet" +
+ "Which fourteen hundred years ago were nail'd" +
+ "For our advantage on the bitter cross." +
+ "But this our purpose now is twelve month old," +
+ "And bootless 'tis to tell you we will go:" +
+ "Therefore we meet not now. Then let me hear" +
+ "Of you, my gentle cousin Westmoreland," +
+ "What yesternight our council did decree" +
+ "In forwarding this dear expedience.",
+
+ "Hear him but reason in divinity," +
+ "And all-admiring with an inward wish" +
+ "You would desire the king were made a prelate:" +
+ "Hear him debate of commonwealth affairs," +
+ "You would say it hath been all in all his study:" +
+ "List his discourse of war, and you shall hear" +
+ "A fearful battle render'd you in music:" +
+ "Turn him to any cause of policy," +
+ "The Gordian knot of it he will unloose," +
+ "Familiar as his garter: that, when he speaks," +
+ "The air, a charter'd libertine, is still," +
+ "And the mute wonder lurketh in men's ears," +
+ "To steal his sweet and honey'd sentences;" +
+ "So that the art and practic part of life" +
+ "Must be the mistress to this theoric:" +
+ "Which is a wonder how his grace should glean it," +
+ "Since his addiction was to courses vain," +
+ "His companies unletter'd, rude and shallow," +
+ "His hours fill'd up with riots, banquets, sports," +
+ "And never noted in him any study," +
+ "Any retirement, any sequestration" +
+ "From open haunts and popularity.",
+
+ "I come no more to make you laugh: things now," +
+ "That bear a weighty and a serious brow," +
+ "Sad, high, and working, full of state and woe," +
+ "Such noble scenes as draw the eye to flow," +
+ "We now present. Those that can pity, here" +
+ "May, if they think it well, let fall a tear;" +
+ "The subject will deserve it. Such as give" +
+ "Their money out of hope they may believe," +
+ "May here find truth too. Those that come to see" +
+ "Only a show or two, and so agree" +
+ "The play may pass, if they be still and willing," +
+ "I'll undertake may see away their shilling" +
+ "Richly in two short hours. Only they" +
+ "That come to hear a merry bawdy play," +
+ "A noise of targets, or to see a fellow" +
+ "In a long motley coat guarded with yellow," +
+ "Will be deceived; for, gentle hearers, know," +
+ "To rank our chosen truth with such a show" +
+ "As fool and fight is, beside forfeiting" +
+ "Our own brains, and the opinion that we bring," +
+ "To make that only true we now intend," +
+ "Will leave us never an understanding friend." +
+ "Therefore, for goodness' sake, and as you are known" +
+ "The first and happiest hearers of the town," +
+ "Be sad, as we would make ye: think ye see" +
+ "The very persons of our noble story" +
+ "As they were living; think you see them great," +
+ "And follow'd with the general throng and sweat" +
+ "Of thousand friends; then in a moment, see" +
+ "How soon this mightiness meets misery:" +
+ "And, if you can be merry then, I'll say" +
+ "A man may weep upon his wedding-day.",
+
+ "First, heaven be the record to my speech!" +
+ "In the devotion of a subject's love," +
+ "Tendering the precious safety of my prince," +
+ "And free from other misbegotten hate," +
+ "Come I appellant to this princely presence." +
+ "Now, Thomas Mowbray, do I turn to thee," +
+ "And mark my greeting well; for what I speak" +
+ "My body shall make good upon this earth," +
+ "Or my divine soul answer it in heaven." +
+ "Thou art a traitor and a miscreant," +
+ "Too good to be so and too bad to live," +
+ "Since the more fair and crystal is the sky," +
+ "The uglier seem the clouds that in it fly." +
+ "Once more, the more to aggravate the note," +
+ "With a foul traitor's name stuff I thy throat;" +
+ "And wish, so please my sovereign, ere I move," +
+ "What my tongue speaks my right drawn sword may prove.",
+
+ "Now is the winter of our discontent" +
+ "Made glorious summer by this sun of York;" +
+ "And all the clouds that lour'd upon our house" +
+ "In the deep bosom of the ocean buried." +
+ "Now are our brows bound with victorious wreaths;" +
+ "Our bruised arms hung up for monuments;" +
+ "Our stern alarums changed to merry meetings," +
+ "Our dreadful marches to delightful measures." +
+ "Grim-visaged war hath smooth'd his wrinkled front;" +
+ "And now, instead of mounting barded steeds" +
+ "To fright the souls of fearful adversaries," +
+ "He capers nimbly in a lady's chamber" +
+ "To the lascivious pleasing of a lute." +
+ "But I, that am not shaped for sportive tricks," +
+ "Nor made to court an amorous looking-glass;" +
+ "I, that am rudely stamp'd, and want love's majesty" +
+ "To strut before a wanton ambling nymph;" +
+ "I, that am curtail'd of this fair proportion," +
+ "Cheated of feature by dissembling nature," +
+ "Deformed, unfinish'd, sent before my time" +
+ "Into this breathing world, scarce half made up," +
+ "And that so lamely and unfashionable" +
+ "That dogs bark at me as I halt by them;" +
+ "Why, I, in this weak piping time of peace," +
+ "Have no delight to pass away the time," +
+ "Unless to spy my shadow in the sun" +
+ "And descant on mine own deformity:" +
+ "And therefore, since I cannot prove a lover," +
+ "To entertain these fair well-spoken days," +
+ "I am determined to prove a villain" +
+ "And hate the idle pleasures of these days." +
+ "Plots have I laid, inductions dangerous," +
+ "By drunken prophecies, libels and dreams," +
+ "To set my brother Clarence and the king" +
+ "In deadly hate the one against the other:" +
+ "And if King Edward be as true and just" +
+ "As I am subtle, false and treacherous," +
+ "This day should Clarence closely be mew'd up," +
+ "About a prophecy, which says that 'G'" +
+ "Of Edward's heirs the murderer shall be." +
+ "Dive, thoughts, down to my soul: here" +
+ "Clarence comes.",
+
+ "To bait fish withal: if it will feed nothing else," +
+ "it will feed my revenge. He hath disgraced me, and" +
+ "hindered me half a million; laughed at my losses," +
+ "mocked at my gains, scorned my nation, thwarted my" +
+ "bargains, cooled my friends, heated mine" +
+ "enemies; and what's his reason? I am a Jew. Hath" +
+ "not a Jew eyes? hath not a Jew hands, organs," +
+ "dimensions, senses, affections, passions? fed with" +
+ "the same food, hurt with the same weapons, subject" +
+ "to the same diseases, healed by the same means," +
+ "warmed and cooled by the same winter and summer, as" +
+ "a Christian is? If you prick us, do we not bleed?" +
+ "if you tickle us, do we not laugh? if you poison" +
+ "us, do we not die? and if you wrong us, shall we not" +
+ "revenge? If we are like you in the rest, we will" +
+ "resemble you in that. If a Jew wrong a Christian," +
+ "what is his humility? Revenge. If a Christian" +
+ "wrong a Jew, what should his sufferance be by" +
+ "Christian example? Why, revenge. The villany you" +
+ "teach me, I will execute, and it shall go hard but I" +
+ "will better the instruction.",
+
+ "Virtue! a fig! 'tis in ourselves that we are thus" +
+ "or thus. Our bodies are our gardens, to the which" +
+ "our wills are gardeners: so that if we will plant" +
+ "nettles, or sow lettuce, set hyssop and weed up" +
+ "thyme, supply it with one gender of herbs, or" +
+ "distract it with many, either to have it sterile" +
+ "with idleness, or manured with industry, why, the" +
+ "power and corrigible authority of this lies in our" +
+ "wills. If the balance of our lives had not one" +
+ "scale of reason to poise another of sensuality, the" +
+ "blood and baseness of our natures would conduct us" +
+ "to most preposterous conclusions: but we have" +
+ "reason to cool our raging motions, our carnal" +
+ "stings, our unbitted lusts, whereof I take this that" +
+ "you call love to be a sect or scion.",
+
+ "Blow, winds, and crack your cheeks! rage! blow!" +
+ "You cataracts and hurricanoes, spout" +
+ "Till you have drench'd our steeples, drown'd the cocks!" +
+ "You sulphurous and thought-executing fires," +
+ "Vaunt-couriers to oak-cleaving thunderbolts," +
+ "Singe my white head! And thou, all-shaking thunder," +
+ "Smite flat the thick rotundity o' the world!" +
+ "Crack nature's moulds, an germens spill at once," +
+ "That make ingrateful man!"
+ };
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
new file mode 100644
index 0000000..d258e1f
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java
@@ -0,0 +1,195 @@
+/*
+ * 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.testutils;
+
+import android.support.v4.util.Pair;
+import android.view.View;
+import android.view.ViewParent;
+import junit.framework.Assert;
+
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Color;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.support.annotation.ColorInt;
+import android.support.annotation.NonNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public class TestUtils {
+ /**
+ * This method takes a view and returns a single bitmap that is the layered combination
+ * of background drawables of this view and all its ancestors. It can be used to abstract
+ * away the specific implementation of a view hierarchy that is not exposed via class APIs
+ * or a view hierarchy that depends on the platform version. Instead of hard-coded lookups
+ * of particular inner implementations of such a view hierarchy that can break during
+ * refactoring or on newer platform versions, calling this API returns a "combined" background
+ * of the view.
+ *
+ * For example, it is useful to get the combined background of a popup / dropdown without
+ * delving into the inner implementation details of how that popup is implemented on a
+ * particular platform version.
+ */
+ public static Bitmap getCombinedBackgroundBitmap(View view) {
+ final int bitmapWidth = view.getWidth();
+ final int bitmapHeight = view.getHeight();
+
+ // Create a bitmap
+ final Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight,
+ Bitmap.Config.ARGB_8888);
+ // Create a canvas that wraps the bitmap
+ final Canvas canvas = new Canvas(bitmap);
+
+ // As the draw pass starts at the top of view hierarchy, our first step is to traverse
+ // the ancestor hierarchy of our view and collect a list of all ancestors with non-null
+ // and visible backgrounds. At each step we're keeping track of the combined offsets
+ // so that we can properly combine all of the visuals together in the next pass.
+ List<View> ancestorsWithBackgrounds = new ArrayList<>();
+ List<Pair<Integer, Integer>> ancestorOffsets = new ArrayList<>();
+ int offsetX = 0;
+ int offsetY = 0;
+ while (true) {
+ final Drawable backgroundDrawable = view.getBackground();
+ if ((backgroundDrawable != null) && backgroundDrawable.isVisible()) {
+ ancestorsWithBackgrounds.add(view);
+ ancestorOffsets.add(Pair.create(offsetX, offsetY));
+ }
+ // Go to the parent
+ ViewParent parent = view.getParent();
+ if (!(parent instanceof View)) {
+ // We're done traversing the ancestor chain
+ break;
+ }
+
+ // Update the offsets based on the location of current view in its parent's bounds
+ offsetX += view.getLeft();
+ offsetY += view.getTop();
+
+ view = (View) parent;
+ }
+
+ // Now we're going to iterate over the collected ancestors in reverse order (starting from
+ // the topmost ancestor) and draw their backgrounds into our combined bitmap. At each step
+ // we are respecting the offsets of our original view in the coordinate system of the
+ // currently drawn ancestor.
+ final int layerCount = ancestorsWithBackgrounds.size();
+ for (int i = layerCount - 1; i >= 0; i--) {
+ View ancestor = ancestorsWithBackgrounds.get(i);
+ Pair<Integer, Integer> offsets = ancestorOffsets.get(i);
+
+ canvas.translate(offsets.first, offsets.second);
+ ancestor.getBackground().draw(canvas);
+ canvas.translate(-offsets.first, -offsets.second);
+ }
+
+ return bitmap;
+ }
+
+ /**
+ * Checks whether all the pixels in the specified drawable are of the same specified color.
+ *
+ * In case there is a color mismatch, the behavior of this method depends on the
+ * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+ * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+ * <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Drawable drawable,
+ int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color,
+ int allowedComponentVariance, boolean throwExceptionIfFails) {
+ // Create a bitmap
+ Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight,
+ Bitmap.Config.ARGB_8888);
+ // Create a canvas that wraps the bitmap
+ Canvas canvas = new Canvas(bitmap);
+ if (callSetBounds) {
+ // Configure the drawable to have bounds that match the passed size
+ drawable.setBounds(0, 0, drawableWidth, drawableHeight);
+ }
+ // And ask the drawable to draw itself to the canvas / bitmap
+ drawable.draw(canvas);
+
+ try {
+ assertAllPixelsOfColor(failMessagePrefix, bitmap, drawableWidth, drawableHeight, color,
+ allowedComponentVariance, throwExceptionIfFails);
+ } finally {
+ bitmap.recycle();
+ }
+ }
+
+ /**
+ * Checks whether all the pixels in the specified bitmap are of the same specified color.
+ *
+ * In case there is a color mismatch, the behavior of this method depends on the
+ * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will
+ * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call
+ * <code>Assert.fail</code> with detailed description of the mismatch.
+ */
+ public static void assertAllPixelsOfColor(String failMessagePrefix, @NonNull Bitmap bitmap,
+ int bitmapWidth, int bitmapHeight, @ColorInt int color,
+ int allowedComponentVariance, boolean throwExceptionIfFails) {
+ int[] rowPixels = new int[bitmapWidth];
+ for (int row = 0; row < bitmapHeight; row++) {
+ bitmap.getPixels(rowPixels, 0, bitmapWidth, 0, row, bitmapWidth, 1);
+ for (int column = 0; column < bitmapWidth; column++) {
+ int sourceAlpha = Color.alpha(rowPixels[column]);
+ int sourceRed = Color.red(rowPixels[column]);
+ int sourceGreen = Color.green(rowPixels[column]);
+ int sourceBlue = Color.blue(rowPixels[column]);
+
+ int expectedAlpha = Color.alpha(color);
+ int expectedRed = Color.red(color);
+ int expectedGreen = Color.green(color);
+ int expectedBlue = Color.blue(color);
+
+ int varianceAlpha = Math.abs(sourceAlpha - expectedAlpha);
+ int varianceRed = Math.abs(sourceRed - expectedRed);
+ int varianceGreen = Math.abs(sourceGreen - expectedGreen);
+ int varianceBlue = Math.abs(sourceBlue - expectedBlue);
+
+ boolean isColorMatch = (varianceAlpha <= allowedComponentVariance)
+ && (varianceRed <= allowedComponentVariance)
+ && (varianceGreen <= allowedComponentVariance)
+ && (varianceBlue <= allowedComponentVariance);
+
+ if (!isColorMatch) {
+ String mismatchDescription = failMessagePrefix
+ + ": expected all drawable colors to be ["
+ + expectedAlpha + "," + expectedRed + ","
+ + expectedGreen + "," + expectedBlue
+ + "] but at position (" + row + "," + column + ") out of ("
+ + bitmapWidth + "," + bitmapHeight + ") found ["
+ + sourceAlpha + "," + sourceRed + ","
+ + sourceGreen + "," + sourceBlue + "]";
+ if (throwExceptionIfFails) {
+ throw new RuntimeException(mismatchDescription);
+ } else {
+ Assert.fail(mismatchDescription);
+ }
+ }
+ }
+ }
+ }
+
+ public static void waitForActivityDestroyed(BaseTestActivity activity) {
+ while (!activity.isDestroyed()) {
+ SystemClock.sleep(30);
+ }
+ }
+}
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
new file mode 100644
index 0000000..5dab9d1
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsActions.java
@@ -0,0 +1,163 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.testutils;
+
+import android.content.res.ColorStateList;
+import android.graphics.PorterDuff;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v4.view.ViewCompat;
+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.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayingAtLeast;
+import static org.hamcrest.core.AllOf.allOf;
+
+public class TestUtilsActions {
+ /**
+ * Sets layout direction on the view.
+ */
+ public static ViewAction setLayoutDirection(final int layoutDirection) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set layout direction";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setLayoutDirection(view, layoutDirection);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets text appearance on {@code TextView}.
+ */
+ public static ViewAction setTextAppearance(final int resId) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return allOf(isDisplayingAtLeast(90), isAssignableFrom(TextView.class));
+ }
+
+ @Override
+ public String getDescription() {
+ return "TextView set text appearance";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ TextView textView = (TextView) view;
+ textView.setTextAppearance(textView.getContext(), resId);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the passed color state list as the background layer on a {@link View} with
+ * {@link ViewCompat#setBackgroundTintList(View, ColorStateList)} API.
+ */
+ public static ViewAction setBackgroundTintListViewCompat(final ColorStateList colorStateList) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background tint list";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setBackgroundTintList(view, colorStateList);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ /**
+ * Sets the passed mode as the background tint mode on a {@link View} with
+ * {@link ViewCompat#setBackgroundTintMode(View, PorterDuff.Mode)} API.
+ */
+ public static ViewAction setBackgroundTintModeViewCompat(final PorterDuff.Mode tintMode) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set background tint mode";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ ViewCompat.setBackgroundTintMode(view, tintMode);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+
+ public static ViewAction setEnabled(final boolean enabled) {
+ return new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "set enabled";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ uiController.loopMainThreadUntilIdle();
+
+ view.setEnabled(enabled);
+
+ uiController.loopMainThreadUntilIdle();
+ }
+ };
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
new file mode 100644
index 0000000..2c20aa8
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java
@@ -0,0 +1,367 @@
+/*
+ * 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.testutils;
+
+import android.database.sqlite.SQLiteCursor;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.test.espresso.matcher.BoundedMatcher;
+import android.support.v4.view.TintableBackgroundView;
+import android.text.TextUtils;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.CheckedTextView;
+import android.widget.ImageView;
+import junit.framework.Assert;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.TypeSafeMatcher;
+
+import java.util.List;
+
+public class TestUtilsMatchers {
+ /**
+ * Returns a matcher that matches <code>ImageView</code>s which have drawable flat-filled
+ * with the specific color.
+ */
+ public static Matcher drawable(@ColorInt final int color) {
+ return new BoundedMatcher<View, ImageView>(ImageView.class) {
+ private String failedComparisonDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("with drawable of color: ");
+
+ description.appendText(failedComparisonDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final ImageView view) {
+ Drawable drawable = view.getDrawable();
+ if (drawable == null) {
+ return false;
+ }
+
+ // One option is to check if we have a ColorDrawable and then call getColor
+ // but that API is v11+. Instead, we call our helper method that checks whether
+ // all pixels in a Drawable are of the same specified color.
+ try {
+ TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
+ view.getHeight(), true, color, 0, true);
+ // If we are here, the color comparison has passed.
+ failedComparisonDescription = null;
+ return true;
+ } catch (Throwable t) {
+ // If we are here, the color comparison has failed.
+ failedComparisonDescription = t.getMessage();
+ return false;
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches <code>View</code>s which have background flat-filled
+ * with the specific color.
+ */
+ public static Matcher isBackground(@ColorInt final int color) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedComparisonDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("with background of color: ");
+
+ description.appendText(failedComparisonDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final View view) {
+ Drawable drawable = view.getBackground();
+ if (drawable == null) {
+ return false;
+ }
+
+ try {
+ TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(),
+ view.getHeight(), false, color, 0, true);
+ // If we are here, the color comparison has passed.
+ failedComparisonDescription = null;
+ return true;
+ } catch (Throwable t) {
+ // If we are here, the color comparison has failed.
+ failedComparisonDescription = t.getMessage();
+ return false;
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches <code>View</code>s whose combined background starting
+ * from the view and up its ancestor chain matches the specified color.
+ */
+ public static Matcher isCombinedBackground(@ColorInt final int color) {
+ return new BoundedMatcher<View, View>(View.class) {
+ private String failedComparisonDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("with ascendant background of color: ");
+
+ description.appendText(failedComparisonDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ // Create a bitmap with combined backgrounds of the view and its ancestors.
+ Bitmap combinedBackgroundBitmap = TestUtils.getCombinedBackgroundBitmap(view);
+ try {
+ TestUtils.assertAllPixelsOfColor("", combinedBackgroundBitmap,
+ combinedBackgroundBitmap.getWidth(),
+ combinedBackgroundBitmap.getHeight(), color, 0, true);
+ // If we are here, the color comparison has passed.
+ failedComparisonDescription = null;
+ return true;
+ } catch (Throwable t) {
+ failedComparisonDescription = t.getMessage();
+ return false;
+ } finally {
+ combinedBackgroundBitmap.recycle();
+ }
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
+ */
+ public static Matcher isCheckedTextView() {
+ return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
+ private String failedDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("checked text view: ");
+
+ description.appendText(failedDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final CheckedTextView view) {
+ if (view.isChecked()) {
+ return true;
+ }
+
+ failedDescription = "not checked";
+ return false;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches <code>CheckedTextView</code>s which are in checked state.
+ */
+ public static Matcher isNonCheckedTextView() {
+ return new BoundedMatcher<View, CheckedTextView>(CheckedTextView.class) {
+ private String failedDescription;
+
+ @Override
+ public void describeTo(final Description description) {
+ description.appendText("non checked text view: ");
+
+ description.appendText(failedDescription);
+ }
+
+ @Override
+ public boolean matchesSafely(final CheckedTextView view) {
+ if (!view.isChecked()) {
+ return true;
+ }
+
+ failedDescription = "checked";
+ return false;
+ }
+ };
+ }
+
+ /**
+ * 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)));
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches Views which implement TintableBackgroundView.
+ */
+ public static Matcher<View> isTintableBackgroundView() {
+ return new TypeSafeMatcher<View>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("is TintableBackgroundView");
+ }
+
+ @Override
+ public boolean matchesSafely(View view) {
+ return TintableBackgroundView.class.isAssignableFrom(view.getClass());
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches lists of float values that fall into the specified range.
+ */
+ public static Matcher<List<Float>> inRange(final float from, final float to) {
+ return new TypeSafeMatcher<List<Float>>() {
+ private String mFailedDescription;
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(mFailedDescription);
+ }
+
+ @Override
+ protected boolean matchesSafely(List<Float> item) {
+ int itemCount = item.size();
+
+ for (int i = 0; i < itemCount; i++) {
+ float curr = item.get(i);
+
+ if ((curr < from) || (curr > to)) {
+ mFailedDescription = "Value #" + i + ":" + curr + " should be between " +
+ from + " and " + to;
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches lists of float values that are in ascending order.
+ */
+ public static Matcher<List<Float>> inAscendingOrder() {
+ return new TypeSafeMatcher<List<Float>>() {
+ private String mFailedDescription;
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(mFailedDescription);
+ }
+
+ @Override
+ protected boolean matchesSafely(List<Float> item) {
+ int itemCount = item.size();
+
+ if (itemCount >= 2) {
+ for (int i = 0; i < itemCount - 1; i++) {
+ float curr = item.get(i);
+ float next = item.get(i + 1);
+
+ if (curr > next) {
+ mFailedDescription = "Values should increase between #" + i +
+ ":" + curr + " and #" + (i + 1) + ":" + next;
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+
+ /**
+ * Returns a matcher that matches lists of float values that are in descending order.
+ */
+ public static Matcher<List<Float>> inDescendingOrder() {
+ return new TypeSafeMatcher<List<Float>>() {
+ private String mFailedDescription;
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(mFailedDescription);
+ }
+
+ @Override
+ protected boolean matchesSafely(List<Float> item) {
+ int itemCount = item.size();
+
+ if (itemCount >= 2) {
+ for (int i = 0; i < itemCount - 1; i++) {
+ float curr = item.get(i);
+ float next = item.get(i + 1);
+
+ if (curr < next) {
+ mFailedDescription = "Values should decrease between #" + i +
+ ":" + curr + " and #" + (i + 1) + ":" + next;
+ return false;
+ }
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+
+
+ /**
+ * Returns a matcher that matches {@link View}s based on the given child type.
+ *
+ * @param childMatcher the type of the child to match on
+ */
+ public static Matcher<ViewGroup> hasChild(final Matcher<View> childMatcher) {
+ return new TypeSafeMatcher<ViewGroup>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has child: ");
+ childMatcher.describeTo(description);
+ }
+
+ @Override
+ public boolean matchesSafely(ViewGroup view) {
+ final int childCount = view.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ if (childMatcher.matches(view.getChildAt(i))) {
+ return true;
+ }
+ }
+ return false;
+ }
+ };
+ }
+
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
new file mode 100644
index 0000000..315c12d
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java
@@ -0,0 +1,630 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.res.ColorStateList;
+import android.content.res.Resources;
+import android.graphics.PorterDuff;
+import android.graphics.drawable.Drawable;
+import android.support.annotation.ColorInt;
+import android.support.annotation.IdRes;
+import android.support.annotation.NonNull;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v4.graphics.ColorUtils;
+import android.support.v7.app.BaseInstrumentationTestCase;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.AppCompatTintableViewActions;
+import android.support.v7.testutils.BaseTestActivity;
+import android.support.v7.testutils.TestUtils;
+import android.support.v7.testutils.TestUtilsActions;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+import android.view.ViewGroup;
+import org.junit.Before;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static org.junit.Assert.assertNull;
+
+/**
+ * Base class for testing custom view extensions in appcompat-v7 that implement the
+ * <code>TintableBackgroundView</code> interface. Extensions of this class run all tests
+ * from here and add test cases specific to the functionality they add to the relevant
+ * base view class (such as <code>AppCompatTextView</code>'s all-caps support).
+ */
+public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extends View>
+ extends BaseInstrumentationTestCase<A> {
+ protected ViewGroup mContainer;
+
+ protected Resources mResources;
+
+ public AppCompatBaseViewTest(Class clazz) {
+ super(clazz);
+ }
+
+ @Before
+ public void setUp() {
+ final A activity = mActivityTestRule.getActivity();
+ mContainer = (ViewGroup) activity.findViewById(R.id.container);
+ mResources = activity.getResources();
+ }
+
+ /**
+ * Subclasses should override this method to return true if by default the matching
+ * view (such as, say, {@link AppCompatSpinner}) has background set it.
+ */
+ protected boolean hasBackgroundByDefault() {
+ return false;
+ }
+
+ private void verifyBackgroundIsColoredAs(String description, @NonNull View view,
+ @ColorInt int color, int allowedComponentVariance) {
+ Drawable background = view.getBackground();
+ TestUtils.assertAllPixelsOfColor(description,
+ background, view.getWidth(), view.getHeight(), true,
+ color, allowedComponentVariance, false);
+ }
+
+ /**
+ * This method tests that background tinting is not applied when the
+ * tintable view has no background.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingWithNoBackground() {
+ if (hasBackgroundByDefault()) {
+ return;
+ }
+
+ final @IdRes int viewId = R.id.view_tinted_no_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ // Note that all the asserts in this test check that the view background
+ // is null. This is because the matching child in the activity doesn't define any
+ // background on itself, and there is nothing to tint.
+
+ assertNull("No background after XML loading", view.getBackground());
+
+ // Disable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ assertNull("No background after disabling", view.getBackground());
+
+ // Enable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ assertNull("No background after re-enabling", view.getBackground());
+
+ // Load a new color state list, set it on the view and check that the background
+ // is still null.
+ final ColorStateList sandColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_sand, null);
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintList(sandColor));
+
+ // Disable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ assertNull("No background after disabling", view.getBackground());
+
+ // Enable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ assertNull("No background after re-enabling", view.getBackground());
+ }
+
+ /**
+ * This method tests that background tinting is not applied when the
+ * tintable view has no background.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingViewCompatWithNoBackground() {
+ if (hasBackgroundByDefault()) {
+ return;
+ }
+
+ final @IdRes int viewId = R.id.view_tinted_no_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ // Note that all the asserts in this test check that the view background
+ // is null. This is because the matching child in the activity doesn't define any
+ // background on itself, and there is nothing to tint.
+
+ assertNull("No background after XML loading", view.getBackground());
+
+ // Disable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ assertNull("No background after disabling", view.getBackground());
+
+ // Enable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ assertNull("No background after re-enabling", view.getBackground());
+
+ // Load a new color state list, set it on the view and check that the background
+ // is still null.
+ final ColorStateList lilacColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_lilac, null);
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintListViewCompat(lilacColor));
+
+ // Disable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ assertNull("No background after disabling", view.getBackground());
+
+ // Enable the view and check that the background is still null.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ assertNull("No background after re-enabling", view.getBackground());
+ }
+
+ /**
+ * This method tests that background tinting is applied to tintable view
+ * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
+ * background tint lists on the same background.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingAcrossStateChange() {
+ final @IdRes int viewId = R.id.view_tinted_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int lilacDefault = ResourcesCompat.getColor(
+ mResources, R.color.lilac_default, null);
+ final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
+ mResources, R.color.lilac_disabled, null);
+ final @ColorInt int sandDefault = ResourcesCompat.getColor(
+ mResources, R.color.sand_default, null);
+ final @ColorInt int sandDisabled = ResourcesCompat.getColor(
+ mResources, R.color.sand_disabled, null);
+ final @ColorInt int oceanDefault = ResourcesCompat.getColor(
+ mResources, R.color.ocean_default, null);
+ final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
+ mResources, R.color.ocean_disabled, null);
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
+ lilacDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
+ lilacDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
+ lilacDefault, 0);
+
+ // Load a new color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList sandColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_sand, null);
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintList(sandColor));
+ verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
+ sandDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
+ sandDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
+ sandDefault, 0);
+
+ // Load another color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_ocean, null);
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintList(oceanColor));
+ verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
+ oceanDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
+ oceanDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
+ oceanDefault, 0);
+ }
+
+ /**
+ * This method tests that background tinting is applied to tintable view
+ * in enabled and disabled state across a number of <code>ColorStateList</code>s set as
+ * background tint lists on the same background.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingViewCompatAcrossStateChange() {
+ final @IdRes int viewId = R.id.view_tinted_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int lilacDefault = ResourcesCompat.getColor(
+ mResources, R.color.lilac_default, null);
+ final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
+ mResources, R.color.lilac_disabled, null);
+ final @ColorInt int sandDefault = ResourcesCompat.getColor(
+ mResources, R.color.sand_default, null);
+ final @ColorInt int sandDisabled = ResourcesCompat.getColor(
+ mResources, R.color.sand_disabled, null);
+ final @ColorInt int oceanDefault = ResourcesCompat.getColor(
+ mResources, R.color.ocean_default, null);
+ final @ColorInt int oceanDisabled = ResourcesCompat.getColor(
+ mResources, R.color.ocean_disabled, null);
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default lilac tinting in enabled state", view,
+ lilacDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view,
+ lilacDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view,
+ lilacDefault, 0);
+
+ // Load a new color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList sandColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_sand, null);
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintListViewCompat(sandColor));
+ verifyBackgroundIsColoredAs("New sand tinting in enabled state", view,
+ sandDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New sand tinting in disabled state", view,
+ sandDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view,
+ sandDefault, 0);
+
+ // Load another color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList oceanColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_ocean, null);
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintListViewCompat(oceanColor));
+ verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view,
+ oceanDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view,
+ oceanDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view,
+ oceanDefault, 0);
+ }
+
+ /**
+ * This method tests that background tinting applied to tintable view
+ * in enabled and disabled state across the same background respects the currently set
+ * background tinting mode.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingAcrossModeChange() {
+ final @IdRes int viewId = R.id.view_untinted_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_default, null);
+ final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_disabled, null);
+ // This is the fill color of R.drawable.test_background_green set on our view
+ // that we'll be testing in this method
+ final @ColorInt int backgroundColor = ResourcesCompat.getColor(
+ mResources, R.color.test_green, null);
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
+ backgroundColor, 0);
+
+ // From this point on in this method we're allowing a margin of error in checking the
+ // color of the view background. This is due to both translucent colors being used
+ // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
+ // translucent color on top of solid fill color. This is where the allowed variance
+ // value of 2 comes from - one for compositing and one for color translucency.
+ final int allowedComponentVariance = 2;
+
+ // Set src_in tint mode on our view
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN));
+
+ // Load a new color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_emerald_translucent, null);
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
+ verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
+ emeraldDefault, allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
+ emeraldDisabled, allowedComponentVariance);
+
+ // Set src_over tint mode on our view. As the currently set tint list is using
+ // translucent colors, we expect the actual background of the view to be different under
+ // this new mode (unlike src_in and src_over that behave identically when the destination is
+ // a fully filled rectangle and the source is an opaque color).
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
+ ColorUtils.compositeColors(emeraldDefault, backgroundColor),
+ allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
+ view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
+ allowedComponentVariance);
+ }
+
+ /**
+ * This method tests that background tinting applied to tintable view
+ * in enabled and disabled state across the same background respects the currently set
+ * background tinting mode.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTintingViewCompatAcrossModeChange() {
+ final @IdRes int viewId = R.id.view_untinted_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_default, null);
+ final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_disabled, null);
+ // This is the fill color of R.drawable.test_background_green set on our view
+ // that we'll be testing in this method
+ final @ColorInt int backgroundColor = ResourcesCompat.getColor(
+ mResources, R.color.test_green, null);
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default no tinting in enabled state", view,
+ backgroundColor, 0);
+
+ // From this point on in this method we're allowing a margin of error in checking the
+ // color of the view background. This is due to both translucent colors being used
+ // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
+ // translucent color on top of solid fill color. This is where the allowed variance
+ // value of 2 comes from - one for compositing and one for color translucency.
+ final int allowedComponentVariance = 2;
+
+ // Set src_in tint mode on our view
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN));
+
+ // Load a new color state list, set it on the view and check that the background has
+ // switched to the matching entry in newly set color state list.
+ final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_emerald_translucent, null);
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintListViewCompat(emeraldColor));
+ verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view,
+ emeraldDefault, allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view,
+ emeraldDisabled, allowedComponentVariance);
+
+ // Set src_over tint mode on our view. As the currently set tint list is using
+ // translucent colors, we expect the actual background of the view to be different under
+ // this new mode (unlike src_in and src_over that behave identically when the destination is
+ // a fully filled rectangle and the source is an opaque color).
+ onView(withId(viewId)).perform(
+ TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER));
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view,
+ ColorUtils.compositeColors(emeraldDefault, backgroundColor),
+ allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the newly set color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over",
+ view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor),
+ allowedComponentVariance);
+ }
+
+ /**
+ * This method tests that opaque background tinting applied to tintable view
+ * is applied correctly after changing the background itself of the view.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundOpaqueTintingAcrossBackgroundChange() {
+ final @IdRes int viewId = R.id.view_tinted_no_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int lilacDefault = ResourcesCompat.getColor(
+ mResources, R.color.lilac_default, null);
+ final @ColorInt int lilacDisabled = ResourcesCompat.getColor(
+ mResources, R.color.lilac_disabled, null);
+
+ if (!hasBackgroundByDefault()) {
+ assertNull("No background after XML loading", view.getBackground());
+ }
+
+ // Set background on our view
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
+ ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background",
+ view, lilacDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on green background",
+ view, lilacDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green background",
+ view, lilacDefault, 0);
+
+ // Set a different background on our view based on resource ID
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
+ R.drawable.test_background_red));
+
+ // Test the default state for tinting set up in the layout XML file.
+ verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on red background",
+ view, lilacDefault, 0);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on red background",
+ view, lilacDisabled, 0);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red background",
+ view, lilacDefault, 0);
+ }
+
+ /**
+ * This method tests that translucent background tinting applied to tintable view
+ * is applied correctly after changing the background itself of the view.
+ */
+ @Test
+ @SmallTest
+ public void testBackgroundTranslucentTintingAcrossBackgroundChange() {
+ final @IdRes int viewId = R.id.view_untinted_no_background;
+ final T view = (T) mContainer.findViewById(viewId);
+
+ final @ColorInt int emeraldDefault = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_default, null);
+ final @ColorInt int emeraldDisabled = ResourcesCompat.getColor(
+ mResources, R.color.emerald_translucent_disabled, null);
+ // This is the fill color of R.drawable.test_background_green set on our view
+ // that we'll be testing in this method
+ final @ColorInt int backgroundColorGreen = ResourcesCompat.getColor(
+ mResources, R.color.test_green, null);
+ final @ColorInt int backgroundColorRed = ResourcesCompat.getColor(
+ mResources, R.color.test_red, null);
+
+ if (!hasBackgroundByDefault()) {
+ assertNull("No background after XML loading", view.getBackground());
+ }
+
+ // Set src_over tint mode on our view. As the currently set tint list is using
+ // translucent colors, we expect the actual background of the view to be different under
+ // this new mode (unlike src_in and src_over that behave identically when the destination is
+ // a fully filled rectangle and the source is an opaque color).
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER));
+ // Load and set a translucent color state list as the background tint list
+ final ColorStateList emeraldColor = ResourcesCompat.getColorStateList(
+ mResources, R.color.color_state_list_emerald_translucent, null);
+ onView(withId(viewId)).perform(
+ AppCompatTintableViewActions.setBackgroundTintList(emeraldColor));
+
+ // Set background on our view
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable(
+ ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null)));
+
+ // From this point on in this method we're allowing a margin of error in checking the
+ // color of the view background. This is due to both translucent colors being used
+ // in the color state list and off-by-one discrepancies of SRC_OVER when it's compositing
+ // translucent color on top of solid fill color. This is where the allowed variance
+ // value of 2 comes from - one for compositing and one for color translucency.
+ final int allowedComponentVariance = 2;
+
+ // Test the default state for tinting set up with the just loaded tint list.
+ verifyBackgroundIsColoredAs("Emerald tinting in enabled state on green background",
+ view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
+ allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Emerald tinting in disabled state on green background",
+ view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorGreen),
+ allowedComponentVariance);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in the default color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green background",
+ view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen),
+ allowedComponentVariance);
+
+ // Set a different background on our view based on resource ID
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundResource(
+ R.drawable.test_background_red));
+
+ // Test the default state for tinting the new background with the same color state list
+ verifyBackgroundIsColoredAs("Emerald tinting in enabled state on red background",
+ view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
+ allowedComponentVariance);
+
+ // Disable the view and check that the background has switched to the matching entry
+ // in our current color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false));
+ verifyBackgroundIsColoredAs("Emerald tinting in disabled state on red background",
+ view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorRed),
+ allowedComponentVariance);
+
+ // Enable the view and check that the background has switched to the matching entry
+ // in our current color state list.
+ onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true));
+ verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red background",
+ view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed),
+ allowedComponentVariance);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java
new file mode 100644
index 0000000..0ede862
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatButtonActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.appcompat_button_activity;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
new file mode 100644
index 0000000..46925ce
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtilsActions;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.junit.Test;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
+import static org.junit.Assert.assertEquals;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatButton} class.
+ */
+@SmallTest
+public class AppCompatButtonTest
+ extends AppCompatBaseViewTest<AppCompatButtonActivity, AppCompatButton> {
+ public AppCompatButtonTest() {
+ super(AppCompatButtonActivity.class);
+ }
+
+ @Override
+ protected boolean hasBackgroundByDefault() {
+ // Button has default background set on it
+ return true;
+ }
+
+ @Test
+ public void testAllCaps() {
+ final String text1 = mResources.getString(R.string.sample_text1);
+ final String text2 = mResources.getString(R.string.sample_text2);
+
+ final AppCompatButton button1 =
+ (AppCompatButton) mContainer.findViewById(R.id.button_caps1);
+ final AppCompatButton button2 =
+ (AppCompatButton) mContainer.findViewById(R.id.button_caps2);
+
+ // Note that Button.getText() returns the original text. We are interested in
+ // the transformed text that is set on the Layout object used to draw the final
+ // (transformed) content.
+ assertEquals("Button starts in all caps on", text1.toUpperCase(),
+ button1.getLayout().getText());
+ assertEquals("Button starts in all caps off", text2,
+ button2.getLayout().getText());
+
+ // Toggle all-caps mode on the two buttons. Note that as with the core Button,
+ // setting a style with textAllCaps=false on a AppCompatButton with all-caps on
+ // will have no effect.
+ onView(withId(R.id.button_caps1)).perform(setTextAppearance(R.style.TextStyleAllCapsOff));
+ onView(withId(R.id.button_caps2)).perform(setTextAppearance(R.style.TextStyleAllCapsOn));
+
+ assertEquals("Button is still in all caps on", text1.toUpperCase(),
+ button1.getLayout().getText());
+ assertEquals("Button is in all caps on", text2.toUpperCase(),
+ button2.getLayout().getText());
+ }
+
+ @Test
+ public void testAppCompatAllCapsFalseOnButton() {
+ final String text = mResources.getString(R.string.sample_text2);
+ final AppCompatButton button =
+ (AppCompatButton) mContainer.findViewById(R.id.button_app_allcaps_false);
+
+ assertEquals("Button is not in all caps", text, button.getLayout().getText());
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java
new file mode 100644
index 0000000..f41f7a3
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatImageViewActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.appcompat_imageview_activity;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
new file mode 100644
index 0000000..ce12349
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatImageViewTest.java
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import org.junit.Test;
+
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.Drawable;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtils;
+import android.widget.ImageView;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatImageView} class.
+ */
+public class AppCompatImageViewTest
+ extends AppCompatBaseViewTest<AppCompatImageViewActivity, AppCompatImageView> {
+ public AppCompatImageViewTest() {
+ super(AppCompatImageViewActivity.class);
+ }
+
+ @Test
+ public void testImageViewBothSrcCompatAndroidSrcSet() {
+ final int expectedColor = mContainer.getResources().getColor(R.color.test_blue);
+
+ final ImageView view = (ImageView) mContainer.findViewById(R.id.view_android_src_srccompat);
+ final Drawable drawable = view.getDrawable();
+
+ TestUtils.assertAllPixelsOfColor("ImageView drawable should be blue",
+ drawable, view.getWidth(), view.getHeight(), true,
+ expectedColor, 0, false);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java
new file mode 100644
index 0000000..c994f9c
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatSpinnerActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.appcompat_spinner_activity;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
new file mode 100644
index 0000000..1d9ea9f
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatSpinnerTest.java
@@ -0,0 +1,120 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.res.Resources;
+import android.support.annotation.ColorInt;
+import android.support.annotation.ColorRes;
+import android.support.annotation.IdRes;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import org.hamcrest.Matcher;
+import org.junit.Test;
+
+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.isPlatformPopup;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static android.support.v7.testutils.TestUtilsMatchers.hasChild;
+import static android.support.v7.testutils.TestUtilsMatchers.isCombinedBackground;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatSpinner} class.
+ */
+@SmallTest
+public class AppCompatSpinnerTest
+ extends AppCompatBaseViewTest<AppCompatSpinnerActivity, AppCompatSpinner> {
+ public AppCompatSpinnerTest() {
+ super(AppCompatSpinnerActivity.class);
+ }
+
+ @Override
+ protected boolean hasBackgroundByDefault() {
+ // Spinner has default background set on it
+ return true;
+ }
+
+ /**
+ * Helper method that verifies that the popup for the specified {@link AppCompatSpinner}
+ * is themed with the specified color.
+ */
+ private void verifySpinnerPopupTheming(@IdRes int spinnerId,
+ @ColorRes int expectedPopupColorResId, boolean matchDropDownListView) {
+ final Resources res = mActivityTestRule.getActivity().getResources();
+ final @ColorInt int expectedPopupColor =
+ ResourcesCompat.getColor(res, expectedPopupColorResId, null);
+ final AppCompatSpinner spinner = (AppCompatSpinner) mContainer.findViewById(spinnerId);
+
+ // Click the spinner to show its popup content
+ onView(withId(spinnerId)).perform(click());
+
+ // The internal implementation details of the AppCompatSpinner's popup content depends
+ // on the platform version itself (in android.widget.PopupWindow) as well as on when the
+ // popup theme is being applied first (in XML or at runtime). Instead of trying to catch
+ // all possible variations of how the popup content is wrapped, we use a view matcher that
+ // creates a single bitmap that combines backgrounds starting from the parent of the
+ // popup content items upwards (drawing them in reverse order), and then tests that the
+ // combined bitmap matches the expected color fill. This should remove dependency on the
+ // internal implementation details on which exact "chrome" part of the popup has the
+ // matching background.
+ String itemText = (String) spinner.getAdapter().getItem(2);
+ Matcher popupContentMatcher = hasChild(withText(itemText));
+ onView(popupContentMatcher).inRoot(isPlatformPopup()).check(
+ matches(isCombinedBackground(expectedPopupColor)));
+
+ // Click an entry in the popup to dismiss it
+ onView(withText(itemText)).perform(click());
+ }
+
+ @Test
+ public void testPopupThemingFromXmlAttribute() {
+ verifySpinnerPopupTheming(R.id.view_magenta_themed_popup, R.color.test_magenta, true);
+ }
+
+ @Test
+ public void testUnthemedPopupRuntimeTheming() {
+ final AppCompatSpinner spinner =
+ (AppCompatSpinner) mContainer.findViewById(R.id.view_unthemed_popup);
+ spinner.setPopupBackgroundResource(R.drawable.test_background_blue);
+ verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_blue, false);
+
+ // Set a different popup background
+ spinner.setPopupBackgroundDrawable(ContextCompat.getDrawable(
+ mActivityTestRule.getActivity(), R.drawable.test_background_green));
+ verifySpinnerPopupTheming(R.id.view_unthemed_popup, R.color.test_green, false);
+ }
+
+ @Test
+ public void testThemedPopupRuntimeTheming() {
+ final AppCompatSpinner spinner =
+ (AppCompatSpinner) mContainer.findViewById(R.id.view_ocean_themed_popup);
+ verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.ocean_default, true);
+
+ // Now set a different popup background
+ spinner.setPopupBackgroundResource(R.drawable.test_background_red);
+ verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.test_red, false);
+
+ // Set a different popup background
+ spinner.setPopupBackgroundDrawable(ContextCompat.getDrawable(
+ mActivityTestRule.getActivity(), R.drawable.test_background_blue));
+ verifySpinnerPopupTheming(R.id.view_ocean_themed_popup, R.color.test_blue, false);
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewActivity.java
new file mode 100644
index 0000000..db5dc0d
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewActivity.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+public class AppCompatTextViewActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.appcompat_textview_activity;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
new file mode 100644
index 0000000..0b19ac7
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatTextViewTest.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import static android.support.test.espresso.Espresso.onView;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+
+import static android.support.v7.testutils.TestUtilsActions.setEnabled;
+import static android.support.v7.testutils.TestUtilsActions.setTextAppearance;
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Color;
+import android.support.test.espresso.action.ViewActions;
+import android.support.v4.content.ContextCompat;
+import android.support.v4.content.res.ResourcesCompat;
+import android.support.v7.appcompat.test.R;
+import android.support.v7.testutils.TestUtilsActions;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.widget.TextView;
+
+import org.junit.Test;
+
+/**
+ * In addition to all tinting-related tests done by the base class, this class provides
+ * tests specific to {@link AppCompatTextView} class.
+ */
+@SmallTest
+public class AppCompatTextViewTest
+ extends AppCompatBaseViewTest<AppCompatTextViewActivity, AppCompatTextView> {
+ public AppCompatTextViewTest() {
+ super(AppCompatTextViewActivity.class);
+ }
+
+ @Test
+ public void testAllCaps() {
+ final String text1 = mResources.getString(R.string.sample_text1);
+ final String text2 = mResources.getString(R.string.sample_text2);
+
+ final AppCompatTextView textView1 =
+ (AppCompatTextView) mContainer.findViewById(R.id.text_view_caps1);
+ final AppCompatTextView textView2 =
+ (AppCompatTextView) mContainer.findViewById(R.id.text_view_caps2);
+
+ // Note that TextView.getText() returns the original text. We are interested in
+ // the transformed text that is set on the Layout object used to draw the final
+ // (transformed) content.
+ assertEquals("Text view starts in all caps on", text1.toUpperCase(),
+ textView1.getLayout().getText());
+ assertEquals("Text view starts in all caps off", text2,
+ textView2.getLayout().getText());
+
+ // Toggle all-caps mode on the two text views
+ onView(withId(R.id.text_view_caps1)).perform(
+ setTextAppearance(R.style.TextStyleAllCapsOff));
+ assertEquals("Text view is still in all caps on", text1,
+ textView1.getLayout().getText());
+
+ onView(withId(R.id.text_view_caps2)).perform(
+ setTextAppearance(R.style.TextStyleAllCapsOn));
+ assertEquals("Text view is in all caps on", text2.toUpperCase(),
+ textView2.getLayout().getText());
+ }
+
+ @Test
+ public void testAppCompatAllCapsFalseOnButton() {
+ final String text = mResources.getString(R.string.sample_text2);
+ final AppCompatTextView textView =
+ (AppCompatTextView) mContainer.findViewById(R.id.text_view_app_allcaps_false);
+
+ assertEquals("Text view is not in all caps", text, textView.getLayout().getText());
+ }
+
+ @Test
+ public void testTextColorSetHex() {
+ final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_hex);
+ assertEquals(Color.RED, textView.getCurrentTextColor());
+ }
+
+ @Test
+ public void testTextColorSetColorStateList() {
+ final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_csl);
+
+ onView(withId(R.id.view_text_color_csl)).perform(setEnabled(true));
+ assertEquals(ContextCompat.getColor(textView.getContext(), R.color.ocean_default),
+ textView.getCurrentTextColor());
+
+ onView(withId(R.id.view_text_color_csl)).perform(setEnabled(false));
+ assertEquals(ContextCompat.getColor(textView.getContext(), R.color.ocean_disabled),
+ textView.getCurrentTextColor());
+ }
+
+ @Test
+ public void testTextColorSetThemeAttrHex() {
+ final TextView textView = (TextView) mContainer.findViewById(R.id.view_text_color_primary);
+ assertEquals(Color.BLUE, textView.getCurrentTextColor());
+ }
+
+ @Test
+ public void testTextColorSetThemeAttrColorStateList() {
+ final TextView textView = (TextView)
+ mContainer.findViewById(R.id.view_text_color_secondary);
+
+ onView(withId(R.id.view_text_color_secondary)).perform(setEnabled(true));
+ assertEquals(ContextCompat.getColor(textView.getContext(), R.color.sand_default),
+ textView.getCurrentTextColor());
+
+ onView(withId(R.id.view_text_color_secondary)).perform(setEnabled(false));
+ assertEquals(ContextCompat.getColor(textView.getContext(), R.color.sand_disabled),
+ textView.getCurrentTextColor());
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
new file mode 100644
index 0000000..6a5a58a
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -0,0 +1,525 @@
+/*
+ * 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.app.Instrumentation;
+import android.content.Context;
+import android.graphics.Rect;
+import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.v7.app.BaseInstrumentationTestCase;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.LayoutInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Button;
+import android.widget.FrameLayout;
+import android.widget.PopupWindow;
+import android.widget.TextView;
+
+import org.junit.Before;
+import org.junit.Test;
+
+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.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.*;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+public class ListPopupWindowTest extends BaseInstrumentationTestCase<PopupTestActivity> {
+ private FrameLayout mContainer;
+
+ private Button mButton;
+
+ private ListPopupWindow mListPopupWindow;
+
+ private BaseAdapter mListPopupAdapter;
+
+ private AdapterView.OnItemClickListener mItemClickListener;
+
+ /**
+ * Item click listener that dismisses our <code>ListPopupWindow</code> when any item
+ * is clicked. Note that this needs to be a separate class that is also protected (not
+ * private) so that Mockito can "spy" on it.
+ */
+ protected class PopupItemClickListener implements AdapterView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ mListPopupWindow.dismiss();
+ }
+ }
+
+ public ListPopupWindowTest() {
+ super(PopupTestActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final PopupTestActivity activity = mActivityTestRule.getActivity();
+ mContainer = (FrameLayout) activity.findViewById(R.id.container);
+ mButton = (Button) mContainer.findViewById(R.id.test_button);
+ mItemClickListener = new PopupItemClickListener();
+ }
+
+ @Test
+ @SmallTest
+ public void testBasicContent() {
+ Builder popupBuilder = new Builder();
+ popupBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertNotNull("Popup window created", mListPopupWindow);
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+
+ final View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
+ onView(withText("Alice"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText("Bob"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText("Charlie"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText("Deirdre"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText("El"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .check(matches(isDisplayed()));
+ }
+
+ @Test
+ @SmallTest
+ public void testAnchoring() {
+ Builder popupBuilder = new Builder();
+ popupBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+ assertEquals("Popup window anchor", mButton, mListPopupWindow.getAnchorView());
+
+ final int[] anchorOnScreenXY = new int[2];
+ final int[] popupOnScreenXY = new int[2];
+ final int[] popupInWindowXY = new int[2];
+ final Rect rect = new Rect();
+
+ mListPopupWindow.getListView().getLocationOnScreen(popupOnScreenXY);
+ mButton.getLocationOnScreen(anchorOnScreenXY);
+ mListPopupWindow.getListView().getLocationInWindow(popupInWindowXY);
+ mListPopupWindow.getBackground().getPadding(rect);
+
+ assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0], popupOnScreenXY[0]);
+ assertEquals("Anchoring Y", anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight(),
+ popupOnScreenXY[1] + rect.top);
+ }
+
+ @Test
+ @SmallTest
+ public void testDismissalViaAPI() {
+ Builder popupBuilder = new Builder().withDismissListener();
+ popupBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mListPopupWindow.dismiss();
+ }
+ });
+
+ // Verify that our dismiss listener has been called
+ verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
+ assertFalse("Popup window not showing after dismissal", mListPopupWindow.isShowing());
+ }
+
+ private void testDismissalViaTouch(boolean setupAsModal) throws Throwable {
+ Builder popupBuilder = new Builder().setModal(setupAsModal).withDismissListener();
+ popupBuilder.wireToActionButton();
+
+ // Also register a click listener on the top-level container
+ View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
+ mContainer.setOnClickListener(mockContainerClickListener);
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+ // Make sure that the modality of the popup window is set up correctly
+ assertEquals("Popup window modality", setupAsModal, mListPopupWindow.isModal());
+
+ // Determine the location of the popup on the screen so that we can emulate
+ // a tap outside of its bounds to dismiss it
+ final int[] popupOnScreenXY = new int[2];
+ final Rect rect = new Rect();
+ mListPopupWindow.getListView().getLocationOnScreen(popupOnScreenXY);
+ mListPopupWindow.getBackground().getPadding(rect);
+
+ int emulatedTapX = popupOnScreenXY[0] - rect.left - 20;
+ int emulatedTapY = popupOnScreenXY[1] + mListPopupWindow.getListView().getHeight() +
+ rect.top + rect.bottom + 20;
+
+ // The logic below uses Instrumentation to emulate a tap outside the bounds of the
+ // displayed list popup window. This tap is then treated by the framework to be "split" as
+ // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
+ // view root if the popup is not modal.
+ // It is not correct to emulate these two sequences separately in the test, as it
+ // wouldn't emulate the user-facing interaction for this test. Note that usage
+ // of Instrumentation is necessary here since Espresso's actions operate at the level
+ // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
+ // that would require emulation of two separate sequences as well.
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Inject DOWN event
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent eventDown = MotionEvent.obtain(
+ downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventDown);
+
+ // Inject MOVE event
+ long moveTime = SystemClock.uptimeMillis();
+ MotionEvent eventMove = MotionEvent.obtain(
+ moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventMove);
+
+ // Inject UP event
+ long upTime = SystemClock.uptimeMillis();
+ MotionEvent eventUp = MotionEvent.obtain(
+ upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventUp);
+
+ // Wait for the system to process all events in the queue
+ instrumentation.waitForIdleSync();
+
+ // At this point our popup should not be showing and should have notified its
+ // dismiss listener
+ verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
+ assertFalse("Popup window not showing after outside click", mListPopupWindow.isShowing());
+
+ // Also test that the click outside the popup bounds has been "delivered" to the main
+ // container only if the popup is not modal
+ verify(mockContainerClickListener, times(setupAsModal ? 0 : 1)).onClick(mContainer);
+ }
+
+ @Test
+ @SmallTest
+ public void testDismissalOutsideNonModal() throws Throwable {
+ testDismissalViaTouch(false);
+ }
+
+ @Test
+ @SmallTest
+ public void testDismissalOutsideModal() throws Throwable {
+ testDismissalViaTouch(true);
+ }
+
+ @Test
+ @SmallTest
+ public void testItemClickViaEvent() {
+ Builder popupBuilder = new Builder().withItemClickListener();
+ popupBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(popupBuilder.mOnItemClickListener, never()).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
+
+ final View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
+ onView(withText("Charlie"))
+ .inRoot(withDecorView(not(is(mainDecorView))))
+ .perform(click());
+ // Verify that out menu item click listener has been called with the expected item
+ // position. Note that we use any() for other parameters, as we don't want to tie ourselves
+ // to the specific implementation details of how ListPopupWindow displays its content.
+ verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+ any(AdapterView.class), any(View.class), eq(2), any(int.class));
+
+ // Our item click listener also dismisses the popup
+ assertFalse("Popup window not showing after click", mListPopupWindow.isShowing());
+ }
+
+ @Test
+ @SmallTest
+ public void testItemClickViaAPI() {
+ Builder popupBuilder = new Builder().withItemClickListener();
+ popupBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertTrue("Popup window showing", mListPopupWindow.isShowing());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(popupBuilder.mOnItemClickListener, never()).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mListPopupWindow.performItemClick(1);
+ }
+ });
+
+ // Verify that out menu item click listener has been called with the expected item
+ // position. Note that we use any() for other parameters, as we don't want to tie ourselves
+ // to the specific implementation details of how ListPopupWindow displays its content.
+ verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+ any(AdapterView.class), any(View.class), eq(1), any(int.class));
+ // Our item click listener also dismisses the popup
+ assertFalse("Popup window not showing after click", mListPopupWindow.isShowing());
+ }
+
+ /**
+ * Emulates a drag-down gestures by injecting ACTION events with {@link Instrumentation}.
+ */
+ private void emulateDragDownGesture(int emulatedX, int emulatedStartY, int swipeAmount) {
+ // The logic below uses Instrumentation to emulate a swipe / drag gesture to bring up
+ // the popup content. Note that we don't want to use Espresso's GeneralSwipeAction
+ // as that operates on the level of an individual view. Here we want to test correct
+ // forwarding of events that cross the boundary between the anchor and the popup menu.
+
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Inject DOWN event
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent eventDown = MotionEvent.obtain(
+ downTime, downTime, MotionEvent.ACTION_DOWN, emulatedX, emulatedStartY, 1);
+ instrumentation.sendPointerSync(eventDown);
+
+ // Inject a sequence of MOVE events that emulate a "swipe down" gesture
+ for (int i = 0; i < 10; i++) {
+ long moveTime = SystemClock.uptimeMillis();
+ final int moveY = emulatedStartY + swipeAmount * i / 10;
+ MotionEvent eventMove = MotionEvent.obtain(
+ moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedX, moveY, 1);
+ instrumentation.sendPointerSync(eventMove);
+ // sleep for a bit to emulate a 200ms swipe
+ SystemClock.sleep(20);
+ }
+
+ // Inject UP event
+ long upTime = SystemClock.uptimeMillis();
+ MotionEvent eventUp = MotionEvent.obtain(
+ upTime, upTime, MotionEvent.ACTION_UP, emulatedX, emulatedStartY + swipeAmount, 1);
+ instrumentation.sendPointerSync(eventUp);
+
+ // Wait for the system to process all events in the queue
+ instrumentation.waitForIdleSync();
+ }
+
+ @Test
+ @MediumTest
+ public void testCreateOnDragListener() throws Throwable {
+ // In this test we want precise control over the height of the popup content since
+ // we need to know by how much to swipe down to end the emulated gesture over the
+ // specific item in the popup. This is why we're using a popup style that removes
+ // all decoration around the popup content, as well as our own row layout with known
+ // height.
+ Builder popupBuilder = new Builder()
+ .withPopupStyleAttr(R.style.PopupEmptyStyle)
+ .withContentRowLayoutId(R.layout.popup_window_item)
+ .withItemClickListener().withDismissListener();
+
+ // Configure ListPopupWindow without showing it
+ popupBuilder.configure();
+
+ // Get the anchor view and configure it with ListPopupWindow's drag-to-open listener
+ final View anchor = mActivityTestRule.getActivity().findViewById(R.id.test_button);
+ View.OnTouchListener dragListener = mListPopupWindow.createDragToOpenListener(anchor);
+ anchor.setOnTouchListener(dragListener);
+ // And also configure it to show the popup window on click
+ anchor.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ mListPopupWindow.show();
+ }
+ });
+
+ // Get the height of a row item in our popup window
+ final int popupRowHeight = mActivityTestRule.getActivity().getResources()
+ .getDimensionPixelSize(R.dimen.popup_row_height);
+
+ final int[] anchorOnScreenXY = new int[2];
+ anchor.getLocationOnScreen(anchorOnScreenXY);
+
+ // Compute the start coordinates of a downward swipe and the amount of swipe. We'll
+ // be swiping by twice the row height. That, combined with the swipe originating in the
+ // center of the anchor should result in clicking the second row in the popup.
+ int emulatedX = anchorOnScreenXY[0] + anchor.getWidth() / 2;
+ int emulatedStartY = anchorOnScreenXY[1] + anchor.getHeight() / 2;
+ int swipeAmount = 2 * popupRowHeight;
+
+ // Emulate drag-down gesture with a sequence of motion events
+ emulateDragDownGesture(emulatedX, emulatedStartY, swipeAmount);
+
+ // We expect the swipe / drag gesture to result in clicking the second item in our list.
+ verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+ any(AdapterView.class), any(View.class), eq(1), eq(1L));
+ // Since our item click listener calls dismiss() on the popup, we expect the popup to not
+ // be showing
+ assertFalse(mListPopupWindow.isShowing());
+ // At this point our popup should have notified its dismiss listener
+ verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
+ }
+
+ /**
+ * Inner helper class to configure an instance of <code>ListPopupWindow</code> for the
+ * specific test. The main reason for its existence is that once a popup window is shown
+ * with the show() method, most of its configuration APIs are no-ops. This means that
+ * we can't add logic that is specific to a certain test (such as dismissing a non-modal
+ * popup window) once it's shown and we have a reference to a displayed ListPopupWindow.
+ */
+ public class Builder {
+ private boolean mIsModal;
+ private boolean mHasDismissListener;
+ private boolean mHasItemClickListener;
+
+ private AdapterView.OnItemClickListener mOnItemClickListener;
+ private PopupWindow.OnDismissListener mOnDismissListener;
+
+ private int mContentRowLayoutId = R.layout.abc_popup_menu_item_layout;
+
+ private boolean mUseCustomPopupStyle;
+ private int mPopupStyleAttr;
+
+ public Builder setModal(boolean isModal) {
+ mIsModal = isModal;
+ return this;
+ }
+
+ public Builder withContentRowLayoutId(int contentRowLayoutId) {
+ mContentRowLayoutId = contentRowLayoutId;
+ return this;
+ }
+
+ public Builder withPopupStyleAttr(int popupStyleAttr) {
+ mUseCustomPopupStyle = true;
+ mPopupStyleAttr = popupStyleAttr;
+ return this;
+ }
+
+ public Builder withItemClickListener() {
+ mHasItemClickListener = true;
+ return this;
+ }
+
+ public Builder withDismissListener() {
+ mHasDismissListener = true;
+ return this;
+ }
+
+ private void configure() {
+ final Context context = mContainer.getContext();
+ if (mUseCustomPopupStyle) {
+ mListPopupWindow = new ListPopupWindow(context, null, mPopupStyleAttr, 0);
+ } else {
+ mListPopupWindow = new ListPopupWindow(context);
+ }
+
+ final String[] POPUP_CONTENT =
+ new String[]{"Alice", "Bob", "Charlie", "Deirdre", "El"};
+ mListPopupAdapter = new BaseAdapter() {
+ class ViewHolder {
+ private TextView title;
+ }
+
+ @Override
+ public int getCount() {
+ return POPUP_CONTENT.length;
+ }
+
+ @Override
+ public Object getItem(int position) {
+ return POPUP_CONTENT[position];
+ }
+
+ @Override
+ public long getItemId(int position) {
+ return position;
+ }
+
+ @Override
+ public View getView(int position, View convertView, ViewGroup parent) {
+ if (convertView == null) {
+ convertView = LayoutInflater.from(parent.getContext()).inflate(
+ mContentRowLayoutId, parent, false);
+ ViewHolder viewHolder = new ViewHolder();
+ viewHolder.title = (TextView) convertView.findViewById(R.id.title);
+ convertView.setTag(viewHolder);
+ }
+
+ ViewHolder viewHolder = (ViewHolder) convertView.getTag();
+ viewHolder.title.setText(POPUP_CONTENT[position]);
+ return convertView;
+ }
+ };
+
+ mListPopupWindow.setAdapter(mListPopupAdapter);
+ mListPopupWindow.setAnchorView(mButton);
+
+ // The following mock listeners have to be set before the call to show() as
+ // they are set on the internally constructed drop down.
+ if (mHasItemClickListener) {
+ // Wrap our item click listener with a Mockito spy
+ mOnItemClickListener = spy(mItemClickListener);
+ // Register that spy as the item click listener on the ListPopupWindow
+ mListPopupWindow.setOnItemClickListener(mOnItemClickListener);
+ // And configure Mockito to call our original listener with onItemClick.
+ // This way we can have both our item click listener running to dismiss the popup
+ // window, and track the invocations of onItemClick with Mockito APIs.
+ doCallRealMethod().when(mOnItemClickListener).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
+ }
+
+ if (mHasDismissListener) {
+ mOnDismissListener = mock(PopupWindow.OnDismissListener.class);
+ mListPopupWindow.setOnDismissListener(mOnDismissListener);
+ }
+
+ mListPopupWindow.setModal(mIsModal);
+ }
+
+ private void show() {
+ configure();
+ mListPopupWindow.show();
+ }
+
+ public void wireToActionButton() {
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ show();
+ }
+ });
+ }
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
new file mode 100644
index 0000000..1fe9633
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
@@ -0,0 +1,587 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.support.test.InstrumentationRegistry;
+import org.hamcrest.Description;
+import org.hamcrest.Matcher;
+import org.hamcrest.Matchers;
+import org.hamcrest.TypeSafeMatcher;
+import org.junit.Before;
+import org.junit.Test;
+
+import android.app.Instrumentation;
+import android.content.res.Resources;
+import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
+import android.os.SystemClock;
+import android.support.test.espresso.Root;
+import android.support.test.espresso.UiController;
+import android.support.test.espresso.ViewAction;
+import android.support.v7.app.BaseInstrumentationTestCase;
+import android.support.v7.appcompat.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.MenuInflater;
+import android.view.MenuItem;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewParent;
+import android.widget.Button;
+import android.widget.FrameLayout;
+
+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.doesNotExist;
+import static android.support.test.espresso.assertion.ViewAssertions.matches;
+import static android.support.test.espresso.matcher.RootMatchers.isPlatformPopup;
+import static android.support.test.espresso.matcher.RootMatchers.withDecorView;
+import static android.support.test.espresso.matcher.ViewMatchers.isDisplayed;
+import static android.support.test.espresso.matcher.ViewMatchers.withClassName;
+import static android.support.test.espresso.matcher.ViewMatchers.withId;
+import static android.support.test.espresso.matcher.ViewMatchers.withText;
+import static org.hamcrest.Matchers.allOf;
+import static org.hamcrest.core.Is.is;
+import static org.hamcrest.core.IsNot.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
+
+public class PopupMenuTest extends BaseInstrumentationTestCase<PopupTestActivity> {
+ // Since PopupMenu doesn't expose any access to the underlying
+ // implementation (like ListPopupWindow.getListView), we're relying on the
+ // class name of the list view from MenuPopupWindow that is being used
+ // in PopupMenu. This is not the cleanest, but it's not making any assumptions
+ // on the platform-specific details of the popup windows.
+ private static final String DROP_DOWN_CLASS_NAME =
+ "android.support.v7.widget.MenuPopupWindow$MenuDropDownListView";
+ private FrameLayout mContainer;
+
+ private Button mButton;
+
+ private PopupMenu mPopupMenu;
+
+ private Resources mResources;
+
+ private View mMainDecorView;
+
+ public PopupMenuTest() {
+ super(PopupTestActivity.class);
+ }
+
+ @Before
+ public void setUp() throws Exception {
+ final PopupTestActivity activity = mActivityTestRule.getActivity();
+ mContainer = (FrameLayout) activity.findViewById(R.id.container);
+ mButton = (Button) mContainer.findViewById(R.id.test_button);
+ mResources = mActivityTestRule.getActivity().getResources();
+ mMainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
+ }
+
+ @Test
+ @SmallTest
+ public void testBasicContent() {
+ final Builder menuBuilder = new Builder();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+ assertNotNull("Popup menu created", mPopupMenu);
+ // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+ // Use a custom matcher to check the visibility of the drop down list view instead.
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(isPlatformPopup()).check(matches(isDisplayed()));
+
+ // Note that MenuItem.isVisible() refers to the current "data" visibility state
+ // and not the "on screen" visibility state. This is why we're testing the display
+ // visibility of our main and sub menu items.
+
+ onView(withText(mResources.getString(R.string.popup_menu_highlight)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_edit)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_delete)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_ignore)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_share)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_print)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(matches(isDisplayed()));
+
+ // Share submenu items should not be visible
+ onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(doesNotExist());
+ onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .check(doesNotExist());
+ }
+
+ /**
+ * Returns the location of our popup menu in its window.
+ */
+ private int[] getPopupLocationInWindow() {
+ final int[] location = new int[2];
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(isPlatformPopup()).perform(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Popup matcher";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ view.getLocationInWindow(location);
+ }
+ });
+ return location;
+ }
+
+ /**
+ * Returns the location of our popup menu on the screen.
+ */
+ private int[] getPopupLocationOnScreen() {
+ final int[] location = new int[2];
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(isPlatformPopup()).perform(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Popup matcher";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ view.getLocationOnScreen(location);
+ }
+ });
+ return location;
+ }
+
+ /**
+ * Returns the combined padding around the content of our popup menu.
+ */
+ private Rect getPopupPadding() {
+ final Rect result = new Rect();
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(isPlatformPopup()).perform(new ViewAction() {
+ @Override
+ public Matcher<View> getConstraints() {
+ return isDisplayed();
+ }
+
+ @Override
+ public String getDescription() {
+ return "Popup matcher";
+ }
+
+ @Override
+ public void perform(UiController uiController, View view) {
+ // Traverse the parent hierarchy and combine all their paddings
+ result.setEmpty();
+ final Rect current = new Rect();
+ while (true) {
+ ViewParent parent = view.getParent();
+ if (parent == null || !(parent instanceof View)) {
+ return;
+ }
+
+ view = (View) parent;
+ Drawable currentBackground = view.getBackground();
+ if (currentBackground != null) {
+ currentBackground.getPadding(current);
+ result.left += current.left;
+ result.right += current.right;
+ result.top += current.top;
+ result.bottom += current.bottom;
+ }
+ }
+ }
+ });
+ return result;
+ }
+
+ /**
+ * Returns a root matcher that matches roots that have window focus on their decor view.
+ */
+ private static Matcher<Root> hasWindowFocus() {
+ return new TypeSafeMatcher<Root>() {
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("has window focus");
+ }
+
+ @Override
+ public boolean matchesSafely(Root root) {
+ View rootView = root.getDecorView();
+ return rootView.hasWindowFocus();
+ }
+ };
+ }
+
+ @Test
+ @SmallTest
+ public void testAnchoring() {
+ Builder menuBuilder = new Builder();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ final int[] anchorOnScreenXY = new int[2];
+ final int[] popupOnScreenXY = getPopupLocationOnScreen();
+ final int[] popupInWindowXY = getPopupLocationInWindow();
+ final Rect popupPadding = getPopupPadding();
+
+ mButton.getLocationOnScreen(anchorOnScreenXY);
+
+ // Allow for off-by-one mismatch in anchoring
+ assertEquals("Anchoring X", anchorOnScreenXY[0] + popupInWindowXY[0],
+ popupOnScreenXY[0], 1);
+ assertEquals("Anchoring Y", anchorOnScreenXY[1] + popupInWindowXY[1] + mButton.getHeight(),
+ popupOnScreenXY[1], 1);
+ }
+
+ @Test
+ @SmallTest
+ public void testDismissalViaAPI() {
+ Builder menuBuilder = new Builder().withDismissListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Since PopupMenu is not a View, we can't use Espresso's view actions to invoke
+ // the dismiss() API
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPopupMenu.dismiss();
+ }
+ });
+
+ verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
+
+ // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+ // Use a custom matcher to check the visibility of the drop down list view instead.
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testDismissalViaTouch() throws Throwable {
+ Builder menuBuilder = new Builder().withDismissListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Determine the location of the popup on the screen so that we can emulate
+ // a tap outside of its bounds to dismiss it
+ final int[] popupOnScreenXY = getPopupLocationOnScreen();
+ final Rect popupPadding = getPopupPadding();
+
+
+ int emulatedTapX = popupOnScreenXY[0] - popupPadding.left - 20;
+ int emulatedTapY = popupOnScreenXY[1] + popupPadding.top + 20;
+
+ // The logic below uses Instrumentation to emulate a tap outside the bounds of the
+ // displayed popup menu. This tap is then treated by the framework to be "split" as
+ // the ACTION_OUTSIDE for the popup itself, as well as DOWN / MOVE / UP for the underlying
+ // view root if the popup is not modal.
+ // It is not correct to emulate these two sequences separately in the test, as it
+ // wouldn't emulate the user-facing interaction for this test. Note that usage
+ // of Instrumentation is necessary here since Espresso's actions operate at the level
+ // of view or data. Also, we don't want to use View.dispatchTouchEvent directly as
+ // that would require emulation of two separate sequences as well.
+
+ Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+
+ // Inject DOWN event
+ long downTime = SystemClock.uptimeMillis();
+ MotionEvent eventDown = MotionEvent.obtain(
+ downTime, downTime, MotionEvent.ACTION_DOWN, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventDown);
+
+ // Inject MOVE event
+ long moveTime = SystemClock.uptimeMillis();
+ MotionEvent eventMove = MotionEvent.obtain(
+ moveTime, moveTime, MotionEvent.ACTION_MOVE, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventMove);
+
+ // Inject UP event
+ long upTime = SystemClock.uptimeMillis();
+ MotionEvent eventUp = MotionEvent.obtain(
+ upTime, upTime, MotionEvent.ACTION_UP, emulatedTapX, emulatedTapY, 1);
+ instrumentation.sendPointerSync(eventUp);
+
+ // Wait for the system to process all events in the queue
+ instrumentation.waitForIdleSync();
+
+ // At this point our popup should not be showing and should have notified its
+ // dismiss listener
+ verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testSimpleMenuItemClickViaEvent() {
+ Builder menuBuilder = new Builder().withMenuItemClickListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
+ onView(withText(mResources.getString(R.string.popup_menu_delete)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .perform(click());
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_delete));
+
+ // Popup menu should be automatically dismissed on selecting an item
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testSimpleMenuItemClickViaAPI() {
+ Builder menuBuilder = new Builder().withMenuItemClickListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPopupMenu.getMenu().performIdentifierAction(R.id.action_highlight, 0);
+ }
+ });
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_highlight));
+
+ // Popup menu should be automatically dismissed on selecting an item
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testSubMenuClicksViaEvent() throws Throwable {
+ Builder menuBuilder = new Builder().withMenuItemClickListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
+ onView(withText(mResources.getString(R.string.popup_menu_share)))
+ .inRoot(withDecorView(not(is(mMainDecorView))))
+ .perform(click());
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share));
+
+ // Sleep for a bit to allow the menu -> submenu transition to complete
+ Thread.sleep(1000);
+
+ // At this point we should now have our sub-menu displayed. At this point on newer
+ // platform versions (L+) we have two view roots on the screen - one for the main popup
+ // menu and one for the submenu that has just been activated. If we only use the
+ // logic based on decor view, Espresso will time out on waiting for the first root
+ // to acquire window focus. This is why from this point on in this test we are using
+ // two root conditions to detect the submenu - one with decor view not being the same
+ // as the decor view of our main activity window, and the other that checks for window
+ // focus.
+
+ // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+ // Use a custom matcher to check the visibility of the drop down list view instead.
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+
+ // Note that MenuItem.isVisible() refers to the current "data" visibility state
+ // and not the "on screen" visibility state. This is why we're testing the display
+ // visibility of our main and sub menu items.
+
+ // Share submenu items should now be visible
+ onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+
+ // Now click an item in the sub-menu
+ onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .perform(click());
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share_circles));
+
+ // Popup menu should be automatically dismissed on selecting an item in the submenu
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ @Test
+ @SmallTest
+ public void testSubMenuClicksViaAPI() throws Throwable {
+ Builder menuBuilder = new Builder().withMenuItemClickListener();
+ menuBuilder.wireToActionButton();
+
+ onView(withId(R.id.test_button)).perform(click());
+
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPopupMenu.getMenu().performIdentifierAction(R.id.action_share, 0);
+ }
+ });
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share));
+
+ // Sleep for a bit to allow the menu -> submenu transition to complete
+ Thread.sleep(1000);
+
+ // At this point we should now have our sub-menu displayed. At this point on newer
+ // platform versions (L+) we have two view roots on the screen - one for the main popup
+ // menu and one for the submenu that has just been activated. If we only use the
+ // logic based on decor view, Espresso will time out on waiting for the first root
+ // to acquire window focus. This is why from this point on in this test we are using
+ // two root conditions to detect the submenu - one with decor view not being the same
+ // as the decor view of our main activity window, and the other that checks for window
+ // focus.
+
+ // Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
+ // Use a custom matcher to check the visibility of the drop down list view instead.
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+
+ // Note that MenuItem.isVisible() refers to the current "data" visibility state
+ // and not the "on screen" visibility state. This is why we're testing the display
+ // visibility of our main and sub menu items.
+
+ // Share submenu items should now be visible
+ onView(withText(mResources.getString(R.string.popup_menu_share_email)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+ onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
+ .inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
+ .check(matches(isDisplayed()));
+
+ // Now ask the share submenu to perform an action on its specific menu item
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ mPopupMenu.getMenu().findItem(R.id.action_share).getSubMenu().
+ performIdentifierAction(R.id.action_share_email, 0);
+ }
+ });
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share_email));
+
+ // Popup menu should be automatically dismissed on selecting an item in the submenu
+ onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
+ }
+
+ /**
+ * Inner helper class to configure an instance of <code>PopupMenu</code> for the
+ * specific test. The main reason for its existence is that once a popup menu is shown
+ * with the show() method, most of its configuration APIs are no-ops. This means that
+ * we can't add logic that is specific to a certain test once it's shown and we have a
+ * reference to a displayed PopupMenu.
+ */
+ public class Builder {
+ private boolean mHasDismissListener;
+ private boolean mHasMenuItemClickListener;
+
+ private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener;
+ private PopupMenu.OnDismissListener mOnDismissListener;
+
+ public Builder withMenuItemClickListener() {
+ mHasMenuItemClickListener = true;
+ return this;
+ }
+
+ public Builder withDismissListener() {
+ mHasDismissListener = true;
+ return this;
+ }
+
+ private void show() {
+ mPopupMenu = new PopupMenu(mContainer.getContext(), mButton);
+ final MenuInflater menuInflater = mPopupMenu.getMenuInflater();
+ menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
+
+ if (mHasMenuItemClickListener) {
+ // Register a mock listener to be notified when a menu item in our popup menu has
+ // been clicked.
+ mOnMenuItemClickListener = mock(PopupMenu.OnMenuItemClickListener.class);
+ mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
+ }
+
+ if (mHasDismissListener) {
+ // Register a mock listener to be notified when our popup menu is dismissed.
+ mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
+ mPopupMenu.setOnDismissListener(mOnDismissListener);
+ }
+
+ // Show the popup menu
+ mPopupMenu.show();
+ }
+
+ public void wireToActionButton() {
+ mButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ show();
+ }
+ });
+ }
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java
new file mode 100644
index 0000000..9a3736a
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupTestActivity.java
@@ -0,0 +1,30 @@
+/*
+ * 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.support.v7.appcompat.test.R;
+import android.support.v7.testutils.BaseTestActivity;
+
+/**
+ * This activity is used to test both {@link ListPopupWindow} and {@link PopupMenu} classes.
+ *
+ */
+public class PopupTestActivity extends BaseTestActivity {
+ @Override
+ protected int getContentViewLayoutResId() {
+ return R.layout.popup_test_activity;
+ }
+}
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java b/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java
new file mode 100644
index 0000000..81cac576
--- /dev/null
+++ b/v7/appcompat/tests/src/android/support/v7/widget/TintResourcesTest.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
+import android.support.v7.app.AppCompatActivity;
+import android.support.v7.app.BaseInstrumentationTestCase;
+
+import org.junit.Test;
+
+public class TintResourcesTest extends BaseInstrumentationTestCase<AppCompatActivity> {
+
+ public TintResourcesTest() {
+ super(AppCompatActivity.class);
+ }
+
+ @Test
+ public void testTintResourcesDelegateBackToOriginalResources() {
+ final TestResources testResources = new TestResources(getActivity().getResources());
+ // First make sure that the flag is false
+ assertFalse(testResources.wasGetDrawableCalled());
+
+ // Now wrap in a TintResources instance and get a Drawable
+ final Resources tintResources = new TintResources(getActivity(), testResources);
+ tintResources.getDrawable(android.R.drawable.ic_delete);
+
+ // ...and assert that the flag was flipped
+ assertTrue(testResources.wasGetDrawableCalled());
+ }
+
+ /**
+ * Special Resources class which returns a known Drawable instance from a special ID
+ */
+ private static class TestResources extends Resources {
+ private boolean mGetDrawableCalled;
+
+ public TestResources(Resources res) {
+ super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ mGetDrawableCalled = true;
+ return super.getDrawable(id);
+ }
+
+ public boolean wasGetDrawableCalled() {
+ return mGetDrawableCalled;
+ }
+ }
+
+}
diff --git a/v7/cardview/Android.mk b/v7/cardview/Android.mk
index a1321d4..cc51239 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -14,13 +14,14 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
+# Build the resources using the latest applicable SDK version.
# We do this here because the final static library must be compiled with an older
# SDK version than the resources. The resources library and the R class that it
# contains will not be linked into the final static library.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-cardview-res
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_JAR_EXCLUDE_FILES := none
@@ -35,6 +36,7 @@
LOCAL_MODULE := android-support-v7-cardview-base
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under, base)
+LOCAL_JAVA_LIBRARIES := android-support-annotations
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -46,7 +48,8 @@
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under, eclair-mr1)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+ android-support-annotations
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -58,7 +61,8 @@
LOCAL_SDK_VERSION := 17
LOCAL_SRC_FILES := $(call all-java-files-under, jellybean-mr1)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-eclair-mr1
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+ android-support-annotations
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -69,25 +73,32 @@
LOCAL_MODULE := android-support-v7-cardview-api21
LOCAL_SDK_VERSION := 21
LOCAL_SRC_FILES := $(call all-java-files-under, api21)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-base \
- android-support-v7-cardview-jellybean-mr1
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-jellybean-mr1
+LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res \
+ android-support-annotations
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-cardview
+#
# in their makefiles to include the resources in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-cardview
LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-cardview-api21
-LOCAL_JAVA_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAVA_LIBRARIES := android-support-annotations
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-cardview-res
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v7/cardview/api/23.1.1.txt b/v7/cardview/api/23.1.1.txt
new file mode 100644
index 0000000..bb62ed7
--- /dev/null
+++ b/v7/cardview/api/23.1.1.txt
@@ -0,0 +1,26 @@
+package android.support.v7.widget {
+
+ public class CardView extends android.widget.FrameLayout {
+ ctor public CardView(android.content.Context);
+ ctor public CardView(android.content.Context, android.util.AttributeSet);
+ ctor public CardView(android.content.Context, android.util.AttributeSet, int);
+ method public float getCardElevation();
+ method public int getContentPaddingBottom();
+ method public int getContentPaddingLeft();
+ method public int getContentPaddingRight();
+ method public int getContentPaddingTop();
+ method public float getMaxCardElevation();
+ method public boolean getPreventCornerOverlap();
+ method public float getRadius();
+ method public boolean getUseCompatPadding();
+ method public void setCardBackgroundColor(int);
+ method public void setCardElevation(float);
+ method public void setContentPadding(int, int, int, int);
+ method public void setMaxCardElevation(float);
+ method public void setPreventCornerOverlap(boolean);
+ method public void setRadius(float);
+ method public void setUseCompatPadding(boolean);
+ }
+
+}
+
diff --git a/v7/cardview/api/current.txt b/v7/cardview/api/current.txt
index bb62ed7..6732501 100644
--- a/v7/cardview/api/current.txt
+++ b/v7/cardview/api/current.txt
@@ -4,6 +4,7 @@
ctor public CardView(android.content.Context);
ctor public CardView(android.content.Context, android.util.AttributeSet);
ctor public CardView(android.content.Context, android.util.AttributeSet, int);
+ method public android.content.res.ColorStateList getCardBackgroundColor();
method public float getCardElevation();
method public int getContentPaddingBottom();
method public int getContentPaddingLeft();
@@ -14,6 +15,7 @@
method public float getRadius();
method public boolean getUseCompatPadding();
method public void setCardBackgroundColor(int);
+ method public void setCardBackgroundColor(android.content.res.ColorStateList);
method public void setCardElevation(float);
method public void setContentPadding(int, int, int, int);
method public void setMaxCardElevation(float);
diff --git a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index 833ccc8..413a287 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -16,16 +16,19 @@
package android.support.v7.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.annotation.Nullable;
import android.view.View;
class CardViewApi21 implements CardViewImpl {
@Override
- public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
- float radius, float elevation, float maxElevation) {
- final RoundRectDrawable backgroundDrawable = new RoundRectDrawable(backgroundColor, radius);
- cardView.setBackgroundDrawable(backgroundDrawable);
- View view = (View) cardView;
+ public void initialize(CardViewDelegate cardView, Context context,
+ ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
+ final RoundRectDrawable background = new RoundRectDrawable(backgroundColor, radius);
+ cardView.setCardBackground(background);
+
+ View view = cardView.getCardView();
view.setClipToOutline(true);
view.setElevation(elevation);
setMaxElevation(cardView, maxElevation);
@@ -33,7 +36,7 @@
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
- ((RoundRectDrawable) (cardView.getBackground())).setRadius(radius);
+ getCardBackground(cardView).setRadius(radius);
}
@Override
@@ -42,14 +45,14 @@
@Override
public void setMaxElevation(CardViewDelegate cardView, float maxElevation) {
- ((RoundRectDrawable) (cardView.getBackground())).setPadding(maxElevation,
+ getCardBackground(cardView).setPadding(maxElevation,
cardView.getUseCompatPadding(), cardView.getPreventCornerOverlap());
updatePadding(cardView);
}
@Override
public float getMaxElevation(CardViewDelegate cardView) {
- return ((RoundRectDrawable) (cardView.getBackground())).getPadding();
+ return getCardBackground(cardView).getPadding();
}
@Override
@@ -64,17 +67,17 @@
@Override
public float getRadius(CardViewDelegate cardView) {
- return ((RoundRectDrawable) (cardView.getBackground())).getRadius();
+ return getCardBackground(cardView).getRadius();
}
@Override
public void setElevation(CardViewDelegate cardView, float elevation) {
- ((View) cardView).setElevation(elevation);
+ cardView.getCardView().setElevation(elevation);
}
@Override
public float getElevation(CardViewDelegate cardView) {
- return ((View) cardView).getElevation();
+ return cardView.getCardView().getElevation();
}
@Override
@@ -103,7 +106,16 @@
}
@Override
- public void setBackgroundColor(CardViewDelegate cardView, int color) {
- ((RoundRectDrawable) (cardView.getBackground())).setColor(color);
+ public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
+ getCardBackground(cardView).setColor(color);
+ }
+
+ @Override
+ public ColorStateList getBackgroundColor(CardViewDelegate cardView) {
+ return getCardBackground(cardView).getColor();
+ }
+
+ private RoundRectDrawable getCardBackground(CardViewDelegate cardView) {
+ return ((RoundRectDrawable) cardView.getCardBackground());
}
}
\ No newline at end of file
diff --git a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
index 5dba5d6..3a85d9c 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -27,6 +27,7 @@
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateVerticalPadding;
import static android.support.v7.widget.RoundRectDrawableWithShadow.calculateHorizontalPadding;
@@ -46,18 +47,25 @@
private boolean mInsetForPadding = false;
private boolean mInsetForRadius = true;
+ private ColorStateList mBackground;
private PorterDuffColorFilter mTintFilter;
private ColorStateList mTint;
- private PorterDuff.Mode mTintMode;
+ private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
- public RoundRectDrawable(int backgroundColor, float radius) {
+ public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
mRadius = radius;
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- mPaint.setColor(backgroundColor);
+ setBackground(backgroundColor);
+
mBoundsF = new RectF();
mBoundsI = new Rect();
}
+ private void setBackground(ColorStateList color) {
+ mBackground = (color == null) ? ColorStateList.valueOf(Color.TRANSPARENT) : color;
+ mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
+ }
+
void setPadding(float padding, boolean insetForPadding, boolean insetForRadius) {
if (padding == mPadding && mInsetForPadding == insetForPadding &&
mInsetForRadius == insetForRadius) {
@@ -147,11 +155,15 @@
return mRadius;
}
- public void setColor(int color) {
- mPaint.setColor(color);
+ public void setColor(@Nullable ColorStateList color) {
+ setBackground(color);
invalidateSelf();
}
+ public ColorStateList getColor() {
+ return mBackground;
+ }
+
@Override
public void setTintList(ColorStateList tint) {
mTint = tint;
@@ -168,16 +180,22 @@
@Override
protected boolean onStateChange(int[] stateSet) {
+ final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
+ final boolean colorChanged = newColor != mPaint.getColor();
+ if (colorChanged) {
+ mPaint.setColor(newColor);
+ }
if (mTint != null && mTintMode != null) {
mTintFilter = createTintFilter(mTint, mTintMode);
return true;
}
- return false;
+ return colorChanged;
}
@Override
public boolean isStateful() {
- return (mTint != null && mTint.isStateful()) || super.isStateful();
+ return (mTint != null && mTint.isStateful())
+ || (mBackground != null && mBackground.isStateful()) || super.isStateful();
}
/**
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
index 69d416b..b5be921 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
@@ -15,8 +15,8 @@
*/
package android.support.v7.widget;
-import android.graphics.Rect;
import android.graphics.drawable.Drawable;
+import android.view.View;
/**
* Interface provided by CardView to implementations.
@@ -24,10 +24,11 @@
* Necessary to resolve circular dependency between base CardView and platform implementations.
*/
interface CardViewDelegate {
- void setBackgroundDrawable(Drawable paramDrawable);
- Drawable getBackground();
+ void setCardBackground(Drawable drawable);
+ Drawable getCardBackground();
boolean getUseCompatPadding();
boolean getPreventCornerOverlap();
- float getRadius();
void setShadowPadding(int left, int top, int right, int bottom);
+ void setMinWidthHeightInternal(int width, int height);
+ View getCardView();
}
\ No newline at end of file
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
index 24b902c..26799da 100644
--- a/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
+++ b/v7/cardview/base/android/support/v7/widget/CardViewImpl.java
@@ -16,12 +16,15 @@
package android.support.v7.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
+import android.support.annotation.Nullable;
+
/**
* Interface for platform specific CardView implementations.
*/
interface CardViewImpl {
- void initialize(CardViewDelegate cardView, Context context, int backgroundColor, float radius,
- float elevation, float maxElevation);
+ void initialize(CardViewDelegate cardView, Context context, ColorStateList backgroundColor,
+ float radius, float elevation, float maxElevation);
void setRadius(CardViewDelegate cardView, float radius);
@@ -47,5 +50,7 @@
void onPreventCornerOverlapChanged(CardViewDelegate cardView);
- void setBackgroundColor(CardViewDelegate cardView, int color);
+ void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color);
+
+ ColorStateList getBackgroundColor(CardViewDelegate cardView);
}
diff --git a/v7/cardview/build.gradle b/v7/cardview/build.gradle
index 143519c..65ca583 100644
--- a/v7/cardview/build.gradle
+++ b/v7/cardview/build.gradle
@@ -1,10 +1,14 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'cardview-v7'
+dependencies {
+ compile project(':support-annotations')
+}
+
android {
// WARNING: should be 7
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
defaultConfig {
minSdkVersion 7
@@ -12,6 +16,11 @@
//targetSdkVersion 19
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDirs = ['base', 'eclair-mr1', 'jellybean-mr1', 'api21', 'src']
@@ -22,6 +31,11 @@
androidTest.java.srcDir 'tests/java'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
index 8f7adfc..e32a0a0 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -16,11 +16,13 @@
package android.support.v7.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.RectF;
-import android.view.View;
+import android.support.annotation.ColorInt;
+import android.support.annotation.Nullable;
class CardViewEclairMr1 implements CardViewImpl {
@@ -31,20 +33,22 @@
// Draws a round rect using 7 draw operations. This is faster than using
// canvas.drawRoundRect before JBMR1 because API 11-16 used alpha mask textures to draw
// shapes.
- RoundRectDrawableWithShadow.sRoundRectHelper
- = new RoundRectDrawableWithShadow.RoundRectHelper() {
+ RoundRectDrawableWithShadow.sRoundRectHelper =
+ new RoundRectDrawableWithShadow.RoundRectHelper() {
@Override
public void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius,
Paint paint) {
final float twoRadius = cornerRadius * 2;
final float innerWidth = bounds.width() - twoRadius - 1;
final float innerHeight = bounds.height() - twoRadius - 1;
- // increment it to account for half pixels.
if (cornerRadius >= 1f) {
- cornerRadius += .5f;
- sCornerRect.set(-cornerRadius, -cornerRadius, cornerRadius, cornerRadius);
+ // increment corner radius to account for half pixels.
+ float roundedCornerRadius = cornerRadius + .5f;
+ sCornerRect.set(-roundedCornerRadius, -roundedCornerRadius, roundedCornerRadius,
+ roundedCornerRadius);
int saved = canvas.save();
- canvas.translate(bounds.left + cornerRadius, bounds.top + cornerRadius);
+ canvas.translate(bounds.left + roundedCornerRadius,
+ bounds.top + roundedCornerRadius);
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.translate(innerWidth, 0);
canvas.rotate(90);
@@ -57,32 +61,34 @@
canvas.drawArc(sCornerRect, 180, 90, true, paint);
canvas.restoreToCount(saved);
//draw top and bottom pieces
- canvas.drawRect(bounds.left + cornerRadius - 1f, bounds.top,
- bounds.right - cornerRadius + 1f, bounds.top + cornerRadius,
- paint);
- canvas.drawRect(bounds.left + cornerRadius - 1f,
- bounds.bottom - cornerRadius + 1f, bounds.right - cornerRadius + 1f,
- bounds.bottom, paint);
+ canvas.drawRect(bounds.left + roundedCornerRadius - 1f, bounds.top,
+ bounds.right - roundedCornerRadius + 1f,
+ bounds.top + roundedCornerRadius, paint);
+
+ canvas.drawRect(bounds.left + roundedCornerRadius - 1f,
+ bounds.bottom - roundedCornerRadius,
+ bounds.right - roundedCornerRadius + 1f, bounds.bottom, paint);
}
-//// center
- canvas.drawRect(bounds.left, bounds.top + Math.max(0, cornerRadius - 1f),
- bounds.right, bounds.bottom - cornerRadius + 1f, paint);
+ // center
+ canvas.drawRect(bounds.left, bounds.top + cornerRadius,
+ bounds.right, bounds.bottom - cornerRadius , paint);
}
};
}
@Override
- public void initialize(CardViewDelegate cardView, Context context, int backgroundColor,
- float radius, float elevation, float maxElevation) {
+ public void initialize(CardViewDelegate cardView, Context context,
+ ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
elevation, maxElevation);
background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
- cardView.setBackgroundDrawable(background);
+ cardView.setCardBackground(background);
updatePadding(cardView);
}
- RoundRectDrawableWithShadow createBackground(Context context, int backgroundColor,
- float radius, float elevation, float maxElevation) {
+ private RoundRectDrawableWithShadow createBackground(Context context,
+ ColorStateList backgroundColor, float radius, float elevation,
+ float maxElevation) {
return new RoundRectDrawableWithShadow(context.getResources(), backgroundColor, radius,
elevation, maxElevation);
}
@@ -91,8 +97,8 @@
public void updatePadding(CardViewDelegate cardView) {
Rect shadowPadding = new Rect();
getShadowBackground(cardView).getMaxShadowAndCornerPadding(shadowPadding);
- ((View) cardView).setMinimumHeight((int) Math.ceil(getMinHeight(cardView)));
- ((View) cardView).setMinimumWidth((int) Math.ceil(getMinWidth(cardView)));
+ cardView.setMinWidthHeightInternal((int) Math.ceil(getMinWidth(cardView)),
+ (int) Math.ceil(getMinHeight(cardView)));
cardView.setShadowPadding(shadowPadding.left, shadowPadding.top,
shadowPadding.right, shadowPadding.bottom);
}
@@ -109,10 +115,14 @@
}
@Override
- public void setBackgroundColor(CardViewDelegate cardView, int color) {
+ public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
getShadowBackground(cardView).setColor(color);
}
+ public ColorStateList getBackgroundColor(CardViewDelegate cardView) {
+ return getShadowBackground(cardView).getColor();
+ }
+
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
getShadowBackground(cardView).setCornerRadius(radius);
@@ -156,6 +166,6 @@
}
private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
- return ((RoundRectDrawableWithShadow) cardView.getBackground());
+ return ((RoundRectDrawableWithShadow) cardView.getCardBackground());
}
}
diff --git a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
index 505edcc..5cefd8f 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/RoundRectDrawableWithShadow.java
@@ -15,8 +15,10 @@
*/
package android.support.v7.widget;
+import android.content.res.ColorStateList;
import android.content.res.Resources;
import android.graphics.Canvas;
+import android.graphics.Color;
import android.graphics.ColorFilter;
import android.graphics.LinearGradient;
import android.graphics.Paint;
@@ -27,6 +29,7 @@
import android.graphics.RectF;
import android.graphics.Shader;
import android.graphics.drawable.Drawable;
+import android.support.annotation.Nullable;
import android.support.v7.cardview.R;
/**
@@ -72,6 +75,8 @@
// actual value set by developer
float mRawShadowSize;
+ private ColorStateList mBackground;
+
private boolean mDirty = true;
private final int mShadowStartColor;
@@ -85,13 +90,13 @@
*/
private boolean mPrintedShadowClipWarning = false;
- RoundRectDrawableWithShadow(Resources resources, int backgroundColor, float radius,
+ RoundRectDrawableWithShadow(Resources resources, ColorStateList backgroundColor, float radius,
float shadowSize, float maxShadowSize) {
mShadowStartColor = resources.getColor(R.color.cardview_shadow_start_color);
mShadowEndColor = resources.getColor(R.color.cardview_shadow_end_color);
mInsetShadow = resources.getDimensionPixelSize(R.dimen.cardview_compat_inset_shadow);
mPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
- mPaint.setColor(backgroundColor);
+ setBackground(backgroundColor);
mCornerShadowPaint = new Paint(Paint.ANTI_ALIAS_FLAG | Paint.DITHER_FLAG);
mCornerShadowPaint.setStyle(Paint.Style.FILL);
mCornerRadius = (int) (radius + .5f);
@@ -101,6 +106,11 @@
setShadowSize(shadowSize, maxShadowSize);
}
+ private void setBackground(ColorStateList color) {
+ mBackground = (color == null) ? ColorStateList.valueOf(Color.TRANSPARENT) : color;
+ mPaint.setColor(mBackground.getColorForState(getState(), mBackground.getDefaultColor()));
+ }
+
/**
* Casts the value to an even integer.
*/
@@ -187,6 +197,23 @@
}
@Override
+ protected boolean onStateChange(int[] stateSet) {
+ final int newColor = mBackground.getColorForState(stateSet, mBackground.getDefaultColor());
+ if (mPaint.getColor() == newColor) {
+ return false;
+ }
+ mPaint.setColor(newColor);
+ mDirty = true;
+ invalidateSelf();
+ return true;
+ }
+
+ @Override
+ public boolean isStateful() {
+ return (mBackground != null && mBackground.isStateful()) || super.isStateful();
+ }
+
+ @Override
public void setColorFilter(ColorFilter cf) {
mPaint.setColorFilter(cf);
}
@@ -350,11 +377,15 @@
return content + (mRawMaxShadowSize * SHADOW_MULTIPLIER + mInsetShadow) * 2;
}
- public void setColor(int color) {
- mPaint.setColor(color);
+ void setColor(@Nullable ColorStateList color) {
+ setBackground(color);
invalidateSelf();
}
+ ColorStateList getColor() {
+ return mBackground;
+ }
+
static interface RoundRectHelper {
void drawRoundRect(Canvas canvas, RectF bounds, float cornerRadius, Paint paint);
}
diff --git a/v7/cardview/res/values-v23/styles.xml b/v7/cardview/res/values-v23/styles.xml
new file mode 100644
index 0000000..6961704
--- /dev/null
+++ b/v7/cardview/res/values-v23/styles.xml
@@ -0,0 +1,23 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+<resources>
+
+ <!-- On API v23+ we can use the platform provided floating background color -->
+ <style name="CardView" parent="Base.CardView">
+ <item name="cardBackgroundColor">?android:attr/colorBackgroundFloating</item>
+ </style>
+
+</resources>
\ No newline at end of file
diff --git a/v7/cardview/res/values/attrs.xml b/v7/cardview/res/values/attrs.xml
index a0bd67d..deed51b 100644
--- a/v7/cardview/res/values/attrs.xml
+++ b/v7/cardview/res/values/attrs.xml
@@ -38,5 +38,9 @@
<attr name="contentPaddingTop" format="dimension" />
<!-- Inner padding between the bottom edge of the Card and children of the CardView. -->
<attr name="contentPaddingBottom" format="dimension" />
+ <!-- Workaround to read user defined minimum width -->
+ <attr name="android:minWidth"/>
+ <!-- Workaround to read user defined minimum height -->
+ <attr name="android:minHeight"/>
</declare-styleable>
</resources>
\ No newline at end of file
diff --git a/v7/cardview/res/values/colors.xml b/v7/cardview/res/values/colors.xml
index 3ed7087..df202d1 100644
--- a/v7/cardview/res/values/colors.xml
+++ b/v7/cardview/res/values/colors.xml
@@ -16,9 +16,9 @@
<resources>
<!-- Background color for light CardView. -->
- <color name="cardview_light_background">#FFFAFAFA</color>
- <!-- Background color for dark CardView. -->
- <color name="cardview_dark_background">#FF202020</color>
+ <color name="cardview_light_background">#FFFFFFFF</color>
+ <!-- Background color for dark CardView. -->
+ <color name="cardview_dark_background">#FF424242</color>
<!-- Shadow color for the first pixels around CardView. -->
<color name="cardview_shadow_start_color">#37000000</color>
<!-- Shadow color for the furthest pixels around CardView. -->
diff --git a/v7/cardview/res/values/styles.xml b/v7/cardview/res/values/styles.xml
index 99dfbc9..2ab8a41 100644
--- a/v7/cardview/res/values/styles.xml
+++ b/v7/cardview/res/values/styles.xml
@@ -14,18 +14,26 @@
limitations under the License.
-->
<resources>
- <style name="CardView">
- <item name="cardBackgroundColor">@color/cardview_light_background</item>
+
+ <style name="Base.CardView" parent="android:Widget">
<item name="cardCornerRadius">@dimen/cardview_default_radius</item>
<item name="cardElevation">@dimen/cardview_default_elevation</item>
<item name="cardMaxElevation">@dimen/cardview_default_elevation</item>
<item name="cardUseCompatPadding">false</item>
<item name="cardPreventCornerOverlap">true</item>
</style>
+
+ <!-- On older platforms we do not explicitly set a background color and let CardView compute
+ one based on the theme. -->
+ <style name="CardView" parent="Base.CardView">
+ </style>
+
<style name="CardView.Light">
<item name="cardBackgroundColor">@color/cardview_light_background</item>
</style>
+
<style name="CardView.Dark">
<item name="cardBackgroundColor">@color/cardview_dark_background</item>
</style>
+
</resources>
\ No newline at end of file
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index eb181bc..ecd0a10 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -17,11 +17,17 @@
package android.support.v7.widget;
import android.content.Context;
+import android.content.res.ColorStateList;
import android.content.res.TypedArray;
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.Drawable;
import android.os.Build;
+import android.support.annotation.ColorInt;
+import android.support.annotation.Nullable;
import android.support.v7.cardview.R;
import android.util.AttributeSet;
+import android.view.View;
import android.widget.FrameLayout;
/**
@@ -67,8 +73,9 @@
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingRight
* @attr ref android.support.v7.cardview.R.styleable#CardView_contentPaddingBottom
*/
-public class CardView extends FrameLayout implements CardViewDelegate {
+public class CardView extends FrameLayout {
+ private static final int[] COLOR_BACKGROUND_ATTR = {android.R.attr.colorBackground};
private static final CardViewImpl IMPL;
static {
@@ -86,11 +93,19 @@
private boolean mPreventCornerOverlap;
+ /**
+ * CardView requires to have a particular minimum size to draw shadows before API 21. If
+ * developer also sets min width/height, they might be overridden.
+ *
+ * CardView works around this issue by recording user given parameters and using an internal
+ * method to set them.
+ */
+ private int mUserSetMinWidth, mUserSetMinHeight;
+
private final Rect mContentPadding = new Rect();
private final Rect mShadowBounds = new Rect();
-
public CardView(Context context) {
super(context);
initialize(context, null, 0);
@@ -121,7 +136,6 @@
* @return <code>true</code> if CardView adds inner padding on platforms Lollipop and after to
* have same dimensions with platforms before Lollipop.
*/
- @Override
public boolean getUseCompatPadding() {
return mCompatPadding;
}
@@ -143,11 +157,10 @@
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardUseCompatPadding
*/
public void setUseCompatPadding(boolean useCompatPadding) {
- if (mCompatPadding == useCompatPadding) {
- return;
+ if (mCompatPadding != useCompatPadding) {
+ mCompatPadding = useCompatPadding;
+ IMPL.onCompatPaddingChanged(mCardViewDelegate);
}
- mCompatPadding = useCompatPadding;
- IMPL.onCompatPaddingChanged(this);
}
/**
@@ -168,17 +181,17 @@
*/
public void setContentPadding(int left, int top, int right, int bottom) {
mContentPadding.set(left, top, right, bottom);
- IMPL.updatePadding(this);
+ IMPL.updatePadding(mCardViewDelegate);
}
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
- if (IMPL instanceof CardViewApi21 == false) {
+ if (!(IMPL instanceof CardViewApi21)) {
final int widthMode = MeasureSpec.getMode(widthMeasureSpec);
switch (widthMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
- final int minWidth = (int) Math.ceil(IMPL.getMinWidth(this));
+ final int minWidth = (int) Math.ceil(IMPL.getMinWidth(mCardViewDelegate));
widthMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minWidth,
MeasureSpec.getSize(widthMeasureSpec)), widthMode);
break;
@@ -188,7 +201,7 @@
switch (heightMode) {
case MeasureSpec.EXACTLY:
case MeasureSpec.AT_MOST:
- final int minHeight = (int) Math.ceil(IMPL.getMinHeight(this));
+ final int minHeight = (int) Math.ceil(IMPL.getMinHeight(mCardViewDelegate));
heightMeasureSpec = MeasureSpec.makeMeasureSpec(Math.max(minHeight,
MeasureSpec.getSize(heightMeasureSpec)), heightMode);
break;
@@ -201,8 +214,23 @@
private void initialize(Context context, AttributeSet attrs, int defStyleAttr) {
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.CardView, defStyleAttr,
- R.style.CardView_Light);
- int backgroundColor = a.getColor(R.styleable.CardView_cardBackgroundColor, 0);
+ R.style.CardView);
+ ColorStateList backgroundColor;
+ if (a.hasValue(R.styleable.CardView_cardBackgroundColor)) {
+ backgroundColor = a.getColorStateList(R.styleable.CardView_cardBackgroundColor);
+ } else {
+ // There isn't one set, so we'll compute one based on the theme
+ final TypedArray aa = getContext().obtainStyledAttributes(COLOR_BACKGROUND_ATTR);
+ final int themeColorBackground = aa.getColor(0, 0);
+ aa.recycle();
+
+ // If the theme colorBackground is light, use our own light color, otherwise dark
+ final float[] hsv = new float[3];
+ Color.colorToHSV(themeColorBackground, hsv);
+ backgroundColor = ColorStateList.valueOf(hsv[2] > 0.5f
+ ? getResources().getColor(R.color.cardview_light_background)
+ : getResources().getColor(R.color.cardview_dark_background));
+ }
float radius = a.getDimension(R.styleable.CardView_cardCornerRadius, 0);
float elevation = a.getDimension(R.styleable.CardView_cardElevation, 0);
float maxElevation = a.getDimension(R.styleable.CardView_cardMaxElevation, 0);
@@ -220,8 +248,24 @@
if (elevation > maxElevation) {
maxElevation = elevation;
}
+ mUserSetMinWidth = a.getDimensionPixelSize(R.styleable.CardView_android_minWidth, 0);
+ mUserSetMinHeight = a.getDimensionPixelSize(R.styleable.CardView_android_minHeight, 0);
a.recycle();
- IMPL.initialize(this, context, backgroundColor, radius, elevation, maxElevation);
+
+ IMPL.initialize(mCardViewDelegate, context, backgroundColor, radius,
+ elevation, maxElevation);
+ }
+
+ @Override
+ public void setMinimumWidth(int minWidth) {
+ mUserSetMinWidth = minWidth;
+ super.setMinimumWidth(minWidth);
+ }
+
+ @Override
+ public void setMinimumHeight(int minHeight) {
+ mUserSetMinHeight = minHeight;
+ super.setMinimumHeight(minHeight);
}
/**
@@ -230,8 +274,27 @@
* @param color The new color to set for the card background
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
*/
- public void setCardBackgroundColor(int color) {
- IMPL.setBackgroundColor(this, color);
+ public void setCardBackgroundColor(@ColorInt int color) {
+ IMPL.setBackgroundColor(mCardViewDelegate, ColorStateList.valueOf(color));
+ }
+
+ /**
+ * Updates the background ColorStateList of the CardView
+ *
+ * @param color The new ColorStateList to set for the card background
+ * @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
+ */
+ public void setCardBackgroundColor(@Nullable ColorStateList color) {
+ IMPL.setBackgroundColor(mCardViewDelegate, color);
+ }
+
+ /**
+ * Returns the background color state list of the CardView.
+ *
+ * @return The background color state list of the CardView.
+ */
+ public ColorStateList getCardBackgroundColor() {
+ return IMPL.getBackgroundColor(mCardViewDelegate);
}
/**
@@ -278,7 +341,7 @@
* @see #setRadius(float)
*/
public void setRadius(float radius) {
- IMPL.setRadius(this, radius);
+ IMPL.setRadius(mCardViewDelegate, radius);
}
/**
@@ -288,19 +351,7 @@
* @see #getRadius()
*/
public float getRadius() {
- return IMPL.getRadius(this);
- }
-
- /**
- * Internal method used by CardView implementations to update the padding.
- *
- * @hide
- */
- @Override
- public void setShadowPadding(int left, int top, int right, int bottom) {
- mShadowBounds.set(left, top, right, bottom);
- super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
- right + mContentPadding.right, bottom + mContentPadding.bottom);
+ return IMPL.getRadius(mCardViewDelegate);
}
/**
@@ -312,7 +363,7 @@
* @see #setMaxCardElevation(float)
*/
public void setCardElevation(float elevation) {
- IMPL.setElevation(this, elevation);
+ IMPL.setElevation(mCardViewDelegate, elevation);
}
/**
@@ -323,7 +374,7 @@
* @see #getMaxCardElevation()
*/
public float getCardElevation() {
- return IMPL.getElevation(this);
+ return IMPL.getElevation(mCardViewDelegate);
}
/**
@@ -338,7 +389,7 @@
* @see #getMaxCardElevation()
*/
public void setMaxCardElevation(float maxElevation) {
- IMPL.setMaxElevation(this, maxElevation);
+ IMPL.setMaxElevation(mCardViewDelegate, maxElevation);
}
/**
@@ -349,7 +400,7 @@
* @see #getCardElevation()
*/
public float getMaxCardElevation() {
- return IMPL.getMaxElevation(this);
+ return IMPL.getMaxElevation(mCardViewDelegate);
}
/**
@@ -359,7 +410,6 @@
* @return True if CardView prevents overlaps with rounded corners on platforms before Lollipop.
* Default value is <code>true</code>.
*/
- @Override
public boolean getPreventCornerOverlap() {
return mPreventCornerOverlap;
}
@@ -378,10 +428,56 @@
* @see #setUseCompatPadding(boolean)
*/
public void setPreventCornerOverlap(boolean preventCornerOverlap) {
- if (preventCornerOverlap == mPreventCornerOverlap) {
- return;
+ if (preventCornerOverlap != mPreventCornerOverlap) {
+ mPreventCornerOverlap = preventCornerOverlap;
+ IMPL.onPreventCornerOverlapChanged(mCardViewDelegate);
}
- mPreventCornerOverlap = preventCornerOverlap;
- IMPL.onPreventCornerOverlapChanged(this);
}
+
+ private final CardViewDelegate mCardViewDelegate = new CardViewDelegate() {
+ private Drawable mCardBackground;
+
+ @Override
+ public void setCardBackground(Drawable drawable) {
+ mCardBackground = drawable;
+ setBackgroundDrawable(drawable);
+ }
+
+ @Override
+ public boolean getUseCompatPadding() {
+ return CardView.this.getUseCompatPadding();
+ }
+
+ @Override
+ public boolean getPreventCornerOverlap() {
+ return CardView.this.getPreventCornerOverlap();
+ }
+
+ @Override
+ public void setShadowPadding(int left, int top, int right, int bottom) {
+ mShadowBounds.set(left, top, right, bottom);
+ CardView.super.setPadding(left + mContentPadding.left, top + mContentPadding.top,
+ right + mContentPadding.right, bottom + mContentPadding.bottom);
+ }
+
+ @Override
+ public void setMinWidthHeightInternal(int width, int height) {
+ if (width > mUserSetMinWidth) {
+ CardView.super.setMinimumWidth(width);
+ }
+ if (height > mUserSetMinHeight) {
+ CardView.super.setMinimumHeight(height);
+ }
+ }
+
+ @Override
+ public Drawable getCardBackground() {
+ return mCardBackground;
+ }
+
+ @Override
+ public View getCardView() {
+ return CardView.this;
+ }
+ };
}
diff --git a/v7/gridlayout/Android.mk b/v7/gridlayout/Android.mk
index 4617db6..f9bd0a1 100644
--- a/v7/gridlayout/Android.mk
+++ b/v7/gridlayout/Android.mk
@@ -15,16 +15,23 @@
LOCAL_PATH := $(call my-dir)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-gridlayout \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-gridlayout
LOCAL_SRC_FILES := $(call all-java-files-under, src)
LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SDK_VERSION := 7
-LOCAL_JAVA_LIBRARIES := android-support-v4
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/gridlayout/api/23.1.1.txt b/v7/gridlayout/api/23.1.1.txt
new file mode 100644
index 0000000..baa25e2
--- /dev/null
+++ b/v7/gridlayout/api/23.1.1.txt
@@ -0,0 +1,74 @@
+package android.support.v7.widget {
+
+ public class GridLayout extends android.view.ViewGroup {
+ ctor public GridLayout(android.content.Context, android.util.AttributeSet, int);
+ ctor public GridLayout(android.content.Context, android.util.AttributeSet);
+ ctor public GridLayout(android.content.Context);
+ method public int getAlignmentMode();
+ method public int getColumnCount();
+ method public int getOrientation();
+ method public android.util.Printer getPrinter();
+ method public int getRowCount();
+ method public boolean getUseDefaultMargins();
+ method public boolean isColumnOrderPreserved();
+ method public boolean isRowOrderPreserved();
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void setAlignmentMode(int);
+ method public void setColumnCount(int);
+ method public void setColumnOrderPreserved(boolean);
+ method public void setOrientation(int);
+ method public void setPrinter(android.util.Printer);
+ method public void setRowCount(int);
+ method public void setRowOrderPreserved(boolean);
+ method public void setUseDefaultMargins(boolean);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, int, android.support.v7.widget.GridLayout.Alignment, float);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, android.support.v7.widget.GridLayout.Alignment, float);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, int, float);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, float);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, int, android.support.v7.widget.GridLayout.Alignment);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, android.support.v7.widget.GridLayout.Alignment);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int, int);
+ method public static android.support.v7.widget.GridLayout.Spec spec(int);
+ field public static final int ALIGN_BOUNDS = 0; // 0x0
+ field public static final int ALIGN_MARGINS = 1; // 0x1
+ field public static final android.support.v7.widget.GridLayout.Alignment BASELINE;
+ field public static final android.support.v7.widget.GridLayout.Alignment BOTTOM;
+ field public static final android.support.v7.widget.GridLayout.Alignment CENTER;
+ field public static final android.support.v7.widget.GridLayout.Alignment END;
+ field public static final android.support.v7.widget.GridLayout.Alignment FILL;
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final android.support.v7.widget.GridLayout.Alignment LEFT;
+ field public static final android.support.v7.widget.GridLayout.Alignment RIGHT;
+ field public static final android.support.v7.widget.GridLayout.Alignment START;
+ field public static final android.support.v7.widget.GridLayout.Alignment TOP;
+ field public static final int UNDEFINED = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static abstract class GridLayout.Alignment {
+ }
+
+ public static class GridLayout.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public GridLayout.LayoutParams(android.support.v7.widget.GridLayout.Spec, android.support.v7.widget.GridLayout.Spec);
+ ctor public GridLayout.LayoutParams();
+ ctor public GridLayout.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public GridLayout.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public GridLayout.LayoutParams(android.support.v7.widget.GridLayout.LayoutParams);
+ ctor public GridLayout.LayoutParams(android.content.Context, android.util.AttributeSet);
+ method public void setGravity(int);
+ field public android.support.v7.widget.GridLayout.Spec columnSpec;
+ field public android.support.v7.widget.GridLayout.Spec rowSpec;
+ }
+
+ public static class GridLayout.Spec {
+ method public android.support.v7.widget.GridLayout.Alignment getAbsoluteAlignment(boolean);
+ }
+
+ public final deprecated class Space extends android.support.v4.widget.Space {
+ ctor public Space(android.content.Context);
+ ctor public Space(android.content.Context, android.util.AttributeSet);
+ ctor public Space(android.content.Context, android.util.AttributeSet, int);
+ }
+
+}
+
diff --git a/v7/gridlayout/build.gradle b/v7/gridlayout/build.gradle
index 9e55c01..7cae6c3 100644
--- a/v7/gridlayout/build.gradle
+++ b/v7/gridlayout/build.gradle
@@ -1,13 +1,25 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'gridlayout-v7'
dependencies {
compile project(':support-v4')
+
+ 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'
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
+
+ defaultConfig {
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
+ }
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -25,6 +37,11 @@
androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
diff --git a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
index a493911..bcc66f4 100644
--- a/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
+++ b/v7/gridlayout/src/android/support/v7/widget/GridLayout.java
@@ -149,12 +149,12 @@
* See {@link GridLayout.LayoutParams} for a full description of the
* layout parameters used by GridLayout.
*
- * @attr ref android.R.styleable#GridLayout_orientation
- * @attr ref android.R.styleable#GridLayout_rowCount
- * @attr ref android.R.styleable#GridLayout_columnCount
- * @attr ref android.R.styleable#GridLayout_useDefaultMargins
- * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
- * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ * @attr name android:orientation
+ * @attr name android:rowCount
+ * @attr name android:columnCount
+ * @attr name android:useDefaultMargins
+ * @attr name android:rowOrderPreserved
+ * @attr name android:columnOrderPreserved
*/
public class GridLayout extends ViewGroup {
@@ -301,7 +301,7 @@
*
* @see #setOrientation(int)
*
- * @attr ref android.R.styleable#GridLayout_orientation
+ * @attr name android:orientation
*/
public int getOrientation() {
return mOrientation;
@@ -341,7 +341,7 @@
*
* @see #getOrientation()
*
- * @attr ref android.R.styleable#GridLayout_orientation
+ * @attr name android:orientation
*/
public void setOrientation(int orientation) {
if (this.mOrientation != orientation) {
@@ -361,7 +361,7 @@
* @see #setRowCount(int)
* @see LayoutParams#rowSpec
*
- * @attr ref android.R.styleable#GridLayout_rowCount
+ * @attr name android:rowCount
*/
public int getRowCount() {
return mVerticalAxis.getCount();
@@ -376,7 +376,7 @@
* @see #getRowCount()
* @see LayoutParams#rowSpec
*
- * @attr ref android.R.styleable#GridLayout_rowCount
+ * @attr name android:rowCount
*/
public void setRowCount(int rowCount) {
mVerticalAxis.setCount(rowCount);
@@ -394,7 +394,7 @@
* @see #setColumnCount(int)
* @see LayoutParams#columnSpec
*
- * @attr ref android.R.styleable#GridLayout_columnCount
+ * @attr name android:columnCount
*/
public int getColumnCount() {
return mHorizontalAxis.getCount();
@@ -409,7 +409,7 @@
* @see #getColumnCount()
* @see LayoutParams#columnSpec
*
- * @attr ref android.R.styleable#GridLayout_columnCount
+ * @attr name android:columnCount
*/
public void setColumnCount(int columnCount) {
mHorizontalAxis.setCount(columnCount);
@@ -425,7 +425,7 @@
*
* @see #setUseDefaultMargins(boolean)
*
- * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ * @attr name android:useDefaultMargins
*/
public boolean getUseDefaultMargins() {
return mUseDefaultMargins;
@@ -455,7 +455,7 @@
* @see MarginLayoutParams#rightMargin
* @see MarginLayoutParams#bottomMargin
*
- * @attr ref android.R.styleable#GridLayout_useDefaultMargins
+ * @attr name android:useDefaultMargins
*/
public void setUseDefaultMargins(boolean useDefaultMargins) {
this.mUseDefaultMargins = useDefaultMargins;
@@ -472,7 +472,7 @@
*
* @see #setAlignmentMode(int)
*
- * @attr ref android.R.styleable#GridLayout_alignmentMode
+ * @attr name android:alignmentMode
*/
public int getAlignmentMode() {
return mAlignmentMode;
@@ -491,7 +491,7 @@
*
* @see #getAlignmentMode()
*
- * @attr ref android.R.styleable#GridLayout_alignmentMode
+ * @attr name android:alignmentMode
*/
public void setAlignmentMode(int alignmentMode) {
this.mAlignmentMode = alignmentMode;
@@ -506,7 +506,7 @@
*
* @see #setRowOrderPreserved(boolean)
*
- * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ * @attr name android:rowOrderPreserved
*/
public boolean isRowOrderPreserved() {
return mVerticalAxis.isOrderPreserved();
@@ -526,7 +526,7 @@
*
* @see #isRowOrderPreserved()
*
- * @attr ref android.R.styleable#GridLayout_rowOrderPreserved
+ * @attr name android:rowOrderPreserved
*/
public void setRowOrderPreserved(boolean rowOrderPreserved) {
mVerticalAxis.setOrderPreserved(rowOrderPreserved);
@@ -542,7 +542,7 @@
*
* @see #setColumnOrderPreserved(boolean)
*
- * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ * @attr name android:columnOrderPreserved
*/
public boolean isColumnOrderPreserved() {
return mHorizontalAxis.isOrderPreserved();
@@ -562,7 +562,7 @@
*
* @see #isColumnOrderPreserved()
*
- * @attr ref android.R.styleable#GridLayout_columnOrderPreserved
+ * @attr name android:columnOrderPreserved
*/
public void setColumnOrderPreserved(boolean columnOrderPreserved) {
mHorizontalAxis.setOrderPreserved(columnOrderPreserved);
@@ -846,8 +846,14 @@
}
@Override
- protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
- return new LayoutParams(p);
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams lp) {
+ if (lp instanceof LayoutParams) {
+ return new LayoutParams((LayoutParams) lp);
+ } else if (lp instanceof MarginLayoutParams) {
+ return new LayoutParams((MarginLayoutParams) lp);
+ } else {
+ return new LayoutParams(lp);
+ }
}
// Draw grid
@@ -1859,13 +1865,13 @@
* See {@link GridLayout} for a more complete description of the conventions
* used by GridLayout in the interpretation of the properties of this class.
*
- * @attr ref android.R.styleable#GridLayout_Layout_layout_row
- * @attr ref android.R.styleable#GridLayout_Layout_layout_rowSpan
- * @attr ref android.R.styleable#GridLayout_Layout_layout_rowWeight
- * @attr ref android.R.styleable#GridLayout_Layout_layout_column
- * @attr ref android.R.styleable#GridLayout_Layout_layout_columnSpan
- * @attr ref android.R.styleable#GridLayout_Layout_layout_columnWeight
- * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+ * @attr name android:row
+ * @attr name android:rowSpan
+ * @attr name android:rowWeight
+ * @attr name android:column
+ * @attr name android:columnSpan
+ * @attr name android:columnWeight
+ * @attr name android:gravity
*/
public static class LayoutParams extends MarginLayoutParams {
@@ -2054,7 +2060,7 @@
*
* @param gravity the new gravity value
*
- * @attr ref android.R.styleable#GridLayout_Layout_layout_gravity
+ * @attr name android:gravity
*/
public void setGravity(int gravity) {
rowSpec = rowSpec.copyWriteAlignment(getAlignment(gravity, false));
diff --git a/v7/gridlayout/tests/Android.mk b/v7/gridlayout/tests/Android.mk
deleted file mode 100644
index 70017f0..0000000
--- a/v7/gridlayout/tests/Android.mk
+++ /dev/null
@@ -1,39 +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.
-
-LOCAL_PATH := $(call my-dir)
-
-include $(CLEAR_VARS)
-
-LOCAL_MODULE_TAGS := tests
-
-LOCAL_SDK_VERSION := current
-
-LOCAL_SRC_FILES := $(call all-java-files-under, src)
-
-LOCAL_RESOURCE_DIR := \
- $(LOCAL_PATH)/res \
- $(LOCAL_PATH)/../res
-
-LOCAL_STATIC_JAVA_LIBRARIES := \
- android-support-v7-gridlayout \
- android-support-v4
-
-LOCAL_PACKAGE_NAME := GridLayoutTests
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.v7.gridlayout
-
-include $(BUILD_PACKAGE)
-
diff --git a/v7/gridlayout/tests/AndroidManifest.xml b/v7/gridlayout/tests/AndroidManifest.xml
index c55a49a..fc502a5 100644
--- a/v7/gridlayout/tests/AndroidManifest.xml
+++ b/v7/gridlayout/tests/AndroidManifest.xml
@@ -1,30 +1,35 @@
<?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.
+ 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.v7.gridlayout.test">
- <uses-sdk android:minSdkVersion="7"/>
+ <uses-sdk
+ android:minSdkVersion="7"
+ android:targetSdkVersion="23"
+ 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" />
- <activity android:name="android.support.v7.widget.test.GridLayoutTestActivity"/>
+
+ <activity android:name="android.support.v7.widget.GridLayoutTestActivity"/>
</application>
- <instrumentation android:name="android.test.InstrumentationTestRunner"
- android:targetPackage="android.support.v7.gridlayout.test"
- android:label="GridLayout Tests" />
-
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.v7.gridlayout.test"/>
</manifest>
diff --git a/v7/gridlayout/tests/NO_DOCS b/v7/gridlayout/tests/NO_DOCS
new file mode 100644
index 0000000..092a39c
--- /dev/null
+++ b/v7/gridlayout/tests/NO_DOCS
@@ -0,0 +1,17 @@
+# Copyright (C) 2016 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+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/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java
new file mode 100644
index 0000000..89ccac7
--- /dev/null
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTest.java
@@ -0,0 +1,169 @@
+/*
+ * 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.app.Activity;
+import android.app.Instrumentation;
+import android.content.Context;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
+import android.support.v7.gridlayout.test.R;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.util.AttributeSet;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GridLayoutTest {
+ @Rule public final ActivityTestRule<GridLayoutTestActivity> mActivityTestRule;
+
+ private View mLeftView;
+ private View mRightView;
+ private View mGridView;
+
+ public GridLayoutTest() {
+ mActivityTestRule = new ActivityTestRule<>(GridLayoutTestActivity.class);
+ }
+
+ private void setContentView(final int layoutId) {
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final Activity activity = mActivityTestRule.getActivity();
+ activity.setContentView(layoutId);
+ // Now that we've set the content view, find the views we'll be testing
+ mLeftView = activity.findViewById(R.id.leftView);
+ mRightView = activity.findViewById(R.id.rightView);
+ mGridView = activity.findViewById(R.id.gridView);
+ }
+ });
+ instrumentation.waitForIdleSync();
+ }
+
+ @Test
+ public void testUseDefaultMargin() {
+ setContentView(R.layout.use_default_margin_test);
+ int left = mLeftView.getWidth();
+ int right = mRightView.getWidth();
+ int total = mGridView.getWidth();
+ assertTrue("left item should get some width", left > 0);
+ assertTrue("right item should get some width", right > 0);
+ assertTrue("test sanity", total > 0);
+ assertTrue("left view should be almost two times right view " + left + " vs " + right,
+ Math.abs(right * 2 - left) < 2);
+ }
+
+ @Test
+ public void testImplicitFillHorizontal() {
+ setContentView(R.layout.fill_horizontal_test);
+ int left = mLeftView.getWidth();
+ int right = mRightView.getWidth();
+ int total = mGridView.getWidth();
+ assertTrue("left item should get some width", left > 0);
+ assertTrue("right item should get some width", right > 0);
+ assertTrue("test sanity", total > 0);
+ assertTrue("left view should be almost two times right view " + left + " vs " + right,
+ Math.abs(right * 2 - left) < 2);
+ }
+
+ @Test
+ public void testMakeViewGone() {
+ setContentView(R.layout.make_view_gone_test);
+ int left = mLeftView.getWidth();
+ int right = mRightView.getWidth();
+ int total = mGridView.getWidth();
+ assertTrue("left item should get some width", left > 0);
+ assertTrue("right item should get some width", right > 0);
+ assertTrue("test sanity", total > 0);
+ // set second view to gone
+ final Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
+ instrumentation.runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ final View rightView = mActivityTestRule.getActivity().findViewById(R.id.rightView);
+ GridLayout.LayoutParams lp = (GridLayout.LayoutParams) rightView.getLayoutParams();
+ lp.setGravity(Gravity.NO_GRAVITY);
+ rightView.setVisibility(View.GONE);
+ }
+ });
+ instrumentation.waitForIdleSync();
+ left = mActivityTestRule.getActivity().findViewById(R.id.leftView).getWidth();
+ assertEquals(total, left);
+ }
+
+ @Test
+ public void testWrapContentInOtherDirection() {
+ setContentView(R.layout.height_wrap_content_test);
+ int left = mLeftView.getHeight();
+ int right = mRightView.getHeight();
+ int total = mGridView.getHeight();
+ assertTrue("test sanity", left > 0);
+ assertTrue("test sanity", right > 0);
+ assertTrue("test sanity", total > 0);
+ assertTrue("right should be taller than left", right > left);
+ assertTrue("total height should be smaller than what it could be",
+ total < ((ViewGroup) mGridView.getParent()).getHeight());
+ }
+
+ @Test
+ public void testGenerateLayoutParamsFromMarginParams() {
+ MyGridLayout gridLayout = new MyGridLayout(mActivityTestRule.getActivity());
+ ViewGroup.MarginLayoutParams lp = new ViewGroup.MarginLayoutParams(3, 5);
+ lp.leftMargin = 1;
+ lp.topMargin = 2;
+ lp.rightMargin = 3;
+ lp.bottomMargin = 4;
+ GridLayout.LayoutParams generated = gridLayout.generateLayoutParams(lp);
+ assertEquals(3, generated.width);
+ assertEquals(5, generated.height);
+
+ assertEquals(1, generated.leftMargin);
+ assertEquals(2, generated.topMargin);
+ assertEquals(3, generated.rightMargin);
+ assertEquals(4, generated.bottomMargin);
+ }
+
+ private static class MyGridLayout extends GridLayout {
+
+ public MyGridLayout(Context context) {
+ super(context);
+ }
+
+ public MyGridLayout(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public MyGridLayout(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ @Override
+ protected LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return super.generateLayoutParams(p);
+ }
+ }
+}
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java
new file mode 100644
index 0000000..3196afc
--- /dev/null
+++ b/v7/gridlayout/tests/src/android/support/v7/widget/GridLayoutTestActivity.java
@@ -0,0 +1,34 @@
+/*
+ * 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.app.Activity;
+import android.os.Bundle;
+
+public class GridLayoutTestActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+ }
+
+ @Override
+ public void finish() {
+ super.finish();
+ overridePendingTransition(0, 0);
+ }
+}
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
deleted file mode 100644
index 1a88be5..0000000
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTest.java
+++ /dev/null
@@ -1,113 +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.test;
-
-import android.app.Activity;
-import android.os.Debug;
-import android.support.v7.widget.GridLayout;
-import android.test.ActivityInstrumentationTestCase2;
-import android.support.v7.gridlayout.R;
-import android.test.UiThreadTest;
-import android.view.Gravity;
-import android.view.View;
-import android.view.ViewGroup;
-import android.widget.Toast;
-
-/**
- * @hide
- */
-public class GridLayoutTest extends ActivityInstrumentationTestCase2 {
-
- public GridLayoutTest() {
- super("android.support.v7.widget.test", GridLayoutTestActivity.class);
- }
-
- private void setContentView(final int layoutId) throws Throwable {
- final Activity activity = getActivity();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- activity.setContentView(layoutId);
- }
- });
- }
-
- public void testUseDefaultMargin() throws Throwable {
- setContentView(R.layout.use_default_margin_test);
- getInstrumentation().waitForIdleSync();
- int left = getActivity().findViewById(R.id.leftView).getWidth();
- int right = getActivity().findViewById(R.id.rightView).getWidth();
- int total = getActivity().findViewById(R.id.gridView).getWidth();
- assertTrue("left item should get some width", left > 0);
- assertTrue("right item should get some width", right > 0);
- assertTrue("test sanity", total > 0);
- assertTrue("left view should be almost two times right view " + left + " vs " + right,
- Math.abs(right * 2 - left) < 2);
- }
-
- public void testImplicitFillHorizontal() throws Throwable {
- setContentView(R.layout.fill_horizontal_test);
- getInstrumentation().waitForIdleSync();
- int left = getActivity().findViewById(R.id.leftView).getWidth();
- int right = getActivity().findViewById(R.id.rightView).getWidth();
- int total = getActivity().findViewById(R.id.gridView).getWidth();
- assertTrue("left item should get some width", left > 0);
- assertTrue("right item should get some width", right > 0);
- assertTrue("test sanity", total > 0);
- assertTrue("left view should be almost two times right view " + left + " vs " + right,
- Math.abs(right * 2 - left) < 2);
- }
-
- public void testMakeViewGone() throws Throwable {
- setContentView(R.layout.make_view_gone_test);
- getInstrumentation().waitForIdleSync();
- int left = getActivity().findViewById(R.id.leftView).getWidth();
- final int right = getActivity().findViewById(R.id.rightView).getWidth();
- int total = getActivity().findViewById(R.id.gridView).getWidth();
- assertTrue("left item should get some width", left > 0);
- assertTrue("right item should get some width", right > 0);
- assertTrue("test sanity", total > 0);
- // set second view to gone
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- final View rightView = getActivity().findViewById(R.id.rightView);
- GridLayout.LayoutParams lp = (GridLayout.LayoutParams) rightView.getLayoutParams();
- lp.setGravity(Gravity.NO_GRAVITY);
- rightView.setVisibility(View.GONE);
- }
- });
- getInstrumentation().waitForIdleSync();
- left = getActivity().findViewById(R.id.leftView).getWidth();
- assertEquals(total, left);
- }
- public void testWrapContentInOtherDirection() throws Throwable {
- setContentView(R.layout.height_wrap_content_test);
- getInstrumentation().waitForIdleSync();
- int left = getActivity().findViewById(R.id.leftView).getHeight();
- final int right = getActivity().findViewById(R.id.rightView).getHeight();
- final View gridView = getActivity().findViewById(R.id.gridView);
- int total = gridView.getHeight();
- assertTrue("test sanity", left > 0);
- assertTrue("test sanity", right > 0);
- assertTrue("test sanity", total > 0);
- assertTrue("right should be taller than left", right > left);
- assertTrue("total height should be smaller than what it could be",
- total < ((ViewGroup)gridView.getParent()).getHeight());
-
- }
-}
diff --git a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java b/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
deleted file mode 100644
index 8126b38..0000000
--- a/v7/gridlayout/tests/src/android/support/v7/widget/test/GridLayoutTestActivity.java
+++ /dev/null
@@ -1,25 +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.test;
-
-import android.app.Activity;
-
-/**
- * @hide
- */
-public class GridLayoutTestActivity extends Activity {
-}
diff --git a/v7/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index 3b0141a..c0c4085 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -14,19 +14,16 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
+# Build the resources using the latest applicable SDK version.
# We do this here because the final static library must be compiled with an older
-# SDK version than the resources. The resources library and the R class that it
-# contains will not be linked into the final static library.
+# SDK version than the resources.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-mediarouter-res
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res \
- frameworks/support/v7/appcompat/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay \
- --extra-packages android.support.v7.appcompat
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-v7-appcompat
LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -65,19 +62,42 @@
support_module_src_files += $(LOCAL_SRC_FILES)
-# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# A helper sub-library that makes direct use of V24 APIs.
include $(CLEAR_VARS)
+LOCAL_MODULE := android-support-v7-mediarouter-api24
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under, api24)
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
+LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+include $(BUILD_STATIC_JAVA_LIBRARY)
+
+support_module_src_files += $(LOCAL_SRC_FILES)
+
+# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-mediarouter \
+# android-support-v7-appcompat \
+# android-support-v7-palette \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
+include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-mediarouter
LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-jellybean-mr2
-LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
+LOCAL_STATIC_JAVA_LIBRARIES := android-support-v7-mediarouter-api24
+LOCAL_STATIC_ANDROID_LIBRARIES := android-support-v7-mediarouter-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
android-support-v7-appcompat \
- android-support-v7-palette
+ android-support-v7-palette \
+ android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v7/mediarouter/api/23.1.1.txt b/v7/mediarouter/api/23.1.1.txt
new file mode 100644
index 0000000..69fc20e
--- /dev/null
+++ b/v7/mediarouter/api/23.1.1.txt
@@ -0,0 +1,694 @@
+package android.support.v7.app {
+
+ public abstract class ActionBar {
+ ctor public ActionBar();
+ method public abstract void addOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
+ method public abstract void addTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract void addTab(android.support.v7.app.ActionBar.Tab, boolean);
+ method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int);
+ method public abstract void addTab(android.support.v7.app.ActionBar.Tab, int, boolean);
+ method public boolean collapseActionView();
+ method public void dispatchMenuVisibilityChanged(boolean);
+ method public abstract android.view.View getCustomView();
+ method public abstract int getDisplayOptions();
+ method public float getElevation();
+ method public abstract int getHeight();
+ method public int getHideOffset();
+ method public abstract int getNavigationItemCount();
+ method public abstract int getNavigationMode();
+ method public abstract int getSelectedNavigationIndex();
+ method public abstract android.support.v7.app.ActionBar.Tab getSelectedTab();
+ method public abstract java.lang.CharSequence getSubtitle();
+ method public abstract android.support.v7.app.ActionBar.Tab getTabAt(int);
+ method public abstract int getTabCount();
+ method public android.content.Context getThemedContext();
+ method public abstract java.lang.CharSequence getTitle();
+ method public abstract void hide();
+ method public boolean invalidateOptionsMenu();
+ method public boolean isHideOnContentScrollEnabled();
+ method public abstract boolean isShowing();
+ method public boolean isTitleTruncated();
+ method public abstract android.support.v7.app.ActionBar.Tab newTab();
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public boolean onKeyShortcut(int, android.view.KeyEvent);
+ method public boolean onMenuKeyEvent(android.view.KeyEvent);
+ method public boolean openOptionsMenu();
+ method public abstract void removeAllTabs();
+ method public abstract void removeOnMenuVisibilityListener(android.support.v7.app.ActionBar.OnMenuVisibilityListener);
+ method public abstract void removeTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract void removeTabAt(int);
+ method public abstract void selectTab(android.support.v7.app.ActionBar.Tab);
+ method public abstract void setBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public abstract void setCustomView(android.view.View);
+ method public abstract void setCustomView(android.view.View, android.support.v7.app.ActionBar.LayoutParams);
+ method public abstract void setCustomView(int);
+ method public void setDefaultDisplayHomeAsUpEnabled(boolean);
+ method public abstract void setDisplayHomeAsUpEnabled(boolean);
+ method public abstract void setDisplayOptions(int);
+ method public abstract void setDisplayOptions(int, int);
+ method public abstract void setDisplayShowCustomEnabled(boolean);
+ method public abstract void setDisplayShowHomeEnabled(boolean);
+ method public abstract void setDisplayShowTitleEnabled(boolean);
+ method public abstract void setDisplayUseLogoEnabled(boolean);
+ method public void setElevation(float);
+ method public void setHideOffset(int);
+ method public void setHideOnContentScrollEnabled(boolean);
+ method public void setHomeActionContentDescription(java.lang.CharSequence);
+ method public void setHomeActionContentDescription(int);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
+ method public void setHomeAsUpIndicator(int);
+ method public void setHomeButtonEnabled(boolean);
+ method public abstract void setIcon(int);
+ method public abstract void setIcon(android.graphics.drawable.Drawable);
+ method public abstract void setListNavigationCallbacks(android.widget.SpinnerAdapter, android.support.v7.app.ActionBar.OnNavigationListener);
+ method public abstract void setLogo(int);
+ method public abstract void setLogo(android.graphics.drawable.Drawable);
+ method public abstract void setNavigationMode(int);
+ method public abstract void setSelectedNavigationItem(int);
+ method public void setShowHideAnimationEnabled(boolean);
+ method public void setSplitBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public void setStackedBackgroundDrawable(android.graphics.drawable.Drawable);
+ method public abstract void setSubtitle(java.lang.CharSequence);
+ method public abstract void setSubtitle(int);
+ method public abstract void setTitle(java.lang.CharSequence);
+ method public abstract void setTitle(int);
+ method public void setWindowTitle(java.lang.CharSequence);
+ method public abstract void show();
+ method public android.support.v7.view.ActionMode startActionMode(android.support.v7.view.ActionMode.Callback);
+ field public static final int DISPLAY_HOME_AS_UP = 4; // 0x4
+ field public static final int DISPLAY_SHOW_CUSTOM = 16; // 0x10
+ field public static final int DISPLAY_SHOW_HOME = 2; // 0x2
+ field public static final int DISPLAY_SHOW_TITLE = 8; // 0x8
+ field public static final int DISPLAY_USE_LOGO = 1; // 0x1
+ field public static final int NAVIGATION_MODE_LIST = 1; // 0x1
+ field public static final int NAVIGATION_MODE_STANDARD = 0; // 0x0
+ field public static final int NAVIGATION_MODE_TABS = 2; // 0x2
+ }
+
+ public static class ActionBar.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public ActionBar.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public ActionBar.LayoutParams(int, int);
+ ctor public ActionBar.LayoutParams(int, int, int);
+ ctor public ActionBar.LayoutParams(int);
+ ctor public ActionBar.LayoutParams(android.support.v7.app.ActionBar.LayoutParams);
+ ctor public ActionBar.LayoutParams(android.view.ViewGroup.LayoutParams);
+ field public int gravity;
+ }
+
+ public static abstract interface ActionBar.OnMenuVisibilityListener {
+ method public abstract void onMenuVisibilityChanged(boolean);
+ }
+
+ public static abstract interface ActionBar.OnNavigationListener {
+ method public abstract boolean onNavigationItemSelected(int, long);
+ }
+
+ public static abstract class ActionBar.Tab {
+ ctor public ActionBar.Tab();
+ method public abstract java.lang.CharSequence getContentDescription();
+ method public abstract android.view.View getCustomView();
+ method public abstract android.graphics.drawable.Drawable getIcon();
+ method public abstract int getPosition();
+ method public abstract java.lang.Object getTag();
+ method public abstract java.lang.CharSequence getText();
+ method public abstract void select();
+ method public abstract android.support.v7.app.ActionBar.Tab setContentDescription(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setContentDescription(java.lang.CharSequence);
+ method public abstract android.support.v7.app.ActionBar.Tab setCustomView(android.view.View);
+ method public abstract android.support.v7.app.ActionBar.Tab setCustomView(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setIcon(android.graphics.drawable.Drawable);
+ method public abstract android.support.v7.app.ActionBar.Tab setIcon(int);
+ method public abstract android.support.v7.app.ActionBar.Tab setTabListener(android.support.v7.app.ActionBar.TabListener);
+ method public abstract android.support.v7.app.ActionBar.Tab setTag(java.lang.Object);
+ method public abstract android.support.v7.app.ActionBar.Tab setText(java.lang.CharSequence);
+ method public abstract android.support.v7.app.ActionBar.Tab setText(int);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static abstract interface ActionBar.TabListener {
+ method public abstract void onTabReselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ method public abstract void onTabSelected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ method public abstract void onTabUnselected(android.support.v7.app.ActionBar.Tab, android.support.v4.app.FragmentTransaction);
+ }
+
+ public class ActionBarDrawerToggle implements android.support.v4.widget.DrawerLayout.DrawerListener {
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, int, int);
+ ctor public ActionBarDrawerToggle(android.app.Activity, android.support.v4.widget.DrawerLayout, android.support.v7.widget.Toolbar, int, int);
+ method public android.view.View.OnClickListener getToolbarNavigationClickListener();
+ method public boolean isDrawerIndicatorEnabled();
+ method public void onConfigurationChanged(android.content.res.Configuration);
+ method public void onDrawerClosed(android.view.View);
+ method public void onDrawerOpened(android.view.View);
+ method public void onDrawerSlide(android.view.View, float);
+ method public void onDrawerStateChanged(int);
+ method public boolean onOptionsItemSelected(android.view.MenuItem);
+ method public void setDrawerIndicatorEnabled(boolean);
+ method public void setHomeAsUpIndicator(android.graphics.drawable.Drawable);
+ method public void setHomeAsUpIndicator(int);
+ method public void setToolbarNavigationClickListener(android.view.View.OnClickListener);
+ method public void syncState();
+ }
+
+ public static abstract interface ActionBarDrawerToggle.Delegate {
+ method public abstract android.content.Context getActionBarThemedContext();
+ method public abstract android.graphics.drawable.Drawable getThemeUpIndicator();
+ method public abstract boolean isNavigationVisible();
+ method public abstract void setActionBarDescription(int);
+ method public abstract void setActionBarUpIndicator(android.graphics.drawable.Drawable, int);
+ }
+
+ public class AlertDialog extends android.support.v7.app.AppCompatDialog implements android.content.DialogInterface {
+ ctor protected AlertDialog(android.content.Context);
+ ctor protected AlertDialog(android.content.Context, int);
+ ctor protected AlertDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
+ method public android.widget.Button getButton(int);
+ method public android.widget.ListView getListView();
+ method public void setButton(int, java.lang.CharSequence, android.os.Message);
+ method public void setButton(int, java.lang.CharSequence, android.content.DialogInterface.OnClickListener);
+ method public void setCustomTitle(android.view.View);
+ method public void setIcon(int);
+ method public void setIcon(android.graphics.drawable.Drawable);
+ method public void setIconAttribute(int);
+ method public void setMessage(java.lang.CharSequence);
+ method public void setView(android.view.View);
+ method public void setView(android.view.View, int, int, int, int);
+ }
+
+ public abstract interface AppCompatCallback {
+ method public abstract void onSupportActionModeFinished(android.support.v7.view.ActionMode);
+ method public abstract void onSupportActionModeStarted(android.support.v7.view.ActionMode);
+ method public abstract android.support.v7.view.ActionMode onWindowStartingSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ }
+
+ public abstract class AppCompatDelegate {
+ method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public static android.support.v7.app.AppCompatDelegate create(android.app.Activity, android.support.v7.app.AppCompatCallback);
+ method public static android.support.v7.app.AppCompatDelegate create(android.app.Dialog, android.support.v7.app.AppCompatCallback);
+ method public abstract android.view.View createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
+ method public abstract android.view.MenuInflater getMenuInflater();
+ method public abstract android.support.v7.app.ActionBar getSupportActionBar();
+ method public abstract boolean hasWindowFeature(int);
+ method public abstract void installViewFactory();
+ method public abstract void invalidateOptionsMenu();
+ method public abstract boolean isHandleNativeActionModesEnabled();
+ method public abstract void onConfigurationChanged(android.content.res.Configuration);
+ method public abstract void onCreate(android.os.Bundle);
+ method public abstract void onDestroy();
+ method public abstract void onPostCreate(android.os.Bundle);
+ method public abstract void onPostResume();
+ method public abstract void onStop();
+ method public abstract boolean requestWindowFeature(int);
+ method public abstract void setContentView(android.view.View);
+ method public abstract void setContentView(int);
+ method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public abstract void setHandleNativeActionModesEnabled(boolean);
+ method public abstract void setSupportActionBar(android.support.v7.widget.Toolbar);
+ method public abstract void setTitle(java.lang.CharSequence);
+ method public abstract android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
+ field public static final int FEATURE_SUPPORT_ACTION_BAR = 108; // 0x6c
+ field public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 109; // 0x6d
+ }
+
+ public class AppCompatDialog extends android.app.Dialog implements android.support.v7.app.AppCompatCallback {
+ ctor public AppCompatDialog(android.content.Context);
+ ctor public AppCompatDialog(android.content.Context, int);
+ ctor protected AppCompatDialog(android.content.Context, boolean, android.content.DialogInterface.OnCancelListener);
+ method public android.support.v7.app.AppCompatDelegate getDelegate();
+ method public android.support.v7.app.ActionBar getSupportActionBar();
+ method public void onSupportActionModeFinished(android.support.v7.view.ActionMode);
+ method public void onSupportActionModeStarted(android.support.v7.view.ActionMode);
+ method public android.support.v7.view.ActionMode onWindowStartingSupportActionMode(android.support.v7.view.ActionMode.Callback);
+ method public boolean supportRequestWindowFeature(int);
+ }
+
+ public class MediaRouteActionProvider extends android.support.v4.view.ActionProvider {
+ ctor public MediaRouteActionProvider(android.content.Context);
+ method public android.support.v7.app.MediaRouteDialogFactory getDialogFactory();
+ method public android.support.v7.app.MediaRouteButton getMediaRouteButton();
+ method public android.support.v7.media.MediaRouteSelector getRouteSelector();
+ method public android.view.View onCreateActionView();
+ method public android.support.v7.app.MediaRouteButton onCreateMediaRouteButton();
+ method public void setDialogFactory(android.support.v7.app.MediaRouteDialogFactory);
+ method public void setRouteSelector(android.support.v7.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteButton extends android.view.View {
+ ctor public MediaRouteButton(android.content.Context);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet);
+ ctor public MediaRouteButton(android.content.Context, android.util.AttributeSet, int);
+ method public android.support.v7.app.MediaRouteDialogFactory getDialogFactory();
+ method public android.support.v7.media.MediaRouteSelector getRouteSelector();
+ method public void onAttachedToWindow();
+ method public void onDetachedFromWindow();
+ method public void setDialogFactory(android.support.v7.app.MediaRouteDialogFactory);
+ method public void setRemoteIndicatorDrawable(android.graphics.drawable.Drawable);
+ method public void setRouteSelector(android.support.v7.media.MediaRouteSelector);
+ method public boolean showDialog();
+ }
+
+ public class MediaRouteChooserDialog extends android.app.Dialog {
+ ctor public MediaRouteChooserDialog(android.content.Context);
+ ctor public MediaRouteChooserDialog(android.content.Context, int);
+ method public android.support.v7.media.MediaRouteSelector getRouteSelector();
+ method public boolean onFilterRoute(android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onFilterRoutes(java.util.List<android.support.v7.media.MediaRouter.RouteInfo>);
+ method public void refreshRoutes();
+ method public void setRouteSelector(android.support.v7.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteChooserDialogFragment extends android.support.v4.app.DialogFragment {
+ ctor public MediaRouteChooserDialogFragment();
+ method public android.support.v7.media.MediaRouteSelector getRouteSelector();
+ method public android.support.v7.app.MediaRouteChooserDialog onCreateChooserDialog(android.content.Context, android.os.Bundle);
+ method public void setRouteSelector(android.support.v7.media.MediaRouteSelector);
+ }
+
+ public class MediaRouteControllerDialog extends android.support.v7.app.AlertDialog {
+ ctor public MediaRouteControllerDialog(android.content.Context);
+ ctor public MediaRouteControllerDialog(android.content.Context, int);
+ method public android.view.View getMediaControlView();
+ method public android.support.v4.media.session.MediaSessionCompat.Token getMediaSession();
+ method public android.support.v7.media.MediaRouter.RouteInfo getRoute();
+ method public boolean isVolumeControlEnabled();
+ method public android.view.View onCreateMediaControlView(android.os.Bundle);
+ method public void setVolumeControlEnabled(boolean);
+ }
+
+ public class MediaRouteControllerDialogFragment extends android.support.v4.app.DialogFragment {
+ ctor public MediaRouteControllerDialogFragment();
+ method public android.support.v7.app.MediaRouteControllerDialog onCreateControllerDialog(android.content.Context, android.os.Bundle);
+ }
+
+ public class MediaRouteDialogFactory {
+ ctor public MediaRouteDialogFactory();
+ method public static android.support.v7.app.MediaRouteDialogFactory getDefault();
+ method public android.support.v7.app.MediaRouteChooserDialogFragment onCreateChooserDialogFragment();
+ method public android.support.v7.app.MediaRouteControllerDialogFragment onCreateControllerDialogFragment();
+ }
+
+ public class MediaRouteDiscoveryFragment extends android.support.v4.app.Fragment {
+ ctor public MediaRouteDiscoveryFragment();
+ method public android.support.v7.media.MediaRouter getMediaRouter();
+ method public android.support.v7.media.MediaRouteSelector getRouteSelector();
+ method public android.support.v7.media.MediaRouter.Callback onCreateCallback();
+ method public int onPrepareCallbackFlags();
+ method public void setRouteSelector(android.support.v7.media.MediaRouteSelector);
+ }
+
+}
+
+package android.support.v7.media {
+
+ public final class MediaControlIntent {
+ field public static final java.lang.String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ field public static final java.lang.String ACTION_ENQUEUE = "android.media.intent.action.ENQUEUE";
+ field public static final java.lang.String ACTION_GET_SESSION_STATUS = "android.media.intent.action.GET_SESSION_STATUS";
+ field public static final java.lang.String ACTION_GET_STATUS = "android.media.intent.action.GET_STATUS";
+ field public static final java.lang.String ACTION_PAUSE = "android.media.intent.action.PAUSE";
+ field public static final java.lang.String ACTION_PLAY = "android.media.intent.action.PLAY";
+ field public static final java.lang.String ACTION_REMOVE = "android.media.intent.action.REMOVE";
+ field public static final java.lang.String ACTION_RESUME = "android.media.intent.action.RESUME";
+ field public static final java.lang.String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final java.lang.String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
+ field public static final java.lang.String ACTION_STOP = "android.media.intent.action.STOP";
+ field public static final java.lang.String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
+ field public static final java.lang.String CATEGORY_LIVE_VIDEO = "android.media.intent.category.LIVE_VIDEO";
+ field public static final java.lang.String CATEGORY_REMOTE_PLAYBACK = "android.media.intent.category.REMOTE_PLAYBACK";
+ field public static final int ERROR_INVALID_ITEM_ID = 3; // 0x3
+ field public static final int ERROR_INVALID_SESSION_ID = 2; // 0x2
+ field public static final int ERROR_UNKNOWN = 0; // 0x0
+ field public static final int ERROR_UNSUPPORTED_OPERATION = 1; // 0x1
+ field public static final java.lang.String EXTRA_ERROR_CODE = "android.media.intent.extra.ERROR_CODE";
+ field public static final java.lang.String EXTRA_ITEM_CONTENT_POSITION = "android.media.intent.extra.ITEM_POSITION";
+ field public static final java.lang.String EXTRA_ITEM_HTTP_HEADERS = "android.media.intent.extra.HTTP_HEADERS";
+ field public static final java.lang.String EXTRA_ITEM_ID = "android.media.intent.extra.ITEM_ID";
+ field public static final java.lang.String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
+ field public static final java.lang.String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
+ field public static final java.lang.String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final java.lang.String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
+ field public static final java.lang.String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
+ field public static final java.lang.String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
+ }
+
+ public final class MediaItemMetadata {
+ field public static final java.lang.String KEY_ALBUM_ARTIST = "android.media.metadata.ALBUM_ARTIST";
+ field public static final java.lang.String KEY_ALBUM_TITLE = "android.media.metadata.ALBUM_TITLE";
+ field public static final java.lang.String KEY_ARTIST = "android.media.metadata.ARTIST";
+ field public static final java.lang.String KEY_ARTWORK_URI = "android.media.metadata.ARTWORK_URI";
+ field public static final java.lang.String KEY_AUTHOR = "android.media.metadata.AUTHOR";
+ field public static final java.lang.String KEY_COMPOSER = "android.media.metadata.COMPOSER";
+ field public static final java.lang.String KEY_DISC_NUMBER = "android.media.metadata.DISC_NUMBER";
+ field public static final java.lang.String KEY_DURATION = "android.media.metadata.DURATION";
+ field public static final java.lang.String KEY_TITLE = "android.media.metadata.TITLE";
+ field public static final java.lang.String KEY_TRACK_NUMBER = "android.media.metadata.TRACK_NUMBER";
+ field public static final java.lang.String KEY_YEAR = "android.media.metadata.YEAR";
+ }
+
+ public final class MediaItemStatus {
+ method public android.os.Bundle asBundle();
+ method public static android.support.v7.media.MediaItemStatus fromBundle(android.os.Bundle);
+ method public long getContentDuration();
+ method public long getContentPosition();
+ method public android.os.Bundle getExtras();
+ method public int getPlaybackState();
+ method public long getTimestamp();
+ field public static final java.lang.String EXTRA_HTTP_RESPONSE_HEADERS = "android.media.status.extra.HTTP_RESPONSE_HEADERS";
+ field public static final java.lang.String EXTRA_HTTP_STATUS_CODE = "android.media.status.extra.HTTP_STATUS_CODE";
+ field public static final int PLAYBACK_STATE_BUFFERING = 3; // 0x3
+ field public static final int PLAYBACK_STATE_CANCELED = 5; // 0x5
+ field public static final int PLAYBACK_STATE_ERROR = 7; // 0x7
+ field public static final int PLAYBACK_STATE_FINISHED = 4; // 0x4
+ field public static final int PLAYBACK_STATE_INVALIDATED = 6; // 0x6
+ field public static final int PLAYBACK_STATE_PAUSED = 2; // 0x2
+ field public static final int PLAYBACK_STATE_PENDING = 0; // 0x0
+ field public static final int PLAYBACK_STATE_PLAYING = 1; // 0x1
+ }
+
+ public static final class MediaItemStatus.Builder {
+ ctor public MediaItemStatus.Builder(int);
+ ctor public MediaItemStatus.Builder(android.support.v7.media.MediaItemStatus);
+ method public android.support.v7.media.MediaItemStatus build();
+ method public android.support.v7.media.MediaItemStatus.Builder setContentDuration(long);
+ method public android.support.v7.media.MediaItemStatus.Builder setContentPosition(long);
+ method public android.support.v7.media.MediaItemStatus.Builder setExtras(android.os.Bundle);
+ method public android.support.v7.media.MediaItemStatus.Builder setPlaybackState(int);
+ method public android.support.v7.media.MediaItemStatus.Builder setTimestamp(long);
+ }
+
+ public final class MediaRouteDescriptor {
+ method public android.os.Bundle asBundle();
+ method public boolean canDisconnectAndKeepPlaying();
+ method public static android.support.v7.media.MediaRouteDescriptor fromBundle(android.os.Bundle);
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter> getControlFilters();
+ method public java.lang.String getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle getExtras();
+ method public java.util.List<java.lang.String> getGroupMemberIds();
+ method public android.net.Uri getIconUri();
+ method public java.lang.String getId();
+ method public java.lang.String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public int getPresentationDisplayId();
+ method public android.content.IntentSender getSettingsActivity();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public deprecated boolean isConnecting();
+ method public boolean isEnabled();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteDescriptor.Builder {
+ ctor public MediaRouteDescriptor.Builder(java.lang.String, java.lang.String);
+ ctor public MediaRouteDescriptor.Builder(android.support.v7.media.MediaRouteDescriptor);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder addControlFilter(android.content.IntentFilter);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder addControlFilters(java.util.Collection<android.content.IntentFilter>);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder addGroupMemberId(java.lang.String);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder addGroupMemberIds(java.util.Collection<java.lang.String>);
+ method public android.support.v7.media.MediaRouteDescriptor build();
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setCanDisconnect(boolean);
+ method public deprecated android.support.v7.media.MediaRouteDescriptor.Builder setConnecting(boolean);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setConnectionState(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setDescription(java.lang.String);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setDeviceType(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setEnabled(boolean);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setExtras(android.os.Bundle);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setIconUri(android.net.Uri);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setId(java.lang.String);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setName(java.lang.String);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setPlaybackStream(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setPlaybackType(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setPresentationDisplayId(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setSettingsActivity(android.content.IntentSender);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setVolume(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setVolumeHandling(int);
+ method public android.support.v7.media.MediaRouteDescriptor.Builder setVolumeMax(int);
+ }
+
+ public final class MediaRouteDiscoveryRequest {
+ ctor public MediaRouteDiscoveryRequest(android.support.v7.media.MediaRouteSelector, boolean);
+ method public android.os.Bundle asBundle();
+ method public static android.support.v7.media.MediaRouteDiscoveryRequest fromBundle(android.os.Bundle);
+ method public android.support.v7.media.MediaRouteSelector getSelector();
+ method public boolean isActiveScan();
+ method public boolean isValid();
+ }
+
+ public abstract class MediaRouteProvider {
+ ctor public MediaRouteProvider(android.content.Context);
+ method public final android.content.Context getContext();
+ method public final android.support.v7.media.MediaRouteProviderDescriptor getDescriptor();
+ method public final android.support.v7.media.MediaRouteDiscoveryRequest getDiscoveryRequest();
+ method public final android.os.Handler getHandler();
+ method public final android.support.v7.media.MediaRouteProvider.ProviderMetadata getMetadata();
+ method public android.support.v7.media.MediaRouteProvider.RouteController onCreateRouteController(java.lang.String);
+ method public void onDiscoveryRequestChanged(android.support.v7.media.MediaRouteDiscoveryRequest);
+ method public final void setCallback(android.support.v7.media.MediaRouteProvider.Callback);
+ method public final void setDescriptor(android.support.v7.media.MediaRouteProviderDescriptor);
+ method public final void setDiscoveryRequest(android.support.v7.media.MediaRouteDiscoveryRequest);
+ }
+
+ public static abstract class MediaRouteProvider.Callback {
+ ctor public MediaRouteProvider.Callback();
+ method public void onDescriptorChanged(android.support.v7.media.MediaRouteProvider, android.support.v7.media.MediaRouteProviderDescriptor);
+ }
+
+ public static final class MediaRouteProvider.ProviderMetadata {
+ method public android.content.ComponentName getComponentName();
+ method public java.lang.String getPackageName();
+ }
+
+ public static abstract class MediaRouteProvider.RouteController {
+ ctor public MediaRouteProvider.RouteController();
+ method public boolean onControlRequest(android.content.Intent, android.support.v7.media.MediaRouter.ControlRequestCallback);
+ method public void onRelease();
+ method public void onSelect();
+ method public void onSetVolume(int);
+ method public void onUnselect();
+ method public void onUnselect(int);
+ method public void onUpdateVolume(int);
+ }
+
+ public final class MediaRouteProviderDescriptor {
+ method public android.os.Bundle asBundle();
+ method public static android.support.v7.media.MediaRouteProviderDescriptor fromBundle(android.os.Bundle);
+ method public java.util.List<android.support.v7.media.MediaRouteDescriptor> getRoutes();
+ method public boolean isValid();
+ }
+
+ public static final class MediaRouteProviderDescriptor.Builder {
+ ctor public MediaRouteProviderDescriptor.Builder();
+ ctor public MediaRouteProviderDescriptor.Builder(android.support.v7.media.MediaRouteProviderDescriptor);
+ method public android.support.v7.media.MediaRouteProviderDescriptor.Builder addRoute(android.support.v7.media.MediaRouteDescriptor);
+ method public android.support.v7.media.MediaRouteProviderDescriptor.Builder addRoutes(java.util.Collection<android.support.v7.media.MediaRouteDescriptor>);
+ method public android.support.v7.media.MediaRouteProviderDescriptor build();
+ }
+
+ public abstract class MediaRouteProviderService extends android.app.Service {
+ ctor public MediaRouteProviderService();
+ method public android.support.v7.media.MediaRouteProvider getMediaRouteProvider();
+ method public android.os.IBinder onBind(android.content.Intent);
+ method public abstract android.support.v7.media.MediaRouteProvider onCreateMediaRouteProvider();
+ field public static final java.lang.String SERVICE_INTERFACE = "android.media.MediaRouteProviderService";
+ }
+
+ public final class MediaRouteSelector {
+ method public android.os.Bundle asBundle();
+ method public boolean contains(android.support.v7.media.MediaRouteSelector);
+ method public static android.support.v7.media.MediaRouteSelector fromBundle(android.os.Bundle);
+ method public java.util.List<java.lang.String> getControlCategories();
+ method public boolean hasControlCategory(java.lang.String);
+ method public boolean isEmpty();
+ method public boolean isValid();
+ method public boolean matchesControlFilters(java.util.List<android.content.IntentFilter>);
+ field public static final android.support.v7.media.MediaRouteSelector EMPTY;
+ }
+
+ public static final class MediaRouteSelector.Builder {
+ ctor public MediaRouteSelector.Builder();
+ ctor public MediaRouteSelector.Builder(android.support.v7.media.MediaRouteSelector);
+ method public android.support.v7.media.MediaRouteSelector.Builder addControlCategories(java.util.Collection<java.lang.String>);
+ method public android.support.v7.media.MediaRouteSelector.Builder addControlCategory(java.lang.String);
+ method public android.support.v7.media.MediaRouteSelector.Builder addSelector(android.support.v7.media.MediaRouteSelector);
+ method public android.support.v7.media.MediaRouteSelector build();
+ }
+
+ public final class MediaRouter {
+ method public void addCallback(android.support.v7.media.MediaRouteSelector, android.support.v7.media.MediaRouter.Callback);
+ method public void addCallback(android.support.v7.media.MediaRouteSelector, android.support.v7.media.MediaRouter.Callback, int);
+ method public void addProvider(android.support.v7.media.MediaRouteProvider);
+ method public void addRemoteControlClient(java.lang.Object);
+ method public android.support.v7.media.MediaRouter.RouteInfo getDefaultRoute();
+ method public static android.support.v7.media.MediaRouter getInstance(android.content.Context);
+ method public android.support.v4.media.session.MediaSessionCompat.Token getMediaSessionToken();
+ method public java.util.List<android.support.v7.media.MediaRouter.ProviderInfo> getProviders();
+ method public java.util.List<android.support.v7.media.MediaRouter.RouteInfo> getRoutes();
+ method public android.support.v7.media.MediaRouter.RouteInfo getSelectedRoute();
+ method public boolean isRouteAvailable(android.support.v7.media.MediaRouteSelector, int);
+ method public void removeCallback(android.support.v7.media.MediaRouter.Callback);
+ method public void removeProvider(android.support.v7.media.MediaRouteProvider);
+ method public void removeRemoteControlClient(java.lang.Object);
+ method public void selectRoute(android.support.v7.media.MediaRouter.RouteInfo);
+ method public void setMediaSession(java.lang.Object);
+ method public void setMediaSessionCompat(android.support.v4.media.session.MediaSessionCompat);
+ method public void unselect(int);
+ method public android.support.v7.media.MediaRouter.RouteInfo updateSelectedRoute(android.support.v7.media.MediaRouteSelector);
+ field public static final int AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE = 1; // 0x1
+ field public static final int AVAILABILITY_FLAG_REQUIRE_MATCH = 2; // 0x2
+ field public static final int CALLBACK_FLAG_FORCE_DISCOVERY = 8; // 0x8
+ field public static final int CALLBACK_FLAG_PERFORM_ACTIVE_SCAN = 1; // 0x1
+ field public static final int CALLBACK_FLAG_REQUEST_DISCOVERY = 4; // 0x4
+ field public static final int CALLBACK_FLAG_UNFILTERED_EVENTS = 2; // 0x2
+ field public static final int UNSELECT_REASON_DISCONNECTED = 1; // 0x1
+ field public static final int UNSELECT_REASON_ROUTE_CHANGED = 3; // 0x3
+ field public static final int UNSELECT_REASON_STOPPED = 2; // 0x2
+ field public static final int UNSELECT_REASON_UNKNOWN = 0; // 0x0
+ }
+
+ public static abstract class MediaRouter.Callback {
+ ctor public MediaRouter.Callback();
+ method public void onProviderAdded(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.ProviderInfo);
+ method public void onProviderChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.ProviderInfo);
+ method public void onProviderRemoved(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.ProviderInfo);
+ method public void onRouteAdded(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRoutePresentationDisplayChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteRemoved(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteSelected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteUnselected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteVolumeChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ }
+
+ public static abstract class MediaRouter.ControlRequestCallback {
+ ctor public MediaRouter.ControlRequestCallback();
+ method public void onError(java.lang.String, android.os.Bundle);
+ method public void onResult(android.os.Bundle);
+ }
+
+ public static final class MediaRouter.ProviderInfo {
+ method public android.content.ComponentName getComponentName();
+ method public java.lang.String getPackageName();
+ method public android.support.v7.media.MediaRouteProvider getProviderInstance();
+ method public java.util.List<android.support.v7.media.MediaRouter.RouteInfo> getRoutes();
+ }
+
+ public static class MediaRouter.RouteGroup extends android.support.v7.media.MediaRouter.RouteInfo {
+ method public android.support.v7.media.MediaRouter.RouteInfo getRouteAt(int);
+ method public int getRouteCount();
+ method public java.util.List<android.support.v7.media.MediaRouter.RouteInfo> getRoutes();
+ }
+
+ public static class MediaRouter.RouteInfo {
+ method public boolean canDisconnect();
+ method public int getConnectionState();
+ method public java.util.List<android.content.IntentFilter> getControlFilters();
+ method public java.lang.String getDescription();
+ method public int getDeviceType();
+ method public android.os.Bundle getExtras();
+ method public android.net.Uri getIconUri();
+ method public java.lang.String getId();
+ method public java.lang.String getName();
+ method public int getPlaybackStream();
+ method public int getPlaybackType();
+ method public android.view.Display getPresentationDisplay();
+ method public android.support.v7.media.MediaRouter.ProviderInfo getProvider();
+ method public android.content.IntentSender getSettingsIntent();
+ method public int getVolume();
+ method public int getVolumeHandling();
+ method public int getVolumeMax();
+ method public boolean isConnecting();
+ method public boolean isDefault();
+ method public boolean isEnabled();
+ method public boolean isSelected();
+ method public boolean matchesSelector(android.support.v7.media.MediaRouteSelector);
+ method public void requestSetVolume(int);
+ method public void requestUpdateVolume(int);
+ method public void select();
+ method public void sendControlRequest(android.content.Intent, android.support.v7.media.MediaRouter.ControlRequestCallback);
+ method public boolean supportsControlAction(java.lang.String, java.lang.String);
+ method public boolean supportsControlCategory(java.lang.String);
+ method public boolean supportsControlRequest(android.content.Intent);
+ field public static final int CONNECTION_STATE_CONNECTED = 2; // 0x2
+ field public static final int CONNECTION_STATE_CONNECTING = 1; // 0x1
+ field public static final int CONNECTION_STATE_DISCONNECTED = 0; // 0x0
+ field public static final int DEVICE_TYPE_SPEAKER = 2; // 0x2
+ field public static final int DEVICE_TYPE_TV = 1; // 0x1
+ field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
+ field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
+ field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
+ field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+ }
+
+ public final class MediaSessionStatus {
+ method public android.os.Bundle asBundle();
+ method public static android.support.v7.media.MediaSessionStatus fromBundle(android.os.Bundle);
+ method public android.os.Bundle getExtras();
+ method public int getSessionState();
+ method public long getTimestamp();
+ method public boolean isQueuePaused();
+ field public static final int SESSION_STATE_ACTIVE = 0; // 0x0
+ field public static final int SESSION_STATE_ENDED = 1; // 0x1
+ field public static final int SESSION_STATE_INVALIDATED = 2; // 0x2
+ }
+
+ public static final class MediaSessionStatus.Builder {
+ ctor public MediaSessionStatus.Builder(int);
+ ctor public MediaSessionStatus.Builder(android.support.v7.media.MediaSessionStatus);
+ method public android.support.v7.media.MediaSessionStatus build();
+ method public android.support.v7.media.MediaSessionStatus.Builder setExtras(android.os.Bundle);
+ method public android.support.v7.media.MediaSessionStatus.Builder setQueuePaused(boolean);
+ method public android.support.v7.media.MediaSessionStatus.Builder setSessionState(int);
+ method public android.support.v7.media.MediaSessionStatus.Builder setTimestamp(long);
+ }
+
+ public class RemotePlaybackClient {
+ ctor public RemotePlaybackClient(android.content.Context, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void endSession(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void enqueue(android.net.Uri, java.lang.String, android.os.Bundle, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public java.lang.String getSessionId();
+ method public void getSessionStatus(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void getStatus(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public boolean hasSession();
+ method public boolean isQueuingSupported();
+ method public boolean isRemotePlaybackSupported();
+ method public boolean isSessionManagementSupported();
+ method public void pause(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void play(android.net.Uri, java.lang.String, android.os.Bundle, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public void release();
+ method public void remove(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public void resume(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void seek(java.lang.String, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public void setSessionId(java.lang.String);
+ method public void setStatusCallback(android.support.v7.media.RemotePlaybackClient.StatusCallback);
+ method public void startSession(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void stop(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ }
+
+ public static abstract class RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ActionCallback();
+ method public void onError(java.lang.String, int, android.os.Bundle);
+ }
+
+ public static abstract class RemotePlaybackClient.ItemActionCallback extends android.support.v7.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.ItemActionCallback();
+ method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus, java.lang.String, android.support.v7.media.MediaItemStatus);
+ }
+
+ public static abstract class RemotePlaybackClient.SessionActionCallback extends android.support.v7.media.RemotePlaybackClient.ActionCallback {
+ ctor public RemotePlaybackClient.SessionActionCallback();
+ method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus);
+ }
+
+ public static abstract class RemotePlaybackClient.StatusCallback {
+ ctor public RemotePlaybackClient.StatusCallback();
+ method public void onItemStatusChanged(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus, java.lang.String, android.support.v7.media.MediaItemStatus);
+ method public void onSessionChanged(java.lang.String);
+ method public void onSessionStatusChanged(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus);
+ }
+
+}
+
diff --git a/v7/mediarouter/api/current.txt b/v7/mediarouter/api/current.txt
index a20f8f8..f8a6770 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -178,7 +178,7 @@
method public android.support.v7.app.AlertDialog.Builder setIcon(int);
method public android.support.v7.app.AlertDialog.Builder setIcon(android.graphics.drawable.Drawable);
method public android.support.v7.app.AlertDialog.Builder setIconAttribute(int);
- method public android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
+ method public deprecated android.support.v7.app.AlertDialog.Builder setInverseBackgroundForced(boolean);
method public android.support.v7.app.AlertDialog.Builder setItems(int, android.content.DialogInterface.OnClickListener);
method public android.support.v7.app.AlertDialog.Builder setItems(java.lang.CharSequence[], android.content.DialogInterface.OnClickListener);
method public android.support.v7.app.AlertDialog.Builder setMessage(int);
@@ -215,33 +215,45 @@
public abstract class AppCompatDelegate {
method public abstract void addContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public abstract boolean applyDayNight();
method public static android.support.v7.app.AppCompatDelegate create(android.app.Activity, android.support.v7.app.AppCompatCallback);
method public static android.support.v7.app.AppCompatDelegate create(android.app.Dialog, android.support.v7.app.AppCompatCallback);
method public abstract android.view.View createView(android.view.View, java.lang.String, android.content.Context, android.util.AttributeSet);
+ method public abstract android.view.View findViewById(int);
+ method public static int getDefaultNightMode();
method public abstract android.support.v7.app.ActionBarDrawerToggle.Delegate getDrawerToggleDelegate();
method public abstract android.view.MenuInflater getMenuInflater();
method public abstract android.support.v7.app.ActionBar getSupportActionBar();
method public abstract boolean hasWindowFeature(int);
method public abstract void installViewFactory();
method public abstract void invalidateOptionsMenu();
+ method public static boolean isCompatVectorFromResourcesEnabled();
method public abstract boolean isHandleNativeActionModesEnabled();
method public abstract void onConfigurationChanged(android.content.res.Configuration);
method public abstract void onCreate(android.os.Bundle);
method public abstract void onDestroy();
method public abstract void onPostCreate(android.os.Bundle);
method public abstract void onPostResume();
+ method public abstract void onSaveInstanceState(android.os.Bundle);
method public abstract void onStop();
method public abstract boolean requestWindowFeature(int);
+ method public static void setCompatVectorFromResourcesEnabled(boolean);
method public abstract void setContentView(android.view.View);
method public abstract void setContentView(int);
method public abstract void setContentView(android.view.View, android.view.ViewGroup.LayoutParams);
+ method public static void setDefaultNightMode(int);
method public abstract void setHandleNativeActionModesEnabled(boolean);
+ method public abstract void setLocalNightMode(int);
method public abstract void setSupportActionBar(android.support.v7.widget.Toolbar);
method public abstract void setTitle(java.lang.CharSequence);
method public abstract android.support.v7.view.ActionMode startSupportActionMode(android.support.v7.view.ActionMode.Callback);
field public static final int FEATURE_ACTION_MODE_OVERLAY = 10; // 0xa
field public static final int FEATURE_SUPPORT_ACTION_BAR = 108; // 0x6c
field public static final int FEATURE_SUPPORT_ACTION_BAR_OVERLAY = 109; // 0x6d
+ field public static final int MODE_NIGHT_AUTO = 0; // 0x0
+ field public static final int MODE_NIGHT_FOLLOW_SYSTEM = -1; // 0xffffffff
+ field public static final int MODE_NIGHT_NO = 1; // 0x1
+ field public static final int MODE_NIGHT_YES = 2; // 0x2
}
public class AppCompatDialog extends android.app.Dialog implements android.support.v7.app.AppCompatCallback {
@@ -344,6 +356,7 @@
field public static final java.lang.String ACTION_REMOVE = "android.media.intent.action.REMOVE";
field public static final java.lang.String ACTION_RESUME = "android.media.intent.action.RESUME";
field public static final java.lang.String ACTION_SEEK = "android.media.intent.action.SEEK";
+ field public static final java.lang.String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
field public static final java.lang.String ACTION_START_SESSION = "android.media.intent.action.START_SESSION";
field public static final java.lang.String ACTION_STOP = "android.media.intent.action.STOP";
field public static final java.lang.String CATEGORY_LIVE_AUDIO = "android.media.intent.category.LIVE_AUDIO";
@@ -360,6 +373,8 @@
field public static final java.lang.String EXTRA_ITEM_METADATA = "android.media.intent.extra.ITEM_METADATA";
field public static final java.lang.String EXTRA_ITEM_STATUS = "android.media.intent.extra.ITEM_STATUS";
field public static final java.lang.String EXTRA_ITEM_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
+ field public static final java.lang.String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+ field public static final java.lang.String EXTRA_MESSAGE_RECEIVER = "android.media.intent.extra.MESSAGE_RECEIVER";
field public static final java.lang.String EXTRA_SESSION_ID = "android.media.intent.extra.SESSION_ID";
field public static final java.lang.String EXTRA_SESSION_STATUS = "android.media.intent.extra.SESSION_STATUS";
field public static final java.lang.String EXTRA_SESSION_STATUS_UPDATE_RECEIVER = "android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
@@ -590,6 +605,7 @@
method public void onRouteRemoved(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
method public void onRouteSelected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
method public void onRouteUnselected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
+ method public void onRouteUnselected(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo, int);
method public void onRouteVolumeChanged(android.support.v7.media.MediaRouter, android.support.v7.media.MediaRouter.RouteInfo);
}
@@ -677,6 +693,7 @@
method public void getSessionStatus(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
method public void getStatus(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
method public boolean hasSession();
+ method public boolean isMessagingSupported();
method public boolean isQueuingSupported();
method public boolean isRemotePlaybackSupported();
method public boolean isSessionManagementSupported();
@@ -686,6 +703,8 @@
method public void remove(java.lang.String, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
method public void resume(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
method public void seek(java.lang.String, long, android.os.Bundle, android.support.v7.media.RemotePlaybackClient.ItemActionCallback);
+ method public void sendMessage(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
+ method public void setOnMessageReceivedListener(android.support.v7.media.RemotePlaybackClient.OnMessageReceivedListener);
method public void setSessionId(java.lang.String);
method public void setStatusCallback(android.support.v7.media.RemotePlaybackClient.StatusCallback);
method public void startSession(android.os.Bundle, android.support.v7.media.RemotePlaybackClient.SessionActionCallback);
@@ -702,6 +721,10 @@
method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus, java.lang.String, android.support.v7.media.MediaItemStatus);
}
+ public static abstract interface RemotePlaybackClient.OnMessageReceivedListener {
+ method public abstract void onMessageReceived(java.lang.String, android.os.Bundle);
+ }
+
public static abstract class RemotePlaybackClient.SessionActionCallback extends android.support.v7.media.RemotePlaybackClient.ActionCallback {
ctor public RemotePlaybackClient.SessionActionCallback();
method public void onResult(android.os.Bundle, java.lang.String, android.support.v7.media.MediaSessionStatus);
diff --git a/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
new file mode 100644
index 0000000..3734b59
--- /dev/null
+++ b/v7/mediarouter/api24/android/support/v7/media/MediaRouterApi24.java
@@ -0,0 +1,25 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.media;
+
+final class MediaRouterApi24 {
+ public static final class RouteInfo {
+ public static int getDeviceType(Object routeObj) {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getDeviceType();
+ }
+ }
+}
diff --git a/v7/mediarouter/build.gradle b/v7/mediarouter/build.gradle
index 48f4750..091c639 100644
--- a/v7/mediarouter/build.gradle
+++ b/v7/mediarouter/build.gradle
@@ -1,47 +1,19 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'mediarouter-v7'
-dependencies {
- compile project(':support-appcompat-v7')
- compile project(':support-palette-v7')
-}
-
// some of the source requires compiling against a newer API.
// Right now, use normal Java source sets to compile those into a jar and
// package it as a local dependencies inside the library aar.
-sourceSets {
- jellybean.java.srcDir 'jellybean'
- jellybeanmr1.java.srcDir 'jellybean-mr1'
- jellybeanmr2.java.srcDir 'jellybean-mr2'
-}
-
-// create a jar task for the code above
-tasks.create(name: "jar", type: Jar) {
- from sourceSets.jellybean.output
- from sourceSets.jellybeanmr1.output
- from sourceSets.jellybeanmr2.output
- baseName "internal_impl"
-}
-
-
-dependencies {
- jellybeanCompile getAndroidPrebuilt('16')
-
- jellybeanmr1Compile getAndroidPrebuilt('17')
- jellybeanmr1Compile sourceSets.jellybean.output
-
- jellybeanmr2Compile getAndroidPrebuilt('current')
- jellybeanmr2Compile sourceSets.jellybean.output
- jellybeanmr2Compile sourceSets.jellybeanmr1.output
-
- compile files(jar.archivePath)
-}
+createApiSourceSets(project, gradle.ext.studioCompat.modules.mediaRouter.apiTargets)
+sourceCompatibility = JavaVersion.VERSION_1_7
+targetCompatibility = JavaVersion.VERSION_1_7
+setApiModuleDependencies(project, dependencies, gradle.ext.studioCompat.modules.mediaRouter.dependencies)
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
@@ -69,8 +41,6 @@
}
android.libraryVariants.all { variant ->
- variant.javaCompile.dependsOn jar
-
def name = variant.buildType.name
if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
@@ -99,12 +69,10 @@
from android.sourceSets.main.java.srcDirs
}
- javadocTask.source project.sourceSets.jellybean.java
- sourcesJarTask.from project.sourceSets.jellybean.java.srcDirs
- javadocTask.source project.sourceSets.jellybeanmr1.java
- sourcesJarTask.from project.sourceSets.jellybeanmr1.java.srcDirs
- javadocTask.source project.sourceSets.jellybeanmr2.java
- sourcesJarTask.from project.sourceSets.jellybeanmr2.java.srcDirs
+ project.ext.allSS.each { ss ->
+ javadocTask.source ss.java
+ sourcesJarTask.from ss.java.srcDirs
+ }
artifacts.add('archives', javadocJarTask);
artifacts.add('archives', sourcesJarTask);
diff --git a/v7/mediarouter/jellybean-mr1/android/support/v7/media/MediaRouterJellybeanMr1.java b/v7/mediarouter/jellybean-mr1/android/support/v7/media/MediaRouterJellybeanMr1.java
index 9837fb2..0ef744f 100644
--- a/v7/mediarouter/jellybean-mr1/android/support/v7/media/MediaRouterJellybeanMr1.java
+++ b/v7/mediarouter/jellybean-mr1/android/support/v7/media/MediaRouterJellybeanMr1.java
@@ -40,7 +40,14 @@
}
public static Display getPresentationDisplay(Object routeObj) {
- return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
+ // android.media.MediaRouter.RouteInfo.getPresentationDisplay() was
+ // added in API 17. However, some factory releases of JB MR1 missed it.
+ try {
+ return ((android.media.MediaRouter.RouteInfo)routeObj).getPresentationDisplay();
+ } catch (NoSuchMethodError ex) {
+ Log.w(TAG, "Cannot get presentation display for the route.", ex);
+ }
+ return null;
}
}
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png
index 71db6b4..f17a78f 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_grey.png b/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_grey.png
deleted file mode 100644
index 0493c80..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_grey.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_white.png b/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_white.png
deleted file mode 100644
index fce1884..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_bluetooth_white.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
index da91591..038e64a 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
index 74b2d16..887fde4 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_grey.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_grey.png
index f0960f8..4eb9f48 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_grey.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_grey.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
index a74163a..e122cc9 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
index 6e02527..58c344a 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
deleted file mode 100644
index 26c6847..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_0_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
deleted file mode 100644
index 2fea78f..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_1_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
deleted file mode 100644
index 2071e8f..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_2_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
deleted file mode 100644
index 54a277e..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_on_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_cast_white.png b/v7/mediarouter/res/drawable-hdpi/ic_cast_white.png
index 60d3915..d649ded 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_cast_white.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_cast_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png
index ceb1a1e..ce64334 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_play.png b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
index 8afcd7d..e925f23 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
index 5b103e9..8ad305d 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
index 0090701..5739df7 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
deleted file mode 100644
index 41eb0b75..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_0_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
deleted file mode 100644
index 49f14cb..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_1_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
deleted file mode 100644
index 5dd6aea..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_2_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
deleted file mode 100644
index 0d5ac5c..0000000
--- a/v7/mediarouter/res/drawable-hdpi/ic_media_route_on_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
index 547ef30..9bc4982 100644
--- a/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png
index 0e1da44..723e455 100755
--- a/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png
index b90bb2f..40c25a3 100755
--- a/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png
index e2c88be..846c109 100755
--- a/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png
index d1335f6..33bf484 100755
--- a/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png
index 7330f56..c911b5c 100755
--- a/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png
+++ b/v7/mediarouter/res/drawable-hdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png
index dc1200e..d384e80 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_grey.png b/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_grey.png
deleted file mode 100644
index ddc8789..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_grey.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_white.png b/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_white.png
deleted file mode 100644
index 27a8a71..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_bluetooth_white.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
index 1121562..f73554f 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
index ebb616c..4d790c6 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_grey.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_grey.png
index 35ffd60..ccbb772 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_grey.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_grey.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
index 7e5a3b6..5e11489 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
index d6114dc..e24d586 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
deleted file mode 100644
index 76da308..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_0_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
deleted file mode 100644
index 25d3809..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_1_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
deleted file mode 100644
index 3d63b0d..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_2_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
deleted file mode 100644
index 20e7619..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_cast_on_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png
index af7f828..dde307e 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
index 7149f56..67bfde2 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_play.png b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
index a1cb5df..79632c8 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
index 01a0c50..4446ea4 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
index 8ec0cb1..c401dc0 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
deleted file mode 100644
index 7cb6dcc..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_0_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
deleted file mode 100644
index 38ee9f6..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_1_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
deleted file mode 100644
index 91f1b4b..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_2_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
deleted file mode 100644
index 6838f80..0000000
--- a/v7/mediarouter/res/drawable-mdpi/ic_media_route_on_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
index a3c80e7..9cc777c 100644
--- a/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_play_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png
index 4db7209..79c4093 100755
--- a/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png
index 82358a9..32e2eca 100755
--- a/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png b/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png
index ba3f3d5..8134310 100755
--- a/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png
+++ b/v7/mediarouter/res/drawable-mdpi/ic_tv_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_grey.png b/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_grey.png
deleted file mode 100644
index c57b9d6..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_grey.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_white.png b/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_white.png
deleted file mode 100644
index 920f5ca..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_bluetooth_white.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
index e9a2a6f..52950881 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
index 88978ff..b14617c 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_grey.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_grey.png
index dba8992..2238d58 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_grey.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_grey.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
index 30f413d..8617163 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
index 4479d12..a05d7d7 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
deleted file mode 100644
index 9f9b6d2..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_0_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
deleted file mode 100644
index 441b943..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_1_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
deleted file mode 100644
index d8e59e6..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_2_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
deleted file mode 100644
index e20a895..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_on_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_cast_white.png b/v7/mediarouter/res/drawable-xhdpi/ic_cast_white.png
index f5f7c14..63c2717 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_cast_white.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_cast_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png
index b7c7ffd..e13f9638 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
index 90b6543..bad6782 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
index f615361..b49c4bd 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_play.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_dark.png
new file mode 100644
index 0000000..3c7f5bc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_light.png
new file mode 100644
index 0000000..58f6b55
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_00_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_dark.png
new file mode 100644
index 0000000..b937ea6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_light.png
new file mode 100644
index 0000000..c2eb82e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_01_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_dark.png
new file mode 100644
index 0000000..03e6997
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_light.png
new file mode 100644
index 0000000..793f2d8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_02_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_dark.png
new file mode 100644
index 0000000..1edf346
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_light.png
new file mode 100644
index 0000000..677bf2a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_03_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_dark.png
new file mode 100644
index 0000000..5fad85f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_light.png
new file mode 100644
index 0000000..f85944e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_04_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_dark.png
new file mode 100644
index 0000000..e13721a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_light.png
new file mode 100644
index 0000000..920e4c3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_05_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_dark.png
new file mode 100644
index 0000000..bc7180f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_light.png
new file mode 100644
index 0000000..a56482a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_06_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_dark.png
new file mode 100644
index 0000000..125c1ea
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_light.png
new file mode 100644
index 0000000..8bf16d0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_07_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_dark.png
new file mode 100644
index 0000000..bcd2616
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_light.png
new file mode 100644
index 0000000..642ae74
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_08_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_dark.png
new file mode 100644
index 0000000..0023252
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_light.png
new file mode 100644
index 0000000..9b0ed1e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_09_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_dark.png
new file mode 100644
index 0000000..5474d78
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_light.png
new file mode 100644
index 0000000..a57b9f3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_10_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_dark.png
new file mode 100644
index 0000000..427c496
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_light.png
new file mode 100644
index 0000000..c21c3b8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_11_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_dark.png
new file mode 100644
index 0000000..333d1e1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_light.png
new file mode 100644
index 0000000..f32d393
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_12_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_dark.png
new file mode 100644
index 0000000..e71e3fb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_light.png
new file mode 100644
index 0000000..eead268
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_13_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_dark.png
new file mode 100644
index 0000000..a6b1e0d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_light.png
new file mode 100644
index 0000000..c3ecb99
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_14_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_dark.png
new file mode 100644
index 0000000..0cbb3fd
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_light.png
new file mode 100644
index 0000000..6de0ee1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_15_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_dark.png
new file mode 100644
index 0000000..40ec436
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_light.png
new file mode 100644
index 0000000..5248068
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_16_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_dark.png
new file mode 100644
index 0000000..424b7b0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_light.png
new file mode 100644
index 0000000..78d03ee
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_17_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_dark.png
new file mode 100644
index 0000000..32b5ab8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_light.png
new file mode 100644
index 0000000..af88b02
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_18_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_dark.png
new file mode 100644
index 0000000..7dbba0b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_light.png
new file mode 100644
index 0000000..19befb6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_19_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_dark.png
new file mode 100644
index 0000000..cd14275
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_light.png
new file mode 100644
index 0000000..186cf61
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_20_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_dark.png
new file mode 100644
index 0000000..092a5b2
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_light.png
new file mode 100644
index 0000000..6fc929f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_21_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_dark.png
new file mode 100644
index 0000000..f1b1d55
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_light.png
new file mode 100644
index 0000000..ea8ce55
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_connecting_22_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
index 8882965..c4dc132 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
index 3f8b212..bb30773 100644
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_dark.png
new file mode 100644
index 0000000..df5a16f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_light.png
new file mode 100644
index 0000000..8609a12
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_00_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_dark.png
new file mode 100644
index 0000000..9bb7a82
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_light.png
new file mode 100644
index 0000000..e51c479
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_01_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_dark.png
new file mode 100644
index 0000000..d3ce71f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_light.png
new file mode 100644
index 0000000..b0c5c08
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_02_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_dark.png
new file mode 100644
index 0000000..75a1b71
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_light.png
new file mode 100644
index 0000000..762a3f4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_03_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_dark.png
new file mode 100644
index 0000000..bf6209d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_light.png
new file mode 100644
index 0000000..416f6f72
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_04_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_dark.png
new file mode 100644
index 0000000..5996e32
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_light.png
new file mode 100644
index 0000000..a70c31b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_05_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_dark.png
new file mode 100644
index 0000000..3fee0df
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_light.png
new file mode 100644
index 0000000..029fe4d9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_06_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_dark.png
new file mode 100644
index 0000000..febab15
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_light.png
new file mode 100644
index 0000000..83db4ad
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_07_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_dark.png
new file mode 100644
index 0000000..677ee5f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_light.png
new file mode 100644
index 0000000..dd5c708
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_08_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_dark.png
new file mode 100644
index 0000000..43d57d1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_light.png
new file mode 100644
index 0000000..fcfe317
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_09_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
deleted file mode 100644
index ab1b6ea..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_0_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_dark.png
new file mode 100644
index 0000000..44b8425
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_light.png
new file mode 100644
index 0000000..c360f5b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_10_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_dark.png
new file mode 100644
index 0000000..c3e2f04
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_light.png
new file mode 100644
index 0000000..9291b62
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_11_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_dark.png
new file mode 100644
index 0000000..be64016
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_light.png
new file mode 100644
index 0000000..005cf5c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_12_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_dark.png
new file mode 100644
index 0000000..a461ff3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_light.png
new file mode 100644
index 0000000..1ecdadd
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_13_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_dark.png
new file mode 100644
index 0000000..0c64d3a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_light.png
new file mode 100644
index 0000000..5abd795
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_14_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_dark.png
new file mode 100644
index 0000000..b0cb6bc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_light.png
new file mode 100644
index 0000000..559d93b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_15_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_dark.png
new file mode 100644
index 0000000..5533da8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_light.png
new file mode 100644
index 0000000..f55803a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_16_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_dark.png
new file mode 100644
index 0000000..26d03b7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_light.png
new file mode 100644
index 0000000..52372e0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_17_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_dark.png
new file mode 100644
index 0000000..7f56f1d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_light.png
new file mode 100644
index 0000000..296199f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_18_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_dark.png
new file mode 100644
index 0000000..9ce7d44
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_light.png
new file mode 100644
index 0000000..744cac9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_19_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
deleted file mode 100644
index daa1310..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_1_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_dark.png
new file mode 100644
index 0000000..7f430ab
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_light.png
new file mode 100644
index 0000000..3e41323
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_20_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_dark.png
new file mode 100644
index 0000000..5200b93
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_light.png
new file mode 100644
index 0000000..b5067c9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_21_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_dark.png
new file mode 100644
index 0000000..1811bb1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_light.png
new file mode 100644
index 0000000..1bb9910
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_22_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
deleted file mode 100644
index 7143236..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_2_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_light.png
new file mode 100644
index 0000000..3c43af0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
deleted file mode 100644
index 6cfe6fb..0000000
--- a/v7/mediarouter/res/drawable-xhdpi/ic_media_route_on_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png
index 8f8a552..e40349d 100755
--- a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png
index 6227ca2..f67c463 100755
--- a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png
index 82599f5..7fcebf59 100755
--- a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png
index 74f9f6d..d6c3811 100755
--- a/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png b/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png
index cef8ac5..3808351 100755
--- a/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png
+++ b/v7/mediarouter/res/drawable-xhdpi/ic_tv_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png
index 0546539..f131e1b 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png
index 8e38265..e5946a2 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_audiotrack_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_grey.png b/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_grey.png
deleted file mode 100644
index 8e9aa70..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_grey.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_white.png b/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_white.png
deleted file mode 100644
index 860c758..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_bluetooth_white.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
index 616bd2e..1e247f1 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
index ad30027..9ce7e3a 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_disabled_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_grey.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_grey.png
index 7582751..792fd77 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_grey.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_grey.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
index d30d4cf..178ebdc 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
index 9fd15ba..34928d7 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_off_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
deleted file mode 100644
index 95ace86..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_0_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
deleted file mode 100644
index ba85bea..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_1_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
deleted file mode 100644
index 8c448fa..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_2_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
deleted file mode 100644
index 7e93e4d..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_on_light.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_white.png b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_white.png
index 7a7673fb..463bbd2 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_cast_white.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_cast_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png
index 6b717e0..b85e87f 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_close_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
index 6391830..347141f 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_pause.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_dark.png
new file mode 100644
index 0000000..8328156
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_light.png
new file mode 100644
index 0000000..f9115e9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_00_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_dark.png
new file mode 100644
index 0000000..4a48bdc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_light.png
new file mode 100644
index 0000000..29bbb10
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_01_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_dark.png
new file mode 100644
index 0000000..d740bd4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_light.png
new file mode 100644
index 0000000..ee1d72a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_02_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_dark.png
new file mode 100644
index 0000000..c6a4cfe
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_light.png
new file mode 100644
index 0000000..395a432
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_03_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_dark.png
new file mode 100644
index 0000000..a38ad7d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_light.png
new file mode 100644
index 0000000..1e8ab20
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_04_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_dark.png
new file mode 100644
index 0000000..b4037be
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_light.png
new file mode 100644
index 0000000..689448f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_05_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_dark.png
new file mode 100644
index 0000000..f91f683
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_light.png
new file mode 100644
index 0000000..15e65e6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_06_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_dark.png
new file mode 100644
index 0000000..5c4ef69
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_light.png
new file mode 100644
index 0000000..c442f17
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_07_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_dark.png
new file mode 100644
index 0000000..cef9f1f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_light.png
new file mode 100644
index 0000000..012964e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_08_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_dark.png
new file mode 100644
index 0000000..b6f1c59
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_light.png
new file mode 100644
index 0000000..718e2e2
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_09_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_dark.png
new file mode 100644
index 0000000..07472f3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_light.png
new file mode 100644
index 0000000..1101653
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_10_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_dark.png
new file mode 100644
index 0000000..27fc974
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_light.png
new file mode 100644
index 0000000..3cfd4f0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_11_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_dark.png
new file mode 100644
index 0000000..9fb9a44
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_light.png
new file mode 100644
index 0000000..25f4dba
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_12_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_dark.png
new file mode 100644
index 0000000..aefd5e1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_light.png
new file mode 100644
index 0000000..0d4cadc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_13_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_dark.png
new file mode 100644
index 0000000..3369168
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_light.png
new file mode 100644
index 0000000..0636a22
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_14_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_dark.png
new file mode 100644
index 0000000..d5f442f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_light.png
new file mode 100644
index 0000000..0af1969
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_15_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_dark.png
new file mode 100644
index 0000000..9d8fcb7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_light.png
new file mode 100644
index 0000000..5d4f3cc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_16_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_dark.png
new file mode 100644
index 0000000..9737f5c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_light.png
new file mode 100644
index 0000000..91ed0f6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_17_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_dark.png
new file mode 100644
index 0000000..525e52bc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_light.png
new file mode 100644
index 0000000..0648e89
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_18_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_dark.png
new file mode 100644
index 0000000..78ea7ac
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_light.png
new file mode 100644
index 0000000..59fd8f3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_19_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_dark.png
new file mode 100644
index 0000000..cd27674c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_light.png
new file mode 100644
index 0000000..621bae5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_20_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_dark.png
new file mode 100644
index 0000000..3998114
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_light.png
new file mode 100644
index 0000000..9b4b901
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_21_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_dark.png
new file mode 100644
index 0000000..355bd6f
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_light.png
new file mode 100644
index 0000000..2697e4c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_connecting_22_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
index 185ba25..fdb2121 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_disabled_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
index 3fa0ee6..e8601ce 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_off_mono_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_dark.png
new file mode 100644
index 0000000..d39a8c5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_light.png
new file mode 100644
index 0000000..0b3c5591
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_00_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_dark.png
new file mode 100644
index 0000000..df710c19
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_light.png
new file mode 100644
index 0000000..16f4c1d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_01_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_dark.png
new file mode 100644
index 0000000..217e318
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_light.png
new file mode 100644
index 0000000..7bb1df7
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_02_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_dark.png
new file mode 100644
index 0000000..37272ce
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_light.png
new file mode 100644
index 0000000..2da9d9d
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_03_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_dark.png
new file mode 100644
index 0000000..46cc052
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_light.png
new file mode 100644
index 0000000..910c9c6
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_04_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_dark.png
new file mode 100644
index 0000000..e4cc9fb
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_light.png
new file mode 100644
index 0000000..e159197
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_05_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_dark.png
new file mode 100644
index 0000000..c5952de
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_light.png
new file mode 100644
index 0000000..12fe0dd
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_06_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_dark.png
new file mode 100644
index 0000000..c5ccf4c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_light.png
new file mode 100644
index 0000000..39cba85
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_07_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_dark.png
new file mode 100644
index 0000000..3d5a27e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_light.png
new file mode 100644
index 0000000..109502b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_08_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_dark.png
new file mode 100644
index 0000000..1876ba0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_light.png
new file mode 100644
index 0000000..540c17a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_09_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
deleted file mode 100644
index d21b07b..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_0_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_dark.png
new file mode 100644
index 0000000..d58e936
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_light.png
new file mode 100644
index 0000000..ffa0e00
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_10_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_dark.png
new file mode 100644
index 0000000..ceb63af
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_light.png
new file mode 100644
index 0000000..31f2773
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_11_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_dark.png
new file mode 100644
index 0000000..826dfbf
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_light.png
new file mode 100644
index 0000000..8b18159
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_12_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_dark.png
new file mode 100644
index 0000000..875d1d5
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_light.png
new file mode 100644
index 0000000..4c6459e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_13_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_dark.png
new file mode 100644
index 0000000..c97fce4
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_light.png
new file mode 100644
index 0000000..a4591a3
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_14_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_dark.png
new file mode 100644
index 0000000..6c20e89
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_light.png
new file mode 100644
index 0000000..12f0813
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_15_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_dark.png
new file mode 100644
index 0000000..da203fc
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_light.png
new file mode 100644
index 0000000..b2a8b29
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_16_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_dark.png
new file mode 100644
index 0000000..042fb2c
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_light.png
new file mode 100644
index 0000000..2ef3412
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_17_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_dark.png
new file mode 100644
index 0000000..96b18a1
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_light.png
new file mode 100644
index 0000000..a08fcc9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_18_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_dark.png
new file mode 100644
index 0000000..4cc6c3a
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_light.png
new file mode 100644
index 0000000..1ea96a0
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_19_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
deleted file mode 100644
index 063709e..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_1_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_dark.png
new file mode 100644
index 0000000..19f16b8
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_light.png
new file mode 100644
index 0000000..c16c70b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_20_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_dark.png
new file mode 100644
index 0000000..2bfc79b
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_light.png
new file mode 100644
index 0000000..d11045e
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_21_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_dark.png
new file mode 100644
index 0000000..f5bb4f9
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_light.png
new file mode 100644
index 0000000..7786537
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_22_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
deleted file mode 100644
index 9f928ed..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_2_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_light.png
new file mode 100644
index 0000000..ce27a79
--- /dev/null
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
deleted file mode 100644
index 4355e79..0000000
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_media_route_on_mono_dark.png
+++ /dev/null
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
index 1c57756b..eda3ba5 100644
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_play_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png
index 874c961..f171a8c 100755
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png
index 6869bdc..c8cb6ca 100755
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_dark.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png
index 35de6f4..9c8863d 100755
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_group_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png
index 65ee187..9335038 100755
--- a/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png
+++ b/v7/mediarouter/res/drawable-xxhdpi/ic_speaker_light.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_grey.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_grey.png
index d124cb8..04a9525 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_grey.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_grey.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_white.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_white.png
index bb3539c..a95215a 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_white.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_cast_white.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00000.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00000.png
index 16e4cbc..b562dd2 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00000.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00000.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00001.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00001.png
index 6cc54b6..7025d19 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00001.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00001.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00002.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00002.png
index ae4f8fb..e8577da 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00002.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00002.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00003.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00003.png
index cd3ffdb..b1707b5 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00003.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00003.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00004.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00004.png
index 2285ec7..c4e4c11 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00004.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00004.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00005.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00005.png
index 8130cb1..6ca2891 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00005.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00005.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00006.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00006.png
index 4369a3d..0a28767 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00006.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00006.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00007.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00007.png
index 301e6c7..d69b20f 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00007.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00007.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00008.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00008.png
index 442cc82..cddaca1 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00008.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00008.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00009.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00009.png
index 78763f6..14ea746 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00009.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00009.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00010.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00010.png
index 1ea59a8..2cb6314 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00010.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00010.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00011.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00011.png
index 77ea83a..a2f4ad5 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00011.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00011.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00012.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00012.png
index 4e742f5..c0d54add 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00012.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00012.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00013.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00013.png
index fa49b62..b99324e 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00013.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00013.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00014.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00014.png
index dc477a2..c8618f0 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00014.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00014.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00015.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00015.png
index dd85400..b1ceb20 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00015.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_collapse_00015.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00000.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00000.png
index dd85400..b1ceb20 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00000.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00000.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00001.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00001.png
index 2586aa0..36187bc 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00001.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00001.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00002.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00002.png
index 4022000..82b5f03 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00002.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00002.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00003.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00003.png
index 358b428..e3f7189 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00003.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00003.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00004.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00004.png
index 1da8c49..078466e 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00004.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00004.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00005.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00005.png
index 342d6c7..aa96ac7 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00005.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00005.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00006.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00006.png
index 738bddd..218ec27 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00006.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00006.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00007.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00007.png
index 48192c4..95fa72b 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00007.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00007.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00008.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00008.png
index ff1a374..44cab360 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00008.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00008.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00009.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00009.png
index c13afcd..8ecf591 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00009.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00009.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00010.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00010.png
index dde5939..c2b5f79 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00010.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00010.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00011.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00011.png
index 5c60a08..a468c21 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00011.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00011.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00012.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00012.png
index 5d75964..280c0e4 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00012.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00012.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00013.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00013.png
index cf1c1cd..bf5921e 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00013.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00013.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00014.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00014.png
index 12ccc03..14b76b1 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00014.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00014.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00015.png b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00015.png
index 16e4cbc..b562dd2 100644
--- a/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00015.png
+++ b/v7/mediarouter/res/drawable-xxxhdpi/ic_expand_00015.png
Binary files differ
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_dark.xml
new file mode 100644
index 0000000..6b5c272
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_dark.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item android:drawable="@drawable/ic_media_route_connecting_00_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_01_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_02_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_03_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_04_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_05_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_06_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_07_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_08_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_09_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_10_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_11_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_12_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_13_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_14_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_15_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_16_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_17_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_18_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_19_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_20_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_21_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_22_dark" android:duration="42" />
+</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_light.xml
new file mode 100644
index 0000000..37a04b17
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_light.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="false">
+ <item android:drawable="@drawable/ic_media_route_connecting_00_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_01_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_02_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_03_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_04_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_05_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_06_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_07_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_08_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_09_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_10_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_11_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_12_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_13_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_14_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_15_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_16_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_17_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_18_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_19_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_20_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_21_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_connecting_22_light" android:duration="42" />
+</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
deleted file mode 100644
index 2d593c3..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_dark.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<animation-list
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <item android:drawable="@drawable/ic_media_route_on_0_mono_dark" android:duration="500" />
- <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
- <item android:drawable="@drawable/ic_media_route_on_2_mono_dark" android:duration="500" />
- <item android:drawable="@drawable/ic_media_route_on_1_mono_dark" android:duration="500" />
-</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
deleted file mode 100644
index d495d99..0000000
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_connecting_mono_light.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 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.
--->
-
-<animation-list
- xmlns:android="http://schemas.android.com/apk/res/android"
- android:oneshot="false">
- <item android:drawable="@drawable/ic_cast_on_0_light" android:duration="500" />
- <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
- <item android:drawable="@drawable/ic_cast_on_2_light" android:duration="500" />
- <item android:drawable="@drawable/ic_cast_on_1_light" android:duration="500" />
-</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
index 6ae3b0b..d7643de 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_dark.xml
@@ -16,9 +16,9 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/ic_media_route_on_mono_dark" />
+ android:drawable="@drawable/mr_ic_media_route_on_dark" />
<item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_mono_dark" />
+ android:drawable="@drawable/mr_ic_media_route_connecting_dark" />
<item android:state_enabled="true"
android:drawable="@drawable/ic_media_route_off_mono_dark" />
<item android:drawable="@drawable/ic_media_route_disabled_mono_dark" />
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
index a8cf6c8..0036193 100644
--- a/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_mono_light.xml
@@ -16,9 +16,9 @@
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_checked="true" android:state_enabled="true"
- android:drawable="@drawable/ic_cast_on_light" />
+ android:drawable="@drawable/mr_ic_media_route_on_light" />
<item android:state_checkable="true" android:state_enabled="true"
- android:drawable="@drawable/mr_ic_media_route_connecting_mono_light" />
+ android:drawable="@drawable/mr_ic_media_route_connecting_light" />
<item android:state_enabled="true"
android:drawable="@drawable/ic_cast_off_light" />
<item android:drawable="@drawable/ic_cast_disabled_light" />
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_on_dark.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_on_dark.xml
new file mode 100644
index 0000000..de183a3
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_on_dark.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_media_route_on_00_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_01_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_02_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_03_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_04_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_05_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_06_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_07_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_08_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_09_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_10_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_11_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_12_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_13_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_14_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_15_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_16_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_17_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_18_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_19_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_20_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_21_dark" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_22_dark" android:duration="42" />
+</animation-list>
diff --git a/v7/mediarouter/res/drawable/mr_ic_media_route_on_light.xml b/v7/mediarouter/res/drawable/mr_ic_media_route_on_light.xml
new file mode 100644
index 0000000..2d4775b
--- /dev/null
+++ b/v7/mediarouter/res/drawable/mr_ic_media_route_on_light.xml
@@ -0,0 +1,43 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<animation-list
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:oneshot="true">
+ <item android:drawable="@drawable/ic_media_route_on_00_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_01_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_02_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_03_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_04_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_05_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_06_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_07_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_08_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_09_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_10_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_11_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_12_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_13_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_14_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_15_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_16_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_17_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_18_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_19_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_20_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_21_light" android:duration="42" />
+ <item android:drawable="@drawable/ic_media_route_on_22_light" android:duration="42" />
+</animation-list>
diff --git a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
index 091f5f8..0147fe3 100644
--- a/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
+++ b/v7/mediarouter/res/layout/mr_controller_material_dialog_b.xml
@@ -61,6 +61,7 @@
android:scaleType="fitXY"
android:background="?attr/colorPrimary"
android:layout_gravity="top"
+ android:contentDescription="@string/mr_controller_album_art"
android:visibility="gone" />
<LinearLayout android:layout_width="fill_parent"
android:layout_height="wrap_content"
@@ -82,13 +83,14 @@
<include android:id="@+id/mr_volume_control"
layout="@layout/mr_volume_control" />
</LinearLayout>
- <ListView android:id="@+id/mr_volume_group_list"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
- android:scrollbarStyle="outsideOverlay"
- android:clipToPadding="false"
- android:visibility="gone" />
+ <android.support.v7.app.OverlayListView
+ android:id="@+id/mr_volume_group_list"
+ android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:paddingTop="@dimen/mr_controller_volume_group_list_padding_top"
+ android:scrollbarStyle="outsideOverlay"
+ android:clipToPadding="false"
+ android:visibility="gone" />
</LinearLayout>
</FrameLayout>
<include layout="@layout/abc_alert_dialog_button_bar_material" />
diff --git a/v7/mediarouter/res/layout/mr_controller_volume_item.xml b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
index 4d693e2..3e7b39b 100644
--- a/v7/mediarouter/res/layout/mr_controller_volume_item.xml
+++ b/v7/mediarouter/res/layout/mr_controller_volume_item.xml
@@ -16,31 +16,36 @@
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
- android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
- android:paddingLeft="24dp"
- android:paddingRight="60dp"
- android:paddingBottom="8dp"
- android:orientation="vertical" >
- <TextView android:id="@+id/mr_name"
- android:layout_width="wrap_content"
- android:layout_height="wrap_content"
- android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
- android:singleLine="true" />
- <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content">
+ <LinearLayout android:id="@+id/volume_item_container"
+ android:layout_width="fill_parent"
+ android:layout_height="@dimen/mr_controller_volume_group_list_item_height"
+ android:paddingLeft="24dp"
+ android:paddingRight="60dp"
+ android:paddingBottom="8dp"
+ android:orientation="vertical" >
+ <TextView android:id="@+id/mr_name"
+ android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:orientation="horizontal"
- android:gravity="center_vertical">
- <ImageView android:id="@+id/mr_volume_item_icon"
- android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
- android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
- android:layout_marginTop="8dp"
- android:layout_marginBottom="8dp"
- android:scaleType="fitCenter"
- android:src="?attr/mediaRouteAudioTrackDrawable" />
- <android.support.v7.app.MediaRouteVolumeSlider android:id="@+id/mr_volume_slider"
- android:layout_width="fill_parent"
- android:layout_height="40dp"
- android:minHeight="40dp"
- android:maxHeight="40dp" />
+ android:textAppearance="?attr/mediaRouteControllerSecondaryTextStyle"
+ android:singleLine="true" />
+ <LinearLayout android:layout_width="fill_parent"
+ android:layout_height="wrap_content"
+ android:orientation="horizontal"
+ android:gravity="center_vertical">
+ <ImageView android:id="@+id/mr_volume_item_icon"
+ android:layout_width="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_height="@dimen/mr_controller_volume_group_list_item_icon_size"
+ android:layout_marginTop="8dp"
+ android:layout_marginBottom="8dp"
+ android:scaleType="fitCenter"
+ android:src="?attr/mediaRouteAudioTrackDrawable" />
+ <android.support.v7.app.MediaRouteVolumeSlider
+ android:id="@+id/mr_volume_slider"
+ android:layout_width="fill_parent"
+ android:layout_height="40dp"
+ android:minHeight="40dp"
+ android:maxHeight="40dp" />
+ </LinearLayout>
</LinearLayout>
</LinearLayout>
diff --git a/v7/mediarouter/res/values-af/strings.xml b/v7/mediarouter/res/values-af/strings.xml
index 024a99c..9811194 100644
--- a/v7/mediarouter/res/values-af/strings.xml
+++ b/v7/mediarouter/res/values-af/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Laat wag"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Vou uit"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Vou in"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumkunswerk"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media is gekies nie"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen inligting beskikbaar nie"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Saai tans skerm uit"</string>
diff --git a/v7/mediarouter/res/values-am/strings.xml b/v7/mediarouter/res/values-am/strings.xml
index 4456b45..6f7931d 100644
--- a/v7/mediarouter/res/values-am/strings.xml
+++ b/v7/mediarouter/res/values-am/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ለአፍታ አቁም"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"አስፋ"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ሰብስብ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"የአልበም ስነ-ጥበብ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ምንም ማህደረመረጃ አልተመረጠም"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ምንም መረጃ አይገኝም"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"ማያ ገጽን በመውሰድ ላይ"</string>
diff --git a/v7/mediarouter/res/values-ar/strings.xml b/v7/mediarouter/res/values-ar/strings.xml
index 7222590..29cab47 100644
--- a/v7/mediarouter/res/values-ar/strings.xml
+++ b/v7/mediarouter/res/values-ar/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"إيقاف مؤقت"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"توسيع"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"تصغير"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"صورة الألبوم"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"لم يتم اختيار أية وسائط"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"لا تتوفر أية معلومات"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"جارٍ إرسال الشاشة"</string>
diff --git a/v7/mediarouter/res/values-az-rAZ/strings.xml b/v7/mediarouter/res/values-az-rAZ/strings.xml
index aa3a70f..765520e 100644
--- a/v7/mediarouter/res/values-az-rAZ/strings.xml
+++ b/v7/mediarouter/res/values-az-rAZ/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Durdurun"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişləndirin"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yığcamlaşdırın"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom incəsənəti"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Heç bir media seçilməyib"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Əlçatan məlumat yoxdur"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayımlanır"</string>
diff --git a/v7/mediarouter/res/values-b+sr+Latn/strings.xml b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..c10c42d
--- /dev/null
+++ b/v7/mediarouter/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,34 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!-- Copyright (C) 2013 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 xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Sistem"</string>
+ <string name="mr_user_route_category_name" msgid="7498112907524977311">"Uređaji"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"Dugme Prebaci"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"Prebacujte na"</string>
+ <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string>
+ <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string>
+ <string name="mr_controller_stop" msgid="4570331844078181931">"Zaustavi prebacivanje"</string>
+ <string name="mr_controller_close_description" msgid="7333862312480583260">"Zatvori"</string>
+ <string name="mr_controller_play" msgid="683634565969987458">"Pusti"</string>
+ <string name="mr_controller_pause" msgid="5451884435510905406">"Pauziraj"</string>
+ <string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširi"</string>
+ <string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skupi"</string>
+ <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nema izabranih medija"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nisu dostupne nikakve informacije"</string>
+ <string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prebacuje se ekran"</string>
+</resources>
diff --git a/v7/mediarouter/res/values-bg/strings.xml b/v7/mediarouter/res/values-bg/strings.xml
index de49179..036b31f 100644
--- a/v7/mediarouter/res/values-bg/strings.xml
+++ b/v7/mediarouter/res/values-bg/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Поставяне на пауза"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Разгъване"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свиване"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка на албума"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Няма избрана мултимедия"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Няма налична информация"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранът се предава"</string>
diff --git a/v7/mediarouter/res/values-bn-rBD/strings.xml b/v7/mediarouter/res/values-bn-rBD/strings.xml
index 7eb5301..0e3e491 100644
--- a/v7/mediarouter/res/values-bn-rBD/strings.xml
+++ b/v7/mediarouter/res/values-bn-rBD/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"বিরাম দিন"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"প্রসারিত করুন"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"সঙ্কুচিত করুন"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"অ্যালবাম শৈলি"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"কোনো মিডিয়া নির্বাচন করা হয়নি"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"কোনো তথ্য উপলব্ধ নেই"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"স্ক্রীন কাস্ট করা হচ্ছে"</string>
diff --git a/v7/mediarouter/res/values-ca/strings.xml b/v7/mediarouter/res/values-ca/strings.xml
index ab8809a..7fc51f7 100644
--- a/v7/mediarouter/res/values-ca/strings.xml
+++ b/v7/mediarouter/res/values-ca/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Posa en pausa"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Desplega"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Replega"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imatge de l\'àlbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No s\'ha seleccionat cap fitxer multimèdia"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hi ha informació disponible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emissió de pantalla"</string>
diff --git a/v7/mediarouter/res/values-cs/strings.xml b/v7/mediarouter/res/values-cs/strings.xml
index 57c1594..f5a286e 100644
--- a/v7/mediarouter/res/values-cs/strings.xml
+++ b/v7/mediarouter/res/values-cs/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pozastavit"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbalit"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sbalit"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Obal alba"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nebyla vybrána žádná média"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nejsou k dispozici žádné informace"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Odesílání obsahu obrazovky"</string>
diff --git a/v7/mediarouter/res/values-da/strings.xml b/v7/mediarouter/res/values-da/strings.xml
index eb6c8fa..9e5f9a5 100644
--- a/v7/mediarouter/res/values-da/strings.xml
+++ b/v7/mediarouter/res/values-da/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Sæt på pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Udvid"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafik"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Der er ikke valgt nogen medier"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Der er ingen tilgængelige oplysninger"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skærmen castes"</string>
diff --git a/v7/mediarouter/res/values-de/strings.xml b/v7/mediarouter/res/values-de/strings.xml
index 17a84af..91d764f 100644
--- a/v7/mediarouter/res/values-de/strings.xml
+++ b/v7/mediarouter/res/values-de/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausieren"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Maximieren"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minimieren"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumcover"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Keine Medien ausgewählt"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Keine Informationen verfügbar"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Bildschirm wird gestreamt."</string>
diff --git a/v7/mediarouter/res/values-el/strings.xml b/v7/mediarouter/res/values-el/strings.xml
index 9258296..0a1a62f 100644
--- a/v7/mediarouter/res/values-el/strings.xml
+++ b/v7/mediarouter/res/values-el/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Παύση"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Ανάπτυξη"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Σύμπτυξη"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Εξώφυλλο άλμπουμ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Δεν έχουν επιλεγεί μέσα"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Δεν υπάρχουν διαθέσιμες πληροφορίες"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Μετάδοση οθόνης"</string>
diff --git a/v7/mediarouter/res/values-en-rAU/strings.xml b/v7/mediarouter/res/values-en-rAU/strings.xml
index 5edd79b..d60689e 100644
--- a/v7/mediarouter/res/values-en-rAU/strings.xml
+++ b/v7/mediarouter/res/values-en-rAU/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
diff --git a/v7/mediarouter/res/values-en-rGB/strings.xml b/v7/mediarouter/res/values-en-rGB/strings.xml
index 5edd79b..d60689e 100644
--- a/v7/mediarouter/res/values-en-rGB/strings.xml
+++ b/v7/mediarouter/res/values-en-rGB/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
diff --git a/v7/mediarouter/res/values-en-rIN/strings.xml b/v7/mediarouter/res/values-en-rIN/strings.xml
index 5edd79b..d60689e 100644
--- a/v7/mediarouter/res/values-en-rIN/strings.xml
+++ b/v7/mediarouter/res/values-en-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expand"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No media selected"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"No info available"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Casting screen"</string>
diff --git a/v7/mediarouter/res/values-es-rUS/strings.xml b/v7/mediarouter/res/values-es-rUS/strings.xml
index b0a0a61..2318059 100644
--- a/v7/mediarouter/res/values-es-rUS/strings.xml
+++ b/v7/mediarouter/res/values-es-rUS/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagen del álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se seleccionó ningún contenido multimedia"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Sin información disponible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitiendo pantalla"</string>
diff --git a/v7/mediarouter/res/values-es/strings.xml b/v7/mediarouter/res/values-es/strings.xml
index 3a1eaeb..9f108fe 100644
--- a/v7/mediarouter/res/values-es/strings.xml
+++ b/v7/mediarouter/res/values-es/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Mostrar"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ocultar"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada del álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"No se ha seleccionado ningún medio"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"No hay información disponible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Enviando pantalla"</string>
diff --git a/v7/mediarouter/res/values-et-rEE/strings.xml b/v7/mediarouter/res/values-et-rEE/strings.xml
index 9058dcf..3fab845 100644
--- a/v7/mediarouter/res/values-et-rEE/strings.xml
+++ b/v7/mediarouter/res/values-et-rEE/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Peatamine"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Laiendamine"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ahendamine"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumi kujundus"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Meediat pole valitud"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Teave puudub"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekraanikuva ülekandmine"</string>
diff --git a/v7/mediarouter/res/values-eu-rES/strings.xml b/v7/mediarouter/res/values-eu-rES/strings.xml
index ad85c5a..bae67ed 100644
--- a/v7/mediarouter/res/values-eu-rES/strings.xml
+++ b/v7/mediarouter/res/values-eu-rES/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausatu"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Zabaldu"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tolestu"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumaren azala"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ez da hautatu multimedia-edukirik"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ez dago informaziorik"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Pantaila igortzen"</string>
diff --git a/v7/mediarouter/res/values-fa/strings.xml b/v7/mediarouter/res/values-fa/strings.xml
index 2c812ae..eb34931 100644
--- a/v7/mediarouter/res/values-fa/strings.xml
+++ b/v7/mediarouter/res/values-fa/strings.xml
@@ -28,7 +28,8 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"مکث"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"بزرگ کردن"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"کوچک کردن"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"عکس روی جلد آلبوم"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"رسانه انتخاب نشده است"</string>
- <string name="mr_controller_no_info_available" msgid="5585418471741142924">"اطلاعات دردسترس نیست"</string>
+ <string name="mr_controller_no_info_available" msgid="5585418471741142924">"اطلاعات در دسترس نیست"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"درحال فرستادن صفحه"</string>
</resources>
diff --git a/v7/mediarouter/res/values-fi/strings.xml b/v7/mediarouter/res/values-fi/strings.xml
index a93d74e..f37317a 100644
--- a/v7/mediarouter/res/values-fi/strings.xml
+++ b/v7/mediarouter/res/values-fi/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Keskeytä"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Laajenna"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Tiivistä"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumin kansikuva"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ei valittua mediaa."</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tietoja ei ole saatavilla"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Suoratoistetaan näyttöä"</string>
diff --git a/v7/mediarouter/res/values-fr-rCA/strings.xml b/v7/mediarouter/res/values-fr-rCA/strings.xml
index f0c6976..5719479 100644
--- a/v7/mediarouter/res/values-fr-rCA/strings.xml
+++ b/v7/mediarouter/res/values-fr-rCA/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Interrompre"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours"</string>
diff --git a/v7/mediarouter/res/values-fr/strings.xml b/v7/mediarouter/res/values-fr/strings.xml
index 6e14ccc..6ce8329 100644
--- a/v7/mediarouter/res/values-fr/strings.xml
+++ b/v7/mediarouter/res/values-fr/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Développer"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Réduire"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Image de l\'album"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Aucun média sélectionné"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Aucune information disponible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Diffusion de l\'écran en cours…"</string>
diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl-rES/strings.xml
index fb76c68..c922b68 100644
--- a/v7/mediarouter/res/values-gl-rES/strings.xml
+++ b/v7/mediarouter/res/values-gl-rES/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Ampliar"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Contraer"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Portada do álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Non se seleccionaron recursos"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Non hai información dispoñible"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emisión de pantalla"</string>
diff --git a/v7/mediarouter/res/values-gu-rIN/strings.xml b/v7/mediarouter/res/values-gu-rIN/strings.xml
index 211f836..e3be8fc2 100644
--- a/v7/mediarouter/res/values-gu-rIN/strings.xml
+++ b/v7/mediarouter/res/values-gu-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"થોભાવો"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"વિસ્તૃત કરો"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"સંકુચિત કરો"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"આલ્બમ કલા"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"કોઈ મીડિયા પસંદ કરેલ નથી"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"કોઈ માહિતી ઉપલબ્ધ નથી"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"સ્ક્રીનને કાસ્ટ કરી રહ્યાં છે"</string>
diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml
index e2c7a43..9d0650b 100644
--- a/v7/mediarouter/res/values-hi/strings.xml
+++ b/v7/mediarouter/res/values-hi/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"रोकें"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तृत करें"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"संक्षिप्त करें"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कोई मीडिया चयनित नहीं है"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोई जानकारी उपलब्ध नहीं"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्ट हो रही है"</string>
diff --git a/v7/mediarouter/res/values-hr/strings.xml b/v7/mediarouter/res/values-hr/strings.xml
index d79258fbf..371088b 100644
--- a/v7/mediarouter/res/values-hr/strings.xml
+++ b/v7/mediarouter/res/values-hr/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pauziranje"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Proširivanje"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sažimanje"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nije odabran nijedan medij"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacije nisu dostupne"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Emitiranje zaslona"</string>
diff --git a/v7/mediarouter/res/values-hu/strings.xml b/v7/mediarouter/res/values-hu/strings.xml
index 7686fea..a3d6990 100644
--- a/v7/mediarouter/res/values-hu/strings.xml
+++ b/v7/mediarouter/res/values-hu/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Szüneteltetés"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Kibontás"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Összecsukás"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Lemezborító"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nincs média kiválasztva"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nincs információ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Képernyőtartalom átküldése"</string>
diff --git a/v7/mediarouter/res/values-hy-rAM/strings.xml b/v7/mediarouter/res/values-hy-rAM/strings.xml
index b05c17c..a8c1cf3 100644
--- a/v7/mediarouter/res/values-hy-rAM/strings.xml
+++ b/v7/mediarouter/res/values-hy-rAM/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Դադար"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Ընդարձակել"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Կոծկել"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ալբոմի շապիկ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Մեդիա ֆայլեր չեն ընտրվել"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Տեղեկությունները հասանելի չեն"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Էկրանը հեռարձակվում է"</string>
diff --git a/v7/mediarouter/res/values-in/strings.xml b/v7/mediarouter/res/values-in/strings.xml
index 4d84852..4bc0852 100644
--- a/v7/mediarouter/res/values-in/strings.xml
+++ b/v7/mediarouter/res/values-in/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Luaskan"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Ciutkan"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Sampul album"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tidak ada media yang dipilih"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Tidak ada info yang tersedia"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmisi layar"</string>
diff --git a/v7/mediarouter/res/values-is-rIS/strings.xml b/v7/mediarouter/res/values-is-rIS/strings.xml
index 366f6f6..08e41e6 100644
--- a/v7/mediarouter/res/values-is-rIS/strings.xml
+++ b/v7/mediarouter/res/values-is-rIS/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Hlé"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Stækka"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Minnka"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Plötuumslag"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Enginn miðill valinn"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Engar upplýsingar í boði"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skjár sendur út"</string>
diff --git a/v7/mediarouter/res/values-it/strings.xml b/v7/mediarouter/res/values-it/strings.xml
index 9febd99..87b570c 100644
--- a/v7/mediarouter/res/values-it/strings.xml
+++ b/v7/mediarouter/res/values-it/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Espandi"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Comprimi"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Copertina"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nessun contenuto multimediale selezionato"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nessuna informazione disponibile"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Trasmissione dello schermo in corso"</string>
diff --git a/v7/mediarouter/res/values-iw/strings.xml b/v7/mediarouter/res/values-iw/strings.xml
index d1698a5..8b52adf 100644
--- a/v7/mediarouter/res/values-iw/strings.xml
+++ b/v7/mediarouter/res/values-iw/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"השהה"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"הרחב"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"כווץ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"עטיפת אלבום"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"לא נבחרה מדיה"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"אין מידע זמין"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"העברת מסך מתבצעת"</string>
diff --git a/v7/mediarouter/res/values-ja/strings.xml b/v7/mediarouter/res/values-ja/strings.xml
index ffe9054..b126965 100644
--- a/v7/mediarouter/res/values-ja/strings.xml
+++ b/v7/mediarouter/res/values-ja/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"一時停止"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"折りたたむ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"アルバムアート"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"メディアが選択されていません"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"情報がありません"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"画面をキャストしています"</string>
diff --git a/v7/mediarouter/res/values-ka-rGE/strings.xml b/v7/mediarouter/res/values-ka-rGE/strings.xml
index 2182b64..046e361 100644
--- a/v7/mediarouter/res/values-ka-rGE/strings.xml
+++ b/v7/mediarouter/res/values-ka-rGE/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"პაუზა"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"გაშლა"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ჩაკეცვა"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ალბომის გარეკანი"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"მედია არჩეული არ არის"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ინფორმაცია არ არის ხელმისაწვდომი"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"მიმდინარეობს ეკრანის გადაცემა"</string>
diff --git a/v7/mediarouter/res/values-kk-rKZ/strings.xml b/v7/mediarouter/res/values-kk-rKZ/strings.xml
index 5c72c5f..5cf4e5a2 100644
--- a/v7/mediarouter/res/values-kk-rKZ/strings.xml
+++ b/v7/mediarouter/res/values-kk-rKZ/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Кідірту"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Жаю"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жию"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом шебері"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ешбір тасушы таңдалмаған"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Қол жетімді ақпарат жоқ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Экранды трансляциялау"</string>
diff --git a/v7/mediarouter/res/values-km-rKH/strings.xml b/v7/mediarouter/res/values-km-rKH/strings.xml
index cdf94db..fd05668 100644
--- a/v7/mediarouter/res/values-km-rKH/strings.xml
+++ b/v7/mediarouter/res/values-km-rKH/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ផ្អាក"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ពង្រីក"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"បង្រួម"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ស្នាដៃសិល្បៈអាល់ប៊ុម"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"គ្មានការជ្រើសមេឌៀទេ"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"មិនមានព័ត៌មានទេ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"កំពុងខាសអេក្រង់"</string>
diff --git a/v7/mediarouter/res/values-kn-rIN/strings.xml b/v7/mediarouter/res/values-kn-rIN/strings.xml
index 970dc41..9cae5be 100644
--- a/v7/mediarouter/res/values-kn-rIN/strings.xml
+++ b/v7/mediarouter/res/values-kn-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ವಿರಾಮ"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ವಿಸ್ತರಿಸು"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ಸಂಕುಚಿಸು"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ಆಲ್ಬಮ್ ಕಲೆ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ಯಾವುದೇ ಮಾಧ್ಯಮ ಆಯ್ಕೆಮಾಡಲಾಗಿಲ್ಲ"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ಯಾವುದೇ ಮಾಹಿತಿ ಲಭ್ಯವಿಲ್ಲ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"ಪರದೆಯನ್ನು ಬಿತ್ತರಿಸಲಾಗುತ್ತಿದೆ"</string>
diff --git a/v7/mediarouter/res/values-ko/strings.xml b/v7/mediarouter/res/values-ko/strings.xml
index fc1dbac..7f53382 100644
--- a/v7/mediarouter/res/values-ko/strings.xml
+++ b/v7/mediarouter/res/values-ko/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"일시중지"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"펼치기"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"접기"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"앨범아트"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"선택한 미디어 없음"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"정보가 없습니다."</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"화면 전송 중"</string>
diff --git a/v7/mediarouter/res/values-ky-rKG/strings.xml b/v7/mediarouter/res/values-ky-rKG/strings.xml
index d7409db..99201dc 100644
--- a/v7/mediarouter/res/values-ky-rKG/strings.xml
+++ b/v7/mediarouter/res/values-ky-rKG/strings.xml
@@ -16,7 +16,7 @@
<resources xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
- <string name="mr_system_route_name" msgid="5441529851481176817">"Систем"</string>
+ <string name="mr_system_route_name" msgid="5441529851481176817">"Тутум"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"Түзмөктөр"</string>
<string name="mr_button_content_description" msgid="3698378085901466129">"Тышкы экранга чыгаруу баскычы"</string>
<string name="mr_chooser_title" msgid="414301941546135990">"Төмөнкүгө чыгаруу"</string>
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Тындыруу"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Жайып көрсөтүү"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Жыйыштыруу"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Альбом мукабасы"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Бир да медиа файл тандалган жок"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Эч маалымат жок"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Тышкы экранга чыгарылууда"</string>
diff --git a/v7/mediarouter/res/values-lo-rLA/strings.xml b/v7/mediarouter/res/values-lo-rLA/strings.xml
index a19dd69..2765364 100644
--- a/v7/mediarouter/res/values-lo-rLA/strings.xml
+++ b/v7/mediarouter/res/values-lo-rLA/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ຢຸດຊົ່ວຄາວ"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ຂະຫຍາຍ"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ຫຍໍ້ລົງ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ໜ້າປົກອະລະບໍ້າ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ບໍ່ໄດ້ເລືອກມີເດຍໃດໄວ້"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ບໍ່ມີຂໍ້ມູນ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"ການສົ່ງພາບໜ້າຈໍ"</string>
diff --git a/v7/mediarouter/res/values-lt/strings.xml b/v7/mediarouter/res/values-lt/strings.xml
index d286e0c..208752f 100644
--- a/v7/mediarouter/res/values-lt/strings.xml
+++ b/v7/mediarouter/res/values-lt/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pristabdyti"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Išskleisti"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sutraukti"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumo viršelis"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nepasirinkta jokia medija"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Informacija nepasiekiama"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Perduodamas ekranas"</string>
diff --git a/v7/mediarouter/res/values-lv/strings.xml b/v7/mediarouter/res/values-lv/strings.xml
index 8f49d85..832a3ba 100644
--- a/v7/mediarouter/res/values-lv/strings.xml
+++ b/v7/mediarouter/res/values-lv/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Apturēt"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Izvērst"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Sakļaut"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albuma vāciņš"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nav atlasīti multivides faili"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nav pieejama informācija"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Notiek ekrāna apraide"</string>
diff --git a/v7/mediarouter/res/values-mk-rMK/strings.xml b/v7/mediarouter/res/values-mk-rMK/strings.xml
index e8ff3e7..726e285 100644
--- a/v7/mediarouter/res/values-mk-rMK/strings.xml
+++ b/v7/mediarouter/res/values-mk-rMK/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Собери"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Корица на албум"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Не се избрани медиуми"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нема достапни информации"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Екранот се емитува"</string>
diff --git a/v7/mediarouter/res/values-ml-rIN/strings.xml b/v7/mediarouter/res/values-ml-rIN/strings.xml
index 7dbdf5d..b1d2cbe 100644
--- a/v7/mediarouter/res/values-ml-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ml-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"തൽക്കാലം നിർത്തൂ"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"വികസിപ്പിക്കുക"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ചുരുക്കുക"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ആൽബം ആർട്ട്"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"മീഡിയയൊന്നും തിരഞ്ഞെടുത്തിട്ടില്ല"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"വിവരങ്ങളൊന്നും ലഭ്യമല്ല"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"സ്ക്രീൻ കാസ്റ്റുചെയ്യുന്നു"</string>
diff --git a/v7/mediarouter/res/values-mn-rMN/strings.xml b/v7/mediarouter/res/values-mn-rMN/strings.xml
index db6d599..d07d314 100644
--- a/v7/mediarouter/res/values-mn-rMN/strings.xml
+++ b/v7/mediarouter/res/values-mn-rMN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Түр зогсоох"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Дэлгэх"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Хураах"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Цомгийн зураг"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ямар ч медиа сонгоогүй"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Мэдээлэл байхгүй байна"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Дэлгэцийг дамжуулж байна"</string>
diff --git a/v7/mediarouter/res/values-mr-rIN/strings.xml b/v7/mediarouter/res/values-mr-rIN/strings.xml
index b3f9264..4e24aff 100644
--- a/v7/mediarouter/res/values-mr-rIN/strings.xml
+++ b/v7/mediarouter/res/values-mr-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"विराम"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तृत करा"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"संकुचित करा"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"अल्बम कला"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"मीडिया निवडला नाही"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"कोणतीही माहिती उपलब्ध नाही"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रीन कास्ट करीत आहे"</string>
diff --git a/v7/mediarouter/res/values-ms-rMY/strings.xml b/v7/mediarouter/res/values-ms-rMY/strings.xml
index 4d7a0c8..2a4cd1c 100644
--- a/v7/mediarouter/res/values-ms-rMY/strings.xml
+++ b/v7/mediarouter/res/values-ms-rMY/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Jeda"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Kembangkan"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Runtuhkan"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Seni album"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Tiada media dipilih"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Maklumat tidak tersedia"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Menghantar skrin"</string>
diff --git a/v7/mediarouter/res/values-my-rMM/strings.xml b/v7/mediarouter/res/values-my-rMM/strings.xml
index 9447089..eca8835 100644
--- a/v7/mediarouter/res/values-my-rMM/strings.xml
+++ b/v7/mediarouter/res/values-my-rMM/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ခဏရပ်ရန်"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ဖြန့်ချရန်၃"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ခေါက်သိမ်းရန်..."</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"အယ်လ်ဘမ်ပုံ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"မည်သည့်မီဒီမှ မရွေးချယ်ထားပါ"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"အချက်အလက် မရရှိနိုင်ပါ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"တည်းဖြတ်ရေး မျက်နှာပြင်"</string>
diff --git a/v7/mediarouter/res/values-nb/strings.xml b/v7/mediarouter/res/values-nb/strings.xml
index aea341f..27f9f03 100644
--- a/v7/mediarouter/res/values-nb/strings.xml
+++ b/v7/mediarouter/res/values-nb/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Sett på pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Utvid"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Skjul"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumgrafikk"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Du har ikke valgt noen medier"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Ingen informasjon er tilgjengelig"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Caster skjermen"</string>
diff --git a/v7/mediarouter/res/values-ne-rNP/strings.xml b/v7/mediarouter/res/values-ne-rNP/strings.xml
index d261553..6abadbf 100644
--- a/v7/mediarouter/res/values-ne-rNP/strings.xml
+++ b/v7/mediarouter/res/values-ne-rNP/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"रोक्नुहोस्"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"विस्तार गर्नुहोस्"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"संक्षिप्त पार्नुहोस्"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"एल्बम आर्ट"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"कुनै मिडिया चयन भएको छैन"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"जानकारी उपलब्ध छैन"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"स्क्रिन cast गर्दै"</string>
diff --git a/v7/mediarouter/res/values-nl/strings.xml b/v7/mediarouter/res/values-nl/strings.xml
index 7843b52..4a9346d 100644
--- a/v7/mediarouter/res/values-nl/strings.xml
+++ b/v7/mediarouter/res/values-nl/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Onderbreken"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Uitvouwen"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Samenvouwen"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albumhoes"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Geen media geselecteerd"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Geen informatie beschikbaar"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Scherm casten"</string>
diff --git a/v7/mediarouter/res/values-pa-rIN/strings.xml b/v7/mediarouter/res/values-pa-rIN/strings.xml
index 55dfd88..842a8b4 100644
--- a/v7/mediarouter/res/values-pa-rIN/strings.xml
+++ b/v7/mediarouter/res/values-pa-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"ਰੋਕੋ"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ਵਿਸਤਾਰ ਕਰੋ"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ਬੰਦ ਕਰੋ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ਐਲਬਮ ਆਰਟ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ਕੋਈ ਵੀ ਮੀਡੀਆ ਨਹੀਂ ਚੁਣਿਆ ਗਿਆ"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ਕੋਈ ਜਾਣਕਾਰੀ ਉਪਲਬਧ ਨਹੀਂ"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"ਸਕ੍ਰੀਨ ਜੋੜ ਰਿਹਾ ਹੈ"</string>
diff --git a/v7/mediarouter/res/values-pl/strings.xml b/v7/mediarouter/res/values-pl/strings.xml
index c6e7f8a..d66be1a 100644
--- a/v7/mediarouter/res/values-pl/strings.xml
+++ b/v7/mediarouter/res/values-pl/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Wstrzymaj"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozwiń"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zwiń"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Okładka albumu"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie wybrano multimediów"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Brak informacji"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Przesyłam ekran"</string>
diff --git a/v7/mediarouter/res/values-pt-rBR/strings.xml b/v7/mediarouter/res/values-pt-rBR/strings.xml
index 17e64f0..4d7e6cc 100644
--- a/v7/mediarouter/res/values-pt-rBR/strings.xml
+++ b/v7/mediarouter/res/values-pt-rBR/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
diff --git a/v7/mediarouter/res/values-pt-rPT/strings.xml b/v7/mediarouter/res/values-pt-rPT/strings.xml
index 3c9d4cb..0c68b92 100644
--- a/v7/mediarouter/res/values-pt-rPT/strings.xml
+++ b/v7/mediarouter/res/values-pt-rPT/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Interromper"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Reduzir"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Imagem do álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhum suporte multimédia selecionado"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"A transmitir o ecrã"</string>
diff --git a/v7/mediarouter/res/values-pt/strings.xml b/v7/mediarouter/res/values-pt/strings.xml
index 17e64f0..4d7e6cc 100644
--- a/v7/mediarouter/res/values-pt/strings.xml
+++ b/v7/mediarouter/res/values-pt/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausar"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Expandir"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Recolher"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Arte do álbum"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nenhuma mídia selecionada"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nenhuma informação disponível"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Transmitindo a tela"</string>
diff --git a/v7/mediarouter/res/values-ro/strings.xml b/v7/mediarouter/res/values-ro/strings.xml
index 20c3b71..9fe26a9 100644
--- a/v7/mediarouter/res/values-ro/strings.xml
+++ b/v7/mediarouter/res/values-ro/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Întrerupeți"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Extindeți"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Restrângeți"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Grafica albumului"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Niciun fișier media selectat"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nu sunt disponibile informații"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Se proiectează ecranul"</string>
diff --git a/v7/mediarouter/res/values-ru/strings.xml b/v7/mediarouter/res/values-ru/strings.xml
index 5a47ec6..4607a8c 100644
--- a/v7/mediarouter/res/values-ru/strings.xml
+++ b/v7/mediarouter/res/values-ru/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Приостановить"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Развернуть"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Свернуть"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обложка"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медиафайл не выбран"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Данных нет"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Подключение к удаленному монитору"</string>
diff --git a/v7/mediarouter/res/values-si-rLK/strings.xml b/v7/mediarouter/res/values-si-rLK/strings.xml
index 45f67ff..144a0d5 100644
--- a/v7/mediarouter/res/values-si-rLK/strings.xml
+++ b/v7/mediarouter/res/values-si-rLK/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"විරාම ගන්වන්න"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"දිග හරින්න"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"හකුළන්න"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ඇල්බම කලාව"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"මාධ්යය තෝරා නැත"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ලබා ගත හැකි තොරතුරු නොමැත"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"විකාශ තිරය"</string>
diff --git a/v7/mediarouter/res/values-sk/strings.xml b/v7/mediarouter/res/values-sk/strings.xml
index df95e00..b546bde 100644
--- a/v7/mediarouter/res/values-sk/strings.xml
+++ b/v7/mediarouter/res/values-sk/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pozastaviť"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Rozbaliť"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Zbaliť"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Obrázok albumu"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nie sú vybrané žiadne médiá"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nie sú k dispozícii žiadne informácie"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Prenáša sa obrazovka"</string>
diff --git a/v7/mediarouter/res/values-sl/strings.xml b/v7/mediarouter/res/values-sl/strings.xml
index 6425222..110c548 100644
--- a/v7/mediarouter/res/values-sl/strings.xml
+++ b/v7/mediarouter/res/values-sl/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Zaustavi"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Razširi"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Strni"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Naslovnica albuma"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ni izbrane predstavnosti"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Podatki niso na voljo"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Predvajanje zaslona"</string>
diff --git a/v7/mediarouter/res/values-sq-rAL/strings.xml b/v7/mediarouter/res/values-sq-rAL/strings.xml
index 6f375cf..8ed93c3 100644
--- a/v7/mediarouter/res/values-sq-rAL/strings.xml
+++ b/v7/mediarouter/res/values-sq-rAL/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pauzë"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Zgjeroje"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Palose"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Kopertina e albumit"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Nuk u zgjodh asnjë media"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Nuk jepet asnjë informacion"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Po transmeton ekranin"</string>
diff --git a/v7/mediarouter/res/values-sr/strings.xml b/v7/mediarouter/res/values-sr/strings.xml
index 26a36a9..5a72bd4 100644
--- a/v7/mediarouter/res/values-sr/strings.xml
+++ b/v7/mediarouter/res/values-sr/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Паузирај"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Прошири"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Скупи"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Омот албума"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Нема изабраних медија"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Нису доступне никакве информације"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Пребацује се екран"</string>
diff --git a/v7/mediarouter/res/values-sv/strings.xml b/v7/mediarouter/res/values-sv/strings.xml
index 3e8c47b..3724902 100644
--- a/v7/mediarouter/res/values-sv/strings.xml
+++ b/v7/mediarouter/res/values-sv/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Utöka"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Komprimera"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Skivomslag"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Inga media har valts"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Det finns ingen information"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Skärmen castas"</string>
diff --git a/v7/mediarouter/res/values-sw/strings.xml b/v7/mediarouter/res/values-sw/strings.xml
index 38fdb45..f12fd5c 100644
--- a/v7/mediarouter/res/values-sw/strings.xml
+++ b/v7/mediarouter/res/values-sw/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Sitisha"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Panua"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Kunja"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Sanaa ya albamu"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Hakuna maudhui yaliyochaguliwa"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hakuna maelezo yaliyopatikana"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Inatuma skrini"</string>
diff --git a/v7/mediarouter/res/values-ta-rIN/strings.xml b/v7/mediarouter/res/values-ta-rIN/strings.xml
index 6147b75..c314178 100644
--- a/v7/mediarouter/res/values-ta-rIN/strings.xml
+++ b/v7/mediarouter/res/values-ta-rIN/strings.xml
@@ -18,8 +18,8 @@
xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
<string name="mr_system_route_name" msgid="5441529851481176817">"அமைப்பு"</string>
<string name="mr_user_route_category_name" msgid="7498112907524977311">"சாதனங்கள்"</string>
- <string name="mr_button_content_description" msgid="3698378085901466129">"அனுப்புதல் பொத்தான்"</string>
- <string name="mr_chooser_title" msgid="414301941546135990">"இதற்கு அனுப்பு"</string>
+ <string name="mr_button_content_description" msgid="3698378085901466129">"திரையிடு பட்டன்"</string>
+ <string name="mr_chooser_title" msgid="414301941546135990">"இதில் திரையிடு"</string>
<string name="mr_chooser_searching" msgid="6349900579507521956">"சாதனங்களைத் தேடுகிறது"</string>
<string name="mr_controller_disconnect" msgid="1227264889412989580">"தொடர்பைத் துண்டி"</string>
<string name="mr_controller_stop" msgid="4570331844078181931">"அனுப்புவதை நிறுத்து"</string>
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"இடைநிறுத்தும்"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"விரிவாக்கு"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"சுருக்கு"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ஆல்பம் ஆர்ட்"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"மீடியா எதுவும் தேர்ந்தெடுக்கப்படவில்லை"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"தகவல் எதுவுமில்லை"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"திரையை அனுப்புகிறீர்கள்"</string>
diff --git a/v7/mediarouter/res/values-te-rIN/strings.xml b/v7/mediarouter/res/values-te-rIN/strings.xml
index 73c75f4..59a4f19 100644
--- a/v7/mediarouter/res/values-te-rIN/strings.xml
+++ b/v7/mediarouter/res/values-te-rIN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"పాజ్ చేస్తుంది"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"విస్తరింపజేస్తుంది"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"కుదిస్తుంది"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ఆల్బమ్ ఆర్ట్"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"మీడియా ఏదీ ఎంచుకోబడలేదు"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"సమాచారం అందుబాటులో లేదు"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"స్క్రీన్ను ప్రసారం చేస్తోంది"</string>
diff --git a/v7/mediarouter/res/values-th/strings.xml b/v7/mediarouter/res/values-th/strings.xml
index fdf0957..1bfd091 100644
--- a/v7/mediarouter/res/values-th/strings.xml
+++ b/v7/mediarouter/res/values-th/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"หยุดชั่วคราว"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"ขยาย"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"ยุบ"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"ปกอัลบั้ม"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"ไม่ได้เลือกสื่อไว้"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"ไม่มีข้อมูล"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"กำลังแคสต์หน้าจอ"</string>
diff --git a/v7/mediarouter/res/values-tl/strings.xml b/v7/mediarouter/res/values-tl/strings.xml
index 7c04b33..82396e1 100644
--- a/v7/mediarouter/res/values-tl/strings.xml
+++ b/v7/mediarouter/res/values-tl/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"I-pause"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Palawakin"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"I-collapse"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Album art"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Walang piniling media"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Walang available na impormasyon"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Kina-cast ang screen"</string>
diff --git a/v7/mediarouter/res/values-tr/strings.xml b/v7/mediarouter/res/values-tr/strings.xml
index f8316f4..e639963 100644
--- a/v7/mediarouter/res/values-tr/strings.xml
+++ b/v7/mediarouter/res/values-tr/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Duraklat"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Genişlet"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Daralt"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albüm kapağı"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Medya seçilmedi"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Bilgi yok"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekran yayınlanıyor"</string>
diff --git a/v7/mediarouter/res/values-uk/strings.xml b/v7/mediarouter/res/values-uk/strings.xml
index fd4e0d0..a768e2c 100644
--- a/v7/mediarouter/res/values-uk/strings.xml
+++ b/v7/mediarouter/res/values-uk/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Призупинити"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Розгорнути"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Згорнути"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Обкладинка альбому"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Медіа-файл не вибрано"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Немає даних"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Трансляція екрана"</string>
diff --git a/v7/mediarouter/res/values-ur-rPK/strings.xml b/v7/mediarouter/res/values-ur-rPK/strings.xml
index afd5534..5cb3b36 100644
--- a/v7/mediarouter/res/values-ur-rPK/strings.xml
+++ b/v7/mediarouter/res/values-ur-rPK/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"موقوف کریں"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"پھیلائیں"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"سکیڑیں"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"البم آرٹ"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"کوئی میڈیا منتخب نہیں ہے"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"کوئی معلومات دستیاب نہیں"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"سکرین کاسٹ ہو رہی ہے"</string>
diff --git a/v7/mediarouter/res/values-uz-rUZ/strings.xml b/v7/mediarouter/res/values-uz-rUZ/strings.xml
index c13b06b..9955cdfd 100644
--- a/v7/mediarouter/res/values-uz-rUZ/strings.xml
+++ b/v7/mediarouter/res/values-uz-rUZ/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"To‘xtatib turish"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Yoyish"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Yig‘ish"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Albom muqovasi"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Multimedia tanlamagan"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Hech qanday ma’lumot yo‘q"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Ekranni translatsiya qilish"</string>
diff --git a/v7/mediarouter/res/values-vi/strings.xml b/v7/mediarouter/res/values-vi/strings.xml
index 3301cee..0080e3e 100644
--- a/v7/mediarouter/res/values-vi/strings.xml
+++ b/v7/mediarouter/res/values-vi/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Tạm dừng"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Mở rộng"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Thu gọn"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ảnh bìa album"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Không có phương tiện nào được chọn"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Không có thông tin nào"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Đang truyền màn hình"</string>
diff --git a/v7/mediarouter/res/values-zh-rCN/strings.xml b/v7/mediarouter/res/values-zh-rCN/strings.xml
index cdd66f53..aabe727 100644
--- a/v7/mediarouter/res/values-zh-rCN/strings.xml
+++ b/v7/mediarouter/res/values-zh-rCN/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"暂停"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"展开"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"折叠"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"专辑封面"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未选择任何媒体"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"没有任何相关信息"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投射屏幕"</string>
diff --git a/v7/mediarouter/res/values-zh-rHK/strings.xml b/v7/mediarouter/res/values-zh-rHK/strings.xml
index 873e5ce..d01c823 100644
--- a/v7/mediarouter/res/values-zh-rHK/strings.xml
+++ b/v7/mediarouter/res/values-zh-rHK/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"尚未選擇媒體"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有詳細資料"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
diff --git a/v7/mediarouter/res/values-zh-rTW/strings.xml b/v7/mediarouter/res/values-zh-rTW/strings.xml
index 27755e7..68347e5 100644
--- a/v7/mediarouter/res/values-zh-rTW/strings.xml
+++ b/v7/mediarouter/res/values-zh-rTW/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"暫停"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"展開"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"收合"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"專輯封面"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未選取任何媒體"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"沒有可用的資訊"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"正在投放螢幕"</string>
diff --git a/v7/mediarouter/res/values-zu/strings.xml b/v7/mediarouter/res/values-zu/strings.xml
index f513fd4..e50acc8 100644
--- a/v7/mediarouter/res/values-zu/strings.xml
+++ b/v7/mediarouter/res/values-zu/strings.xml
@@ -28,6 +28,7 @@
<string name="mr_controller_pause" msgid="5451884435510905406">"Misa isikhashana"</string>
<string name="mr_controller_expand_group" msgid="8062427022744266907">"Nweba"</string>
<string name="mr_controller_collapse_group" msgid="7924809056904240926">"Goqa"</string>
+ <string name="mr_controller_album_art" msgid="6422801843540543585">"Ubuciko be-albhamu"</string>
<string name="mr_controller_no_media_selected" msgid="6547130360349182381">"Ayikho imidiya ekhethiwe"</string>
<string name="mr_controller_no_info_available" msgid="5585418471741142924">"Alukho ulwazi olutholakalayo"</string>
<string name="mr_controller_casting_screen" msgid="4868457957151124867">"Isikrini sokusakaza"</string>
diff --git a/v7/mediarouter/res/values/attrs.xml b/v7/mediarouter/res/values/attrs.xml
index 81073dc..f0e5b51 100644
--- a/v7/mediarouter/res/values/attrs.xml
+++ b/v7/mediarouter/res/values/attrs.xml
@@ -37,7 +37,6 @@
<attr name="mediaRouteCastDrawable" format="reference" />
<attr name="mediaRouteAudioTrackDrawable" format="reference" />
<attr name="mediaRouteDefaultIconDrawable" format="reference" />
- <attr name="mediaRouteBluetoothIconDrawable" format="reference" />
<attr name="mediaRouteTvIconDrawable" format="reference" />
<attr name="mediaRouteSpeakerIconDrawable" format="reference" />
<attr name="mediaRouteSpeakerGroupIconDrawable" format="reference" />
diff --git a/v7/mediarouter/res/values/dimens.xml b/v7/mediarouter/res/values/dimens.xml
index 10e5751..d41b0b7 100644
--- a/v7/mediarouter/res/values/dimens.xml
+++ b/v7/mediarouter/res/values/dimens.xml
@@ -35,5 +35,9 @@
<dimen name="mr_controller_volume_group_list_padding_top">16dp</dimen>
<!-- Group list expand/collapse animation duration. -->
- <integer name="mr_controller_volume_group_list_animation_duration_ms">200</integer>
+ <integer name="mr_controller_volume_group_list_animation_duration_ms">400</integer>
+ <!-- Group list fade in animation duration. -->
+ <integer name="mr_controller_volume_group_list_fade_in_duration_ms">400</integer>
+ <!-- Group list fade out animation duration. -->
+ <integer name="mr_controller_volume_group_list_fade_out_duration_ms">200</integer>
</resources>
diff --git a/v7/mediarouter/res/values/strings.xml b/v7/mediarouter/res/values/strings.xml
index fad0408..b6a949a 100644
--- a/v7/mediarouter/res/values/strings.xml
+++ b/v7/mediarouter/res/values/strings.xml
@@ -52,6 +52,9 @@
<!-- Content description for accessibility (not shown on the screen): group collapse button. Pressing button hides group members of a selected route group. [CHAR LIMIT=NONE] -->
<string name="mr_controller_collapse_group">Collapse</string>
+ <!-- Content description for accessibility (not shown on the screen): album art button. Clicking on the album art takes user to a predefined activity per media app. [CHAR LIMIT=50] -->
+ <string name="mr_controller_album_art">Album art</string>
+
<!-- Placeholder text to show when no media have been selected for playback. [CHAR LIMIT=50] -->
<string name="mr_controller_no_media_selected">No media selected</string>
diff --git a/v7/mediarouter/res/values/themes.xml b/v7/mediarouter/res/values/themes.xml
index 95aac1c..c655243 100644
--- a/v7/mediarouter/res/values/themes.xml
+++ b/v7/mediarouter/res/values/themes.xml
@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2013 The Android Open Source Project
+<!-- Copyright (C) 2016 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
@@ -22,15 +22,14 @@
<item name="MediaRouteControllerWindowBackground">@drawable/mr_dialog_material_background_dark</item>
<item name="mediaRouteOffDrawable">@drawable/ic_media_route_off_mono_dark</item>
- <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_dark</item>
- <item name="mediaRouteOnDrawable">@drawable/ic_media_route_on_mono_dark</item>
+ <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_dark</item>
+ <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_dark</item>
<item name="mediaRouteCloseDrawable">@drawable/mr_ic_close_dark</item>
<item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_dark</item>
<item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_dark</item>
<item name="mediaRouteCastDrawable">@drawable/mr_ic_cast_dark</item>
<item name="mediaRouteAudioTrackDrawable">@drawable/ic_audiotrack</item>
<item name="mediaRouteDefaultIconDrawable">@drawable/ic_cast_white</item>
- <item name="mediaRouteBluetoothIconDrawable">@drawable/ic_bluetooth_white</item>
<item name="mediaRouteTvIconDrawable">@drawable/ic_tv_dark</item>
<item name="mediaRouteSpeakerIconDrawable">@drawable/ic_speaker_dark</item>
<item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_speaker_group_dark</item>
@@ -55,15 +54,14 @@
<item name="MediaRouteControllerWindowBackground">@drawable/mr_dialog_material_background_light</item>
<item name="mediaRouteOffDrawable">@drawable/ic_cast_off_light</item>
- <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_mono_light</item>
- <item name="mediaRouteOnDrawable">@drawable/ic_cast_on_light</item>
+ <item name="mediaRouteConnectingDrawable">@drawable/mr_ic_media_route_connecting_light</item>
+ <item name="mediaRouteOnDrawable">@drawable/mr_ic_media_route_on_light</item>
<item name="mediaRouteCloseDrawable">@drawable/mr_ic_close_light</item>
<item name="mediaRoutePlayDrawable">@drawable/mr_ic_play_light</item>
<item name="mediaRoutePauseDrawable">@drawable/mr_ic_pause_light</item>
<item name="mediaRouteCastDrawable">@drawable/mr_ic_cast_light</item>
<item name="mediaRouteAudioTrackDrawable">@drawable/mr_ic_audiotrack_light</item>
<item name="mediaRouteDefaultIconDrawable">@drawable/ic_cast_grey</item>
- <item name="mediaRouteBluetoothIconDrawable">@drawable/ic_bluetooth_grey</item>
<item name="mediaRouteTvIconDrawable">@drawable/ic_tv_light</item>
<item name="mediaRouteSpeakerIconDrawable">@drawable/ic_speaker_light</item>
<item name="mediaRouteSpeakerGroupIconDrawable">@drawable/ic_speaker_group_light</item>
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
index c44e8d2..da5c194 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteButton.java
@@ -231,7 +231,7 @@
}
MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
- if (route.isDefault() || !route.matchesSelector(mSelector)) {
+ if (route.isDefaultOrBluetooth() || !route.matchesSelector(mSelector)) {
if (fm.findFragmentByTag(CHOOSER_FRAGMENT_TAG) != null) {
Log.w(TAG, "showDialog(): Route chooser dialog already showing!");
return false;
@@ -492,7 +492,8 @@
private void refreshRoute() {
if (mAttachedToWindow) {
final MediaRouter.RouteInfo route = mRouter.getSelectedRoute();
- final boolean isRemote = !route.isDefault() && route.matchesSelector(mSelector);
+ final boolean isRemote = !route.isDefaultOrBluetooth()
+ && route.matchesSelector(mSelector);
final boolean isConnecting = isRemote && route.isConnecting();
boolean needsRefresh = false;
@@ -507,7 +508,7 @@
if (needsRefresh) {
refreshDrawableState();
- if (mIsConnecting && mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
+ if (mRemoteIndicator.getCurrent() instanceof AnimationDrawable) {
AnimationDrawable curDrawable =
(AnimationDrawable) mRemoteIndicator.getCurrent();
if (!curDrawable.isRunning()) {
@@ -515,9 +516,6 @@
}
}
}
-
- setEnabled(mRouter.isRouteAvailable(mSelector,
- MediaRouter.AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE));
}
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
index 5856f38..08338b6 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialog.java
@@ -25,6 +25,7 @@
import android.content.res.TypedArray;
import android.graphics.drawable.Drawable;
import android.net.Uri;
+import android.os.AsyncTask;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.support.annotation.NonNull;
@@ -72,6 +73,8 @@
private RouteAdapter mAdapter;
private ListView mListView;
private boolean mAttachedToWindow;
+ private AsyncTask<Void, Void, Void> mRefreshRoutesTask;
+ private AsyncTask<Void, Void, Void> mOnItemClickTask;
public MediaRouteChooserDialog(Context context) {
this(context, 0);
@@ -147,7 +150,8 @@
* @return True if the route should be included in the chooser dialog.
*/
public boolean onFilterRoute(@NonNull MediaRouter.RouteInfo route) {
- return !route.isDefault() && route.isEnabled() && route.matchesSelector(mSelector);
+ return !route.isDefaultOrBluetooth() && route.isEnabled()
+ && route.matchesSelector(mSelector);
}
@Override
@@ -197,12 +201,40 @@
*/
public void refreshRoutes() {
if (mAttachedToWindow) {
- mRoutes.clear();
- mRoutes.addAll(mRouter.getRoutes());
- onFilterRoutes(mRoutes);
- RouteComparator.loadRouteUsageScores(getContext(), mRoutes);
- Collections.sort(mRoutes, RouteComparator.sInstance);
- mAdapter.notifyDataSetChanged();
+ if (mRefreshRoutesTask != null) {
+ mRefreshRoutesTask.cancel(true);
+ mRefreshRoutesTask = null;
+ }
+ mRefreshRoutesTask = new AsyncTask<Void, Void, Void>() {
+ private ArrayList<MediaRouter.RouteInfo> mNewRoutes;
+
+ @Override
+ protected void onPreExecute() {
+ mNewRoutes = new ArrayList<>(mRouter.getRoutes());
+ onFilterRoutes(mNewRoutes);
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ // In API 4 ~ 10, AsyncTasks are running in parallel. Needs synchronization.
+ synchronized (MediaRouteChooserDialog.this) {
+ if (!isCancelled()) {
+ RouteComparator.getInstance(getContext())
+ .loadRouteUsageScores(mNewRoutes);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void params) {
+ mRoutes.clear();
+ mRoutes.addAll(mNewRoutes);
+ Collections.sort(mRoutes, RouteComparator.sInstance);
+ mAdapter.notifyDataSetChanged();
+ mRefreshRoutesTask = null;
+ }
+ }.execute();
}
}
@@ -210,7 +242,6 @@
implements ListView.OnItemClickListener {
private final LayoutInflater mInflater;
private final Drawable mDefaultIcon;
- private final Drawable mBluetoothIcon;
private final Drawable mTvIcon;
private final Drawable mSpeakerIcon;
private final Drawable mSpeakerGroupIcon;
@@ -220,15 +251,13 @@
mInflater = LayoutInflater.from(context);
TypedArray styledAttributes = getContext().obtainStyledAttributes(new int[] {
R.attr.mediaRouteDefaultIconDrawable,
- R.attr.mediaRouteBluetoothIconDrawable,
R.attr.mediaRouteTvIconDrawable,
R.attr.mediaRouteSpeakerIconDrawable,
R.attr.mediaRouteSpeakerGroupIconDrawable});
mDefaultIcon = styledAttributes.getDrawable(0);
- mBluetoothIcon = styledAttributes.getDrawable(1);
- mTvIcon = styledAttributes.getDrawable(2);
- mSpeakerIcon = styledAttributes.getDrawable(3);
- mSpeakerGroupIcon = styledAttributes.getDrawable(4);
+ mTvIcon = styledAttributes.getDrawable(1);
+ mSpeakerIcon = styledAttributes.getDrawable(2);
+ mSpeakerGroupIcon = styledAttributes.getDrawable(3);
styledAttributes.recycle();
}
@@ -277,11 +306,27 @@
@Override
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
- MediaRouter.RouteInfo route = getItem(position);
- if (route.isEnabled()) {
- route.select();
- RouteComparator.storeRouteUsageScores(getContext(), route.getId());
- dismiss();
+ final MediaRouter.RouteInfo route = getItem(position);
+ if (route.isEnabled() && mOnItemClickTask == null) {
+ mOnItemClickTask = new AsyncTask<Void, Void, Void>() {
+ @Override
+ protected void onPreExecute() {
+ route.select();
+ }
+
+ @Override
+ protected Void doInBackground(Void... params) {
+ RouteComparator.getInstance(getContext())
+ .storeRouteUsageScores(route.getId());
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Void params) {
+ dismiss();
+ mOnItemClickTask = null;
+ }
+ }.execute();
}
}
@@ -305,8 +350,6 @@
private Drawable getDefaultIconDrawable(MediaRouter.RouteInfo route) {
// If the type of the receiver device is specified, use it.
switch (route.getDeviceType()) {
- case MediaRouter.RouteInfo.DEVICE_TYPE_BLUETOOTH:
- return mBluetoothIcon;
case MediaRouter.RouteInfo.DEVICE_TYPE_TV:
return mTvIcon;
case MediaRouter.RouteInfo.DEVICE_TYPE_SPEAKER:
@@ -318,9 +361,6 @@
// Only speakers can be grouped for now.
return mSpeakerGroupIcon;
}
- if (route.isDeviceTypeBluetooth()) {
- return mBluetoothIcon;
- }
return mDefaultIcon;
}
}
@@ -356,8 +396,21 @@
private static final float MIN_USAGE_SCORE = 0.1f;
private static final float USAGE_SCORE_DECAY_FACTOR = 0.95f;
- public static final RouteComparator sInstance = new RouteComparator();
- public static final HashMap<String, Float> sRouteUsageScoreMap = new HashMap();
+ private static RouteComparator sInstance;
+ private final HashMap<String, Float> mRouteUsageScoreMap;
+ private final SharedPreferences mPreferences;
+
+ public static RouteComparator getInstance(Context context) {
+ if (sInstance == null) {
+ sInstance = new RouteComparator(context);
+ }
+ return sInstance;
+ }
+
+ private RouteComparator(Context context) {
+ mRouteUsageScoreMap = new HashMap();
+ mPreferences = PreferenceManager.getDefaultSharedPreferences(context);
+ }
@Override
public int compare(MediaRouter.RouteInfo lhs, MediaRouter.RouteInfo rhs) {
@@ -366,18 +419,11 @@
} else if (rhs == null) {
return 1;
}
- if (lhs.isDeviceTypeBluetooth()) {
- if (!rhs.isDeviceTypeBluetooth()) {
- return 1;
- }
- } else if (rhs.isDeviceTypeBluetooth()) {
- return -1;
- }
- Float lhsUsageScore = sRouteUsageScoreMap.get(lhs.getId());
+ Float lhsUsageScore = mRouteUsageScoreMap.get(lhs.getId());
if (lhsUsageScore == null) {
lhsUsageScore = 0f;
}
- Float rhsUsageScore = sRouteUsageScoreMap.get(rhs.getId());
+ Float rhsUsageScore = mRouteUsageScoreMap.get(rhs.getId());
if (rhsUsageScore == null) {
rhsUsageScore = 0f;
}
@@ -387,21 +433,19 @@
return lhs.getName().compareTo(rhs.getName());
}
- private static void loadRouteUsageScores(
- Context context, List<MediaRouter.RouteInfo> routes) {
- sRouteUsageScoreMap.clear();
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
+ private void loadRouteUsageScores(List<MediaRouter.RouteInfo> routes) {
for (MediaRouter.RouteInfo route : routes) {
- sRouteUsageScoreMap.put(route.getId(),
- preferences.getFloat(PREF_USAGE_SCORE_PREFIX + route.getId(), 0f));
+ if (mRouteUsageScoreMap.get(route.getId()) == null) {
+ mRouteUsageScoreMap.put(route.getId(),
+ mPreferences.getFloat(PREF_USAGE_SCORE_PREFIX + route.getId(), 0f));
+ }
}
}
- private static void storeRouteUsageScores(Context context, String selectedRouteId) {
- SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
- SharedPreferences.Editor prefEditor = preferences.edit();
- List<String> routeIds = new ArrayList<String>(
- Arrays.asList(preferences.getString(PREF_ROUTE_IDS, "").split(",")));
+ private void storeRouteUsageScores(String selectedRouteId) {
+ SharedPreferences.Editor prefEditor = mPreferences.edit();
+ List<String> routeIds = new ArrayList<>(
+ Arrays.asList(mPreferences.getString(PREF_ROUTE_IDS, "").split(",")));
if (!routeIds.contains(selectedRouteId)) {
routeIds.add(selectedRouteId);
}
@@ -412,14 +456,16 @@
// 2) 0, if usageScore * USAGE_SCORE_DECAY_FACTOR < MIN_USAGE_SCORE, or
// 3) usageScore * USAGE_SCORE_DECAY_FACTOR, otherwise,
String routeUsageScoreKey = PREF_USAGE_SCORE_PREFIX + routeId;
- float newUsageScore = preferences.getFloat(routeUsageScoreKey, 0f)
+ float newUsageScore = mPreferences.getFloat(routeUsageScoreKey, 0f)
* USAGE_SCORE_DECAY_FACTOR;
if (selectedRouteId.equals(routeId)) {
newUsageScore += 1f;
}
if (newUsageScore < MIN_USAGE_SCORE) {
+ mRouteUsageScoreMap.remove(routeId);
prefEditor.remove(routeId);
} else {
+ mRouteUsageScoreMap.put(routeId, newUsageScore);
prefEditor.putFloat(routeUsageScoreKey, newUsageScore);
if (routeIdsBuilder.length() > 0) {
routeIdsBuilder.append(',');
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialogFragment.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialogFragment.java
index 835b613..0e0268b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialogFragment.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteChooserDialogFragment.java
@@ -110,7 +110,7 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- mDialog = onCreateChooserDialog(getActivity(), savedInstanceState);
+ mDialog = onCreateChooserDialog(getContext(), savedInstanceState);
mDialog.setRouteSelector(getRouteSelector());
return mDialog;
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
index 1f375a0..f1e0af0 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialog.java
@@ -34,6 +34,7 @@
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
+import android.support.v7.app.OverlayListView.OverlayObject;
import android.support.v7.graphics.Palette;
import android.support.v7.media.MediaRouteSelector;
import android.support.v7.media.MediaRouter;
@@ -48,24 +49,36 @@
import android.view.ViewTreeObserver;
import android.view.accessibility.AccessibilityEvent;
import android.view.accessibility.AccessibilityManager;
+import android.view.animation.AccelerateDecelerateInterpolator;
+import android.view.animation.AlphaAnimation;
import android.view.animation.Animation;
+import android.view.animation.AnimationSet;
+import android.view.animation.AnimationUtils;
+import android.view.animation.Interpolator;
import android.view.animation.Transformation;
+import android.view.animation.TranslateAnimation;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.FrameLayout;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.SeekBar;
import android.widget.TextView;
import java.io.BufferedInputStream;
import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.ArrayList;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
/**
* This class implements the route controller dialog for {@link MediaRouter}.
@@ -77,12 +90,15 @@
* @see MediaRouteActionProvider
*/
public class MediaRouteControllerDialog extends AlertDialog {
- private static final String TAG = "MediaRouteControllerDialog";
+ // Tags should be less than 24 characters long (see docs for android.util.Log.isLoggable())
+ private static final String TAG = "MediaRouteCtrlDialog";
+ private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
// Time to wait before updating the volume when the user lets go of the seek bar
// to allow the route provider time to propagate the change and publish a new
// route descriptor.
private static final int VOLUME_UPDATE_DELAY_MILLIS = 500;
+ private static final int CONNECTION_TIMEOUT_MILLIS = (int) TimeUnit.SECONDS.toMillis(30L);
private static final int BUTTON_NEUTRAL_RES_ID = android.R.id.button3;
private static final int BUTTON_DISCONNECT_RES_ID = android.R.id.button2;
@@ -122,7 +138,12 @@
private LinearLayout mVolumeControlLayout;
private View mDividerView;
- private ListView mVolumeGroupList;
+ private OverlayListView mVolumeGroupList;
+ private VolumeGroupAdapter mVolumeGroupAdapter;
+ private List<MediaRouter.RouteInfo> mGroupMemberRoutes;
+ private Set<MediaRouter.RouteInfo> mGroupMemberRoutesAdded;
+ private Set<MediaRouter.RouteInfo> mGroupMemberRoutesRemoved;
+ private Set<MediaRouter.RouteInfo> mGroupMemberRoutesAnimatingWithBitmap;
private SeekBar mVolumeSlider;
private VolumeChangeListener mVolumeChangeListener;
private MediaRouter.RouteInfo mRouteInVolumeSliderTouched;
@@ -141,11 +162,26 @@
private Bitmap mArtIconBitmap;
private Uri mArtIconUri;
private boolean mIsGroupExpanded;
- private boolean mIsGroupListAnimationNeeded;
+ private boolean mIsGroupListAnimating;
+ private boolean mIsGroupListAnimationPending;
private int mGroupListAnimationDurationMs;
+ private int mGroupListFadeInDurationMs;
+ private int mGroupListFadeOutDurationMs;
+
+ private Interpolator mInterpolator;
+ private Interpolator mLinearOutSlowInInterpolator;
+ private Interpolator mFastOutSlowInInterpolator;
+ private Interpolator mAccelerateDecelerateInterpolator;
private final AccessibilityManager mAccessibilityManager;
+ private Runnable mGroupListFadeInAnimation = new Runnable() {
+ @Override
+ public void run() {
+ startGroupListFadeInAnimation();
+ }
+ };
+
public MediaRouteControllerDialog(Context context) {
this(context, 0);
}
@@ -163,6 +199,13 @@
R.dimen.mr_controller_volume_group_list_padding_top);
mAccessibilityManager =
(AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE);
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ mLinearOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.mr_linear_out_slow_in);
+ mFastOutSlowInInterpolator = AnimationUtils.loadInterpolator(context,
+ R.interpolator.mr_fast_out_slow_in);
+ }
+ mAccelerateDecelerateInterpolator = new AccelerateDecelerateInterpolator();
}
/**
@@ -211,6 +254,7 @@
mVolumeControlEnabled = enable;
if (mCreated) {
updateVolumeControlLayout();
+ updateLayoutHeight(false);
}
}
}
@@ -253,7 +297,7 @@
: mMediaController.getMetadata();
mDescription = metadata == null ? null : metadata.getDescription();
mState = mMediaController == null ? null : mMediaController.getPlaybackState();
- update();
+ update(false);
}
/**
@@ -345,7 +389,12 @@
mVolumeChangeListener = new VolumeChangeListener();
mVolumeSlider.setOnSeekBarChangeListener(mVolumeChangeListener);
- mVolumeGroupList = (ListView) findViewById(R.id.mr_volume_group_list);
+ mVolumeGroupList = (OverlayListView) findViewById(R.id.mr_volume_group_list);
+ mGroupMemberRoutes = new ArrayList<MediaRouter.RouteInfo>();
+ mVolumeGroupAdapter = new VolumeGroupAdapter(mContext, mGroupMemberRoutes);
+ mVolumeGroupList.setAdapter(mVolumeGroupAdapter);
+ mGroupMemberRoutesAnimatingWithBitmap = new HashSet<>();
+
MediaRouterThemeHelper.setMediaControlsBackgroundColor(mContext,
mMediaMainControlLayout, mVolumeGroupList, getGroup() != null);
MediaRouterThemeHelper.setVolumeSliderColor(mContext,
@@ -361,18 +410,18 @@
mIsGroupExpanded = !mIsGroupExpanded;
if (mIsGroupExpanded) {
mVolumeGroupList.setVisibility(View.VISIBLE);
- mVolumeGroupList.setAdapter(
- new VolumeGroupAdapter(mContext, getGroup().getRoutes()));
- } else {
- // Request layout to update UI based on {@code mIsGroupExpanded}.
- mDefaultControlLayout.requestLayout();
}
- mIsGroupListAnimationNeeded = true;
- updateLayoutHeight();
+ loadInterpolator();
+ updateLayoutHeight(true);
}
});
+ loadInterpolator();
mGroupListAnimationDurationMs = mContext.getResources().getInteger(
R.integer.mr_controller_volume_group_list_animation_duration_ms);
+ mGroupListFadeInDurationMs = mContext.getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_fade_in_duration_ms);
+ mGroupListFadeOutDurationMs = mContext.getResources().getInteger(
+ R.integer.mr_controller_volume_group_list_fade_out_duration_ms);
mCustomControlView = onCreateMediaControlView(savedInstanceState);
if (mCustomControlView != null) {
@@ -404,7 +453,7 @@
// Ensure the mArtView is updated.
mArtIconBitmap = null;
mArtIconUri = null;
- update();
+ update(false);
}
@Override
@@ -444,8 +493,8 @@
return super.onKeyUp(keyCode, event);
}
- private void update() {
- if (!mRoute.isSelected() || mRoute.isDefault()) {
+ private void update(boolean animate) {
+ if (!mRoute.isSelected() || mRoute.isDefaultOrBluetooth()) {
dismiss();
return;
}
@@ -465,6 +514,7 @@
}
updateVolumeControlLayout();
updatePlaybackControlLayout();
+ updateLayoutHeight(animate);
}
private boolean canShowPlaybackControlLayout() {
@@ -502,16 +552,21 @@
&& !canShowPlaybackControlLayout) ? View.GONE : View.VISIBLE);
}
- private void updateLayoutHeight() {
+ private void updateLayoutHeight(final boolean animate) {
// We need to defer the update until the first layout has occurred, as we don't yet know the
// overall visible display size in which the window this view is attached to has been
// positioned in.
+ mDefaultControlLayout.requestLayout();
ViewTreeObserver observer = mDefaultControlLayout.getViewTreeObserver();
observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
mDefaultControlLayout.getViewTreeObserver().removeGlobalOnLayoutListener(this);
- updateLayoutHeightInternal();
+ if (mIsGroupListAnimating) {
+ mIsGroupListAnimationPending = true;
+ } else {
+ updateLayoutHeightInternal(animate);
+ }
}
});
}
@@ -519,7 +574,7 @@
/**
* Updates the height of views and hide artwork or metadata if space is limited.
*/
- private void updateLayoutHeightInternal() {
+ private void updateLayoutHeightInternal(boolean animate) {
// Measure the size of widgets and get the height of main components.
int oldHeight = getLayoutHeight(mMediaMainControlLayout);
setLayoutHeight(mMediaMainControlLayout, ViewGroup.LayoutParams.FILL_PARENT);
@@ -539,13 +594,10 @@
}
}
int mainControllerHeight = getMainControllerHeight(canShowPlaybackControlLayout());
- int volumeGroupListCount = mVolumeGroupList.getAdapter() != null
- ? mVolumeGroupList.getAdapter().getCount() : 0;
+ int volumeGroupListCount = mGroupMemberRoutes.size();
// Scale down volume group list items in landscape mode.
- for (int i = 0; i < mVolumeGroupList.getChildCount(); i++) {
- updateVolumeGroupItemHeight(mVolumeGroupList.getChildAt(i));
- }
- int expandedGroupListHeight = mVolumeGroupListItemHeight * volumeGroupListCount;
+ int expandedGroupListHeight = getGroup() == null ? 0 :
+ mVolumeGroupListItemHeight * getGroup().getRoutes().size();
if (volumeGroupListCount > 0) {
expandedGroupListHeight += mVolumeGroupListPaddingTop;
}
@@ -599,7 +651,7 @@
mMediaMainControlLayout.clearAnimation();
mVolumeGroupList.clearAnimation();
mDefaultControlLayout.clearAnimation();
- if (mIsGroupListAnimationNeeded) {
+ if (animate) {
animateLayoutHeight(mMediaMainControlLayout, mainControllerHeight);
animateLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
animateLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
@@ -608,13 +660,14 @@
setLayoutHeight(mVolumeGroupList, visibleGroupListHeight);
setLayoutHeight(mDefaultControlLayout, desiredControlLayoutHeight);
}
- mIsGroupListAnimationNeeded = false;
// Maximize the window size with a transparent layout in advance for smooth animation.
setLayoutHeight(mExpandableAreaLayout, visibleRect.height());
+ rebuildVolumeGroupList(animate);
}
private void updateVolumeGroupItemHeight(View item) {
- setLayoutHeight(item, mVolumeGroupListItemHeight);
+ LinearLayout container = (LinearLayout) item.findViewById(R.id.volume_item_container);
+ setLayoutHeight(container, mVolumeGroupListItemHeight);
View icon = item.findViewById(R.id.mr_volume_item_icon);
ViewGroup.LayoutParams lp = icon.getLayoutParams();
lp.width = mVolumeGroupListItemIconSize;
@@ -634,49 +687,261 @@
};
anim.setDuration(mGroupListAnimationDurationMs);
if (android.os.Build.VERSION.SDK_INT >= 21) {
- anim.setInterpolator(mContext, mIsGroupExpanded ? R.interpolator.mr_linear_out_slow_in
- : R.interpolator.mr_fast_out_slow_in);
- }
- if (view == mVolumeGroupList) {
- anim.setAnimationListener(new Animation.AnimationListener() {
- @Override
- public void onAnimationStart(Animation animation) {
- mVolumeGroupList.setTranscriptMode(ListView.TRANSCRIPT_MODE_ALWAYS_SCROLL);
- }
-
- @Override
- public void onAnimationEnd(Animation animation) {
- mVolumeGroupList.setTranscriptMode(ListView.TRANSCRIPT_MODE_DISABLED);
- }
-
- @Override
- public void onAnimationRepeat(Animation animation) { }
- });
+ anim.setInterpolator(mInterpolator);
}
view.startAnimation(anim);
}
+ private void loadInterpolator() {
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
+ mInterpolator = mIsGroupExpanded ? mLinearOutSlowInInterpolator
+ : mFastOutSlowInInterpolator;
+ } else {
+ mInterpolator = mAccelerateDecelerateInterpolator;
+ }
+ }
+
private void updateVolumeControlLayout() {
if (isVolumeControlAvailable(mRoute)) {
if (mVolumeControlLayout.getVisibility() == View.GONE) {
mVolumeControlLayout.setVisibility(View.VISIBLE);
mVolumeSlider.setMax(mRoute.getVolumeMax());
mVolumeSlider.setProgress(mRoute.getVolume());
- if (getGroup() == null) {
- mGroupExpandCollapseButton.setVisibility(View.GONE);
- } else {
- mGroupExpandCollapseButton.setVisibility(View.VISIBLE);
- VolumeGroupAdapter adapter =
- (VolumeGroupAdapter) mVolumeGroupList.getAdapter();
- if (adapter != null) {
- adapter.notifyDataSetChanged();
- }
- }
+ mGroupExpandCollapseButton.setVisibility(getGroup() == null ? View.GONE
+ : View.VISIBLE);
}
} else {
mVolumeControlLayout.setVisibility(View.GONE);
}
- updateLayoutHeight();
+ }
+
+ private void rebuildVolumeGroupList(boolean animate) {
+ List<MediaRouter.RouteInfo> routes = getGroup() == null ? null : getGroup().getRoutes();
+ if (routes == null) {
+ mGroupMemberRoutes.clear();
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ } else if (MediaRouteDialogHelper.listUnorderedEquals(mGroupMemberRoutes, routes)) {
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ } else {
+ HashMap<MediaRouter.RouteInfo, Rect> previousRouteBoundMap = animate
+ ? MediaRouteDialogHelper.getItemBoundMap(mVolumeGroupList, mVolumeGroupAdapter)
+ : null;
+ HashMap<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap = animate
+ ? MediaRouteDialogHelper.getItemBitmapMap(mContext, mVolumeGroupList,
+ mVolumeGroupAdapter) : null;
+ mGroupMemberRoutesAdded =
+ MediaRouteDialogHelper.getItemsAdded(mGroupMemberRoutes, routes);
+ mGroupMemberRoutesRemoved = MediaRouteDialogHelper.getItemsRemoved(mGroupMemberRoutes,
+ routes);
+ mGroupMemberRoutes.addAll(0, mGroupMemberRoutesAdded);
+ mGroupMemberRoutes.removeAll(mGroupMemberRoutesRemoved);
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ if (animate && mIsGroupExpanded
+ && mGroupMemberRoutesAdded.size() + mGroupMemberRoutesRemoved.size() > 0) {
+ animateGroupListItems(previousRouteBoundMap, previousRouteBitmapMap);
+ } else {
+ mGroupMemberRoutesAdded = null;
+ mGroupMemberRoutesRemoved = null;
+ }
+ }
+ }
+
+ private void animateGroupListItems(final Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+ final Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+ mVolumeGroupList.setEnabled(false);
+ mVolumeGroupList.requestLayout();
+ mIsGroupListAnimating = true;
+ ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ animateGroupListItemsInternal(previousRouteBoundMap, previousRouteBitmapMap);
+ }
+ });
+ }
+
+ private void animateGroupListItemsInternal(
+ Map<MediaRouter.RouteInfo, Rect> previousRouteBoundMap,
+ Map<MediaRouter.RouteInfo, BitmapDrawable> previousRouteBitmapMap) {
+ if (mGroupMemberRoutesAdded == null || mGroupMemberRoutesRemoved == null) {
+ return;
+ }
+ int groupSizeDelta = mGroupMemberRoutesAdded.size() - mGroupMemberRoutesRemoved.size();
+ boolean listenerRegistered = false;
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) {
+ mVolumeGroupList.startAnimationAll();
+ mVolumeGroupList.postDelayed(mGroupListFadeInAnimation,
+ mGroupListAnimationDurationMs);
+ }
+
+ @Override
+ public void onAnimationEnd(Animation animation) { }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ };
+
+ // Animate visible items from previous positions to current positions except routes added
+ // just before. Added routes will remain hidden until translate animation finishes.
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ Rect previousBounds = previousRouteBoundMap.get(route);
+ int currentTop = view.getTop();
+ int previousTop = previousBounds != null ? previousBounds.top
+ : (currentTop + mVolumeGroupListItemHeight * groupSizeDelta);
+ AnimationSet animSet = new AnimationSet(true);
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+ previousTop = currentTop;
+ Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+ alphaAnim.setDuration(mGroupListFadeInDurationMs);
+ animSet.addAnimation(alphaAnim);
+ }
+ Animation translationAnim = new TranslateAnimation(0, 0, previousTop - currentTop, 0);
+ translationAnim.setDuration(mGroupListAnimationDurationMs);
+ animSet.addAnimation(translationAnim);
+ animSet.setFillAfter(true);
+ animSet.setFillEnabled(true);
+ animSet.setInterpolator(mInterpolator);
+ if (!listenerRegistered) {
+ listenerRegistered = true;
+ animSet.setAnimationListener(listener);
+ }
+ view.clearAnimation();
+ view.startAnimation(animSet);
+ previousRouteBoundMap.remove(route);
+ previousRouteBitmapMap.remove(route);
+ }
+
+ // If a member route doesn't exist any longer, it can be either removed or moved out of the
+ // ListView layout boundary. In this case, use the previously captured bitmaps for
+ // animation.
+ for (Map.Entry<MediaRouter.RouteInfo, BitmapDrawable> item
+ : previousRouteBitmapMap.entrySet()) {
+ final MediaRouter.RouteInfo route = item.getKey();
+ final BitmapDrawable bitmap = item.getValue();
+ final Rect bounds = previousRouteBoundMap.get(route);
+ OverlayObject object = null;
+ if (mGroupMemberRoutesRemoved.contains(route)) {
+ object = new OverlayObject(bitmap, bounds).setAlphaAnimation(1.0f, 0.0f)
+ .setDuration(mGroupListFadeOutDurationMs)
+ .setInterpolator(mInterpolator);
+ } else {
+ int deltaY = groupSizeDelta * mVolumeGroupListItemHeight;
+ object = new OverlayObject(bitmap, bounds).setTranslateYAnimation(deltaY)
+ .setDuration(mGroupListAnimationDurationMs)
+ .setInterpolator(mInterpolator)
+ .setAnimationEndListener(new OverlayObject.OnAnimationEndListener() {
+ @Override
+ public void onAnimationEnd() {
+ mGroupMemberRoutesAnimatingWithBitmap.remove(route);
+ mVolumeGroupAdapter.notifyDataSetChanged();
+ }
+ });
+ mGroupMemberRoutesAnimatingWithBitmap.add(route);
+ }
+ mVolumeGroupList.addOverlayObject(object);
+ }
+ }
+
+ private void startGroupListFadeInAnimation() {
+ clearGroupListAnimation(true);
+ mVolumeGroupList.requestLayout();
+ ViewTreeObserver observer = mVolumeGroupList.getViewTreeObserver();
+ observer.addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
+ @Override
+ public void onGlobalLayout() {
+ mVolumeGroupList.getViewTreeObserver().removeGlobalOnLayoutListener(this);
+ startGroupListFadeInAnimationInternal();
+ }
+ });
+ }
+
+ private void startGroupListFadeInAnimationInternal() {
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.size() != 0) {
+ fadeInAddedRoutes();
+ } else {
+ finishAnimation(true);
+ }
+ }
+
+ private void finishAnimation(boolean animate) {
+ mGroupMemberRoutesAdded = null;
+ mGroupMemberRoutesRemoved = null;
+ mIsGroupListAnimating = false;
+ if (mIsGroupListAnimationPending) {
+ mIsGroupListAnimationPending = false;
+ updateLayoutHeight(animate);
+ }
+ mVolumeGroupList.setEnabled(true);
+ }
+
+ private void fadeInAddedRoutes() {
+ Animation.AnimationListener listener = new Animation.AnimationListener() {
+ @Override
+ public void onAnimationStart(Animation animation) { }
+
+ @Override
+ public void onAnimationEnd(Animation animation) {
+ finishAnimation(true);
+ }
+
+ @Override
+ public void onAnimationRepeat(Animation animation) { }
+ };
+ boolean listenerRegistered = false;
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ if (mGroupMemberRoutesAdded.contains(route)) {
+ Animation alphaAnim = new AlphaAnimation(0.0f, 1.0f);
+ alphaAnim.setDuration(mGroupListFadeInDurationMs);
+ alphaAnim.setFillEnabled(true);
+ alphaAnim.setFillAfter(true);
+ if (!listenerRegistered) {
+ listenerRegistered = true;
+ alphaAnim.setAnimationListener(listener);
+ }
+ view.clearAnimation();
+ view.startAnimation(alphaAnim);
+ }
+ }
+ }
+
+ void clearGroupListAnimation(boolean exceptAddedRoutes) {
+ int first = mVolumeGroupList.getFirstVisiblePosition();
+ for (int i = 0; i < mVolumeGroupList.getChildCount(); ++i) {
+ View view = mVolumeGroupList.getChildAt(i);
+ int position = first + i;
+ MediaRouter.RouteInfo route = mVolumeGroupAdapter.getItem(position);
+ if (exceptAddedRoutes && mGroupMemberRoutesAdded != null
+ && mGroupMemberRoutesAdded.contains(route)) {
+ continue;
+ }
+ LinearLayout container = (LinearLayout) view.findViewById(R.id.volume_item_container);
+ container.setVisibility(View.VISIBLE);
+ AnimationSet animSet = new AnimationSet(true);
+ Animation alphaAnim = new AlphaAnimation(1.0f, 1.0f);
+ alphaAnim.setDuration(0);
+ animSet.addAnimation(alphaAnim);
+ Animation translationAnim = new TranslateAnimation(0, 0, 0, 0);
+ translationAnim.setDuration(0);
+ animSet.setFillAfter(true);
+ animSet.setFillEnabled(true);
+ view.clearAnimation();
+ view.startAnimation(animSet);
+ }
+ mVolumeGroupList.stopAnimationAll();
+ if (!exceptAddedRoutes) {
+ finishAnimation(false);
+ }
}
private void updatePlaybackControlLayout() {
@@ -696,11 +961,8 @@
showTitle = true;
} else if (mState == null || mState.getState() == PlaybackStateCompat.STATE_NONE) {
// Show "No media selected" as we don't yet know the playback state.
- // (Only exception is bluetooth where we don't show anything.)
- if (!mRoute.isDeviceTypeBluetooth()) {
- mTitleView.setText(R.string.mr_controller_no_media_selected);
- showTitle = true;
- }
+ mTitleView.setText(R.string.mr_controller_no_media_selected);
+ showTitle = true;
} else if (!hasTitle && !hasSubtitle) {
mTitleView.setText(R.string.mr_controller_no_info_available);
showTitle = true;
@@ -741,7 +1003,6 @@
}
}
}
- updateLayoutHeight();
}
private boolean isVolumeControlAvailable(MediaRouter.RouteInfo route) {
@@ -759,6 +1020,15 @@
view.setLayoutParams(lp);
}
+ private static boolean uriEquals(Uri uri1, Uri uri2) {
+ if (uri1 != null && uri1.equals(uri2)) {
+ return true;
+ } else if (uri1 == null && uri2 == null) {
+ return true;
+ }
+ return false;
+ }
+
/**
* Returns desired art height to fit into controller dialog.
*/
@@ -774,19 +1044,23 @@
private final class MediaRouterCallback extends MediaRouter.Callback {
@Override
public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
- update();
+ update(false);
}
@Override
public void onRouteChanged(MediaRouter router, MediaRouter.RouteInfo route) {
- update();
+ update(true);
}
@Override
public void onRouteVolumeChanged(MediaRouter router, MediaRouter.RouteInfo route) {
SeekBar volumeSlider = mVolumeSliderMap.get(route);
+ int volume = route.getVolume();
+ if (DEBUG) {
+ Log.d(TAG, "onRouteVolumeChanged(), route.getVolume:" + volume);
+ }
if (volumeSlider != null && mRouteInVolumeSliderTouched != route) {
- volumeSlider.setProgress(route.getVolume());
+ volumeSlider.setProgress(volume);
}
}
}
@@ -803,13 +1077,13 @@
@Override
public void onPlaybackStateChanged(PlaybackStateCompat state) {
mState = state;
- update();
+ update(false);
}
@Override
public void onMetadataChanged(MediaMetadataCompat metadata) {
mDescription = metadata == null ? null : metadata.getDescription();
- update();
+ update(false);
}
}
@@ -855,8 +1129,6 @@
@Override
public void run() {
if (mRouteInVolumeSliderTouched != null) {
- SeekBar volumeSlider = mVolumeSliderMap.get(mRouteInVolumeSliderTouched);
- volumeSlider.setProgress(mRouteInVolumeSliderTouched.getVolume());
mRouteInVolumeSliderTouched = null;
}
}
@@ -882,9 +1154,11 @@
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
MediaRouter.RouteInfo route = (MediaRouter.RouteInfo) seekBar.getTag();
- if (route.getVolume() != progress) {
- route.requestSetVolume(progress);
+ if (DEBUG) {
+ Log.d(TAG, "onProgressChanged(): calling "
+ + "MediaRouter.RouteInfo.requestSetVolume(" + progress + ")");
}
+ route.requestSetVolume(progress);
}
}
}
@@ -938,6 +1212,22 @@
ImageView volumeItemIcon =
(ImageView) v.findViewById(R.id.mr_volume_item_icon);
volumeItemIcon.setAlpha(isEnabled ? 0xFF : (int) (0xFF * mDisabledAlpha));
+
+ // If overlay bitmap exists, real view should remain hidden until
+ // the animation ends.
+ LinearLayout container = (LinearLayout) v.findViewById(R.id.volume_item_container);
+ container.setVisibility(mGroupMemberRoutesAnimatingWithBitmap.contains(route)
+ ? View.INVISIBLE : View.VISIBLE);
+
+ // Routes which are being added will be invisible until animation ends.
+ if (mGroupMemberRoutesAdded != null && mGroupMemberRoutesAdded.contains(route)) {
+ Animation alphaAnim = new AlphaAnimation(0.0f, 0.0f);
+ alphaAnim.setDuration(0);
+ alphaAnim.setFillEnabled(true);
+ alphaAnim.setFillAfter(true);
+ v.clearAnimation();
+ v.startAnimation(alphaAnim);
+ }
}
return v;
}
@@ -955,7 +1245,7 @@
@Override
protected void onPreExecute() {
- if (mArtIconBitmap == mIconBitmap && mArtIconUri == mIconUri) {
+ if (!isIconChanged()) {
// Already handled the current art.
cancel(true);
}
@@ -967,18 +1257,12 @@
if (mIconBitmap != null) {
art = mIconBitmap;
} else if (mIconUri != null) {
- String scheme = mIconUri.getScheme();
- if (!(ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
- || ContentResolver.SCHEME_CONTENT.equals(scheme)
- || ContentResolver.SCHEME_FILE.equals(scheme))) {
- Log.w(TAG, "Icon Uri should point to local resources.");
- return null;
- }
- BufferedInputStream stream = null;
+ InputStream stream = null;
try {
- stream = new BufferedInputStream(
- mContext.getContentResolver().openInputStream(mIconUri));
-
+ if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+ Log.w(TAG, "Unable to open: " + mIconUri);
+ return null;
+ }
// Query art size.
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
@@ -992,8 +1276,10 @@
} catch (IOException e) {
// Failed to rewind the stream, try to reopen it.
stream.close();
- stream = new BufferedInputStream(mContext.getContentResolver()
- .openInputStream(mIconUri));
+ if ((stream = openInputStreamByScheme(mIconUri)) == null) {
+ Log.w(TAG, "Unable to open: " + mIconUri);
+ return null;
+ }
}
// Calculate required size to decode the art and possibly resize it.
options.inJustDecodeBounds = false;
@@ -1038,8 +1324,39 @@
mArtView.setImageBitmap(art);
mArtView.setBackgroundColor(mBackgroundColor);
- updateLayoutHeight();
+ updateLayoutHeight(true);
}
}
+
+ /**
+ * Returns whether a new art image is different from an original art image. Compares
+ * Bitmap objects first, and then compares URIs only if bitmap is unchanged with
+ * a null value.
+ */
+ private boolean isIconChanged() {
+ if (mIconBitmap != mArtIconBitmap) {
+ return true;
+ } else if (mIconBitmap == null && !uriEquals(mIconUri, mArtIconUri)) {
+ return true;
+ }
+ return false;
+ }
+
+ private InputStream openInputStreamByScheme(Uri uri) throws IOException {
+ String scheme = uri.getScheme().toLowerCase();
+ InputStream stream = null;
+ if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)
+ || ContentResolver.SCHEME_CONTENT.equals(scheme)
+ || ContentResolver.SCHEME_FILE.equals(scheme)) {
+ stream = mContext.getContentResolver().openInputStream(uri);
+ } else {
+ URL url = new URL(uri.toString());
+ URLConnection conn = url.openConnection();
+ conn.setConnectTimeout(CONNECTION_TIMEOUT_MILLIS);
+ conn.setReadTimeout(CONNECTION_TIMEOUT_MILLIS);
+ stream = conn.getInputStream();
+ }
+ return (stream == null) ? null : new BufferedInputStream(stream);
+ }
}
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialogFragment.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialogFragment.java
index 6cadb9a..ba9ba12 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialogFragment.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteControllerDialogFragment.java
@@ -54,11 +54,19 @@
@Override
public Dialog onCreateDialog(Bundle savedInstanceState) {
- mDialog = onCreateControllerDialog(getActivity(), savedInstanceState);
+ mDialog = onCreateControllerDialog(getContext(), savedInstanceState);
return mDialog;
}
@Override
+ public void onStop() {
+ super.onStop();
+ if (mDialog != null) {
+ mDialog.clearGroupListAnimation(false);
+ }
+ }
+
+ @Override
public void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
if (mDialog != null) {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogHelper.java
index 99d414f..78f550b 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteDialogHelper.java
@@ -17,10 +17,22 @@
package android.support.v7.app;
import android.content.Context;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
import android.support.v7.mediarouter.R;
import android.util.DisplayMetrics;
import android.util.TypedValue;
+import android.view.View;
import android.view.ViewGroup;
+import android.widget.ArrayAdapter;
+import android.widget.ListView;
+
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
final class MediaRouteDialogHelper {
/**
@@ -41,4 +53,99 @@
}
return ViewGroup.LayoutParams.WRAP_CONTENT;
}
+
+ /**
+ * Compares two lists regardless of order.
+ *
+ * @param list1 A list
+ * @param list2 A list to be compared with {@code list1}
+ * @return True if two lists have exactly same items regardless of order, false otherwise.
+ */
+ public static <E> boolean listUnorderedEquals(List<E> list1, List<E> list2) {
+ HashSet<E> set1 = new HashSet<>(list1);
+ HashSet<E> set2 = new HashSet<>(list2);
+ return set1.equals(set2);
+ }
+
+ /**
+ * Compares two lists and returns a set of items which exist
+ * after-list but before-list, which means newly added items.
+ *
+ * @param before A list
+ * @param after A list to be compared with {@code before}
+ * @return A set of items which contains newly added items while
+ * comparing {@code after} to {@code before}.
+ */
+ public static <E> Set<E> getItemsAdded(List<E> before, List<E> after) {
+ HashSet<E> set = new HashSet<>(after);
+ set.removeAll(before);
+ return set;
+ }
+
+ /**
+ * Compares two lists and returns a set of items which exist
+ * before-list but after-list, which means removed items.
+ *
+ * @param before A list
+ * @param after A list to be compared with {@code before}
+ * @return A set of items which contains removed items while
+ * comparing {@code after} to {@code before}.
+ */
+ public static <E> Set<E> getItemsRemoved(List<E> before, List<E> after) {
+ HashSet<E> set = new HashSet<>(before);
+ set.removeAll(after);
+ return set;
+ }
+
+ /**
+ * Generates an item-Rect map which indicates where member
+ * items are located in the given ListView.
+ *
+ * @param listView A list view
+ * @param adapter An array adapter which contains an array of items.
+ * @return A map of items and bounds of their views located in the given list view.
+ */
+ public static <E> HashMap<E, Rect> getItemBoundMap(ListView listView,
+ ArrayAdapter<E> adapter) {
+ HashMap<E, Rect> itemBoundMap = new HashMap<>();
+ int firstVisiblePosition = listView.getFirstVisiblePosition();
+ for (int i = 0; i < listView.getChildCount(); ++i) {
+ int position = firstVisiblePosition + i;
+ E item = adapter.getItem(position);
+ View view = listView.getChildAt(i);
+ itemBoundMap.put(item,
+ new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom()));
+ }
+ return itemBoundMap;
+ }
+
+ /**
+ * Generates an item-BitmapDrawable map which stores snapshots
+ * of member items in the given ListView.
+ *
+ * @param context A context
+ * @param listView A list view
+ * @param adapter An array adapter which contains an array of items.
+ * @return A map of items and snapshots of their views in the given list view.
+ */
+ public static <E> HashMap<E, BitmapDrawable> getItemBitmapMap(Context context,
+ ListView listView, ArrayAdapter<E> adapter) {
+ HashMap<E, BitmapDrawable> itemBitmapMap = new HashMap<>();
+ int firstVisiblePosition = listView.getFirstVisiblePosition();
+ for (int i = 0; i < listView.getChildCount(); ++i) {
+ int position = firstVisiblePosition + i;
+ E item = adapter.getItem(position);
+ View view = listView.getChildAt(i);
+ itemBitmapMap.put(item, getViewBitmap(context, view));
+ }
+ return itemBitmapMap;
+ }
+
+ private static BitmapDrawable getViewBitmap(Context context, View view) {
+ Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(),
+ Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ view.draw(canvas);
+ return new BitmapDrawable(context.getResources(), bitmap);
+ }
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
index 4e97d57..3d10b1e 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteDiscoveryFragment.java
@@ -25,12 +25,16 @@
* Media route discovery fragment.
* <p>
* This fragment takes care of registering a callback for media route discovery
- * during the activity's {@link android.app.Activity#onStart onStart()} phase
- * and removing it during the {@link android.app.Activity#onStart onStop()} phase.
+ * during the {@link Fragment#onStart onStart()} phase
+ * and removing it during the {@link Fragment#onStop onStop()} phase.
* </p><p>
* The application must supply a route selector to specify the kinds of routes
* to discover. The application may also override {@link #onCreateCallback} to
* provide the {@link MediaRouter} callback to register.
+ * </p><p>
+ * Note that the discovery callback makes the application be connected with all the
+ * {@link android.support.v7.media.MediaRouteProviderService media route provider services}
+ * while it is registered.
* </p>
*/
public class MediaRouteDiscoveryFragment extends Fragment {
@@ -53,7 +57,7 @@
private void ensureRouter() {
if (mRouter == null) {
- mRouter = MediaRouter.getInstance(getActivity());
+ mRouter = MediaRouter.getInstance(getContext());
}
}
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
index a7aafd2..a7a0dd3 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouteVolumeSlider.java
@@ -42,7 +42,7 @@
}
public MediaRouteVolumeSlider(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.seekBarStyle);
+ this(context, attrs, android.support.v7.appcompat.R.attr.seekBarStyle);
}
public MediaRouteVolumeSlider(Context context, AttributeSet attrs, int defStyleAttr) {
diff --git a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
index 33ab7bc..3d55951 100644
--- a/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
+++ b/v7/mediarouter/src/android/support/v7/app/MediaRouterThemeHelper.java
@@ -84,7 +84,8 @@
}
public static @ControllerColorType int getControllerColor(Context context, int style) {
- int primaryColor = getThemeColor(context, style, R.attr.colorPrimary);
+ int primaryColor = getThemeColor(context, style,
+ android.support.v7.appcompat.R.attr.colorPrimary);
if (ColorUtils.calculateContrast(COLOR_WHITE_ON_DARK_BACKGROUND, primaryColor)
>= MIN_CONTRAST) {
return COLOR_WHITE_ON_DARK_BACKGROUND;
@@ -93,23 +94,24 @@
}
public static int getButtonTextColor(Context context) {
- int primaryColor = getThemeColor(context, 0, R.attr.colorPrimary);
+ int primaryColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimary);
int backgroundColor = getThemeColor(context, 0, android.R.attr.colorBackground);
if (ColorUtils.calculateContrast(primaryColor, backgroundColor) < MIN_CONTRAST) {
// Default to colorAccent if the contrast ratio is low.
- return getThemeColor(context, 0, R.attr.colorAccent);
+ return getThemeColor(context, 0, android.support.v7.appcompat.R.attr.colorAccent);
}
return primaryColor;
}
public static void setMediaControlsBackgroundColor(
Context context, View mainControls, View groupControls, boolean hasGroup) {
- int primaryColor = getThemeColor(context, 0, R.attr.colorPrimary);
- int primaryDarkColor = getThemeColor(context, 0, R.attr.colorPrimaryDark);
- int controllerColor = getControllerColor(context, 0);
- if (hasGroup && controllerColor == COLOR_DARK_ON_LIGHT_BACKGROUND
- && ColorUtils.calculateContrast(controllerColor, primaryDarkColor) < MIN_CONTRAST) {
+ int primaryColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimary);
+ int primaryDarkColor = getThemeColor(context, 0,
+ android.support.v7.appcompat.R.attr.colorPrimaryDark);
+ if (hasGroup && getControllerColor(context, 0) == COLOR_DARK_ON_LIGHT_BACKGROUND) {
// Instead of showing dark controls in a possibly dark (i.e. the primary dark), model
// the white dialog and use the primary color for the group controls.
primaryDarkColor = primaryColor;
@@ -137,7 +139,8 @@
private static boolean isLightTheme(Context context) {
TypedValue value = new TypedValue();
- return context.getTheme().resolveAttribute(R.attr.isLightTheme, value, true)
+ return context.getTheme().resolveAttribute(
+ android.support.v7.appcompat.R.attr.isLightTheme, value, true)
&& value.data != 0;
}
diff --git a/v7/mediarouter/src/android/support/v7/app/OverlayListView.java b/v7/mediarouter/src/android/support/v7/app/OverlayListView.java
new file mode 100644
index 0000000..ef322fd
--- /dev/null
+++ b/v7/mediarouter/src/android/support/v7/app/OverlayListView.java
@@ -0,0 +1,265 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.app;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Rect;
+import android.graphics.drawable.BitmapDrawable;
+import android.util.AttributeSet;
+import android.view.animation.Interpolator;
+import android.widget.ListView;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+/**
+ * A ListView which has an additional overlay layer. {@link BitmapDrawable}
+ * can be added to the layer and can be animated.
+ */
+final class OverlayListView extends ListView {
+ private final List<OverlayObject> mOverlayObjects = new ArrayList<>();
+
+ public OverlayListView(Context context) {
+ super(context);
+ }
+
+ public OverlayListView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public OverlayListView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ /**
+ * Adds an object to the overlay layer.
+ *
+ * @param object An object to be added.
+ */
+ public void addOverlayObject(OverlayObject object) {
+ mOverlayObjects.add(object);
+ }
+
+ /**
+ * Starts all animations of objects in the overlay layer.
+ */
+ public void startAnimationAll() {
+ for (OverlayObject object : mOverlayObjects) {
+ if (!object.isAnimationStarted()) {
+ object.startAnimation(getDrawingTime());
+ }
+ }
+ }
+
+ /**
+ * Stops all animations of objects in the overlay layer.
+ */
+ public void stopAnimationAll() {
+ for (OverlayObject object : mOverlayObjects) {
+ object.stopAnimation();
+ }
+ }
+
+ @Override
+ public void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+ if (mOverlayObjects.size() > 0) {
+ Iterator<OverlayObject> it = mOverlayObjects.iterator();
+ while (it.hasNext()) {
+ OverlayObject object = it.next();
+ BitmapDrawable bitmap = object.getBitmapDrawable();
+ if (bitmap != null) {
+ bitmap.draw(canvas);
+ }
+ if (!object.update(getDrawingTime())) {
+ it.remove();
+ }
+ }
+ }
+ }
+
+ /**
+ * A class that represents an object to be shown in the overlay layer.
+ */
+ public static class OverlayObject {
+ private BitmapDrawable mBitmap;
+ private float mCurrentAlpha = 1.0f;
+ private Rect mCurrentBounds;
+ private Interpolator mInterpolator;
+ private long mDuration;
+ private Rect mStartRect;
+ private int mDeltaY;
+ private float mStartAlpha = 1.0f;
+ private float mEndAlpha = 1.0f;
+ private long mStartTime;
+ private boolean mIsAnimationStarted;
+ private boolean mIsAnimationEnded;
+ private OnAnimationEndListener mListener;
+
+ public OverlayObject(BitmapDrawable bitmap, Rect startRect) {
+ mBitmap = bitmap;
+ mStartRect = startRect;
+ mCurrentBounds = new Rect(startRect);
+ if (mBitmap != null && mCurrentBounds != null) {
+ mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+ mBitmap.setBounds(mCurrentBounds);
+ }
+ }
+
+ /**
+ * Returns the bitmap that this object represents.
+ *
+ * @return BitmapDrawable that this object has.
+ */
+ public BitmapDrawable getBitmapDrawable() {
+ return mBitmap;
+ }
+
+ /**
+ * Returns the started status of the animation.
+ *
+ * @return True if the animation has started, false otherwise.
+ */
+ public boolean isAnimationStarted() {
+ return mIsAnimationStarted;
+ }
+
+ /**
+ * Sets animation for varying alpha.
+ *
+ * @param startAlpha Starting alpha value for the animation, where 1.0 means
+ * fully opaque and 0.0 means fully transparent.
+ * @param endAlpha Ending alpha value for the animation.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setAlphaAnimation(float startAlpha, float endAlpha) {
+ mStartAlpha = startAlpha;
+ mEndAlpha = endAlpha;
+ return this;
+ }
+
+ /**
+ * Sets animation for moving objects vertically.
+ *
+ * @param deltaY Distance to move in pixels.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setTranslateYAnimation(int deltaY) {
+ mDeltaY = deltaY;
+ return this;
+ }
+
+ /**
+ * Sets how long the animation will last.
+ *
+ * @param duration Duration in milliseconds
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setDuration(long duration) {
+ mDuration = duration;
+ return this;
+ }
+
+ /**
+ * Sets the acceleration curve for this animation.
+ *
+ * @param interpolator The interpolator which defines the acceleration curve
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setInterpolator(Interpolator interpolator) {
+ mInterpolator = interpolator;
+ return this;
+ }
+
+ /**
+ * Binds an animation end listener to the animation.
+ *
+ * @param listener the animation end listener to be notified.
+ * @return This OverlayObject to allow for chaining of calls.
+ */
+ public OverlayObject setAnimationEndListener(OnAnimationEndListener listener) {
+ mListener = listener;
+ return this;
+ }
+
+ /**
+ * Starts the animation and sets the start time.
+ *
+ * @param startTime Start time to be set in Millis
+ */
+ public void startAnimation(long startTime) {
+ mStartTime = startTime;
+ mIsAnimationStarted = true;
+ }
+
+ /**
+ * Stops the animation.
+ */
+ public void stopAnimation() {
+ mIsAnimationStarted = true;
+ mIsAnimationEnded = true;
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+
+ /**
+ * Calculates and updates current bounds and alpha value.
+ *
+ * @param currentTime Current time.in millis
+ */
+ public boolean update(long currentTime) {
+ if (mIsAnimationEnded) {
+ return false;
+ }
+ float normalizedTime = (currentTime - mStartTime) / (float) mDuration;
+ normalizedTime = Math.max(0.0f, Math.min(1.0f, normalizedTime));
+ if (!mIsAnimationStarted) {
+ normalizedTime = 0.0f;
+ }
+ float interpolatedTime = (mInterpolator == null) ? normalizedTime
+ : mInterpolator.getInterpolation(normalizedTime);
+ int deltaY = (int) (mDeltaY * interpolatedTime);
+ mCurrentBounds.top = mStartRect.top + deltaY;
+ mCurrentBounds.bottom = mStartRect.bottom + deltaY;
+ mCurrentAlpha = mStartAlpha + (mEndAlpha - mStartAlpha) * interpolatedTime;
+ if (mBitmap != null && mCurrentBounds != null) {
+ mBitmap.setAlpha((int) (mCurrentAlpha * 255));
+ mBitmap.setBounds(mCurrentBounds);
+ }
+ if (mIsAnimationStarted && normalizedTime >= 1.0f) {
+ mIsAnimationEnded = true;
+ if (mListener != null) {
+ mListener.onAnimationEnd();
+ }
+ }
+ return !mIsAnimationEnded;
+ }
+
+ /**
+ * An animation listener that receives notifications when the animation ends.
+ */
+ public interface OnAnimationEndListener {
+ /**
+ * Notifies the end of the animation.
+ */
+ public void onAnimationEnd();
+ }
+ }
+}
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
index 638701f5..c827534 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
@@ -765,6 +765,9 @@
* <li>{@link #EXTRA_SESSION_STATUS_UPDATE_RECEIVER} <em>(optional)</em>: Specifies a
* {@link PendingIntent} for a broadcast receiver that will receive status updates
* about the media session.
+ * <li>{@link #EXTRA_MESSAGE_RECEIVER} <em>(optional)</em>: Specifies a
+ * {@link PendingIntent} for a broadcast receiver that will receive messages from
+ * the media session.
* </ul>
*
* <h3>Result data</h3>
@@ -787,6 +790,15 @@
* Refer to {@link MediaSessionStatus} for details.
* </p>
*
+ * <h3>Custom messages</h3>
+ * <p>
+ * If the client supplies a {@link #EXTRA_MESSAGE_RECEIVER message receiver}
+ * then the media route provider is responsible for sending messages to the receiver
+ * when the session has any messages to send.
+ * </p><p>
+ * Refer to {@link #EXTRA_MESSAGE} for details.
+ * </p>
+ *
* <h3>Errors</h3>
* <p>
* This action returns an error if the session could not be created.
@@ -880,6 +892,29 @@
*/
public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
+ /**
+ * Custom media control action: Send {@link #EXTRA_MESSAGE}.
+ * <p>
+ * This action asks a route to handle a message described by EXTRA_MESSAGE.
+ * </p>
+ *
+ * <h3>Request parameters</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of the session
+ * to which will handle this message.
+ * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message to send.
+ * </ul>
+ *
+ * <h3>Result data</h3>
+ * Any messages defined by each media route provider.
+ *
+ * <h3>Errors</h3>
+ * Any error messages defined by each media route provider.
+ *
+ * @see MediaRouter.RouteInfo#sendControlRequest
+ */
+ public static final String ACTION_SEND_MESSAGE = "android.media.intent.action.SEND_MESSAGE";
+
/* Extras and related constants. */
/**
@@ -944,7 +979,7 @@
"android.media.intent.extra.SESSION_STATUS";
/**
- * Bundle extra: Media item status update receiver.
+ * Bundle extra: Media session status update receiver.
* <p>
* Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
* broadcast receiver that will receive status updates about the media session.
@@ -970,6 +1005,32 @@
"android.media.intent.extra.SESSION_STATUS_UPDATE_RECEIVER";
/**
+ * Bundle extra: Media message receiver.
+ * <p>
+ * Used with {@link #ACTION_START_SESSION} to specify a {@link PendingIntent} for a
+ * broadcast receiver that will receive messages from the media session.
+ * </p><p>
+ * When the media session has a message to send, the media route provider will
+ * send a broadcast to the pending intent with extras that identify the session
+ * id and its message.
+ * </p><p>
+ * The value is a {@link PendingIntent}.
+ * </p>
+ *
+ * <h3>Broadcast extras</h3>
+ * <ul>
+ * <li>{@link #EXTRA_SESSION_ID} <em>(required)</em>: Specifies the session id of
+ * the session.
+ * <li>{@link #EXTRA_MESSAGE} <em>(required)</em>: Specifies the message from
+ * the session as a bundle object.
+ * </ul>
+ *
+ * @see #ACTION_START_SESSION
+ */
+ public static final String EXTRA_MESSAGE_RECEIVER =
+ "android.media.intent.extra.MESSAGE_RECEIVER";
+
+ /**
* Bundle extra: Media item id.
* <p>
* An opaque unique identifier returned as a result from {@link #ACTION_PLAY} or
@@ -1111,6 +1172,18 @@
"android.media.intent.extra.ITEM_STATUS_UPDATE_RECEIVER";
/**
+ * Bundle extra: Message.
+ * <p>
+ * Used with {@link #ACTION_SEND_MESSAGE}, and included in broadcast intents sent to
+ * {@link #EXTRA_MESSAGE_RECEIVER message receivers} to describe a message between a
+ * session and a media route provider.
+ * </p><p>
+ * The value is a {@link android.os.Bundle}.
+ * </p>
+ */
+ public static final String EXTRA_MESSAGE = "android.media.intent.extra.MESSAGE";
+
+ /**
* Integer extra: Error code.
* <p>
* Used with all media control requests to describe the cause of an error.
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
index 97a2cd7..eb38264 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteDescriptor.java
@@ -133,6 +133,7 @@
* Gets whether the route is connecting.
* @deprecated Use {@link #getConnectionState} instead
*/
+ @Deprecated
public boolean isConnecting() {
return mBundle.getBoolean(KEY_CONNECTING, false);
}
@@ -481,6 +482,7 @@
* ready for use.
* @deprecated Use {@link #setConnectionState} instead.
*/
+ @Deprecated
public Builder setConnecting(boolean connecting) {
mBundle.putBoolean(KEY_CONNECTING, connecting);
return this;
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index 79d87e1..343d21b 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -37,6 +37,13 @@
/**
* Base class for media route provider services.
* <p>
+ * A media router will bind to media route provider services when a callback is added via
+ * {@link MediaRouter#addCallback(MediaRouteSelector, MediaRouter.Callback, int)} with a discovery
+ * flag: {@link MediaRouter#CALLBACK_FLAG_REQUEST_DISCOVERY},
+ * {@link MediaRouter#CALLBACK_FLAG_FORCE_DISCOVERY}, or
+ * {@link MediaRouter#CALLBACK_FLAG_PERFORM_ACTIVE_SCAN}, and will unbind when the callback
+ * is removed via {@link MediaRouter#removeCallback(MediaRouter.Callback)}.
+ * </p><p>
* To implement your own media route provider service, extend this class and
* override the {@link #onCreateMediaRouteProvider} method to return an
* instance of your {@link MediaRouteProvider}.
@@ -134,6 +141,14 @@
return null;
}
+ @Override
+ public boolean onUnbind(Intent intent) {
+ if (mProvider != null) {
+ mProvider.setCallback(null);
+ }
+ return super.onUnbind(intent);
+ }
+
private boolean onRegisterClient(Messenger messenger, int requestId, int version) {
if (version >= CLIENT_VERSION_1) {
int index = findClient(messenger);
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
index b2e74e4..f8c76dc 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -50,9 +50,12 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
+import java.util.Set;
/**
* MediaRouter allows applications to control the routing of media channels
@@ -78,12 +81,14 @@
/**
* Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
- * when the reason the route was unselected is unknown.
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the reason the route
+ * was unselected is unknown.
*/
public static final int UNSELECT_REASON_UNKNOWN = 0;
/**
* Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
- * when the user pressed the disconnect button to disconnect and keep playing.
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+ * the disconnect button to disconnect and keep playing.
* <p>
*
* @see {@link MediaRouteDescriptor#canDisconnectAndKeepPlaying()}.
@@ -91,12 +96,14 @@
public static final int UNSELECT_REASON_DISCONNECTED = 1;
/**
* Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
- * when the user pressed the stop casting button.
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user pressed
+ * the stop casting button.
*/
public static final int UNSELECT_REASON_STOPPED = 2;
/**
* Passed to {@link android.support.v7.media.MediaRouteProvider.RouteController#onUnselect(int)}
- * when the user selected a different route.
+ * and {@link Callback#onRouteUnselected(MediaRouter, RouteInfo, int)} when the user selected
+ * a different route.
*/
public static final int UNSELECT_REASON_ROUTE_CHANGED = 3;
@@ -339,7 +346,8 @@
/**
* Returns the selected route if it matches the specified selector, otherwise
- * selects the default route and returns it.
+ * selects the default route and returns it. If there is one live audio route
+ * (usually Bluetooth A2DP), it will be selected instead of default route.
*
* @param selector The selector to match.
* @return The previously selected route if it matched the selector, otherwise the
@@ -347,7 +355,6 @@
*
* @see MediaRouteSelector
* @see RouteInfo#matchesSelector
- * @see RouteInfo#isDefault
*/
@NonNull
public RouteInfo updateSelectedRoute(@NonNull MediaRouteSelector selector) {
@@ -360,8 +367,8 @@
Log.d(TAG, "updateSelectedRoute: " + selector);
}
RouteInfo route = sGlobal.getSelectedRoute();
- if (!route.isDefault() && !route.matchesSelector(selector)) {
- route = sGlobal.getDefaultRoute();
+ if (!route.isDefaultOrBluetooth() && !route.matchesSelector(selector)) {
+ route = sGlobal.chooseFallbackRoute();
sGlobal.selectRoute(route);
}
return route;
@@ -404,7 +411,14 @@
}
checkCallingThread();
- sGlobal.selectRoute(getDefaultRoute(), reason);
+ // Choose the fallback route if it's not already selected.
+ // Otherwise, select the default route.
+ RouteInfo fallbackRoute = sGlobal.chooseFallbackRoute();
+ if (sGlobal.getSelectedRoute() != fallbackRoute) {
+ sGlobal.selectRoute(fallbackRoute, reason);
+ } else {
+ sGlobal.selectRoute(sGlobal.getDefaultRoute(), reason);
+ }
}
/**
@@ -854,16 +868,6 @@
*/
public static final int DEVICE_TYPE_UNKNOWN = 0;
-
- /**
- * A receiver device type of the route indicating the presentation of the media is happening
- * on a bluetooth device such as a bluetooth speaker.
- *
- * @see #getDeviceType
- * @hide
- */
- public static final int DEVICE_TYPE_BLUETOOTH = -1;
-
/**
* A receiver device type of the route indicating the presentation of the media is happening
* on a TV.
@@ -880,6 +884,15 @@
*/
public static final int DEVICE_TYPE_SPEAKER = 2;
+ /**
+ * A receiver device type of the route indicating the presentation of the media is happening
+ * on a bluetooth device such as a bluetooth speaker.
+ *
+ * @see #getDeviceType
+ * @hide
+ */
+ public static final int DEVICE_TYPE_BLUETOOTH = 3;
+
/** @hide */
@IntDef({PLAYBACK_VOLUME_FIXED,PLAYBACK_VOLUME_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
@@ -1232,21 +1245,14 @@
/**
- * Gets whether the type of the receiver device associated with this route is
- * {@link #DEVICE_TYPE_BLUETOOTH}.
- * <p>
- * This is a workaround for platform version 23 or below where the system route provider
- * doesn't specify device type for bluetooth media routes.
- * </p>
- *
- * @return True if the receiver device type can be assumed to be
- * {@link #DEVICE_TYPE_BLUETOOTH}, false otherwise.
* @hide
*/
- public boolean isDeviceTypeBluetooth() {
- if (mDeviceType == DEVICE_TYPE_BLUETOOTH) {
+ public boolean isDefaultOrBluetooth() {
+ if (isDefault() || mDeviceType == DEVICE_TYPE_BLUETOOTH) {
return true;
}
+ // This is a workaround for platform version 23 or below where the system route
+ // provider doesn't specify device type for bluetooth media routes.
return isSystemMediaRouteProvider(this)
&& supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_AUDIO)
&& !supportsControlCategory(MediaControlIntent.CATEGORY_LIVE_VIDEO);
@@ -1713,6 +1719,8 @@
/**
* Called when the supplied media route becomes unselected as the active route.
+ * For detailed reason, override {@link #onRouteUnselected(MediaRouter, RouteInfo, int)}
+ * instead.
*
* @param router The media router reporting the event.
* @param route The route that has been unselected.
@@ -1721,6 +1729,26 @@
}
/**
+ * Called when the supplied media route becomes unselected as the active route.
+ * The default implementation calls {@link #onRouteUnselected}.
+ * <p>
+ * The reason provided will be one of the following:
+ * <ul>
+ * <li>{@link MediaRouter#UNSELECT_REASON_UNKNOWN}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_DISCONNECTED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_STOPPED}</li>
+ * <li>{@link MediaRouter#UNSELECT_REASON_ROUTE_CHANGED}</li>
+ * </ul>
+ *
+ * @param router The media router reporting the event.
+ * @param route The route that has been unselected.
+ * @param reason The reason for unselecting the route.
+ */
+ public void onRouteUnselected(MediaRouter router, RouteInfo route, int reason) {
+ onRouteUnselected(router, route);
+ }
+
+ /**
* Called when a media route has been added.
*
* @param router The media router reporting the event.
@@ -1873,7 +1901,9 @@
private RouteInfo mDefaultRoute;
private RouteInfo mSelectedRoute;
private RouteController mSelectedRouteController;
- private Map<String, RouteController> mGroupMemberControllers;
+ // A map from route descriptor ID to RouteController for the member routes in the currently
+ // selected route group.
+ private final Map<String, RouteController> mRouteControllerMap = new HashMap<>();
private MediaRouteDiscoveryRequest mDiscoveryRequest;
private MediaSessionRecord mMediaSession;
private MediaSessionCompat mRccMediaSession;
@@ -1964,8 +1994,8 @@
public void requestSetVolume(RouteInfo route, int volume) {
if (route == mSelectedRoute && mSelectedRouteController != null) {
mSelectedRouteController.onSetVolume(volume);
- } else if (mGroupMemberControllers != null) {
- RouteController controller = mGroupMemberControllers.get(route.mDescriptorId);
+ } else if (!mRouteControllerMap.isEmpty()) {
+ RouteController controller = mRouteControllerMap.get(route.mDescriptorId);
if (controller != null) {
controller.onSetVolume(volume);
}
@@ -2049,7 +2079,7 @@
for (int i = 0; i < routeCount; i++) {
RouteInfo route = mRoutes.get(i);
if ((flags & AVAILABILITY_FLAG_IGNORE_DEFAULT_ROUTE) != 0
- && route.isDefault()) {
+ && route.isDefaultOrBluetooth()) {
continue;
}
if (route.matchesSelector(selector)) {
@@ -2400,12 +2430,43 @@
setSelectedRouteInternal(chooseFallbackRoute(),
MediaRouter.UNSELECT_REASON_UNKNOWN);
} else if (selectedRouteDescriptorChanged) {
+ // In case the selected route is a route group, select/unselect route controllers
+ // for the added/removed route members.
+ if (mSelectedRoute instanceof RouteGroup) {
+ List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+ // Build a set of descriptor IDs for the new route group.
+ Set idSet = new HashSet<String>();
+ for (RouteInfo route : routes) {
+ idSet.add(route.mDescriptorId);
+ }
+ // Unselect route controllers for the removed routes.
+ Iterator<Map.Entry<String, RouteController>> iter =
+ mRouteControllerMap.entrySet().iterator();
+ while (iter.hasNext()) {
+ Map.Entry<String, RouteController> entry = iter.next();
+ if (!idSet.contains(entry.getKey())) {
+ RouteController controller = entry.getValue();
+ controller.onUnselect();
+ controller.onRelease();
+ iter.remove();
+ }
+ }
+ // Select route controllers for the added routes.
+ for (RouteInfo route : routes) {
+ if (!mRouteControllerMap.containsKey(route.mDescriptorId)) {
+ RouteController controller = route.getProviderInstance()
+ .onCreateRouteController(route.mDescriptorId);
+ controller.onSelect();
+ mRouteControllerMap.put(route.mDescriptorId, controller);
+ }
+ }
+ }
// Update the playback info because the properties of the route have changed.
updatePlaybackInfoFromSelectedRoute();
}
}
- private RouteInfo chooseFallbackRoute() {
+ RouteInfo chooseFallbackRoute() {
// When the current route is removed or no longer selectable,
// we want to revert to a live audio route if there is
// one (usually Bluetooth A2DP). Failing that, use
@@ -2445,18 +2506,19 @@
Log.d(TAG, "Route unselected: " + mSelectedRoute + " reason: "
+ unselectReason);
}
- mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute);
+ mCallbackHandler.post(CallbackHandler.MSG_ROUTE_UNSELECTED, mSelectedRoute,
+ unselectReason);
if (mSelectedRouteController != null) {
mSelectedRouteController.onUnselect(unselectReason);
mSelectedRouteController.onRelease();
mSelectedRouteController = null;
}
- if (mGroupMemberControllers != null) {
- for (RouteController controller : mGroupMemberControllers.values()) {
- controller.onUnselect();
+ if (!mRouteControllerMap.isEmpty()) {
+ for (RouteController controller : mRouteControllerMap.values()) {
+ controller.onUnselect(unselectReason);
controller.onRelease();
}
- mGroupMemberControllers = null;
+ mRouteControllerMap.clear();
}
}
@@ -2474,13 +2536,13 @@
mCallbackHandler.post(CallbackHandler.MSG_ROUTE_SELECTED, mSelectedRoute);
if (mSelectedRoute instanceof RouteGroup) {
- mGroupMemberControllers = new HashMap<>();
- RouteGroup group = (RouteGroup) mSelectedRoute;
- for (RouteInfo groupMember : group.getRoutes()) {
- RouteController controller = groupMember.getProviderInstance()
- .onCreateRouteController(groupMember.mDescriptorId);
+ List<RouteInfo> routes = ((RouteGroup) mSelectedRoute).getRoutes();
+ mRouteControllerMap.clear();
+ for (RouteInfo r : routes) {
+ RouteController controller = r.getProviderInstance()
+ .onCreateRouteController(r.mDescriptorId);
controller.onSelect();
- mGroupMemberControllers.put(groupMember.mDescriptorId, controller);
+ mRouteControllerMap.put(r.mDescriptorId, controller);
}
}
}
@@ -2734,10 +2796,17 @@
obtainMessage(msg, obj).sendToTarget();
}
+ public void post(int msg, Object obj, int arg) {
+ Message message = obtainMessage(msg, obj);
+ message.arg1 = arg;
+ message.sendToTarget();
+ }
+
@Override
public void handleMessage(Message msg) {
final int what = msg.what;
final Object obj = msg.obj;
+ final int arg = msg.arg1;
// Synchronize state with the system media router.
syncWithSystemProvider(what, obj);
@@ -2757,7 +2826,7 @@
final int callbackCount = mTempCallbackRecords.size();
for (int i = 0; i < callbackCount; i++) {
- invokeCallback(mTempCallbackRecords.get(i), what, obj);
+ invokeCallback(mTempCallbackRecords.get(i), what, obj, arg);
}
} finally {
mTempCallbackRecords.clear();
@@ -2781,7 +2850,7 @@
}
}
- private void invokeCallback(CallbackRecord record, int what, Object obj) {
+ private void invokeCallback(CallbackRecord record, int what, Object obj, int arg) {
final MediaRouter router = record.mRouter;
final MediaRouter.Callback callback = record.mCallback;
switch (what & MSG_TYPE_MASK) {
@@ -2810,7 +2879,7 @@
callback.onRouteSelected(router, route);
break;
case MSG_ROUTE_UNSELECTED:
- callback.onRouteUnselected(router, route);
+ callback.onRouteUnselected(router, route, arg);
break;
}
break;
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index b2cb289..1716af3 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -24,6 +24,8 @@
import android.os.Bundle;
import android.util.Log;
+import java.util.Iterator;
+
/**
* A helper class for playing media on remote routes using the remote playback protocol
* defined by {@link MediaControlIntent}.
@@ -38,16 +40,19 @@
private final Context mContext;
private final MediaRouter.RouteInfo mRoute;
- private final StatusReceiver mStatusReceiver;
+ private final ActionReceiver mActionReceiver;
private final PendingIntent mItemStatusPendingIntent;
private final PendingIntent mSessionStatusPendingIntent;
+ private final PendingIntent mMessagePendingIntent;
private boolean mRouteSupportsRemotePlayback;
private boolean mRouteSupportsQueuing;
private boolean mRouteSupportsSessionManagement;
+ private boolean mRouteSupportsMessaging;
private String mSessionId;
private StatusCallback mStatusCallback;
+ private OnMessageReceivedListener mOnMessageReceivedListener;
/**
* Creates a remote playback client for a route.
@@ -65,22 +70,27 @@
mContext = context;
mRoute = route;
- IntentFilter statusFilter = new IntentFilter();
- statusFilter.addAction(StatusReceiver.ACTION_ITEM_STATUS_CHANGED);
- statusFilter.addAction(StatusReceiver.ACTION_SESSION_STATUS_CHANGED);
- mStatusReceiver = new StatusReceiver();
- context.registerReceiver(mStatusReceiver, statusFilter);
+ IntentFilter actionFilter = new IntentFilter();
+ actionFilter.addAction(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
+ actionFilter.addAction(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
+ actionFilter.addAction(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+ mActionReceiver = new ActionReceiver();
+ context.registerReceiver(mActionReceiver, actionFilter);
- Intent itemStatusIntent = new Intent(StatusReceiver.ACTION_ITEM_STATUS_CHANGED);
+ Intent itemStatusIntent = new Intent(ActionReceiver.ACTION_ITEM_STATUS_CHANGED);
itemStatusIntent.setPackage(context.getPackageName());
mItemStatusPendingIntent = PendingIntent.getBroadcast(
context, 0, itemStatusIntent, 0);
- Intent sessionStatusIntent = new Intent(StatusReceiver.ACTION_SESSION_STATUS_CHANGED);
+ Intent sessionStatusIntent = new Intent(ActionReceiver.ACTION_SESSION_STATUS_CHANGED);
sessionStatusIntent.setPackage(context.getPackageName());
mSessionStatusPendingIntent = PendingIntent.getBroadcast(
context, 0, sessionStatusIntent, 0);
+ Intent messageIntent = new Intent(ActionReceiver.ACTION_MESSAGE_RECEIVED);
+ messageIntent.setPackage(context.getPackageName());
+ mMessagePendingIntent = PendingIntent.getBroadcast(
+ context, 0, messageIntent, 0);
detectFeatures();
}
@@ -88,7 +98,7 @@
* Releases resources owned by the client.
*/
public void release() {
- mContext.unregisterReceiver(mStatusReceiver);
+ mContext.unregisterReceiver(mActionReceiver);
}
/**
@@ -156,6 +166,25 @@
}
/**
+ * Returns true if the route supports messages.
+ * <p>
+ * This method returns true if the route supports all of the basic remote playback
+ * actions and all of the following actions:
+ * {@link MediaControlIntent#ACTION_START_SESSION start session},
+ * {@link MediaControlIntent#ACTION_SEND_MESSAGE send message},
+ * {@link MediaControlIntent#ACTION_END_SESSION end session}.
+ * </p>
+ *
+ * @return True if session management is supported.
+ * Implies {@link #isRemotePlaybackSupported} is also true.
+ *
+ * @see #isRemotePlaybackSupported
+ */
+ public boolean isMessagingSupported() {
+ return mRouteSupportsMessaging;
+ }
+
+ /**
* Gets the current session id if there is one.
*
* @return The current session id, or null if none.
@@ -215,6 +244,19 @@
}
/**
+ * Sets a callback that should receive messages when a message is sent from
+ * media sessions created by this instance of the remote playback client changes.
+ * <p>
+ * The callback should be set before the session is created.
+ * </p>
+ *
+ * @param listener The callback to set. May be null to remove the previous callback.
+ */
+ public void setOnMessageReceivedListener(OnMessageReceivedListener listener) {
+ mOnMessageReceivedListener = listener;
+ }
+
+ /**
* Sends a request to play a media item.
* <p>
* Clears the queue and starts playing the new item immediately. If the queue
@@ -516,10 +558,39 @@
Intent intent = new Intent(MediaControlIntent.ACTION_START_SESSION);
intent.putExtra(MediaControlIntent.EXTRA_SESSION_STATUS_UPDATE_RECEIVER,
mSessionStatusPendingIntent);
+ if (mRouteSupportsMessaging) {
+ intent.putExtra(MediaControlIntent.EXTRA_MESSAGE_RECEIVER, mMessagePendingIntent);
+ }
performSessionAction(intent, null, extras, callback);
}
/**
+ * Sends a message.
+ * <p>
+ * The request is issued in the current session.
+ * </p><p>
+ * Please refer to {@link MediaControlIntent#ACTION_SEND_MESSAGE} for
+ * more information about the semantics of this request.
+ * </p>
+ *
+ * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+ * @param callback A callback to invoke when the request has been processed, or null if none.
+ *
+ * @throws IllegalStateException if there is no current session.
+ * @throws UnsupportedOperationException if the route does not support messages.
+ *
+ * @see MediaControlIntent#ACTION_SEND_MESSAGE
+ * @see #isMessagingSupported
+ */
+ public void sendMessage(Bundle message, SessionActionCallback callback) {
+ throwIfNoCurrentSession();
+ throwIfMessageNotSupported();
+
+ Intent intent = new Intent(MediaControlIntent.ACTION_SEND_MESSAGE);
+ performSessionAction(intent, mSessionId, message, callback);
+ }
+
+ /**
* Sends a request to get the status of the media playback session.
* <p>
* The request is issued in the current session.
@@ -722,12 +793,22 @@
&& routeSupportsAction(MediaControlIntent.ACTION_START_SESSION)
&& routeSupportsAction(MediaControlIntent.ACTION_GET_SESSION_STATUS)
&& routeSupportsAction(MediaControlIntent.ACTION_END_SESSION);
+ mRouteSupportsMessaging = doesRouteSupportMessaging();
}
private boolean routeSupportsAction(String action) {
return mRoute.supportsControlAction(MediaControlIntent.CATEGORY_REMOTE_PLAYBACK, action);
}
+ private boolean doesRouteSupportMessaging() {
+ for (IntentFilter filter : mRoute.getControlFilters()) {
+ if (filter.hasAction(MediaControlIntent.ACTION_SEND_MESSAGE)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
private void throwIfRemotePlaybackNotSupported() {
if (!mRouteSupportsRemotePlayback) {
throw new UnsupportedOperationException("The route does not support remote playback.");
@@ -747,6 +828,12 @@
}
}
+ private void throwIfMessageNotSupported() {
+ if (!mRouteSupportsMessaging) {
+ throw new UnsupportedOperationException("The route does not support message.");
+ }
+ }
+
private void throwIfNoCurrentSession() {
if (mSessionId == null) {
throw new IllegalStateException("There is no current session.");
@@ -780,11 +867,13 @@
return "null";
}
- private final class StatusReceiver extends BroadcastReceiver {
+ private final class ActionReceiver extends BroadcastReceiver {
public static final String ACTION_ITEM_STATUS_CHANGED =
"android.support.v7.media.actions.ACTION_ITEM_STATUS_CHANGED";
public static final String ACTION_SESSION_STATUS_CHANGED =
"android.support.v7.media.actions.ACTION_SESSION_STATUS_CHANGED";
+ public static final String ACTION_MESSAGE_RECEIVED =
+ "android.support.v7.media.actions.ACTION_MESSAGE_RECEIVED";
@Override
public void onReceive(Context context, Intent intent) {
@@ -839,6 +928,15 @@
mStatusCallback.onSessionStatusChanged(intent.getExtras(),
sessionId, sessionStatus);
}
+ } else if (action.equals(ACTION_MESSAGE_RECEIVED)) {
+ if (DEBUG) {
+ Log.d(TAG, "Received message callback: sessionId=" + sessionId);
+ }
+
+ if (mOnMessageReceivedListener != null) {
+ mOnMessageReceivedListener.onMessageReceived(sessionId,
+ intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
+ }
}
}
}
@@ -929,4 +1027,17 @@
public void onResult(Bundle data, String sessionId, MediaSessionStatus sessionStatus) {
}
}
+
+ /**
+ * A callback that will receive messages from media sessions.
+ */
+ public interface OnMessageReceivedListener {
+ /**
+ * Called when a message received.
+ *
+ * @param sessionId The session id.
+ * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
+ */
+ void onMessageReceived(String sessionId, Bundle message);
+ }
}
diff --git a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
index 6a09650..21ff5b4 100644
--- a/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
+++ b/v7/mediarouter/src/android/support/v7/media/SystemMediaRouteProvider.java
@@ -49,6 +49,9 @@
}
public static SystemMediaRouteProvider obtain(Context context, SyncCallback syncCallback) {
+ if (Build.VERSION.SDK_INT >= 24) {
+ return new Api24Impl(context, syncCallback);
+ }
if (Build.VERSION.SDK_INT >= 18) {
return new JellybeanMr2Impl(context, syncCallback);
}
@@ -836,4 +839,21 @@
return MediaRouterJellybeanMr2.RouteInfo.isConnecting(record.mRouteObj);
}
}
+
+ /**
+ * Api24 implementation.
+ */
+ private static class Api24Impl extends JellybeanMr2Impl {
+ public Api24Impl(Context context, SyncCallback syncCallback) {
+ super(context, syncCallback);
+ }
+
+ @Override
+ protected void onBuildSystemRouteDescriptor(SystemRouteRecord record,
+ MediaRouteDescriptor.Builder builder) {
+ super.onBuildSystemRouteDescriptor(record, builder);
+
+ builder.setDeviceType(MediaRouterApi24.RouteInfo.getDeviceType(record.mRouteObj));
+ }
+ }
}
diff --git a/v7/palette/Android.mk b/v7/palette/Android.mk
index 0c4cb05..814ef24 100644
--- a/v7/palette/Android.mk
+++ b/v7/palette/Android.mk
@@ -15,13 +15,24 @@
LOCAL_PATH := $(call my-dir)
# Here is the final static library that apps can link against.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-palette \
+# android-support-v4
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-palette
LOCAL_SDK_VERSION := 7
LOCAL_SRC_FILES := $(call all-java-files-under, src/main)
-LOCAL_MANIFEST_FILE := $(LOCAL_PATH)/src/main/AndroidManifest.xml
-LOCAL_JAVA_LIBRARIES += android-support-v4
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_MANIFEST_FILE := src/main/AndroidManifest.xml
+LOCAL_SHARED_ANDROID_LIBRARIES += android-support-v4
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
diff --git a/v7/palette/api/23.1.1.txt b/v7/palette/api/23.1.1.txt
new file mode 100644
index 0000000..fac6a55
--- /dev/null
+++ b/v7/palette/api/23.1.1.txt
@@ -0,0 +1,56 @@
+package android.support.v7.graphics {
+
+ public final class Palette {
+ method public static android.support.v7.graphics.Palette.Builder from(android.graphics.Bitmap);
+ method public static android.support.v7.graphics.Palette from(java.util.List<android.support.v7.graphics.Palette.Swatch>);
+ method public static deprecated android.support.v7.graphics.Palette generate(android.graphics.Bitmap);
+ method public static deprecated android.support.v7.graphics.Palette generate(android.graphics.Bitmap, int);
+ method public static deprecated android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generateAsync(android.graphics.Bitmap, android.support.v7.graphics.Palette.PaletteAsyncListener);
+ method public static deprecated android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generateAsync(android.graphics.Bitmap, int, android.support.v7.graphics.Palette.PaletteAsyncListener);
+ method public int getDarkMutedColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getDarkMutedSwatch();
+ method public int getDarkVibrantColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getDarkVibrantSwatch();
+ method public int getLightMutedColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getLightMutedSwatch();
+ method public int getLightVibrantColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getLightVibrantSwatch();
+ method public int getMutedColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getMutedSwatch();
+ method public java.util.List<android.support.v7.graphics.Palette.Swatch> getSwatches();
+ method public int getVibrantColor(int);
+ method public android.support.v7.graphics.Palette.Swatch getVibrantSwatch();
+ }
+
+ public static final class Palette.Builder {
+ ctor public Palette.Builder(android.graphics.Bitmap);
+ ctor public Palette.Builder(java.util.List<android.support.v7.graphics.Palette.Swatch>);
+ method public android.support.v7.graphics.Palette.Builder addFilter(android.support.v7.graphics.Palette.Filter);
+ method public android.support.v7.graphics.Palette.Builder clearFilters();
+ method public android.support.v7.graphics.Palette.Builder clearRegion();
+ method public android.support.v7.graphics.Palette generate();
+ method public android.os.AsyncTask<android.graphics.Bitmap, java.lang.Void, android.support.v7.graphics.Palette> generate(android.support.v7.graphics.Palette.PaletteAsyncListener);
+ method public android.support.v7.graphics.Palette.Builder maximumColorCount(int);
+ method public android.support.v7.graphics.Palette.Builder resizeBitmapSize(int);
+ method public android.support.v7.graphics.Palette.Builder setRegion(int, int, int, int);
+ }
+
+ public static abstract interface Palette.Filter {
+ method public abstract boolean isAllowed(int, float[]);
+ }
+
+ public static abstract interface Palette.PaletteAsyncListener {
+ method public abstract void onGenerated(android.support.v7.graphics.Palette);
+ }
+
+ public static final class Palette.Swatch {
+ ctor public Palette.Swatch(int, int);
+ method public int getBodyTextColor();
+ method public float[] getHsl();
+ method public int getPopulation();
+ method public int getRgb();
+ method public int getTitleTextColor();
+ }
+
+}
+
diff --git a/v7/palette/build.gradle b/v7/palette/build.gradle
index 3a977a4..65e298d 100644
--- a/v7/palette/build.gradle
+++ b/v7/palette/build.gradle
@@ -1,9 +1,14 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'palette-v7'
dependencies {
compile project(':support-v4')
+
+ androidTestCompile ('com.android.support.test:runner:0.4.1') {
+ exclude module: 'support-annotations'
+ }
+ testCompile 'junit:junit:4.12'
}
android {
@@ -11,6 +16,7 @@
defaultConfig {
minSdkVersion 7
+ testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
lintOptions {
diff --git a/v7/palette/src/androidTest/AndroidManifest.xml b/v7/palette/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..f9b8b1e
--- /dev/null
+++ b/v7/palette/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
+ package="android.support.v7.palette.test">
+
+ <uses-sdk android:minSdkVersion="7"
+ tools:overrideLibrary="android.support.test, android.app, android.support.test.rule"/>
+
+ <application>
+ <uses-library android:name="android.test.runner"/>
+ </application>
+
+ <instrumentation
+ android:name="android.test.InstrumentationTestRunner"
+ android:targetPackage="android.support.v7.palette.test"/>
+
+</manifest>
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/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java
index 3635c3e..ddeeb10 100644
--- a/v7/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java
+++ b/v7/palette/src/androidTest/java/android/support/v7/graphics/BucketTests.java
@@ -16,46 +16,44 @@
package android.support.v7.graphics;
+import static android.support.v7.graphics.TestUtils.assertCloseColors;
+import static android.support.v7.graphics.TestUtils.loadSampleBitmap;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+
import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import android.graphics.Canvas;
import android.graphics.Color;
-import android.test.InstrumentationTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
-/**
- * @hide
- */
-public class BucketTests extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class BucketTests {
- private Bitmap mSource;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSource = BitmapFactory.decodeResource(getInstrumentation().getContext().getResources(),
- android.R.drawable.sym_def_app_icon);
- }
-
+ @Test
+ @SmallTest
public void testSourceBitmapNotRecycled() {
- Palette.from(mSource).generate();
- assertFalse(mSource.isRecycled());
+ final Bitmap sample = loadSampleBitmap();
+
+ Palette.from(sample).generate();
+ assertFalse(sample.isRecycled());
}
+ @Test(expected = UnsupportedOperationException.class)
+ @SmallTest
public void testSwatchesUnmodifiable() {
- Palette p = Palette.from(mSource).generate();
- boolean thrown = false;
-
- try {
- p.getSwatches().remove(0);
- } catch (UnsupportedOperationException e) {
- thrown = true;
- }
-
- assertTrue(thrown);
+ Palette p = Palette.from(loadSampleBitmap()).generate();
+ p.getSwatches().remove(0);
}
+ @Test
+ @SmallTest
public void testSwatchesBuilder() {
ArrayList<Palette.Swatch> swatches = new ArrayList<>();
swatches.add(new Palette.Swatch(Color.BLACK, 40));
@@ -67,71 +65,85 @@
assertEquals(swatches, p.getSwatches());
}
+ @Test
+ @SmallTest
public void testRegionWhole() {
- Palette.Builder b = new Palette.Builder(mSource);
- b.setRegion(0, 0, mSource.getWidth(), mSource.getHeight());
+ final Bitmap sample = loadSampleBitmap();
- Throwable thrown = null;
- try {
- b.generate();
- } catch (Exception e) {
- thrown = e;
- }
- assertNull(thrown);
+ Palette.Builder b = new Palette.Builder(sample);
+ b.setRegion(0, 0, sample.getWidth(), sample.getHeight());
+ b.generate();
}
+ @Test
+ @SmallTest
public void testRegionUpperLeft() {
- Palette.Builder b = new Palette.Builder(mSource);
- b.setRegion(0, 0, mSource.getWidth() / 2, mSource.getHeight() / 2);
+ final Bitmap sample = loadSampleBitmap();
- Throwable thrown = null;
- try {
- b.generate();
- } catch (Exception e) {
- thrown = e;
- }
- assertNull(thrown);
+ Palette.Builder b = new Palette.Builder(sample);
+ b.setRegion(0, 0, sample.getWidth() / 2, sample.getHeight() / 2);
+ b.generate();
}
+ @Test
+ @SmallTest
public void testRegionBottomRight() {
- Palette.Builder b = new Palette.Builder(mSource);
- b.setRegion(mSource.getWidth() / 2, mSource.getHeight() / 2,
- mSource.getWidth(), mSource.getHeight());
+ final Bitmap sample = loadSampleBitmap();
- Throwable thrown = null;
- try {
- b.generate();
- } catch (Exception e) {
- thrown = e;
- }
- assertNull(thrown);
+ Palette.Builder b = new Palette.Builder(sample);
+ b.setRegion(sample.getWidth() / 2, sample.getHeight() / 2,
+ sample.getWidth(), sample.getHeight());
+ b.generate();
}
+ @Test
+ @SmallTest
public void testOnePixelTallBitmap() {
- Bitmap bitmap = Bitmap.createBitmap(1000, 1, Bitmap.Config.ARGB_8888);
+ final Bitmap bitmap = Bitmap.createBitmap(1000, 1, Bitmap.Config.ARGB_8888);
Palette.Builder b = new Palette.Builder(bitmap);
-
- Throwable thrown = null;
- try {
- b.generate();
- } catch (Exception e) {
- thrown = e;
- }
- assertNull(thrown);
+ b.generate();
}
+ @Test
+ @SmallTest
public void testOnePixelWideBitmap() {
- Bitmap bitmap = Bitmap.createBitmap(1, 1000, Bitmap.Config.ARGB_8888);
+ final Bitmap bitmap = Bitmap.createBitmap(1, 1000, Bitmap.Config.ARGB_8888);
Palette.Builder b = new Palette.Builder(bitmap);
-
- Throwable thrown = null;
- try {
- b.generate();
- } catch (Exception e) {
- thrown = e;
- }
- assertNull(thrown);
+ b.generate();
}
+
+ @Test
+ @SmallTest
+ public void testBlueBitmapReturnsBlueSwatch() {
+ final Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(Color.BLUE);
+
+ final Palette palette = Palette.from(bitmap).generate();
+
+ assertEquals(1, palette.getSwatches().size());
+
+ final Palette.Swatch swatch = palette.getSwatches().get(0);
+ assertCloseColors(Color.BLUE, swatch.getRgb());
+ }
+
+ @Test
+ @SmallTest
+ public void testBlueBitmapWithRegionReturnsBlueSwatch() {
+ final Bitmap bitmap = Bitmap.createBitmap(300, 300, Bitmap.Config.ARGB_8888);
+ Canvas canvas = new Canvas(bitmap);
+ canvas.drawColor(Color.BLUE);
+
+ final Palette palette = Palette.from(bitmap)
+ .setRegion(0, bitmap.getHeight() / 2, bitmap.getWidth(), bitmap.getHeight())
+ .generate();
+
+ assertEquals(1, palette.getSwatches().size());
+
+ final Palette.Swatch swatch = palette.getSwatches().get(0);
+ assertCloseColors(Color.BLUE, swatch.getRgb());
+ }
+
}
diff --git a/v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java
index 0c4dc89..284eab3 100644
--- a/v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java
+++ b/v7/palette/src/androidTest/java/android/support/v7/graphics/ConsistencyTest.java
@@ -16,26 +16,28 @@
package android.support.v7.graphics;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.support.test.runner.AndroidJUnit4;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.MediumTest;
-/**
- * @hide
- */
-public class ConsistencyTest extends InstrumentationTestCase {
+import static android.support.v7.graphics.TestUtils.loadSampleBitmap;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(AndroidJUnit4.class)
+public class ConsistencyTest {
private static final int NUMBER_TRIALS = 10;
+ @Test
+ @MediumTest
public void testConsistency() {
- Bitmap icon = BitmapFactory.decodeResource(getInstrumentation().getContext().getResources(),
- android.R.drawable.sym_def_app_icon);
-
- testConsistencyForBitmap(icon);
- }
-
- private void testConsistencyForBitmap(Bitmap bitmap) {
Palette lastPalette = null;
+ final Bitmap bitmap = loadSampleBitmap();
for (int i = 0; i < NUMBER_TRIALS; i++) {
Palette newPalette = Palette.from(bitmap).generate();
diff --git a/v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java
index 4b7b0e4c..0af8821 100644
--- a/v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java
+++ b/v7/palette/src/androidTest/java/android/support/v7/graphics/MaxColorsTest.java
@@ -16,39 +16,44 @@
package android.support.v7.graphics;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
+import android.support.test.runner.AndroidJUnit4;
import android.test.InstrumentationTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
-/**
- * @hide
- */
-public class MaxColorsTest extends InstrumentationTestCase {
+import static android.support.v7.graphics.TestUtils.loadSampleBitmap;
+import static org.junit.Assert.assertTrue;
- private Bitmap mSource;
+@RunWith(AndroidJUnit4.class)
+public class MaxColorsTest {
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSource = BitmapFactory.decodeResource(getInstrumentation().getContext().getResources(),
- android.R.drawable.sym_def_app_icon);
- }
-
+ @Test
+ @SmallTest
public void testMaxColorCount32() {
testMaxColorCount(32);
}
+ @Test
+ @SmallTest
public void testMaxColorCount1() {
testMaxColorCount(1);
}
+ @Test
+ @SmallTest
public void testMaxColorCount15() {
testMaxColorCount(15);
}
private void testMaxColorCount(int colorCount) {
- Palette newPalette = Palette.from(mSource).maximumColorCount(colorCount).generate();
+ Palette newPalette = Palette.from(loadSampleBitmap())
+ .maximumColorCount(colorCount)
+ .generate();
assertTrue(newPalette.getSwatches().size() <= colorCount);
}
}
diff --git a/v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java
index 396ded9..0f55bca 100644
--- a/v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java
+++ b/v7/palette/src/androidTest/java/android/support/v7/graphics/SwatchTests.java
@@ -16,51 +16,51 @@
package android.support.v7.graphics;
-import android.graphics.Bitmap;
-import android.graphics.BitmapFactory;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.graphics.Color;
-import android.support.v4.graphics.ColorUtils;
-import android.test.InstrumentationTestCase;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.SmallTest;
import static android.support.v4.graphics.ColorUtils.HSLToColor;
import static android.support.v4.graphics.ColorUtils.calculateContrast;
+import static android.support.v7.graphics.TestUtils.loadSampleBitmap;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
-/**
- * @hide
- */
-public class SwatchTests extends InstrumentationTestCase {
+@RunWith(AndroidJUnit4.class)
+public class SwatchTests {
private static final float MIN_CONTRAST_TITLE_TEXT = 3.0f;
private static final float MIN_CONTRAST_BODY_TEXT = 4.5f;
- private Bitmap mSource;
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
-
- mSource = BitmapFactory.decodeResource(getInstrumentation().getContext().getResources(),
- android.R.drawable.sym_def_app_icon);
- }
-
+ @Test
+ @SmallTest
public void testTextColorContrasts() {
- Palette p = Palette.from(mSource).generate();
+ final Palette p = Palette.from(loadSampleBitmap()).generate();
for (Palette.Swatch swatch : p.getSwatches()) {
testSwatchTextColorContrasts(swatch);
}
}
+ @Test
+ @SmallTest
public void testHslNotNull() {
- Palette p = Palette.from(mSource).generate();
+ final Palette p = Palette.from(loadSampleBitmap()).generate();
for (Palette.Swatch swatch : p.getSwatches()) {
assertNotNull(swatch.getHsl());
}
}
+ @Test
+ @SmallTest
public void testHslIsRgb() {
- Palette p = Palette.from(mSource).generate();
+ final Palette p = Palette.from(loadSampleBitmap()).generate();
for (Palette.Swatch swatch : p.getSwatches()) {
assertEquals(HSLToColor(swatch.getHsl()), swatch.getRgb());
@@ -75,24 +75,32 @@
assertTrue(calculateContrast(titleTextColor, swatch.getRgb()) >= MIN_CONTRAST_TITLE_TEXT);
}
+ @Test
+ @SmallTest
public void testEqualsWhenSame() {
Palette.Swatch swatch1 = new Palette.Swatch(Color.WHITE, 50);
Palette.Swatch swatch2 = new Palette.Swatch(Color.WHITE, 50);
assertEquals(swatch1, swatch2);
}
+ @Test
+ @SmallTest
public void testEqualsWhenColorDifferent() {
Palette.Swatch swatch1 = new Palette.Swatch(Color.BLACK, 50);
Palette.Swatch swatch2 = new Palette.Swatch(Color.WHITE, 50);
assertFalse(swatch1.equals(swatch2));
}
+ @Test
+ @SmallTest
public void testEqualsWhenPopulationDifferent() {
Palette.Swatch swatch1 = new Palette.Swatch(Color.BLACK, 50);
Palette.Swatch swatch2 = new Palette.Swatch(Color.BLACK, 100);
assertFalse(swatch1.equals(swatch2));
}
+ @Test
+ @SmallTest
public void testEqualsWhenDifferent() {
Palette.Swatch swatch1 = new Palette.Swatch(Color.BLUE, 50);
Palette.Swatch swatch2 = new Palette.Swatch(Color.BLACK, 100);
diff --git a/v7/palette/src/androidTest/java/android/support/v7/graphics/TestUtils.java b/v7/palette/src/androidTest/java/android/support/v7/graphics/TestUtils.java
new file mode 100644
index 0000000..5002111
--- /dev/null
+++ b/v7/palette/src/androidTest/java/android/support/v7/graphics/TestUtils.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.graphics;
+
+import static org.junit.Assert.assertEquals;
+
+import android.graphics.Bitmap;
+import android.graphics.BitmapFactory;
+import android.graphics.Color;
+import android.support.test.InstrumentationRegistry;
+import android.support.v7.palette.test.R;
+
+class TestUtils {
+
+ static Bitmap loadSampleBitmap() {
+ return BitmapFactory.decodeResource(
+ InstrumentationRegistry.getContext().getResources(),
+ R.drawable.photo);
+ }
+
+ static void assertCloseColors(int expected, int actual) {
+ assertEquals(Color.red(expected), Color.red(actual), 2);
+ assertEquals(Color.green(expected), Color.green(actual), 2);
+ assertEquals(Color.blue(expected), Color.blue(actual), 2);
+ }
+
+}
diff --git a/v7/palette/src/androidTest/res/drawable-nodpi/photo.jpg b/v7/palette/src/androidTest/res/drawable-nodpi/photo.jpg
new file mode 100644
index 0000000..d5a2ef0
--- /dev/null
+++ b/v7/palette/src/androidTest/res/drawable-nodpi/photo.jpg
Binary files differ
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
index 3b89748..1516261 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java
@@ -503,8 +503,9 @@
private static int modifyWordWidth(int value, int currentWidth, int targetWidth) {
final int newValue;
if (targetWidth > currentWidth) {
- // If we're approximating up in word width, we'll shift up
- newValue = value << (targetWidth - currentWidth);
+ // If we're approximating up in word width, we'll use scaling to approximate the
+ // new value
+ newValue = value * ((1 << targetWidth) - 1) / ((1 << currentWidth) - 1);
} else {
// Else, we will just shift and keep the MSB
newValue = value >> (currentWidth - targetWidth);
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
index d0dff30..f729380 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Palette.java
@@ -773,11 +773,13 @@
if (bitmap != mBitmap && region != null) {
// If we have a scaled bitmap and a selected region, we need to scale down the
// region to match the new scale
- final float scale = bitmap.getWidth() / (float) mBitmap.getWidth();
+ final double scale = bitmap.getWidth() / (double) mBitmap.getWidth();
region.left = (int) Math.floor(region.left * scale);
region.top = (int) Math.floor(region.top * scale);
- region.right = (int) Math.ceil(region.right * scale);
- region.bottom = (int) Math.ceil(region.bottom * scale);
+ region.right = Math.min((int) Math.ceil(region.right * scale),
+ bitmap.getWidth());
+ region.bottom = Math.min((int) Math.ceil(region.bottom * scale),
+ bitmap.getHeight());
}
// Now generate a quantizer from the Bitmap
@@ -848,21 +850,18 @@
final int bitmapWidth = bitmap.getWidth();
final int bitmapHeight = bitmap.getHeight();
final int[] pixels = new int[bitmapWidth * bitmapHeight];
+ bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
if (mRegion == null) {
// If we don't have a region, return all of the pixels
- bitmap.getPixels(pixels, 0, bitmapWidth, 0, 0, bitmapWidth, bitmapHeight);
return pixels;
} else {
// If we do have a region, lets create a subset array containing only the region's
// pixels
final int regionWidth = mRegion.width();
final int regionHeight = mRegion.height();
- // First read the pixels within the region
- bitmap.getPixels(pixels, 0, bitmapWidth, mRegion.left, mRegion.top,
- regionWidth, regionHeight);
- // pixels now contains all of the pixels, but not packed together. We need to
- // iterate through each row and copy them into a new smaller array
+ // pixels contains all of the pixels, so we need to iterate through each row and
+ // copy the regions pixels into a new smaller array
final int[] subsetPixels = new int[regionWidth * regionHeight];
for (int row = 0; row < regionHeight; row++) {
System.arraycopy(pixels, ((row + mRegion.top) * bitmapWidth) + mRegion.left,
diff --git a/v7/palette/src/main/java/android/support/v7/graphics/Target.java b/v7/palette/src/main/java/android/support/v7/graphics/Target.java
index 8ac8205..d1e0742 100644
--- a/v7/palette/src/main/java/android/support/v7/graphics/Target.java
+++ b/v7/palette/src/main/java/android/support/v7/graphics/Target.java
@@ -179,21 +179,37 @@
}
/**
- * The weight of important that a color's saturation value has on selection.
+ * Returns the weight of importance that this target places on a color's saturation within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * @see #getTargetSaturation()
*/
public float getSaturationWeight() {
return mWeights[INDEX_WEIGHT_SAT];
}
/**
- * The weight of important that a color's lightness value has on selection.
+ * Returns the weight of importance that this target places on a color's lightness within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * @see #getTargetLightness()
*/
public float getLightnessWeight() {
return mWeights[INDEX_WEIGHT_LUMA];
}
/**
- * The weight of important that a color's population value has on selection.
+ * Returns the weight of importance that this target places on a color's population within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a
+ * color's population being close to the most populous has on selection.</p>
*/
public float getPopulationWeight() {
return mWeights[INDEX_WEIGHT_POP];
@@ -332,8 +348,15 @@
}
/**
- * Set the weight of important that a color's saturation value has on selection. A weight
- * of <= 0 means that it has no weight and is ignored.
+ * Set the weight of importance that this target will place on saturation values.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
+ *
+ * @see #setTargetSaturation(float)
*/
public Builder setSaturationWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_SAT] = weight;
@@ -341,8 +364,15 @@
}
/**
- * Set the weight of important that a color's lightness value has on selection. A weight
- * of <= 0 means that it has no weight and is ignored.
+ * Set the weight of importance that this target will place on lightness values.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a color
+ * being close to the target value has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
+ *
+ * @see #setTargetLightness(float)
*/
public Builder setLightnessWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_LUMA] = weight;
@@ -350,8 +380,14 @@
}
/**
- * Set the weight of important that a color's population value has on selection. A weight
- * of <= 0 means that it has no weight and is ignored.
+ * Set the weight of importance that this target will place on a color's population within
+ * the image.
+ *
+ * <p>The larger the weight, relative to the other weights, the more important that a
+ * color's population being close to the most populous has on selection.</p>
+ *
+ * <p>A weight of 0 means that it has no weight, and thus has no
+ * bearing on the selection.</p>
*/
public Builder setPopulationWeight(@FloatRange(from = 0) float weight) {
mTarget.mWeights[INDEX_WEIGHT_POP] = weight;
diff --git a/v7/preference/Android.mk b/v7/preference/Android.mk
index 1151ed9..43fc74c 100644
--- a/v7/preference/Android.mk
+++ b/v7/preference/Android.mk
@@ -14,40 +14,48 @@
LOCAL_PATH := $(call my-dir)
-# Build the resources using the current SDK version.
-# We do this here because the final static library must be compiled with an older
-# SDK version than the resources. The resources library and the R class that it
-# contains will not be linked into the final static library.
+# Build the resources separately because the constants built with the resources need to access
+# the latest SDK but the actual code needs to build against SDK 7.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-preference-res
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := \
- frameworks/support/v7/appcompat/res \
- frameworks/support/v7/recyclerview/res \
- $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
+LOCAL_SRC_FILES := $(call all-java-files-under,constants)
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-v7-appcompat \
+ android-support-v7-recyclerview
LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-preference \
+# android-support-v7-appcompat \
+# android-support-v7-recyclerview \
+# android-support-v4 \
+# android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-preference
LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
-# LOCAL_STATIC_JAVA_LIBRARIES :=
-LOCAL_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-v7-appcompat \
- android-support-v7-recyclerview \
- android-support-annotations \
- android-support-v7-preference-res
+LOCAL_STATIC_ANDROID_LIBRARIES := \
+ android-support-v7-preference-res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-v7-appcompat \
+ android-support-v7-recyclerview \
+ android-support-v4 \
+ android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
+LOCAL_AAPT_FLAGS := --add-javadoc-annotation doconly
include $(BUILD_STATIC_JAVA_LIBRARY)
# API Check
diff --git a/v7/preference/api/23.1.1.txt b/v7/preference/api/23.1.1.txt
new file mode 100644
index 0000000..7d7aeb1
--- /dev/null
+++ b/v7/preference/api/23.1.1.txt
@@ -0,0 +1,318 @@
+package android.support.v7.preference {
+
+ public class CheckBoxPreference extends android.support.v7.preference.TwoStatePreference {
+ ctor public CheckBoxPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public CheckBoxPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public CheckBoxPreference(android.content.Context, android.util.AttributeSet);
+ ctor public CheckBoxPreference(android.content.Context);
+ }
+
+ public abstract class DialogPreference extends android.support.v7.preference.Preference {
+ ctor public DialogPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public DialogPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public DialogPreference(android.content.Context, android.util.AttributeSet);
+ ctor public DialogPreference(android.content.Context);
+ method public android.graphics.drawable.Drawable getDialogIcon();
+ method public int getDialogLayoutResource();
+ method public java.lang.CharSequence getDialogMessage();
+ method public java.lang.CharSequence getDialogTitle();
+ method public java.lang.CharSequence getNegativeButtonText();
+ method public java.lang.CharSequence getPositiveButtonText();
+ method public void setDialogIcon(android.graphics.drawable.Drawable);
+ method public void setDialogIcon(int);
+ method public void setDialogLayoutResource(int);
+ method public void setDialogMessage(java.lang.CharSequence);
+ method public void setDialogMessage(int);
+ method public void setDialogTitle(java.lang.CharSequence);
+ method public void setDialogTitle(int);
+ method public void setNegativeButtonText(java.lang.CharSequence);
+ method public void setNegativeButtonText(int);
+ method public void setPositiveButtonText(java.lang.CharSequence);
+ method public void setPositiveButtonText(int);
+ }
+
+ public static abstract interface DialogPreference.TargetFragment {
+ method public abstract android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+ }
+
+ public class EditTextPreference extends android.support.v7.preference.DialogPreference {
+ ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public EditTextPreference(android.content.Context, android.util.AttributeSet);
+ ctor public EditTextPreference(android.content.Context);
+ method public java.lang.String getText();
+ method public void setText(java.lang.String);
+ }
+
+ public class EditTextPreferenceDialogFragmentCompat extends android.support.v7.preference.PreferenceDialogFragmentCompat {
+ ctor public EditTextPreferenceDialogFragmentCompat();
+ method public static android.support.v7.preference.EditTextPreferenceDialogFragmentCompat newInstance(java.lang.String);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class ListPreference extends android.support.v7.preference.DialogPreference {
+ ctor public ListPreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public ListPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public ListPreference(android.content.Context, android.util.AttributeSet);
+ ctor public ListPreference(android.content.Context);
+ method public int findIndexOfValue(java.lang.String);
+ method public java.lang.CharSequence[] getEntries();
+ method public java.lang.CharSequence getEntry();
+ method public java.lang.CharSequence[] getEntryValues();
+ method public java.lang.String getValue();
+ method public void setEntries(java.lang.CharSequence[]);
+ method public void setEntries(int);
+ method public void setEntryValues(java.lang.CharSequence[]);
+ method public void setEntryValues(int);
+ method public void setValue(java.lang.String);
+ method public void setValueIndex(int);
+ }
+
+ public class ListPreferenceDialogFragmentCompat extends android.support.v7.preference.PreferenceDialogFragmentCompat {
+ ctor public ListPreferenceDialogFragmentCompat();
+ method public static android.support.v7.preference.ListPreferenceDialogFragmentCompat newInstance(java.lang.String);
+ method public void onDialogClosed(boolean);
+ }
+
+ public class Preference {
+ ctor public Preference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public Preference(android.content.Context, android.util.AttributeSet, int);
+ ctor public Preference(android.content.Context, android.util.AttributeSet);
+ ctor public Preference(android.content.Context);
+ method public boolean callChangeListener(java.lang.Object);
+ method public int compareTo(android.support.v7.preference.Preference);
+ method protected android.support.v7.preference.Preference findPreferenceInHierarchy(java.lang.String);
+ method public android.content.Context getContext();
+ method public java.lang.String getDependency();
+ method public android.os.Bundle getExtras();
+ method public java.lang.String getFragment();
+ method public android.graphics.drawable.Drawable getIcon();
+ method public android.content.Intent getIntent();
+ method public java.lang.String getKey();
+ method public final int getLayoutResource();
+ method public android.support.v7.preference.Preference.OnPreferenceChangeListener getOnPreferenceChangeListener();
+ method public android.support.v7.preference.Preference.OnPreferenceClickListener getOnPreferenceClickListener();
+ method public int getOrder();
+ method protected boolean getPersistedBoolean(boolean);
+ method protected float getPersistedFloat(float);
+ method protected int getPersistedInt(int);
+ method protected long getPersistedLong(long);
+ method protected java.lang.String getPersistedString(java.lang.String);
+ method public android.support.v7.preference.PreferenceManager getPreferenceManager();
+ method public android.content.SharedPreferences getSharedPreferences();
+ method public boolean getShouldDisableView();
+ method public java.lang.CharSequence getSummary();
+ method public java.lang.CharSequence getTitle();
+ method public final int getWidgetLayoutResource();
+ method public boolean hasKey();
+ method public boolean isEnabled();
+ method public boolean isPersistent();
+ method public boolean isSelectable();
+ method public final boolean isVisible();
+ method protected void notifyChanged();
+ method public void notifyDependencyChange(boolean);
+ method protected void notifyHierarchyChanged();
+ method public void onAttached();
+ method protected void onAttachedToHierarchy(android.support.v7.preference.PreferenceManager);
+ method public void onBindViewHolder(android.support.v7.preference.PreferenceViewHolder);
+ method protected void onClick();
+ method public void onDependencyChanged(android.support.v7.preference.Preference, boolean);
+ method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int);
+ method public void onParentChanged(android.support.v7.preference.Preference, boolean);
+ method protected void onPrepareForRemoval();
+ method protected void onRestoreInstanceState(android.os.Parcelable);
+ method protected android.os.Parcelable onSaveInstanceState();
+ method protected void onSetInitialValue(boolean, java.lang.Object);
+ method public android.os.Bundle peekExtras();
+ method protected boolean persistBoolean(boolean);
+ method protected boolean persistFloat(float);
+ method protected boolean persistInt(int);
+ method protected boolean persistLong(long);
+ method protected boolean persistString(java.lang.String);
+ method public void restoreHierarchyState(android.os.Bundle);
+ method public void saveHierarchyState(android.os.Bundle);
+ method public void setDefaultValue(java.lang.Object);
+ method public void setDependency(java.lang.String);
+ method public void setEnabled(boolean);
+ method public void setFragment(java.lang.String);
+ method public void setIcon(android.graphics.drawable.Drawable);
+ method public void setIcon(int);
+ method public void setIntent(android.content.Intent);
+ method public void setKey(java.lang.String);
+ method public void setLayoutResource(int);
+ method public void setOnPreferenceChangeListener(android.support.v7.preference.Preference.OnPreferenceChangeListener);
+ method public void setOnPreferenceClickListener(android.support.v7.preference.Preference.OnPreferenceClickListener);
+ method public void setOrder(int);
+ method public void setPersistent(boolean);
+ method public void setSelectable(boolean);
+ method public void setShouldDisableView(boolean);
+ method public void setSummary(java.lang.CharSequence);
+ method public void setSummary(int);
+ method public void setTitle(java.lang.CharSequence);
+ method public void setTitle(int);
+ method public final void setVisible(boolean);
+ method public void setWidgetLayoutResource(int);
+ method public boolean shouldDisableDependents();
+ method protected boolean shouldPersist();
+ field public static final int DEFAULT_ORDER = 2147483647; // 0x7fffffff
+ }
+
+ public static class Preference.BaseSavedState extends android.view.AbsSavedState {
+ ctor public Preference.BaseSavedState(android.os.Parcel);
+ ctor public Preference.BaseSavedState(android.os.Parcelable);
+ field public static final android.os.Parcelable.Creator<android.support.v7.preference.Preference.BaseSavedState> CREATOR;
+ }
+
+ public static abstract interface Preference.OnPreferenceChangeListener {
+ method public abstract boolean onPreferenceChange(android.support.v7.preference.Preference, java.lang.Object);
+ }
+
+ public static abstract interface Preference.OnPreferenceClickListener {
+ method public abstract boolean onPreferenceClick(android.support.v7.preference.Preference);
+ }
+
+ public class PreferenceCategory extends android.support.v7.preference.PreferenceGroup {
+ ctor public PreferenceCategory(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public PreferenceCategory(android.content.Context, android.util.AttributeSet, int);
+ ctor public PreferenceCategory(android.content.Context, android.util.AttributeSet);
+ ctor public PreferenceCategory(android.content.Context);
+ }
+
+ public abstract class PreferenceDialogFragmentCompat extends android.support.v4.app.DialogFragment implements android.content.DialogInterface.OnClickListener {
+ ctor public PreferenceDialogFragmentCompat();
+ method public android.support.v7.preference.DialogPreference getPreference();
+ method protected void onBindDialogView(android.view.View);
+ method public void onClick(android.content.DialogInterface, int);
+ method protected android.view.View onCreateDialogView(android.content.Context);
+ method public abstract void onDialogClosed(boolean);
+ method protected void onPrepareDialogBuilder(android.support.v7.app.AlertDialog.Builder);
+ field protected static final java.lang.String ARG_KEY = "key";
+ }
+
+ public abstract class PreferenceFragmentCompat extends android.support.v4.app.Fragment implements android.support.v7.preference.DialogPreference.TargetFragment android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener {
+ ctor public PreferenceFragmentCompat();
+ method public void addPreferencesFromResource(int);
+ method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+ method public final android.support.v7.widget.RecyclerView getListView();
+ method public android.support.v7.preference.PreferenceManager getPreferenceManager();
+ method public android.support.v7.preference.PreferenceScreen getPreferenceScreen();
+ method protected android.support.v7.widget.RecyclerView.Adapter onCreateAdapter(android.support.v7.preference.PreferenceScreen);
+ method public android.support.v7.widget.RecyclerView.LayoutManager onCreateLayoutManager();
+ method public abstract void onCreatePreferences(android.os.Bundle, java.lang.String);
+ method public android.support.v7.widget.RecyclerView onCreateRecyclerView(android.view.LayoutInflater, android.view.ViewGroup, android.os.Bundle);
+ method public void onDisplayPreferenceDialog(android.support.v7.preference.Preference);
+ method public void onNavigateToScreen(android.support.v7.preference.PreferenceScreen);
+ method public boolean onPreferenceTreeClick(android.support.v7.preference.Preference);
+ method public void setPreferenceScreen(android.support.v7.preference.PreferenceScreen);
+ method public void setPreferencesFromResource(int, java.lang.String);
+ field public static final java.lang.String ARG_PREFERENCE_ROOT = "android.support.v7.preference.PreferenceFragmentCompat.PREFERENCE_ROOT";
+ }
+
+ public static abstract interface PreferenceFragmentCompat.OnPreferenceDisplayDialogCallback {
+ method public abstract boolean onPreferenceDisplayDialog(android.support.v7.preference.PreferenceFragmentCompat, android.support.v7.preference.Preference);
+ }
+
+ public static abstract interface PreferenceFragmentCompat.OnPreferenceStartFragmentCallback {
+ method public abstract boolean onPreferenceStartFragment(android.support.v7.preference.PreferenceFragmentCompat, android.support.v7.preference.Preference);
+ }
+
+ public static abstract interface PreferenceFragmentCompat.OnPreferenceStartScreenCallback {
+ method public abstract boolean onPreferenceStartScreen(android.support.v7.preference.PreferenceFragmentCompat, android.support.v7.preference.PreferenceScreen);
+ }
+
+ public abstract class PreferenceGroup extends android.support.v7.preference.Preference {
+ ctor public PreferenceGroup(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public PreferenceGroup(android.content.Context, android.util.AttributeSet, int);
+ ctor public PreferenceGroup(android.content.Context, android.util.AttributeSet);
+ method public void addItemFromInflater(android.support.v7.preference.Preference);
+ method public boolean addPreference(android.support.v7.preference.Preference);
+ method protected void dispatchRestoreInstanceState(android.os.Bundle);
+ method protected void dispatchSaveInstanceState(android.os.Bundle);
+ method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+ method public android.support.v7.preference.Preference getPreference(int);
+ method public int getPreferenceCount();
+ method protected boolean isOnSameScreenAsChildren();
+ method public boolean isOrderingAsAdded();
+ method protected boolean onPrepareAddPreference(android.support.v7.preference.Preference);
+ method public void removeAll();
+ method public boolean removePreference(android.support.v7.preference.Preference);
+ method public void setOrderingAsAdded(boolean);
+ }
+
+ public class PreferenceManager {
+ method public android.support.v7.preference.PreferenceScreen createPreferenceScreen(android.content.Context);
+ method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
+ method public android.content.Context getContext();
+ method public static android.content.SharedPreferences getDefaultSharedPreferences(android.content.Context);
+ method public android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener getOnDisplayPreferenceDialogListener();
+ method public android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener getOnNavigateToScreenListener();
+ method public android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener getOnPreferenceTreeClickListener();
+ method public android.support.v7.preference.PreferenceScreen getPreferenceScreen();
+ method public android.content.SharedPreferences getSharedPreferences();
+ method public int getSharedPreferencesMode();
+ method public java.lang.String getSharedPreferencesName();
+ method public static void setDefaultValues(android.content.Context, int, boolean);
+ method public static void setDefaultValues(android.content.Context, java.lang.String, int, int, boolean);
+ method public void setOnDisplayPreferenceDialogListener(android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener);
+ method public void setOnNavigateToScreenListener(android.support.v7.preference.PreferenceManager.OnNavigateToScreenListener);
+ method public void setOnPreferenceTreeClickListener(android.support.v7.preference.PreferenceManager.OnPreferenceTreeClickListener);
+ method public boolean setPreferences(android.support.v7.preference.PreferenceScreen);
+ method public void setSharedPreferencesMode(int);
+ method public void setSharedPreferencesName(java.lang.String);
+ method public void showDialog(android.support.v7.preference.Preference);
+ field public static final java.lang.String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
+ }
+
+ public static abstract interface PreferenceManager.OnDisplayPreferenceDialogListener {
+ method public abstract void onDisplayPreferenceDialog(android.support.v7.preference.Preference);
+ }
+
+ public static abstract interface PreferenceManager.OnNavigateToScreenListener {
+ method public abstract void onNavigateToScreen(android.support.v7.preference.PreferenceScreen);
+ }
+
+ public static abstract interface PreferenceManager.OnPreferenceTreeClickListener {
+ method public abstract boolean onPreferenceTreeClick(android.support.v7.preference.Preference);
+ }
+
+ public final class PreferenceScreen extends android.support.v7.preference.PreferenceGroup {
+ }
+
+ public class PreferenceViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder {
+ method public android.view.View findViewById(int);
+ }
+
+ public class SwitchPreferenceCompat extends android.support.v7.preference.TwoStatePreference {
+ ctor public SwitchPreferenceCompat(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public SwitchPreferenceCompat(android.content.Context, android.util.AttributeSet, int);
+ ctor public SwitchPreferenceCompat(android.content.Context, android.util.AttributeSet);
+ ctor public SwitchPreferenceCompat(android.content.Context);
+ method public java.lang.CharSequence getSwitchTextOff();
+ method public java.lang.CharSequence getSwitchTextOn();
+ method public void setSwitchTextOff(java.lang.CharSequence);
+ method public void setSwitchTextOff(int);
+ method public void setSwitchTextOn(java.lang.CharSequence);
+ method public void setSwitchTextOn(int);
+ }
+
+ public abstract class TwoStatePreference extends android.support.v7.preference.Preference {
+ ctor public TwoStatePreference(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public TwoStatePreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public TwoStatePreference(android.content.Context, android.util.AttributeSet);
+ ctor public TwoStatePreference(android.content.Context);
+ method public boolean getDisableDependentsState();
+ method public java.lang.CharSequence getSummaryOff();
+ method public java.lang.CharSequence getSummaryOn();
+ method public boolean isChecked();
+ method public void setChecked(boolean);
+ method public void setDisableDependentsState(boolean);
+ method public void setSummaryOff(java.lang.CharSequence);
+ method public void setSummaryOff(int);
+ method public void setSummaryOn(java.lang.CharSequence);
+ method public void setSummaryOn(int);
+ method protected void syncSummaryView(android.support.v7.preference.PreferenceViewHolder);
+ field protected boolean mChecked;
+ }
+
+}
+
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index 3f3cab5..f0e9fc6 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -35,6 +35,14 @@
method public abstract android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
}
+ public class DropDownPreference extends android.support.v7.preference.ListPreference {
+ ctor public DropDownPreference(android.content.Context);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int);
+ ctor public DropDownPreference(android.content.Context, android.util.AttributeSet, int, int);
+ method protected android.widget.ArrayAdapter createAdapter();
+ }
+
public class EditTextPreference extends android.support.v7.preference.DialogPreference {
ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int, int);
ctor public EditTextPreference(android.content.Context, android.util.AttributeSet, int);
@@ -117,7 +125,9 @@
method public void onBindViewHolder(android.support.v7.preference.PreferenceViewHolder);
method protected void onClick();
method public void onDependencyChanged(android.support.v7.preference.Preference, boolean);
+ method public void onDetached();
method protected java.lang.Object onGetDefaultValue(android.content.res.TypedArray, int);
+ method public void onInitializeAccessibilityNodeInfo(android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
method public void onParentChanged(android.support.v7.preference.Preference, boolean);
method protected void onPrepareForRemoval();
method protected void onRestoreInstanceState(android.os.Parcelable);
@@ -150,6 +160,7 @@
method public void setSummary(int);
method public void setTitle(java.lang.CharSequence);
method public void setTitle(int);
+ method public void setViewId(int);
method public final void setVisible(boolean);
method public void setWidgetLayoutResource(int);
method public boolean shouldDisableDependents();
@@ -203,6 +214,8 @@
method public void onDisplayPreferenceDialog(android.support.v7.preference.Preference);
method public void onNavigateToScreen(android.support.v7.preference.PreferenceScreen);
method public boolean onPreferenceTreeClick(android.support.v7.preference.Preference);
+ method public void scrollToPreference(java.lang.String);
+ method public void scrollToPreference(android.support.v7.preference.Preference);
method public void setDivider(android.graphics.drawable.Drawable);
method public void setDividerHeight(int);
method public void setPreferenceScreen(android.support.v7.preference.PreferenceScreen);
@@ -241,6 +254,11 @@
method public void setOrderingAsAdded(boolean);
}
+ public static abstract interface PreferenceGroup.PreferencePositionCallback {
+ method public abstract int getPreferenceAdapterPosition(java.lang.String);
+ method public abstract int getPreferenceAdapterPosition(android.support.v7.preference.Preference);
+ }
+
public class PreferenceManager {
method public android.support.v7.preference.PreferenceScreen createPreferenceScreen(android.content.Context);
method public android.support.v7.preference.Preference findPreference(java.lang.CharSequence);
@@ -253,6 +271,8 @@
method public android.content.SharedPreferences getSharedPreferences();
method public int getSharedPreferencesMode();
method public java.lang.String getSharedPreferencesName();
+ method public boolean isStorageDefault();
+ method public boolean isStorageDeviceProtected();
method public static void setDefaultValues(android.content.Context, int, boolean);
method public static void setDefaultValues(android.content.Context, java.lang.String, int, int, boolean);
method public void setOnDisplayPreferenceDialogListener(android.support.v7.preference.PreferenceManager.OnDisplayPreferenceDialogListener);
@@ -261,6 +281,8 @@
method public boolean setPreferences(android.support.v7.preference.PreferenceScreen);
method public void setSharedPreferencesMode(int);
method public void setSharedPreferencesName(java.lang.String);
+ method public void setStorageDefault();
+ method public void setStorageDeviceProtected();
method public void showDialog(android.support.v7.preference.Preference);
field public static final java.lang.String KEY_HAS_SET_DEFAULT_VALUES = "_has_set_default_values";
}
@@ -278,6 +300,8 @@
}
public final class PreferenceScreen extends android.support.v7.preference.PreferenceGroup {
+ method public void setShouldUseGeneratedIds(boolean);
+ method public boolean shouldUseGeneratedIds();
}
public class PreferenceViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder {
diff --git a/v7/preference/api/removed.txt b/v7/preference/api/removed.txt
index e69de29..b7152570 100644
--- a/v7/preference/api/removed.txt
+++ b/v7/preference/api/removed.txt
@@ -0,0 +1,8 @@
+package android.support.v7.preference {
+
+ public class PreferenceManager {
+ method public deprecated void setStorageDeviceEncrypted();
+ }
+
+}
+
diff --git a/v7/preference/build.gradle b/v7/preference/build.gradle
index a0681ed..ed752ee 100644
--- a/v7/preference/build.gradle
+++ b/v7/preference/build.gradle
@@ -14,7 +14,7 @@
* limitations under the License
*/
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'preference-v7'
@@ -25,11 +25,12 @@
}
android {
- compileSdkVersion 'current'
+ compileSdkVersion project.ext.currentSdk
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDir 'src'
+ main.java.srcDir 'constants'
main.res.srcDir 'res'
main.assets.srcDir 'assets'
main.resources.srcDir 'src'
@@ -56,6 +57,39 @@
}
}
+android.libraryVariants.all { variant ->
+ def name = variant.buildType.name
+
+ if (name.equals(com.android.builder.core.BuilderConstants.DEBUG)) {
+ return; // Skip debug builds.
+ }
+ def suffix = name.capitalize()
+
+ def jarTask = project.tasks.create(name: "jar${suffix}", type: Jar){
+ dependsOn variant.javaCompile
+ from variant.javaCompile.destinationDir
+ from 'LICENSE.txt'
+ }
+ def javadocTask = project.tasks.create(name: "javadoc${suffix}", type: Javadoc) {
+ source android.sourceSets.main.java
+ classpath = files(variant.javaCompile.classpath.files) + files(
+ "${android.sdkDirectory}/platforms/${android.compileSdkVersion}/android.jar")
+ }
+
+ def javadocJarTask = project.tasks.create(name: "javadocJar${suffix}", type: Jar) {
+ classifier = 'javadoc'
+ from 'build/docs/javadoc'
+ }
+
+ def sourcesJarTask = project.tasks.create(name: "sourceJar${suffix}", type: Jar) {
+ classifier = 'sources'
+ from android.sourceSets.main.java.srcDirs
+ }
+
+ artifacts.add('archives', javadocJarTask);
+ artifacts.add('archives', sourcesJarTask);
+}
+
uploadArchives {
repositories {
mavenDeployer {
diff --git a/v7/preference/constants/android/support/v7/preference/AndroidResources.java b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
new file mode 100644
index 0000000..d529ad2
--- /dev/null
+++ b/v7/preference/constants/android/support/v7/preference/AndroidResources.java
@@ -0,0 +1,29 @@
+/*
+ * 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.preference;
+
+public class AndroidResources {
+
+ public static final int ANDROID_R_ICON_FRAME = android.R.id.icon_frame;
+ public static final int ANDROID_R_LIST_CONTAINER = android.R.id.list_container;
+ public static final int ANDROID_R_SWITCH_WIDGET = android.R.id.switch_widget;
+ public static final int ANDROID_R_PREFERENCE_FRAGMENT_STYLE
+ = android.R.attr.preferenceFragmentStyle;
+ public static final int ANDROID_R_EDITTEXT_PREFERENCE_STYLE
+ = android.R.attr.editTextPreferenceStyle;
+
+}
diff --git a/v7/preference/res/layout/preference_dropdown.xml b/v7/preference/res/layout/preference_dropdown.xml
new file mode 100644
index 0000000..09ea8fb
--- /dev/null
+++ b/v7/preference/res/layout/preference_dropdown.xml
@@ -0,0 +1,30 @@
+<?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
+ -->
+<FrameLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content">
+
+ <Spinner
+ android:id="@+id/spinner"
+ android:layout_width="0dp"
+ android:layout_height="wrap_content"
+ android:visibility="invisible" />
+
+ <include layout="@layout/preference" />
+
+</FrameLayout>
diff --git a/v7/preference/res/layout/preference_list_fragment.xml b/v7/preference/res/layout/preference_list_fragment.xml
index 44c5438..fe11fb2 100644
--- a/v7/preference/res/layout/preference_list_fragment.xml
+++ b/v7/preference/res/layout/preference_list_fragment.xml
@@ -21,7 +21,7 @@
android:layout_width="match_parent" >
<FrameLayout
- android:id="@+id/list_container"
+ android:id="@android:id/list_container"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
diff --git a/v7/preference/res/layout/preference_widget_checkbox.xml b/v7/preference/res/layout/preference_widget_checkbox.xml
index e53b7dfc..cc99443 100644
--- a/v7/preference/res/layout/preference_widget_checkbox.xml
+++ b/v7/preference/res/layout/preference_widget_checkbox.xml
@@ -18,7 +18,7 @@
<!-- Layout used by CheckBoxPreference for the checkbox style. This is inflated
inside android.R.layout.preference. -->
<CheckBox xmlns:android="http://schemas.android.com/apk/res/android"
- android:id="@+id/checkbox"
+ android:id="@android:id/checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="false"
diff --git a/v7/preference/res/values/attrs.xml b/v7/preference/res/values/attrs.xml
index 5d39b63..bd64109 100644
--- a/v7/preference/res/values/attrs.xml
+++ b/v7/preference/res/values/attrs.xml
@@ -16,7 +16,7 @@
-->
<resources>
- <declare-styleable name="Theme">
+ <declare-styleable name="PreferenceTheme">
<!-- =================== -->
<!-- Preference styles -->
@@ -50,6 +50,8 @@
<attr name="editTextPreferenceStyle" format="reference" />
<!-- Default style for RingtonePreference. -->
<attr name="ringtonePreferenceStyle" format="reference" />
+ <!-- Default style for DropDownPreference. -->
+ <attr name="dropdownPreferenceStyle" format="reference" />
<!-- The preference layout that has the child/tabbed effect. -->
<attr name="preferenceLayoutChild" format="reference" />
<!-- Preference panel style -->
@@ -245,4 +247,11 @@
<attr name="android:maxHeight" />
</declare-styleable>
+ <!-- Used to access some android attrs -->
+ <declare-styleable name="BackgroundStyle">
+ <attr name="android:selectableItemBackground" />
+ <!-- Need a non-android: attr here so that gradle doesn't remove it -->
+ <attr name="selectableItemBackground" />
+ </declare-styleable>
+
</resources>
diff --git a/v7/preference/res/values/styles.xml b/v7/preference/res/values/styles.xml
index 774c0c9..06b24fa4 100644
--- a/v7/preference/res/values/styles.xml
+++ b/v7/preference/res/values/styles.xml
@@ -65,4 +65,8 @@
<item name="android:paddingLeft">16dp</item>
<item name="android:paddingRight">16dp</item>
</style>
+
+ <style name="Preference.DropDown">
+ <item name="android:layout">@layout/preference_dropdown</item>
+ </style>
</resources>
diff --git a/v7/preference/res/values/themes.xml b/v7/preference/res/values/themes.xml
index 8a7eaa3..bb7f496 100644
--- a/v7/preference/res/values/themes.xml
+++ b/v7/preference/res/values/themes.xml
@@ -27,5 +27,6 @@
<item name="dialogPreferenceStyle">@style/Preference.DialogPreference</item>
<item name="editTextPreferenceStyle">@style/Preference.DialogPreference.EditTextPreference</item>
<item name="preferenceFragmentListStyle">@style/PreferenceFragmentList</item>
+ <item name="dropdownPreferenceStyle">@style/Preference.DropDown</item>
</style>
</resources>
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index bd85c35..b6dad92 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -31,9 +31,9 @@
* <p>
* This preference will store a boolean into the SharedPreferences.
*
- * @attr ref android.R.styleable#CheckBoxPreference_summaryOff
- * @attr ref android.R.styleable#CheckBoxPreference_summaryOn
- * @attr ref android.R.styleable#CheckBoxPreference_disableDependentsState
+ * @attr name android:summaryOff
+ * @attr name android:summaryOn
+ * @attr name android:disableDependentsState
*/
public class CheckBoxPreference extends TwoStatePreference {
private final Listener mListener = new Listener();
@@ -76,7 +76,8 @@
}
public CheckBoxPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.checkBoxPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.checkBoxPreferenceStyle,
+ android.R.attr.checkBoxPreferenceStyle));
}
public CheckBoxPreference(Context context) {
@@ -87,10 +88,7 @@
public void onBindViewHolder(PreferenceViewHolder holder) {
super.onBindViewHolder(holder);
- View checkboxView = holder.findViewById(R.id.checkbox);
- if (checkboxView != null && checkboxView instanceof Checkable) {
- ((Checkable) checkboxView).setChecked(mChecked);
- }
+ syncCheckboxView(holder.findViewById(android.R.id.checkbox));
syncSummaryView(holder);
}
@@ -111,7 +109,7 @@
return;
}
- View checkboxView = view.findViewById(R.id.checkbox);
+ View checkboxView = view.findViewById(android.support.v7.appcompat.R.id.checkbox);
syncCheckboxView(checkboxView);
View summaryView = view.findViewById(android.R.id.summary);
diff --git a/v7/preference/src/android/support/v7/preference/DialogPreference.java b/v7/preference/src/android/support/v7/preference/DialogPreference.java
index bf0f4ef..aa7efee 100644
--- a/v7/preference/src/android/support/v7/preference/DialogPreference.java
+++ b/v7/preference/src/android/support/v7/preference/DialogPreference.java
@@ -29,12 +29,12 @@
* dialog-based. These preferences will, when clicked, open a dialog showing the
* actual preference controls.
*
- * @attr ref android.R.styleable#DialogPreference_dialogTitle
- * @attr ref android.R.styleable#DialogPreference_dialogMessage
- * @attr ref android.R.styleable#DialogPreference_dialogIcon
- * @attr ref android.R.styleable#DialogPreference_dialogLayout
- * @attr ref android.R.styleable#DialogPreference_positiveButtonText
- * @attr ref android.R.styleable#DialogPreference_negativeButtonText
+ * @attr name android:dialogTitle
+ * @attr name android:dialogMessage
+ * @attr name android:dialogIcon
+ * @attr name android:dialogLayout
+ * @attr name android:positiveButtonText
+ * @attr name android:negativeButtonText
*/
public abstract class DialogPreference extends Preference {
@@ -90,7 +90,8 @@
}
public DialogPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public DialogPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/DropDownPreference.java b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
new file mode 100644
index 0000000..ee1e723
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
@@ -0,0 +1,145 @@
+/*
+ * 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.preference;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemSelectedListener;
+import android.widget.ArrayAdapter;
+import android.widget.Spinner;
+
+/**
+ * A version of {@link ListPreference} that presents the options in a
+ * drop down menu rather than a dialog.
+ */
+public class DropDownPreference extends ListPreference {
+
+ private final Context mContext;
+ private final ArrayAdapter<String> mAdapter;
+
+ private Spinner mSpinner;
+
+ public DropDownPreference(Context context) {
+ this(context, null);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs) {
+ this(context, attrs, R.attr.dropdownPreferenceStyle);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs, int defStyle) {
+ this(context, attrs, defStyle, 0);
+ }
+
+ public DropDownPreference(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ mContext = context;
+ mAdapter = createAdapter();
+
+ updateEntries();
+ }
+
+ @Override
+ protected void onClick() {
+ mSpinner.performClick();
+ }
+
+ @Override
+ public void setEntries(@NonNull CharSequence[] entries) {
+ super.setEntries(entries);
+ updateEntries();
+ }
+
+
+ /**
+ * By default, this class uses a simple {@link android.widget.ArrayAdapter}. But if you need
+ * a more complicated {@link android.widget.ArrayAdapter}, this method can be overriden to
+ * create a custom one.
+ * <p> Note: This method is called from the constructor. So, overriden methods will get called
+ * before any subclass initialization.
+ *
+ * @return The custom {@link android.widget.ArrayAdapter} that needs to be used with this class.
+ */
+ protected ArrayAdapter createAdapter() {
+ return new ArrayAdapter<>(mContext, android.R.layout.simple_spinner_dropdown_item);
+ }
+
+ private void updateEntries() {
+ mAdapter.clear();
+ if (getEntries() != null) {
+ for (CharSequence c : getEntries()) {
+ mAdapter.add(c.toString());
+ }
+ }
+ }
+
+ public void setValueIndex(int index) {
+ setValue(getEntryValues()[index].toString());
+ }
+
+ /**
+ * @hide
+ */
+ public int findSpinnerIndexOfValue(String value) {
+ CharSequence[] entryValues = getEntryValues();
+ if (value != null && entryValues != null) {
+ for (int i = entryValues.length - 1; i >= 0; i--) {
+ if (entryValues[i].equals(value)) {
+ return i;
+ }
+ }
+ }
+ return Spinner.INVALID_POSITION;
+ }
+
+ @Override
+ protected void notifyChanged() {
+ super.notifyChanged();
+ mAdapter.notifyDataSetChanged();
+ }
+
+ @Override
+ public void onBindViewHolder(PreferenceViewHolder view) {
+ mSpinner = (Spinner) view.itemView.findViewById(R.id.spinner);
+ mSpinner.setAdapter(mAdapter);
+ mSpinner.setOnItemSelectedListener(mItemSelectedListener);
+ mSpinner.setSelection(findSpinnerIndexOfValue(getValue()));
+ super.onBindViewHolder(view);
+ }
+
+ private final OnItemSelectedListener mItemSelectedListener = new OnItemSelectedListener() {
+ @Override
+ public void onItemSelected(AdapterView<?> parent, View v, int position, long id) {
+ if (position >= 0) {
+ String value = getEntryValues()[position].toString();
+ if (!value.equals(getValue()) && callChangeListener(value)) {
+ setValue(value);
+ }
+ }
+ }
+
+ @Override
+ public void onNothingSelected(AdapterView<?> parent) {
+ // noop
+ }
+ };
+}
+
diff --git a/v7/preference/src/android/support/v7/preference/EditTextPreference.java b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
index c4941e6..423a0c9 100644
--- a/v7/preference/src/android/support/v7/preference/EditTextPreference.java
+++ b/v7/preference/src/android/support/v7/preference/EditTextPreference.java
@@ -20,6 +20,7 @@
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.v4.content.res.TypedArrayUtils;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.widget.EditText;
@@ -46,7 +47,8 @@
}
public EditTextPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.editTextPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.editTextPreferenceStyle,
+ AndroidResources.ANDROID_R_EDITTEXT_PREFERENCE_STYLE));
}
public EditTextPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
index 6e7663c..2abafe7 100644
--- a/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/EditTextPreferenceDialogFragmentCompat.java
@@ -17,13 +17,18 @@
package android.support.v7.preference;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.view.View;
import android.widget.EditText;
public class EditTextPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
+ private static final String SAVE_STATE_TEXT = "EditTextPreferenceDialogFragment.text";
+
private EditText mEditText;
+ private CharSequence mText;
+
public static EditTextPreferenceDialogFragmentCompat newInstance(String key) {
final EditTextPreferenceDialogFragmentCompat
fragment = new EditTextPreferenceDialogFragmentCompat();
@@ -34,6 +39,22 @@
}
@Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ mText = getEditTextPreference().getText();
+ } else {
+ mText = savedInstanceState.getCharSequence(SAVE_STATE_TEXT);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putCharSequence(SAVE_STATE_TEXT, mText);
+ }
+
+ @Override
protected void onBindDialogView(View view) {
super.onBindDialogView(view);
@@ -44,7 +65,7 @@
" @android:id/edit");
}
- mEditText.setText(getEditTextPreference().getText());
+ mEditText.setText(mText);
}
private EditTextPreference getEditTextPreference() {
diff --git a/v7/preference/src/android/support/v7/preference/ListPreference.java b/v7/preference/src/android/support/v7/preference/ListPreference.java
index 848cfad..2b433e5 100644
--- a/v7/preference/src/android/support/v7/preference/ListPreference.java
+++ b/v7/preference/src/android/support/v7/preference/ListPreference.java
@@ -33,8 +33,8 @@
* This preference will store a string into the SharedPreferences. This string will be the value
* from the {@link #setEntryValues(CharSequence[])} array.
*
- * @attr ref android.R.styleable#ListPreference_entries
- * @attr ref android.R.styleable#ListPreference_entryValues
+ * @attr name android:entries
+ * @attr name android:entryValues
*/
public class ListPreference extends DialogPreference {
private CharSequence[] mEntries;
@@ -74,7 +74,8 @@
}
public ListPreference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.dialogPreferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ android.R.attr.dialogPreferenceStyle));
}
public ListPreference(Context context) {
diff --git a/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
index 351ed0b..dda461d 100644
--- a/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/ListPreferenceDialogFragmentCompat.java
@@ -18,11 +18,21 @@
import android.content.DialogInterface;
import android.os.Bundle;
+import android.support.annotation.NonNull;
import android.support.v7.app.AlertDialog;
+import java.util.ArrayList;
+
public class ListPreferenceDialogFragmentCompat extends PreferenceDialogFragmentCompat {
+ private static final String SAVE_STATE_INDEX = "ListPreferenceDialogFragment.index";
+ private static final String SAVE_STATE_ENTRIES = "ListPreferenceDialogFragment.entries";
+ private static final String SAVE_STATE_ENTRY_VALUES =
+ "ListPreferenceDialogFragment.entryValues";
+
private int mClickedDialogEntryIndex;
+ private CharSequence[] mEntries;
+ private CharSequence[] mEntryValues;
public static ListPreferenceDialogFragmentCompat newInstance(String key) {
final ListPreferenceDialogFragmentCompat fragment =
@@ -33,6 +43,51 @@
return fragment;
}
+ @Override
+ public void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if (savedInstanceState == null) {
+ final ListPreference preference = getListPreference();
+
+ if (preference.getEntries() == null || preference.getEntryValues() == null) {
+ throw new IllegalStateException(
+ "ListPreference requires an entries array and an entryValues array.");
+ }
+
+ mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
+ mEntries = preference.getEntries();
+ mEntryValues = preference.getEntryValues();
+ } else {
+ mClickedDialogEntryIndex = savedInstanceState.getInt(SAVE_STATE_INDEX, 0);
+ mEntries = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRIES);
+ mEntryValues = getCharSequenceArray(savedInstanceState, SAVE_STATE_ENTRY_VALUES);
+ }
+ }
+
+ @Override
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt(SAVE_STATE_INDEX, mClickedDialogEntryIndex);
+ putCharSequenceArray(outState, SAVE_STATE_ENTRIES, mEntries);
+ putCharSequenceArray(outState, SAVE_STATE_ENTRY_VALUES, mEntryValues);
+ }
+
+ private static void putCharSequenceArray(Bundle out, String key, CharSequence[] entries) {
+ final ArrayList<String> stored = new ArrayList<>(entries.length);
+
+ for (final CharSequence cs : entries) {
+ stored.add(cs.toString());
+ }
+
+ out.putStringArrayList(key, stored);
+ }
+
+ private static CharSequence[] getCharSequenceArray(Bundle in, String key) {
+ final ArrayList<String> stored = in.getStringArrayList(key);
+
+ return stored == null ? null : stored.toArray(new CharSequence[stored.size()]);
+ }
+
private ListPreference getListPreference() {
return (ListPreference) getPreference();
}
@@ -41,15 +96,7 @@
protected void onPrepareDialogBuilder(AlertDialog.Builder builder) {
super.onPrepareDialogBuilder(builder);
- final ListPreference preference = getListPreference();
-
- if (preference.getEntries() == null || preference.getEntryValues() == null) {
- throw new IllegalStateException(
- "ListPreference requires an entries array and an entryValues array.");
- }
-
- mClickedDialogEntryIndex = preference.findIndexOfValue(preference.getValue());
- builder.setSingleChoiceItems(preference.getEntries(), mClickedDialogEntryIndex,
+ builder.setSingleChoiceItems(mEntries, mClickedDialogEntryIndex,
new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int which) {
mClickedDialogEntryIndex = which;
@@ -75,9 +122,8 @@
@Override
public void onDialogClosed(boolean positiveResult) {
final ListPreference preference = getListPreference();
- if (positiveResult && mClickedDialogEntryIndex >= 0 &&
- preference.getEntryValues() != null) {
- String value = preference.getEntryValues()[mClickedDialogEntryIndex].toString();
+ if (positiveResult && mClickedDialogEntryIndex >= 0) {
+ String value = mEntryValues[mClickedDialogEntryIndex].toString();
if (preference.callChangeListener(value)) {
preference.setValue(value);
}
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index f386592..c8897fe 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -24,10 +24,12 @@
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.CallSuper;
import android.support.annotation.NonNull;
import android.support.v4.content.ContextCompat;
import android.support.v4.content.SharedPreferencesCompat;
import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.text.TextUtils;
import android.util.AttributeSet;
import android.view.AbsSavedState;
@@ -61,20 +63,20 @@
* guide.</p>
* </div>
*
- * @attr ref android.R.styleable#Preference_icon
- * @attr ref android.R.styleable#Preference_key
- * @attr ref android.R.styleable#Preference_title
- * @attr ref android.R.styleable#Preference_summary
- * @attr ref android.R.styleable#Preference_order
- * @attr ref android.R.styleable#Preference_fragment
- * @attr ref android.R.styleable#Preference_layout
- * @attr ref android.R.styleable#Preference_widgetLayout
- * @attr ref android.R.styleable#Preference_enabled
- * @attr ref android.R.styleable#Preference_selectable
- * @attr ref android.R.styleable#Preference_dependency
- * @attr ref android.R.styleable#Preference_persistent
- * @attr ref android.R.styleable#Preference_defaultValue
- * @attr ref android.R.styleable#Preference_shouldDisableView
+ * @attr name android:icon
+ * @attr name android:key
+ * @attr name android:title
+ * @attr name android:summary
+ * @attr name android:order
+ * @attr name android:fragment
+ * @attr name android:layout
+ * @attr name android:widgetLayout
+ * @attr name android:enabled
+ * @attr name android:selectable
+ * @attr name android:dependency
+ * @attr name android:persistent
+ * @attr name android:defaultValue
+ * @attr name android:shouldDisableView
*/
public class Preference implements Comparable<Preference> {
/**
@@ -91,10 +93,17 @@
*/
private long mId;
+ /**
+ * Set true temporarily to keep {@link #onAttachedToHierarchy(PreferenceManager)} from
+ * overwriting mId
+ */
+ private boolean mHasId;
+
private OnPreferenceChangeListener mOnChangeListener;
private OnPreferenceClickListener mOnClickListener;
private int mOrder = DEFAULT_ORDER;
+ private int mViewId = 0;
private CharSequence mTitle;
private CharSequence mSummary;
/**
@@ -272,7 +281,7 @@
mShouldDisableView =
TypedArrayUtils.getBoolean(a, R.styleable.Preference_shouldDisableView,
- R.styleable.Preference_shouldDisableView, true);
+ R.styleable.Preference_android_shouldDisableView, true);
a.recycle();
}
@@ -316,7 +325,8 @@
* @see #Preference(Context, AttributeSet, int)
*/
public Preference(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.preferenceStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceStyle,
+ android.R.attr.preferenceStyle));
}
/**
@@ -470,6 +480,7 @@
*/
public void onBindViewHolder(PreferenceViewHolder holder) {
holder.itemView.setOnClickListener(mClickListener);
+ holder.itemView.setId(mViewId);
final TextView titleView = (TextView) holder.findViewById(android.R.id.title);
if (titleView != null) {
@@ -506,7 +517,10 @@
imageView.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
}
- final View imageFrame = holder.findViewById(R.id.icon_frame);
+ View imageFrame = holder.findViewById(R.id.icon_frame);
+ if (imageFrame == null) {
+ imageFrame = holder.findViewById(AndroidResources.ANDROID_R_ICON_FRAME);
+ }
if (imageFrame != null) {
imageFrame.setVisibility(mIcon != null ? View.VISIBLE : View.GONE);
}
@@ -519,6 +533,7 @@
final boolean selectable = isSelectable();
holder.itemView.setFocusable(selectable);
+ holder.itemView.setClickable(selectable);
holder.setDividerAllowedAbove(selectable);
holder.setDividerAllowedBelow(selectable);
@@ -572,6 +587,16 @@
}
/**
+ * Set the ID that will be assigned to the overall View representing this
+ * preference, once bound.
+ *
+ * @see View#setId(int)
+ */
+ public void setViewId(int viewId) {
+ mViewId = viewId;
+ }
+
+ /**
* Sets the title for this Preference with a CharSequence.
* This title will be placed into the ID
* {@link android.R.id#title} within the View bound by
@@ -1066,12 +1091,28 @@
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
mPreferenceManager = preferenceManager;
- mId = preferenceManager.getNextId();
+ if (!mHasId) {
+ mId = preferenceManager.getNextId();
+ }
dispatchSetInitialValue();
}
/**
+ * Called from {@link PreferenceGroup} to pass in an ID for reuse
+ * @hide
+ */
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager, long id) {
+ mId = id;
+ mHasId = true;
+ try {
+ onAttachedToHierarchy(preferenceManager);
+ } finally {
+ mHasId = false;
+ }
+ }
+
+ /**
* Called when the Preference hierarchy has been attached to the
* list of preferences. This can also be called when this
* Preference has been attached to a group that was already attached
@@ -1083,6 +1124,16 @@
registerDependency();
}
+ /**
+ * Called when the Preference hierarchy has been detached from the
+ * list of preferences. This can also be called when this
+ * Preference has been removed from a group that was attached
+ * to the list of preferences.
+ */
+ public void onDetached() {
+ unregisterDependency();
+ }
+
private void registerDependency() {
if (TextUtils.isEmpty(mDependencyKey)) return;
@@ -1669,6 +1720,14 @@
}
/**
+ * Initializes an {@link android.view.accessibility.AccessibilityNodeInfo} with information
+ * about the View for this Preference.
+ */
+ @CallSuper
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) {
+ }
+
+ /**
* A base class for managing the instance state of a {@link Preference}.
*/
public static class BaseSavedState extends AbsSavedState {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
index bed7f7e..b375b45c 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceCategory.java
@@ -17,6 +17,9 @@
package android.support.v7.preference;
import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat.CollectionItemInfoCompat;
import android.util.AttributeSet;
/**
@@ -43,7 +46,8 @@
}
public PreferenceCategory(Context context, AttributeSet attrs) {
- this(context, attrs, R.attr.preferenceCategoryStyle);
+ this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceCategoryStyle,
+ android.R.attr.preferenceCategoryStyle));
}
public PreferenceCategory(Context context) {
@@ -69,4 +73,23 @@
public boolean shouldDisableDependents() {
return !super.isEnabled();
}
+
+ @Override
+ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfoCompat info) {
+ super.onInitializeAccessibilityNodeInfo(info);
+
+ CollectionItemInfoCompat existingItemInfo = info.getCollectionItemInfo();
+ if (existingItemInfo == null) {
+ return;
+ }
+
+ final CollectionItemInfoCompat newItemInfo = CollectionItemInfoCompat.obtain(
+ existingItemInfo.getRowIndex(),
+ existingItemInfo.getRowSpan(),
+ existingItemInfo.getColumnIndex(),
+ existingItemInfo.getColumnSpan(),
+ true /* heading */,
+ existingItemInfo.isSelected());
+ info.setCollectionItemInfo(newItemInfo);
+ }
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
index f96d2ce..9b6fd5a 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceDialogFragmentCompat.java
@@ -19,7 +19,12 @@
import android.app.Dialog;
import android.content.Context;
import android.content.DialogInterface;
+import android.graphics.Bitmap;
+import android.graphics.Canvas;
+import android.graphics.drawable.BitmapDrawable;
+import android.graphics.drawable.Drawable;
import android.os.Bundle;
+import android.support.annotation.LayoutRes;
import android.support.annotation.NonNull;
import android.support.v4.app.DialogFragment;
import android.support.v4.app.Fragment;
@@ -31,13 +36,35 @@
import android.view.WindowManager;
import android.widget.TextView;
+/**
+ * Abstract base class which presents a dialog associated with a
+ * {@link android.support.v7.preference.DialogPreference}. Since the preference object may
+ * not be available during fragment re-creation, the necessary information for displaying the dialog
+ * is read once during the initial call to {@link #onCreate(Bundle)} and saved/restored in the saved
+ * instance state. Custom subclasses should also follow this pattern.
+ */
public abstract class PreferenceDialogFragmentCompat extends DialogFragment implements
DialogInterface.OnClickListener {
protected static final String ARG_KEY = "key";
+ private static final String SAVE_STATE_TITLE = "PreferenceDialogFragment.title";
+ private static final String SAVE_STATE_POSITIVE_TEXT = "PreferenceDialogFragment.positiveText";
+ private static final String SAVE_STATE_NEGATIVE_TEXT = "PreferenceDialogFragment.negativeText";
+ private static final String SAVE_STATE_MESSAGE = "PreferenceDialogFragment.message";
+ private static final String SAVE_STATE_LAYOUT = "PreferenceDialogFragment.layout";
+ private static final String SAVE_STATE_ICON = "PreferenceDialogFragment.icon";
+
private DialogPreference mPreference;
+ private CharSequence mDialogTitle;
+ private CharSequence mPositiveButtonText;
+ private CharSequence mNegativeButtonText;
+ private CharSequence mDialogMessage;
+ private @LayoutRes int mDialogLayoutRes;
+
+ private BitmapDrawable mDialogIcon;
+
/** Which button was clicked. */
private int mWhichButtonClicked;
@@ -55,26 +82,70 @@
(DialogPreference.TargetFragment) rawFragment;
final String key = getArguments().getString(ARG_KEY);
- mPreference = (DialogPreference) fragment.findPreference(key);
+ if (savedInstanceState == null) {
+ mPreference = (DialogPreference) fragment.findPreference(key);
+ mDialogTitle = mPreference.getDialogTitle();
+ mPositiveButtonText = mPreference.getPositiveButtonText();
+ mNegativeButtonText = mPreference.getNegativeButtonText();
+ mDialogMessage = mPreference.getDialogMessage();
+ mDialogLayoutRes = mPreference.getDialogLayoutResource();
+
+ final Drawable icon = mPreference.getDialogIcon();
+ if (icon == null || icon instanceof BitmapDrawable) {
+ mDialogIcon = (BitmapDrawable) icon;
+ } else {
+ final Bitmap bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
+ icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ icon.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ icon.draw(canvas);
+ mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+ }
+ } else {
+ mDialogTitle = savedInstanceState.getCharSequence(SAVE_STATE_TITLE);
+ mPositiveButtonText = savedInstanceState.getCharSequence(SAVE_STATE_POSITIVE_TEXT);
+ mNegativeButtonText = savedInstanceState.getCharSequence(SAVE_STATE_NEGATIVE_TEXT);
+ mDialogMessage = savedInstanceState.getCharSequence(SAVE_STATE_MESSAGE);
+ mDialogLayoutRes = savedInstanceState.getInt(SAVE_STATE_LAYOUT, 0);
+ final Bitmap bitmap = savedInstanceState.getParcelable(SAVE_STATE_ICON);
+ if (bitmap != null) {
+ mDialogIcon = new BitmapDrawable(getResources(), bitmap);
+ }
+ }
}
@Override
- public @NonNull Dialog onCreateDialog(Bundle savedInstanceState) {
+ public void onSaveInstanceState(@NonNull Bundle outState) {
+ super.onSaveInstanceState(outState);
+
+ outState.putCharSequence(SAVE_STATE_TITLE, mDialogTitle);
+ outState.putCharSequence(SAVE_STATE_POSITIVE_TEXT, mPositiveButtonText);
+ outState.putCharSequence(SAVE_STATE_NEGATIVE_TEXT, mNegativeButtonText);
+ outState.putCharSequence(SAVE_STATE_MESSAGE, mDialogMessage);
+ outState.putInt(SAVE_STATE_LAYOUT, mDialogLayoutRes);
+ if (mDialogIcon != null) {
+ outState.putParcelable(SAVE_STATE_ICON, mDialogIcon.getBitmap());
+ }
+ }
+
+ @Override
+ public @NonNull
+ Dialog onCreateDialog(Bundle savedInstanceState) {
final Context context = getActivity();
mWhichButtonClicked = DialogInterface.BUTTON_NEGATIVE;
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
- .setTitle(mPreference.getDialogTitle())
- .setIcon(mPreference.getDialogIcon())
- .setPositiveButton(mPreference.getPositiveButtonText(), this)
- .setNegativeButton(mPreference.getNegativeButtonText(), this);
+ .setTitle(mDialogTitle)
+ .setIcon(mDialogIcon)
+ .setPositiveButton(mPositiveButtonText, this)
+ .setNegativeButton(mNegativeButtonText, this);
View contentView = onCreateDialogView(context);
if (contentView != null) {
onBindDialogView(contentView);
builder.setView(contentView);
} else {
- builder.setMessage(mPreference.getDialogMessage());
+ builder.setMessage(mDialogMessage);
}
onPrepareDialogBuilder(builder);
@@ -85,18 +156,23 @@
requestInputMethod(dialog);
}
-
- return builder.create();
+ return dialog;
}
/**
* Get the preference that requested this dialog. Available after {@link #onCreate(Bundle)} has
- * been called.
+ * been called on the {@link PreferenceFragmentCompat} which launched this dialog.
*
* @return The {@link DialogPreference} associated with this
* dialog.
*/
public DialogPreference getPreference() {
+ if (mPreference == null) {
+ final String key = getArguments().getString(ARG_KEY);
+ final DialogPreference.TargetFragment fragment =
+ (DialogPreference.TargetFragment) getTargetFragment();
+ mPreference = (DialogPreference) fragment.findPreference(key);
+ }
return mPreference;
}
@@ -136,7 +212,7 @@
* @see DialogPreference#setLayoutResource(int)
*/
protected View onCreateDialogView(Context context) {
- final int resId = mPreference.getDialogLayoutResource();
+ final int resId = mDialogLayoutRes;
if (resId == 0) {
return null;
}
@@ -156,7 +232,7 @@
View dialogMessageView = view.findViewById(android.R.id.message);
if (dialogMessageView != null) {
- final CharSequence message = mPreference.getDialogMessage();
+ final CharSequence message = mDialogMessage;
int newVisibility = View.GONE;
if (!TextUtils.isEmpty(message)) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
index 221d3ea..cf58fc6 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -148,6 +148,8 @@
}
};
+ private Runnable mSelectPreferenceRunnable;
+
/**
* Interface that PreferenceFragment's containing activity should
* implement to be able to process preference items that wish to
@@ -242,7 +244,7 @@
final Drawable divider = a.getDrawable(
R.styleable.PreferenceFragmentCompat_android_divider);
- final int dividerHeight = a.getInt(
+ final int dividerHeight = a.getDimensionPixelSize(
R.styleable.PreferenceFragmentCompat_android_dividerHeight, -1);
a.recycle();
@@ -257,10 +259,10 @@
final View view = themedInflater.inflate(mLayoutResId, container, false);
- final View rawListContainer = view.findViewById(R.id.list_container);
+ final View rawListContainer = view.findViewById(AndroidResources.ANDROID_R_LIST_CONTAINER);
if (!(rawListContainer instanceof ViewGroup)) {
- throw new RuntimeException("Content has view with id attribute 'R.id.list_container' "
- + "that is not a ViewGroup class");
+ throw new RuntimeException("Content has view with id attribute "
+ + "'android.R.id.list_container' that is not a ViewGroup class");
}
final ViewGroup listContainer = (ViewGroup) rawListContainer;
@@ -281,6 +283,7 @@
listContainer.addView(mList);
mHandler.post(mRequestFocus);
+
return view;
}
@@ -309,14 +312,23 @@
}
@Override
- public void onActivityCreated(Bundle savedInstanceState) {
- super.onActivityCreated(savedInstanceState);
+ public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
+ super.onViewCreated(view, savedInstanceState);
if (mHavePrefs) {
bindPreferences();
+ if (mSelectPreferenceRunnable != null) {
+ mSelectPreferenceRunnable.run();
+ mSelectPreferenceRunnable = null;
+ }
}
mInitDone = true;
+ }
+
+ @Override
+ public void onActivityCreated(Bundle savedInstanceState) {
+ super.onActivityCreated(savedInstanceState);
if (savedInstanceState != null) {
Bundle container = savedInstanceState.getBundle(PREFERENCES_TAG);
@@ -345,9 +357,12 @@
@Override
public void onDestroyView() {
- mList = null;
mHandler.removeCallbacks(mRequestFocus);
mHandler.removeMessages(MSG_BIND_PREFERENCES);
+ if (mHavePrefs) {
+ unbindPreferences();
+ }
+ mList = null;
super.onDestroyView();
}
@@ -514,6 +529,14 @@
onBindPreferences();
}
+ private void unbindPreferences() {
+ final PreferenceScreen preferenceScreen = getPreferenceScreen();
+ if (preferenceScreen != null) {
+ preferenceScreen.onDetached();
+ }
+ onUnbindPreferences();
+ }
+
/** @hide */
protected void onBindPreferences() {
}
@@ -545,6 +568,8 @@
.inflate(R.layout.preference_recyclerview, parent, false);
recyclerView.setLayoutManager(onCreateLayoutManager());
+ recyclerView.setAccessibilityDelegateCompat(
+ new PreferenceRecyclerViewAccessibilityDelegate(recyclerView));
return recyclerView;
}
@@ -620,6 +645,113 @@
return null;
}
+ public void scrollToPreference(final String key) {
+ scrollToPreferenceInternal(null, key);
+ }
+
+ public void scrollToPreference(final Preference preference) {
+ scrollToPreferenceInternal(preference, null);
+ }
+
+ private void scrollToPreferenceInternal(final Preference preference, final String key) {
+ final Runnable r = new Runnable() {
+ @Override
+ public void run() {
+ final RecyclerView.Adapter adapter = mList.getAdapter();
+ if (!(adapter instanceof
+ PreferenceGroup.PreferencePositionCallback)) {
+ if (adapter != null) {
+ throw new IllegalStateException("Adapter must implement "
+ + "PreferencePositionCallback");
+ } else {
+ // Adapter was set to null, so don't scroll I guess?
+ return;
+ }
+ }
+ final int position;
+ if (preference != null) {
+ position = ((PreferenceGroup.PreferencePositionCallback) adapter)
+ .getPreferenceAdapterPosition(preference);
+ } else {
+ position = ((PreferenceGroup.PreferencePositionCallback) adapter)
+ .getPreferenceAdapterPosition(key);
+ }
+ if (position != RecyclerView.NO_POSITION) {
+ mList.scrollToPosition(position);
+ } else {
+ // Item not found, wait for an update and try again
+ adapter.registerAdapterDataObserver(
+ new ScrollToPreferenceObserver(adapter, mList, preference, key));
+ }
+ }
+ };
+ if (mList == null) {
+ mSelectPreferenceRunnable = r;
+ } else {
+ r.run();
+ }
+ }
+
+ private static class ScrollToPreferenceObserver extends RecyclerView.AdapterDataObserver {
+ private final RecyclerView.Adapter mAdapter;
+ private final RecyclerView mList;
+ private final Preference mPreference;
+ private final String mKey;
+
+ public ScrollToPreferenceObserver(RecyclerView.Adapter adapter, RecyclerView list,
+ Preference preference, String key) {
+ mAdapter = adapter;
+ mList = list;
+ mPreference = preference;
+ mKey = key;
+ }
+
+ private void scrollToPreference() {
+ mAdapter.unregisterAdapterDataObserver(this);
+ final int position;
+ if (mPreference != null) {
+ position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
+ .getPreferenceAdapterPosition(mPreference);
+ } else {
+ position = ((PreferenceGroup.PreferencePositionCallback) mAdapter)
+ .getPreferenceAdapterPosition(mKey);
+ }
+ if (position != RecyclerView.NO_POSITION) {
+ mList.scrollToPosition(position);
+ }
+ }
+
+ @Override
+ public void onChanged() {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeInserted(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeRemoved(int positionStart, int itemCount) {
+ scrollToPreference();
+ }
+
+ @Override
+ public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+ scrollToPreference();
+ }
+ }
+
private class DividerDecoration extends RecyclerView.ItemDecoration {
private Drawable mDivider;
@@ -634,11 +766,6 @@
final int width = parent.getWidth();
for (int childViewIndex = 0; childViewIndex < childCount; childViewIndex++) {
final View view = parent.getChildAt(childViewIndex);
- if (shouldDrawDividerAbove(view, parent)) {
- int top = (int) ViewCompat.getY(view);
- mDivider.setBounds(0, top, width, top + mDividerHeight);
- mDivider.draw(c);
- }
if (shouldDrawDividerBelow(view, parent)) {
int top = (int) ViewCompat.getY(view) + view.getHeight();
mDivider.setBounds(0, top, width, top + mDividerHeight);
@@ -650,32 +777,27 @@
@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
RecyclerView.State state) {
- if (shouldDrawDividerAbove(view, parent)) {
- outRect.top = mDividerHeight;
- }
if (shouldDrawDividerBelow(view, parent)) {
outRect.bottom = mDividerHeight;
}
}
- private boolean shouldDrawDividerAbove(View view, RecyclerView parent) {
- final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
- return holder.getAdapterPosition() == 0 &&
- ((PreferenceViewHolder) holder).isDividerAllowedAbove();
- }
-
private boolean shouldDrawDividerBelow(View view, RecyclerView parent) {
- final PreferenceViewHolder holder =
- (PreferenceViewHolder) parent.getChildViewHolder(view);
+ final RecyclerView.ViewHolder holder = parent.getChildViewHolder(view);
+ final boolean dividerAllowedBelow = holder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) holder).isDividerAllowedBelow();
+ if (!dividerAllowedBelow) {
+ return false;
+ }
boolean nextAllowed = true;
int index = parent.indexOfChild(view);
if (index < parent.getChildCount() - 1) {
final View nextView = parent.getChildAt(index + 1);
- final PreferenceViewHolder nextHolder =
- (PreferenceViewHolder) parent.getChildViewHolder(nextView);
- nextAllowed = nextHolder.isDividerAllowedAbove();
+ final RecyclerView.ViewHolder nextHolder = parent.getChildViewHolder(nextView);
+ nextAllowed = nextHolder instanceof PreferenceViewHolder
+ && ((PreferenceViewHolder) nextHolder).isDividerAllowedAbove();
}
- return nextAllowed && holder.isDividerAllowedBelow();
+ return nextAllowed;
}
public void setDivider(Drawable divider) {
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
index 968b777..4768e01 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
@@ -19,7 +19,9 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v4.util.SimpleArrayMap;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -39,7 +41,7 @@
* guide.</p>
* </div>
*
- * @attr ref android.R.styleable#PreferenceGroup_orderingFromXml
+ * @attr name android:orderingFromXml
*/
public abstract class PreferenceGroup extends Preference {
/**
@@ -55,6 +57,17 @@
private boolean mAttachedToHierarchy = false;
+ private final SimpleArrayMap<String, Long> mIdRecycleCache = new SimpleArrayMap<>();
+ private final Handler mHandler = new Handler();
+ private final Runnable mClearRecycleCacheRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (this) {
+ mIdRecycleCache.clear();
+ }
+ }
+ };
+
public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
@@ -166,7 +179,16 @@
mPreferenceList.add(insertionIndex, preference);
}
- preference.onAttachedToHierarchy(getPreferenceManager());
+ final PreferenceManager preferenceManager = getPreferenceManager();
+ final String key = preference.getKey();
+ final long id;
+ if (key != null && mIdRecycleCache.containsKey(key)) {
+ id = mIdRecycleCache.get(key);
+ mIdRecycleCache.remove(key);
+ } else {
+ id = preferenceManager.getNextId();
+ }
+ preference.onAttachedToHierarchy(preferenceManager, id);
if (mAttachedToHierarchy) {
preference.onAttached();
@@ -192,7 +214,31 @@
private boolean removePreferenceInt(Preference preference) {
synchronized(this) {
preference.onPrepareForRemoval();
- return mPreferenceList.remove(preference);
+ boolean success = mPreferenceList.remove(preference);
+ if (success) {
+ // If this preference, or another preference with the same key, gets re-added
+ // immediately, we want it to have the same id so that it can be correctly tracked
+ // in the adapter by RecyclerView, to make it appear as if it has only been
+ // seamlessly updated. If the preference is not re-added by the time the handler
+ // runs, we take that as a signal that the preference will not be re-added soon
+ // in which case it does not need to retain the same id.
+
+ // If two (or more) preferences have the same (or null) key and both are removed
+ // and then re-added, only one id will be recycled and the second (and later)
+ // preferences will receive a newly generated id. This use pattern of the preference
+ // API is strongly discouraged.
+ final String key = preference.getKey();
+ if (key != null) {
+ mIdRecycleCache.put(key, preference.getId());
+ mHandler.removeCallbacks(mClearRecycleCacheRunnable);
+ mHandler.post(mClearRecycleCacheRunnable);
+ }
+ if (mAttachedToHierarchy) {
+ preference.onDetached();
+ }
+ }
+
+ return success;
}
}
@@ -269,6 +315,14 @@
return true;
}
+ /**
+ * Returns true if we're between {@link #onAttached()} and {@link #onPrepareForRemoval()}
+ * @hide
+ */
+ public boolean isAttached() {
+ return mAttachedToHierarchy;
+ }
+
@Override
public void onAttached() {
super.onAttached();
@@ -285,11 +339,17 @@
}
@Override
- protected void onPrepareForRemoval() {
- super.onPrepareForRemoval();
+ public void onDetached() {
+ super.onDetached();
// We won't be attached to the activity anymore
mAttachedToHierarchy = false;
+
+ // Dispatch to all contained preferences
+ final int preferenceCount = getPreferenceCount();
+ for (int i = 0; i < preferenceCount; i++) {
+ getPreference(i).onDetached();
+ }
}
@Override
@@ -332,4 +392,30 @@
}
}
+ /**
+ * Interface for PreferenceGroup Adapters to implement so that
+ * {@link android.support.v14.preference.PreferenceFragment#scrollToPreference(String)} and
+ * {@link android.support.v14.preference.PreferenceFragment#scrollToPreference(Preference)} or
+ * {@link PreferenceFragmentCompat#scrollToPreference(String)} and
+ * {@link PreferenceFragmentCompat#scrollToPreference(Preference)}
+ * can determine the correct scroll position to request.
+ */
+ public interface PreferencePositionCallback {
+
+ /**
+ * Return the adapter position of the first {@link Preference} with the specified key
+ * @param key Key of {@link Preference} to find
+ * @return Adapter position of the {@link Preference} or
+ * {@link android.support.v7.widget.RecyclerView#NO_POSITION} if not found
+ */
+ int getPreferenceAdapterPosition(String key);
+
+ /**
+ * Return the adapter position of the specified {@link Preference} object
+ * @param preference {@link Preference} object to find
+ * @return Adapter position of the {@link Preference} or
+ * {@link android.support.v7.widget.RecyclerView#NO_POSITION} if not found
+ */
+ int getPreferenceAdapterPosition(Preference preference);
+ }
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
index c4ccc66..fcdbe65 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -16,13 +16,14 @@
package android.support.v7.preference;
+import android.content.res.TypedArray;
+import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.text.TextUtils;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
@@ -34,7 +35,8 @@
* @hide
*/
public class PreferenceGroupAdapter extends RecyclerView.Adapter<PreferenceViewHolder>
- implements Preference.OnPreferenceChangeInternalListener {
+ implements Preference.OnPreferenceChangeInternalListener,
+ PreferenceGroup.PreferencePositionCallback {
private static final String TAG = "PreferenceGroupAdapter";
@@ -64,8 +66,6 @@
private PreferenceLayout mTempPreferenceLayout = new PreferenceLayout();
- private volatile boolean mIsSyncing = false;
-
private Handler mHandler = new Handler();
private Runnable mSyncRunnable = new Runnable() {
@@ -117,38 +117,31 @@
mPreferenceListInternal = new ArrayList<>();
mPreferenceLayouts = new ArrayList<>();
- setHasStableIds(true);
+ if (mPreferenceGroup instanceof PreferenceScreen) {
+ setHasStableIds(((PreferenceScreen) mPreferenceGroup).shouldUseGeneratedIds());
+ } else {
+ setHasStableIds(true);
+ }
syncMyPreferences();
}
private void syncMyPreferences() {
- synchronized(this) {
- if (mIsSyncing) {
- return;
- }
+ final List<Preference> fullPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
+ flattenPreferenceGroup(fullPreferenceList, mPreferenceGroup);
- mIsSyncing = true;
- }
-
- List<Preference> newPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
- flattenPreferenceGroup(newPreferenceList, mPreferenceGroup);
- mPreferenceListInternal = newPreferenceList;
-
- mPreferenceList = new ArrayList<>(mPreferenceListInternal.size());
+ final List<Preference> visiblePreferenceList = new ArrayList<>(fullPreferenceList.size());
// Copy only the visible preferences to the active list
- for (final Preference preference : mPreferenceListInternal) {
+ for (final Preference preference : fullPreferenceList) {
if (preference.isVisible()) {
- mPreferenceList.add(preference);
+ visiblePreferenceList.add(preference);
}
}
+ mPreferenceList = visiblePreferenceList;
+ mPreferenceListInternal = fullPreferenceList;
+
notifyDataSetChanged();
-
- synchronized(this) {
- mIsSyncing = false;
- notifyAll();
- }
}
private void flattenPreferenceGroup(List<Preference> preferences, PreferenceGroup group) {
@@ -204,12 +197,19 @@
}
public long getItemId(int position) {
- if (position < 0 || position >= getItemCount()) return ListView.INVALID_ROW_ID;
+ if (!hasStableIds()) {
+ return RecyclerView.NO_ID;
+ }
return this.getItem(position).getId();
}
public void onPreferenceChange(Preference preference) {
- notifyDataSetChanged();
+ final int index = mPreferenceList.indexOf(preference);
+ // If we don't find the preference, we don't need to notify anyone
+ if (index != -1) {
+ // Send the pref object as a placeholder to ensure the view holder is recycled in place
+ notifyItemChanged(index, preference);
+ }
}
public void onPreferenceHierarchyChange(Preference preference) {
@@ -271,8 +271,18 @@
public PreferenceViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final PreferenceLayout pl = mPreferenceLayouts.get(viewType);
final LayoutInflater inflater = LayoutInflater.from(parent.getContext());
+ TypedArray a
+ = parent.getContext().obtainStyledAttributes(null, R.styleable.BackgroundStyle);
+ Drawable background
+ = a.getDrawable(R.styleable.BackgroundStyle_android_selectableItemBackground);
+ if (background == null) {
+ background = parent.getContext().getResources()
+ .getDrawable(android.R.drawable.list_selector_background);
+ }
+ a.recycle();
final View view = inflater.inflate(pl.resId, parent, false);
+ view.setBackgroundDrawable(background);
final ViewGroup widgetFrame = (ViewGroup) view.findViewById(android.R.id.widget_frame);
if (widgetFrame != null) {
@@ -291,4 +301,28 @@
final Preference preference = getItem(position);
preference.onBindViewHolder(holder);
}
+
+ @Override
+ public int getPreferenceAdapterPosition(String key) {
+ final int size = mPreferenceList.size();
+ for (int i = 0; i < size; i++) {
+ final Preference candidate = mPreferenceList.get(i);
+ if (TextUtils.equals(key, candidate.getKey())) {
+ return i;
+ }
+ }
+ return RecyclerView.NO_POSITION;
+ }
+
+ @Override
+ public int getPreferenceAdapterPosition(Preference preference) {
+ final int size = mPreferenceList.size();
+ for (int i = 0; i < size; i++) {
+ final Preference canidate = mPreferenceList.get(i);
+ if (canidate != null && canidate.equals(preference)) {
+ return i;
+ }
+ }
+ return RecyclerView.NO_POSITION;
+ }
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceInflater.java b/v7/preference/src/android/support/v7/preference/PreferenceInflater.java
index fa4e29f..30f92c1 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceInflater.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceInflater.java
@@ -231,6 +231,7 @@
for (final String prefix : prefixes) {
try {
clazz = classLoader.loadClass(prefix + name);
+ break;
} catch (final ClassNotFoundException e) {
notFoundException = e;
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceManager.java b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
index 0c3e65f..c99dde2 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceManager.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceManager.java
@@ -18,7 +18,9 @@
import android.content.Context;
import android.content.SharedPreferences;
+import android.support.v4.content.ContextCompat;
import android.support.v4.content.SharedPreferencesCompat;
+import android.support.v4.os.BuildCompat;
/**
* Used to help create {@link Preference} hierarchies
@@ -76,6 +78,11 @@
*/
private int mSharedPreferencesMode;
+ private static final int STORAGE_DEFAULT = 0;
+ private static final int STORAGE_DEVICE_PROTECTED = 1;
+
+ private int mStorage = STORAGE_DEFAULT;
+
/**
* The {@link PreferenceScreen} at the root of the preference hierarchy.
*/
@@ -185,6 +192,83 @@
}
/**
+ * Sets the storage location used internally by this class to be the default
+ * provided by the hosting {@link Context}.
+ */
+ public void setStorageDefault() {
+ if (BuildCompat.isAtLeastN()) {
+ mStorage = STORAGE_DEFAULT;
+ mSharedPreferences = null;
+ }
+ }
+
+ /**
+ * Explicitly set the storage location used internally by this class to be
+ * device-protected storage.
+ * <p>
+ * On devices with direct boot, data stored in this location is encrypted
+ * with a key tied to the physical device, and it can be accessed
+ * immediately after the device has booted successfully, both
+ * <em>before and after</em> the user has authenticated with their
+ * credentials (such as a lock pattern or PIN).
+ * <p>
+ * Because device-protected data is available without user authentication,
+ * you should carefully limit the data you store using this Context. For
+ * example, storing sensitive authentication tokens or passwords in the
+ * device-protected area is strongly discouraged.
+ * <p>
+ * Prior to {@link BuildCompat#isAtLeastN()} this method has no effect,
+ * since device-protected storage is not available.
+ *
+ * @see Context#createDeviceProtectedStorageContext()
+ */
+ public void setStorageDeviceProtected() {
+ if (BuildCompat.isAtLeastN()) {
+ mStorage = STORAGE_DEVICE_PROTECTED;
+ mSharedPreferences = null;
+ }
+ }
+
+ /**
+ * @removed
+ * @deprecated
+ */
+ @Deprecated
+ public void setStorageDeviceEncrypted() {
+ setStorageDeviceProtected();
+ }
+
+ /**
+ * Indicates if the storage location used internally by this class is the
+ * default provided by the hosting {@link Context}.
+ *
+ * @see #setStorageDefault()
+ * @see #setStorageDeviceProtected()
+ */
+ public boolean isStorageDefault() {
+ if (BuildCompat.isAtLeastN()) {
+ return mStorage == STORAGE_DEFAULT;
+ } else {
+ return true;
+ }
+ }
+
+ /**
+ * Indicates if the storage location used internally by this class is backed
+ * by device-protected storage.
+ *
+ * @see #setStorageDefault()
+ * @see #setStorageDeviceProtected()
+ */
+ public boolean isStorageDeviceProtected() {
+ if (BuildCompat.isAtLeastN()) {
+ return mStorage == STORAGE_DEVICE_PROTECTED;
+ } else {
+ return false;
+ }
+ }
+
+ /**
* Gets a SharedPreferences instance that preferences managed by this will
* use.
*
@@ -193,7 +277,17 @@
*/
public SharedPreferences getSharedPreferences() {
if (mSharedPreferences == null) {
- mSharedPreferences = mContext.getSharedPreferences(mSharedPreferencesName,
+ final Context storageContext;
+ switch (mStorage) {
+ case STORAGE_DEVICE_PROTECTED:
+ storageContext = ContextCompat.createDeviceProtectedStorageContext(mContext);
+ break;
+ default:
+ storageContext = mContext;
+ break;
+ }
+
+ mSharedPreferences = storageContext.getSharedPreferences(mSharedPreferencesName,
mSharedPreferencesMode);
}
@@ -238,6 +332,9 @@
*/
public boolean setPreferences(PreferenceScreen preferenceScreen) {
if (preferenceScreen != mPreferenceScreen) {
+ if (mPreferenceScreen != null) {
+ mPreferenceScreen.onDetached();
+ }
mPreferenceScreen = preferenceScreen;
return true;
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceRecyclerViewAccessibilityDelegate.java b/v7/preference/src/android/support/v7/preference/PreferenceRecyclerViewAccessibilityDelegate.java
new file mode 100644
index 0000000..032fbf9
--- /dev/null
+++ b/v7/preference/src/android/support/v7/preference/PreferenceRecyclerViewAccessibilityDelegate.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License
+ */
+
+package android.support.v7.preference;
+
+import android.os.Bundle;
+import android.support.v4.view.AccessibilityDelegateCompat;
+import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
+import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerViewAccessibilityDelegate;
+import android.view.View;
+import android.view.accessibility.AccessibilityEvent;
+
+/**
+ * The AccessibilityDelegate used by the RecyclerView that displays Views for Preferences.
+ *
+ * @hide
+ */
+public class PreferenceRecyclerViewAccessibilityDelegate
+ extends RecyclerViewAccessibilityDelegate {
+ final RecyclerView mRecyclerView;
+ final AccessibilityDelegateCompat mDefaultItemDelegate = super.getItemDelegate();
+
+ public PreferenceRecyclerViewAccessibilityDelegate(RecyclerView recyclerView) {
+ super(recyclerView);
+ mRecyclerView = recyclerView;
+ }
+
+ @Override
+ public AccessibilityDelegateCompat getItemDelegate() {
+ return mItemDelegate;
+ }
+
+ final AccessibilityDelegateCompat mItemDelegate = new AccessibilityDelegateCompat() {
+ @Override
+ public void onInitializeAccessibilityNodeInfo(View host, AccessibilityNodeInfoCompat info) {
+ mDefaultItemDelegate.onInitializeAccessibilityNodeInfo(host, info);
+ int position = mRecyclerView.getChildAdapterPosition(host);
+
+ RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+ if (!(adapter instanceof PreferenceGroupAdapter)) {
+ return;
+ }
+
+ PreferenceGroupAdapter preferenceGroupAdapter = (PreferenceGroupAdapter) adapter;
+ Preference preference = preferenceGroupAdapter.getItem(position);
+ if (preference == null) {
+ return;
+ }
+
+ preference.onInitializeAccessibilityNodeInfo(info);
+ }
+
+ @Override
+ public boolean performAccessibilityAction(View host, int action, Bundle args) {
+ // Must forward actions since the default delegate will handle actions.
+ return mDefaultItemDelegate.performAccessibilityAction(host, action, args);
+ }
+ };
+}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
index e64ebcc..cbabf21 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
@@ -17,6 +17,7 @@
package android.support.v7.preference;
import android.content.Context;
+import android.support.v4.content.res.TypedArrayUtils;
import android.util.AttributeSet;
/**
@@ -74,12 +75,15 @@
*/
public final class PreferenceScreen extends PreferenceGroup {
+ private boolean mShouldUseGeneratedIds = true;
+
/**
* Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
* @hide-
*/
public PreferenceScreen(Context context, AttributeSet attrs) {
- super(context, attrs, R.attr.preferenceScreenStyle);
+ super(context, attrs, TypedArrayUtils.getAttr(context, R.attr.preferenceScreenStyle,
+ android.R.attr.preferenceScreenStyle));
}
@Override
@@ -99,4 +103,34 @@
return false;
}
+ /**
+ * See {@link #setShouldUseGeneratedIds(boolean)}
+ * @return {@code true} if the adapter should use the preference IDs generated by
+ * {@link PreferenceGroup#addPreference(Preference)} as stable item IDs
+ */
+ public boolean shouldUseGeneratedIds() {
+ return mShouldUseGeneratedIds;
+ }
+
+ /**
+ * Set whether the adapter created for this screen should attempt to use the preference IDs
+ * generated by {@link PreferenceGroup#addPreference(Preference)} as stable item IDs. Setting
+ * this to false can suppress unwanted animations if {@link Preference} objects are frequently
+ * removed from and re-added to their containing {@link PreferenceGroup}.
+ * <p>
+ * This method may only be called when the preference screen is not attached to the hierarchy.
+ * <p>
+ * Default value is {@code true}.
+ *
+ * @param shouldUseGeneratedIds {@code true} if the adapter should use the preference ID as a
+ * stable ID, or {@code false} to disable the use of
+ * stable IDs
+ */
+ public void setShouldUseGeneratedIds(boolean shouldUseGeneratedIds) {
+ if (isAttached()) {
+ throw new IllegalStateException("Cannot change the usage of generated IDs while" +
+ " attached to the preference hierarchy");
+ }
+ mShouldUseGeneratedIds = shouldUseGeneratedIds;
+ }
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
index f9eaf22..97a907a 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceViewHolder.java
@@ -39,6 +39,8 @@
mCachedViews.put(android.R.id.summary, itemView.findViewById(android.R.id.summary));
mCachedViews.put(android.R.id.icon, itemView.findViewById(android.R.id.icon));
mCachedViews.put(R.id.icon_frame, itemView.findViewById(R.id.icon_frame));
+ mCachedViews.put(AndroidResources.ANDROID_R_ICON_FRAME,
+ itemView.findViewById(AndroidResources.ANDROID_R_ICON_FRAME));
}
/**
@@ -75,6 +77,10 @@
* Dividers are only drawn between items if both items allow it, or above the first and below
* the last item if that item allows it.
*
+ * By default, {@link Preference#onBindViewHolder(PreferenceViewHolder)} will set this to the
+ * same value as returned by {@link Preference#isSelectable()}, so that non-selectable items
+ * do not have a divider drawn above them.
+ *
* @param allowed false to prevent dividers being drawn above this item
*/
public void setDividerAllowedAbove(boolean allowed) {
@@ -95,6 +101,10 @@
* Dividers are only drawn between items if both items allow it, or above the first and below
* the last item if that item allows it.
*
+ * By default, {@link Preference#onBindViewHolder(PreferenceViewHolder)} will set this to the
+ * same value as returned by {@link Preference#isSelectable()}, so that non-selectable items
+ * do not have a divider drawn below them.
+ *
* @param allowed false to prevent dividers being drawn below this item
*/
public void setDividerAllowedBelow(boolean allowed) {
diff --git a/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java b/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
index 295d835..ce41a25 100644
--- a/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
+++ b/v7/preference/src/android/support/v7/preference/SwitchPreferenceCompat.java
@@ -31,11 +31,11 @@
* <p>
* This preference will store a boolean into the SharedPreferences.
*
-* @attr ref android.R.styleable#SwitchPreference_summaryOff
-* @attr ref android.R.styleable#SwitchPreference_summaryOn
-* @attr ref android.R.styleable#SwitchPreference_switchTextOff
-* @attr ref android.R.styleable#SwitchPreference_switchTextOn
-* @attr ref android.R.styleable#SwitchPreference_disableDependentsState
+* @attr name android:summaryOff
+* @attr name android:summaryOn
+* @attr name android:switchTextOff
+* @attr name android:switchTextOn
+* @attr name android:disableDependentsState
*/
public class SwitchPreferenceCompat extends TwoStatePreference {
private final Listener mListener = new Listener();
diff --git a/v7/recyclerview/Android.mk b/v7/recyclerview/Android.mk
index a4c856f..021296e 100644
--- a/v7/recyclerview/Android.mk
+++ b/v7/recyclerview/Android.mk
@@ -14,34 +14,26 @@
LOCAL_PATH := $(call my-dir)
-# # Build the resources using the current SDK version.
-# # We do this here because the final static library must be compiled with an older
-# # SDK version than the resources. The resources library and the R class that it
-# # contains will not be linked into the final static library.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v7-recyclerview-res
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
-LOCAL_JAR_EXCLUDE_FILES := none
-LOCAL_JAVA_LANGUAGE_VERSION := 1.7
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
# Here is the final static library that apps can link against.
-# The R class is automatically excluded from the generated library.
-# Applications that use this library must specify LOCAL_RESOURCE_DIR
-# in their makefiles to include the resources in their package.
+# Applications that use this library must specify
+#
+# LOCAL_STATIC_ANDROID_LIBRARIES := \
+# android-support-v7-recycler-view \
+# android-support-v4 \
+# android-support-annotations
+#
+# in their makefiles to include the resources and their dependencies in their package.
include $(CLEAR_VARS)
+LOCAL_USE_AAPT2 := true
LOCAL_MODULE := android-support-v7-recyclerview
-LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
LOCAL_SDK_VERSION := 7
+LOCAL_SDK_RES_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under,src)
-LOCAL_JAVA_LIBRARIES := \
- android-support-v4 \
- android-support-annotations \
- android-support-v7-recyclerview-res
+LOCAL_RESOURCE_DIR := $(LOCAL_PATH)/res
+LOCAL_SHARED_ANDROID_LIBRARIES := \
+ android-support-v4 \
+ android-support-annotations
+LOCAL_JAR_EXCLUDE_FILES := none
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
diff --git a/v7/recyclerview/api/23.1.1.txt b/v7/recyclerview/api/23.1.1.txt
new file mode 100644
index 0000000..1f91036
--- /dev/null
+++ b/v7/recyclerview/api/23.1.1.txt
@@ -0,0 +1,864 @@
+package android.support.v7.recyclerview {
+
+ public final class R {
+ ctor public R();
+ }
+
+ public static final class R.attr {
+ ctor public R.attr();
+ field public static int layoutManager;
+ field public static int reverseLayout;
+ field public static int spanCount;
+ field public static int stackFromEnd;
+ }
+
+ public static final class R.dimen {
+ ctor public R.dimen();
+ field public static int item_touch_helper_max_drag_scroll_per_frame;
+ }
+
+ public static final class R.id {
+ ctor public R.id();
+ field public static int item_touch_helper_previous_elevation;
+ }
+
+ public static final class R.styleable {
+ ctor public R.styleable();
+ field public static final int[] RecyclerView;
+ field public static int RecyclerView_android_orientation;
+ field public static int RecyclerView_layoutManager;
+ field public static int RecyclerView_reverseLayout;
+ field public static int RecyclerView_spanCount;
+ field public static int RecyclerView_stackFromEnd;
+ }
+
+}
+
+package android.support.v7.util {
+
+ public class AsyncListUtil {
+ ctor public AsyncListUtil(java.lang.Class<T>, int, android.support.v7.util.AsyncListUtil.DataCallback<T>, android.support.v7.util.AsyncListUtil.ViewCallback);
+ method public T getItem(int);
+ method public int getItemCount();
+ method public void onRangeChanged();
+ method public void refresh();
+ }
+
+ public static abstract class AsyncListUtil.DataCallback {
+ ctor public AsyncListUtil.DataCallback();
+ method public abstract void fillData(T[], int, int);
+ method public int getMaxCachedTiles();
+ method public void recycleData(T[], int);
+ method public abstract int refreshData();
+ }
+
+ public static abstract class AsyncListUtil.ViewCallback {
+ ctor public AsyncListUtil.ViewCallback();
+ method public void extendRangeInto(int[], int[], int);
+ method public abstract void getItemRangeInto(int[]);
+ method public abstract void onDataRefresh();
+ method public abstract void onItemLoaded(int);
+ field public static final int HINT_SCROLL_ASC = 2; // 0x2
+ field public static final int HINT_SCROLL_DESC = 1; // 0x1
+ field public static final int HINT_SCROLL_NONE = 0; // 0x0
+ }
+
+ public class SortedList {
+ ctor public SortedList(java.lang.Class<T>, android.support.v7.util.SortedList.Callback<T>);
+ ctor public SortedList(java.lang.Class<T>, android.support.v7.util.SortedList.Callback<T>, int);
+ method public int add(T);
+ method public void addAll(T[], boolean);
+ method public void addAll(T...);
+ method public void addAll(java.util.Collection<T>);
+ method public void beginBatchedUpdates();
+ method public void clear();
+ method public void endBatchedUpdates();
+ method public T get(int) throws java.lang.IndexOutOfBoundsException;
+ method public int indexOf(T);
+ method public void recalculatePositionOfItemAt(int);
+ method public boolean remove(T);
+ method public T removeItemAt(int);
+ method public int size();
+ method public void updateItemAt(int, T);
+ field public static final int INVALID_POSITION = -1; // 0xffffffff
+ }
+
+ public static class SortedList.BatchedCallback extends android.support.v7.util.SortedList.Callback {
+ ctor public SortedList.BatchedCallback(android.support.v7.util.SortedList.Callback<T2>);
+ method public boolean areContentsTheSame(T2, T2);
+ method public boolean areItemsTheSame(T2, T2);
+ method public int compare(T2, T2);
+ method public void dispatchLastEvent();
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+ public static abstract class SortedList.Callback implements java.util.Comparator {
+ ctor public SortedList.Callback();
+ method public abstract boolean areContentsTheSame(T2, T2);
+ method public abstract boolean areItemsTheSame(T2, T2);
+ method public abstract int compare(T2, T2);
+ method public abstract void onChanged(int, int);
+ method public abstract void onInserted(int, int);
+ method public abstract void onMoved(int, int);
+ method public abstract void onRemoved(int, int);
+ }
+
+}
+
+package android.support.v7.widget {
+
+ public class DefaultItemAnimator extends android.support.v7.widget.SimpleItemAnimator {
+ ctor public DefaultItemAnimator();
+ method public boolean animateAdd(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
+ method public boolean animateMove(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
+ method public boolean animateRemove(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void endAnimation(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void endAnimations();
+ method public boolean isRunning();
+ method public void runPendingAnimations();
+ }
+
+ public class GridLayoutManager extends android.support.v7.widget.LinearLayoutManager {
+ ctor public GridLayoutManager(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public GridLayoutManager(android.content.Context, int);
+ ctor public GridLayoutManager(android.content.Context, int, int, boolean);
+ method public int getSpanCount();
+ method public android.support.v7.widget.GridLayoutManager.SpanSizeLookup getSpanSizeLookup();
+ method public void setSpanCount(int);
+ method public void setSpanSizeLookup(android.support.v7.widget.GridLayoutManager.SpanSizeLookup);
+ field public static final int DEFAULT_SPAN_COUNT = -1; // 0xffffffff
+ }
+
+ public static final class GridLayoutManager.DefaultSpanSizeLookup extends android.support.v7.widget.GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.DefaultSpanSizeLookup();
+ method public int getSpanSize(int);
+ }
+
+ public static class GridLayoutManager.LayoutParams extends android.support.v7.widget.RecyclerView.LayoutParams {
+ ctor public GridLayoutManager.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public GridLayoutManager.LayoutParams(int, int);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public GridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public GridLayoutManager.LayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+ method public int getSpanIndex();
+ method public int getSpanSize();
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+ public static abstract class GridLayoutManager.SpanSizeLookup {
+ ctor public GridLayoutManager.SpanSizeLookup();
+ method public int getSpanGroupIndex(int, int);
+ method public int getSpanIndex(int, int);
+ method public abstract int getSpanSize(int);
+ method public void invalidateSpanIndexCache();
+ method public boolean isSpanIndexCacheEnabled();
+ method public void setSpanIndexCacheEnabled(boolean);
+ }
+
+ public class LinearLayoutManager extends android.support.v7.widget.RecyclerView.LayoutManager implements android.support.v7.widget.helper.ItemTouchHelper.ViewDropHandler {
+ ctor public LinearLayoutManager(android.content.Context);
+ ctor public LinearLayoutManager(android.content.Context, int, boolean);
+ ctor public LinearLayoutManager(android.content.Context, android.util.AttributeSet, int, int);
+ method public android.graphics.PointF computeScrollVectorForPosition(int);
+ method public int findFirstCompletelyVisibleItemPosition();
+ method public int findFirstVisibleItemPosition();
+ method public int findLastCompletelyVisibleItemPosition();
+ method public int findLastVisibleItemPosition();
+ method public android.support.v7.widget.RecyclerView.LayoutParams generateDefaultLayoutParams();
+ method protected int getExtraLayoutSpace(android.support.v7.widget.RecyclerView.State);
+ method public int getOrientation();
+ method public boolean getRecycleChildrenOnDetach();
+ method public boolean getReverseLayout();
+ method public boolean getStackFromEnd();
+ method protected boolean isLayoutRTL();
+ method public boolean isSmoothScrollbarEnabled();
+ method public void prepareForDrop(android.view.View, android.view.View, int, int);
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setOrientation(int);
+ method public void setRecycleChildrenOnDetach(boolean);
+ method public void setReverseLayout(boolean);
+ method public void setSmoothScrollbarEnabled(boolean);
+ method public void setStackFromEnd(boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_OFFSET = -2147483648; // 0x80000000
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ protected static class LinearLayoutManager.LayoutChunkResult {
+ ctor protected LinearLayoutManager.LayoutChunkResult();
+ field public int mConsumed;
+ field public boolean mFinished;
+ field public boolean mFocusable;
+ field public boolean mIgnoreConsumed;
+ }
+
+ public abstract class LinearSmoothScroller extends android.support.v7.widget.RecyclerView.SmoothScroller {
+ ctor public LinearSmoothScroller(android.content.Context);
+ method public int calculateDtToFit(int, int, int, int, int);
+ method public int calculateDxToMakeVisible(android.view.View, int);
+ method public int calculateDyToMakeVisible(android.view.View, int);
+ method protected float calculateSpeedPerPixel(android.util.DisplayMetrics);
+ method protected int calculateTimeForDeceleration(int);
+ method protected int calculateTimeForScrolling(int);
+ method public abstract android.graphics.PointF computeScrollVectorForPosition(int);
+ method protected int getHorizontalSnapPreference();
+ method protected int getVerticalSnapPreference();
+ method protected void onSeekTargetStep(int, int, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.SmoothScroller.Action);
+ method protected void onStart();
+ method protected void onStop();
+ method protected void onTargetFound(android.view.View, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.SmoothScroller.Action);
+ method protected void updateActionForInterimTarget(android.support.v7.widget.RecyclerView.SmoothScroller.Action);
+ field public static final int SNAP_TO_ANY = 0; // 0x0
+ field public static final int SNAP_TO_END = 1; // 0x1
+ field public static final int SNAP_TO_START = -1; // 0xffffffff
+ field protected final android.view.animation.DecelerateInterpolator mDecelerateInterpolator;
+ field protected int mInterimTargetDx;
+ field protected int mInterimTargetDy;
+ field protected final android.view.animation.LinearInterpolator mLinearInterpolator;
+ field protected android.graphics.PointF mTargetVector;
+ }
+
+ public abstract class OrientationHelper {
+ method public static android.support.v7.widget.OrientationHelper createHorizontalHelper(android.support.v7.widget.RecyclerView.LayoutManager);
+ method public static android.support.v7.widget.OrientationHelper createOrientationHelper(android.support.v7.widget.RecyclerView.LayoutManager, int);
+ method public static android.support.v7.widget.OrientationHelper createVerticalHelper(android.support.v7.widget.RecyclerView.LayoutManager);
+ method public abstract int getDecoratedEnd(android.view.View);
+ method public abstract int getDecoratedMeasurement(android.view.View);
+ method public abstract int getDecoratedMeasurementInOther(android.view.View);
+ method public abstract int getDecoratedStart(android.view.View);
+ method public abstract int getEnd();
+ method public abstract int getEndAfterPadding();
+ method public abstract int getEndPadding();
+ method public abstract int getStartAfterPadding();
+ method public abstract int getTotalSpace();
+ method public int getTotalSpaceChange();
+ method public abstract void offsetChild(android.view.View, int);
+ method public abstract void offsetChildren(int);
+ method public void onLayoutComplete();
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int VERTICAL = 1; // 0x1
+ field protected final android.support.v7.widget.RecyclerView.LayoutManager mLayoutManager;
+ }
+
+ public class RecyclerView extends android.view.ViewGroup {
+ ctor public RecyclerView(android.content.Context);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet);
+ ctor public RecyclerView(android.content.Context, android.util.AttributeSet, int);
+ method public void addItemDecoration(android.support.v7.widget.RecyclerView.ItemDecoration, int);
+ method public void addItemDecoration(android.support.v7.widget.RecyclerView.ItemDecoration);
+ method public void addOnChildAttachStateChangeListener(android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void addOnItemTouchListener(android.support.v7.widget.RecyclerView.OnItemTouchListener);
+ method public void addOnScrollListener(android.support.v7.widget.RecyclerView.OnScrollListener);
+ method public void clearOnChildAttachStateChangeListeners();
+ method public void clearOnScrollListeners();
+ method public int computeHorizontalScrollExtent();
+ method public int computeHorizontalScrollOffset();
+ method public int computeHorizontalScrollRange();
+ method public int computeVerticalScrollExtent();
+ method public int computeVerticalScrollOffset();
+ 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.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);
+ method public deprecated android.support.v7.widget.RecyclerView.ViewHolder findViewHolderForPosition(int);
+ method public boolean fling(int, int);
+ method public android.support.v7.widget.RecyclerView.Adapter getAdapter();
+ method public int getChildAdapterPosition(android.view.View);
+ method public long getChildItemId(android.view.View);
+ method public int getChildLayoutPosition(android.view.View);
+ method public deprecated int getChildPosition(android.view.View);
+ method public android.support.v7.widget.RecyclerView.ViewHolder getChildViewHolder(android.view.View);
+ method public android.support.v7.widget.RecyclerViewAccessibilityDelegate getCompatAccessibilityDelegate();
+ method public android.support.v7.widget.RecyclerView.ItemAnimator getItemAnimator();
+ method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
+ method public int getMaxFlingVelocity();
+ method public int getMinFlingVelocity();
+ method public android.support.v7.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
+ method public int getScrollState();
+ method public boolean hasFixedSize();
+ method public boolean hasPendingAdapterUpdates();
+ method public void invalidateItemDecorations();
+ method public boolean isAnimating();
+ method public boolean isComputingLayout();
+ method public boolean isLayoutFrozen();
+ method public void offsetChildrenHorizontal(int);
+ method public void offsetChildrenVertical(int);
+ method public void onChildAttachedToWindow(android.view.View);
+ method public void onChildDetachedFromWindow(android.view.View);
+ method public void onDraw(android.graphics.Canvas);
+ method protected void onLayout(boolean, int, int, int, int);
+ method public void onScrollStateChanged(int);
+ method public void onScrolled(int, int);
+ method public void removeItemDecoration(android.support.v7.widget.RecyclerView.ItemDecoration);
+ method public void removeOnChildAttachStateChangeListener(android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener);
+ method public void removeOnItemTouchListener(android.support.v7.widget.RecyclerView.OnItemTouchListener);
+ method public void removeOnScrollListener(android.support.v7.widget.RecyclerView.OnScrollListener);
+ method public void scrollToPosition(int);
+ method public void setAccessibilityDelegateCompat(android.support.v7.widget.RecyclerViewAccessibilityDelegate);
+ method public void setAdapter(android.support.v7.widget.RecyclerView.Adapter);
+ method public void setChildDrawingOrderCallback(android.support.v7.widget.RecyclerView.ChildDrawingOrderCallback);
+ method public void setHasFixedSize(boolean);
+ method public void setItemAnimator(android.support.v7.widget.RecyclerView.ItemAnimator);
+ method public void setItemViewCacheSize(int);
+ method public void setLayoutFrozen(boolean);
+ method public void setLayoutManager(android.support.v7.widget.RecyclerView.LayoutManager);
+ method public deprecated void setOnScrollListener(android.support.v7.widget.RecyclerView.OnScrollListener);
+ method public void setRecycledViewPool(android.support.v7.widget.RecyclerView.RecycledViewPool);
+ method public void setRecyclerListener(android.support.v7.widget.RecyclerView.RecyclerListener);
+ method public void setScrollingTouchSlop(int);
+ method public void setViewCacheExtension(android.support.v7.widget.RecyclerView.ViewCacheExtension);
+ method public void smoothScrollBy(int, int);
+ method public void smoothScrollToPosition(int);
+ method public void stopScroll();
+ method public void swapAdapter(android.support.v7.widget.RecyclerView.Adapter, boolean);
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final int INVALID_TYPE = -1; // 0xffffffff
+ field public static final long NO_ID = -1L; // 0xffffffffffffffffL
+ field public static final int NO_POSITION = -1; // 0xffffffff
+ field public static final int SCROLL_STATE_DRAGGING = 1; // 0x1
+ field public static final int SCROLL_STATE_IDLE = 0; // 0x0
+ field public static final int SCROLL_STATE_SETTLING = 2; // 0x2
+ field public static final int TOUCH_SLOP_DEFAULT = 0; // 0x0
+ field public static final int TOUCH_SLOP_PAGING = 1; // 0x1
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static abstract class RecyclerView.Adapter {
+ ctor public RecyclerView.Adapter();
+ method public final void bindViewHolder(VH, int);
+ method public final VH createViewHolder(android.view.ViewGroup, int);
+ method public abstract int getItemCount();
+ method public long getItemId(int);
+ method public int getItemViewType(int);
+ method public final boolean hasObservers();
+ method public final boolean hasStableIds();
+ method public final void notifyDataSetChanged();
+ method public final void notifyItemChanged(int);
+ method public final void notifyItemChanged(int, java.lang.Object);
+ method public final void notifyItemInserted(int);
+ method public final void notifyItemMoved(int, int);
+ method public final void notifyItemRangeChanged(int, int);
+ method public final void notifyItemRangeChanged(int, int, java.lang.Object);
+ method public final void notifyItemRangeInserted(int, int);
+ method public final void notifyItemRangeRemoved(int, int);
+ method public final void notifyItemRemoved(int);
+ method public void onAttachedToRecyclerView(android.support.v7.widget.RecyclerView);
+ method public abstract void onBindViewHolder(VH, int);
+ method public void onBindViewHolder(VH, int, java.util.List<java.lang.Object>);
+ method public abstract VH onCreateViewHolder(android.view.ViewGroup, int);
+ method public void onDetachedFromRecyclerView(android.support.v7.widget.RecyclerView);
+ method public boolean onFailedToRecycleView(VH);
+ method public void onViewAttachedToWindow(VH);
+ method public void onViewDetachedFromWindow(VH);
+ method public void onViewRecycled(VH);
+ method public void registerAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver);
+ method public void setHasStableIds(boolean);
+ method public void unregisterAdapterDataObserver(android.support.v7.widget.RecyclerView.AdapterDataObserver);
+ }
+
+ public static abstract class RecyclerView.AdapterDataObserver {
+ ctor public RecyclerView.AdapterDataObserver();
+ method public void onChanged();
+ method public void onItemRangeChanged(int, int);
+ method public void onItemRangeChanged(int, int, java.lang.Object);
+ method public void onItemRangeInserted(int, int);
+ method public void onItemRangeMoved(int, int, int);
+ method public void onItemRangeRemoved(int, int);
+ }
+
+ public static abstract interface RecyclerView.ChildDrawingOrderCallback {
+ method public abstract int onGetChildDrawingOrder(int, int);
+ }
+
+ public static abstract class RecyclerView.ItemAnimator {
+ ctor public RecyclerView.ItemAnimator();
+ method public abstract boolean animateAppearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animatePersistence(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean canReuseUpdatedViewHolder(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationStarted(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAnimationsFinished();
+ method public abstract void endAnimation(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public abstract void endAnimations();
+ method public long getAddDuration();
+ method public long getChangeDuration();
+ method public long getMoveDuration();
+ method public long getRemoveDuration();
+ method public abstract boolean isRunning();
+ method public final boolean isRunning(android.support.v7.widget.RecyclerView.ItemAnimator.ItemAnimatorFinishedListener);
+ method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo obtainHolderInfo();
+ method public void onAnimationFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onAnimationStarted(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPostLayoutInformation(android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo recordPreLayoutInformation(android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.ViewHolder, int, java.util.List<java.lang.Object>);
+ method public abstract void runPendingAnimations();
+ method public void setAddDuration(long);
+ method public void setChangeDuration(long);
+ method public void setMoveDuration(long);
+ method public void setRemoveDuration(long);
+ field public static final int FLAG_APPEARED_IN_PRE_LAYOUT = 4096; // 0x1000
+ field public static final int FLAG_CHANGED = 2; // 0x2
+ field public static final int FLAG_INVALIDATED = 4; // 0x4
+ field public static final int FLAG_MOVED = 2048; // 0x800
+ field public static final int FLAG_REMOVED = 8; // 0x8
+ }
+
+ public static abstract class RecyclerView.ItemAnimator.AdapterChanges implements java.lang.annotation.Annotation {
+ }
+
+ public static abstract interface RecyclerView.ItemAnimator.ItemAnimatorFinishedListener {
+ method public abstract void onAnimationsFinished();
+ }
+
+ public static class RecyclerView.ItemAnimator.ItemHolderInfo {
+ ctor public RecyclerView.ItemAnimator.ItemHolderInfo();
+ method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo setFrom(android.support.v7.widget.RecyclerView.ViewHolder, int);
+ field public int bottom;
+ field public int changeFlags;
+ field public int left;
+ field public int right;
+ field public int top;
+ }
+
+ public static abstract class RecyclerView.ItemDecoration {
+ ctor public RecyclerView.ItemDecoration();
+ method public deprecated void getItemOffsets(android.graphics.Rect, int, android.support.v7.widget.RecyclerView);
+ method public void getItemOffsets(android.graphics.Rect, android.view.View, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State);
+ method public void onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State);
+ method public deprecated void onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView);
+ method public void onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State);
+ method public deprecated void onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView);
+ }
+
+ public static abstract class RecyclerView.LayoutManager {
+ ctor public RecyclerView.LayoutManager();
+ method public void addDisappearingView(android.view.View);
+ method public void addDisappearingView(android.view.View, int);
+ method public void addView(android.view.View);
+ method public void addView(android.view.View, int);
+ method public void assertInLayoutOrScroll(java.lang.String);
+ method public void assertNotInLayoutOrScroll(java.lang.String);
+ method public void attachView(android.view.View, int, android.support.v7.widget.RecyclerView.LayoutParams);
+ method public void attachView(android.view.View, int);
+ method public void attachView(android.view.View);
+ method public void calculateItemDecorationsForChild(android.view.View, android.graphics.Rect);
+ method public boolean canScrollHorizontally();
+ method public boolean canScrollVertically();
+ method public boolean checkLayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+ method public int computeHorizontalScrollExtent(android.support.v7.widget.RecyclerView.State);
+ method public int computeHorizontalScrollOffset(android.support.v7.widget.RecyclerView.State);
+ method public int computeHorizontalScrollRange(android.support.v7.widget.RecyclerView.State);
+ method public int computeVerticalScrollExtent(android.support.v7.widget.RecyclerView.State);
+ method public int computeVerticalScrollOffset(android.support.v7.widget.RecyclerView.State);
+ method public int computeVerticalScrollRange(android.support.v7.widget.RecyclerView.State);
+ method public void detachAndScrapAttachedViews(android.support.v7.widget.RecyclerView.Recycler);
+ method public void detachAndScrapView(android.view.View, android.support.v7.widget.RecyclerView.Recycler);
+ method public void detachAndScrapViewAt(int, android.support.v7.widget.RecyclerView.Recycler);
+ method public void detachView(android.view.View);
+ method public void detachViewAt(int);
+ method public void endAnimation(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);
+ method public android.support.v7.widget.RecyclerView.LayoutParams generateLayoutParams(android.content.Context, android.util.AttributeSet);
+ method public int getBaseline();
+ method public int getBottomDecorationHeight(android.view.View);
+ method public android.view.View getChildAt(int);
+ method public int getChildCount();
+ method public static int getChildMeasureSpec(int, int, int, boolean);
+ method public boolean getClipToPadding();
+ method public int getColumnCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public int getDecoratedBottom(android.view.View);
+ method public int getDecoratedLeft(android.view.View);
+ method public int getDecoratedMeasuredHeight(android.view.View);
+ method public int getDecoratedMeasuredWidth(android.view.View);
+ method public int getDecoratedRight(android.view.View);
+ method public int getDecoratedTop(android.view.View);
+ method public android.view.View getFocusedChild();
+ method public int getHeight();
+ method public int getItemCount();
+ method public int getItemViewType(android.view.View);
+ method public int getLayoutDirection();
+ method public int getLeftDecorationWidth(android.view.View);
+ method public int getMinimumHeight();
+ method public int getMinimumWidth();
+ method public int getPaddingBottom();
+ method public int getPaddingEnd();
+ method public int getPaddingLeft();
+ method public int getPaddingRight();
+ method public int getPaddingStart();
+ method public int getPaddingTop();
+ method public int getPosition(android.view.View);
+ method public static android.support.v7.widget.RecyclerView.LayoutManager.Properties getProperties(android.content.Context, android.util.AttributeSet, int, int);
+ method public int getRightDecorationWidth(android.view.View);
+ method public int getRowCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public int getSelectionModeForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public int getTopDecorationHeight(android.view.View);
+ method public int getWidth();
+ method public boolean hasFocus();
+ method public void ignoreView(android.view.View);
+ method public boolean isAttachedToWindow();
+ method public boolean isFocused();
+ method public boolean isLayoutHierarchical(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public boolean isSmoothScrolling();
+ method public void layoutDecorated(android.view.View, int, int, int, int);
+ method public void measureChild(android.view.View, int, int);
+ method public void measureChildWithMargins(android.view.View, int, int);
+ method public void moveView(int, int);
+ method public void offsetChildrenHorizontal(int);
+ method public void offsetChildrenVertical(int);
+ method public void onAdapterChanged(android.support.v7.widget.RecyclerView.Adapter, android.support.v7.widget.RecyclerView.Adapter);
+ method public boolean onAddFocusables(android.support.v7.widget.RecyclerView, java.util.ArrayList<android.view.View>, int, int);
+ method public void onAttachedToWindow(android.support.v7.widget.RecyclerView);
+ method public deprecated void onDetachedFromWindow(android.support.v7.widget.RecyclerView);
+ method public void onDetachedFromWindow(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.Recycler);
+ method public android.view.View onFocusSearchFailed(android.view.View, int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void onInitializeAccessibilityEvent(android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityEvent(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, android.view.accessibility.AccessibilityEvent);
+ method public void onInitializeAccessibilityNodeInfo(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public void onInitializeAccessibilityNodeInfoForItem(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, android.view.View, android.support.v4.view.accessibility.AccessibilityNodeInfoCompat);
+ method public android.view.View onInterceptFocusSearch(android.view.View, int);
+ method public void onItemsAdded(android.support.v7.widget.RecyclerView, int, int);
+ method public void onItemsChanged(android.support.v7.widget.RecyclerView);
+ method public void onItemsMoved(android.support.v7.widget.RecyclerView, int, int, int);
+ method public void onItemsRemoved(android.support.v7.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int);
+ method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int, java.lang.Object);
+ method public void onLayoutChildren(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void onMeasure(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, int, int);
+ method public deprecated boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.view.View, android.view.View);
+ method public boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, android.view.View, android.view.View);
+ method public void onRestoreInstanceState(android.os.Parcelable);
+ method public android.os.Parcelable onSaveInstanceState();
+ method public void onScrollStateChanged(int);
+ method public boolean performAccessibilityAction(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, int, android.os.Bundle);
+ method public boolean performAccessibilityActionForItem(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, android.view.View, int, android.os.Bundle);
+ method public void postOnAnimation(java.lang.Runnable);
+ method public void removeAllViews();
+ method public void removeAndRecycleAllViews(android.support.v7.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleView(android.view.View, android.support.v7.widget.RecyclerView.Recycler);
+ method public void removeAndRecycleViewAt(int, android.support.v7.widget.RecyclerView.Recycler);
+ method public boolean removeCallbacks(java.lang.Runnable);
+ method public void removeDetachedView(android.view.View);
+ method public void removeView(android.view.View);
+ method public void removeViewAt(int);
+ method public boolean requestChildRectangleOnScreen(android.support.v7.widget.RecyclerView, android.view.View, android.graphics.Rect, boolean);
+ method public void requestLayout();
+ method public void requestSimpleAnimationsInNextLayout();
+ method public int scrollHorizontallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void scrollToPosition(int);
+ method public int scrollVerticallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void setMeasuredDimension(int, int);
+ method public void smoothScrollToPosition(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, int);
+ method public void startSmoothScroll(android.support.v7.widget.RecyclerView.SmoothScroller);
+ method public void stopIgnoringView(android.view.View);
+ method public boolean supportsPredictiveItemAnimations();
+ }
+
+ public static class RecyclerView.LayoutManager.Properties {
+ ctor public RecyclerView.LayoutManager.Properties();
+ field public int orientation;
+ field public boolean reverseLayout;
+ field public int spanCount;
+ field public boolean stackFromEnd;
+ }
+
+ public static class RecyclerView.LayoutParams extends android.view.ViewGroup.MarginLayoutParams {
+ ctor public RecyclerView.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public RecyclerView.LayoutParams(int, int);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public RecyclerView.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public RecyclerView.LayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+ method public int getViewAdapterPosition();
+ method public int getViewLayoutPosition();
+ method public deprecated int getViewPosition();
+ method public boolean isItemChanged();
+ method public boolean isItemRemoved();
+ method public boolean isViewInvalid();
+ method public boolean viewNeedsUpdate();
+ }
+
+ public static abstract interface RecyclerView.OnChildAttachStateChangeListener {
+ method public abstract void onChildViewAttachedToWindow(android.view.View);
+ method public abstract void onChildViewDetachedFromWindow(android.view.View);
+ }
+
+ public static abstract interface RecyclerView.OnItemTouchListener {
+ method public abstract boolean onInterceptTouchEvent(android.support.v7.widget.RecyclerView, android.view.MotionEvent);
+ method public abstract void onRequestDisallowInterceptTouchEvent(boolean);
+ method public abstract void onTouchEvent(android.support.v7.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public static abstract class RecyclerView.OnScrollListener {
+ ctor public RecyclerView.OnScrollListener();
+ method public void onScrollStateChanged(android.support.v7.widget.RecyclerView, int);
+ method public void onScrolled(android.support.v7.widget.RecyclerView, int, int);
+ }
+
+ public static class RecyclerView.RecycledViewPool {
+ ctor public RecyclerView.RecycledViewPool();
+ method public void clear();
+ method public android.support.v7.widget.RecyclerView.ViewHolder getRecycledView(int);
+ method public void putRecycledView(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void setMaxRecycledViews(int, int);
+ }
+
+ public final class RecyclerView.Recycler {
+ ctor public RecyclerView.Recycler();
+ method public void bindViewToPosition(android.view.View, int);
+ method public void clear();
+ method public int convertPreLayoutPositionToPostLayout(int);
+ method public java.util.List<android.support.v7.widget.RecyclerView.ViewHolder> getScrapList();
+ method public android.view.View getViewForPosition(int);
+ method public void recycleView(android.view.View);
+ method public void setViewCacheSize(int);
+ }
+
+ public static abstract interface RecyclerView.RecyclerListener {
+ method public abstract void onViewRecycled(android.support.v7.widget.RecyclerView.ViewHolder);
+ }
+
+ public static class RecyclerView.SimpleOnItemTouchListener implements android.support.v7.widget.RecyclerView.OnItemTouchListener {
+ ctor public RecyclerView.SimpleOnItemTouchListener();
+ method public boolean onInterceptTouchEvent(android.support.v7.widget.RecyclerView, android.view.MotionEvent);
+ method public void onRequestDisallowInterceptTouchEvent(boolean);
+ method public void onTouchEvent(android.support.v7.widget.RecyclerView, android.view.MotionEvent);
+ }
+
+ public static abstract class RecyclerView.SmoothScroller {
+ ctor public RecyclerView.SmoothScroller();
+ method public android.view.View findViewByPosition(int);
+ method public int getChildCount();
+ method public int getChildPosition(android.view.View);
+ method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
+ method public int getTargetPosition();
+ method public deprecated void instantScrollToPosition(int);
+ method public boolean isPendingInitialRun();
+ method public boolean isRunning();
+ method protected void normalize(android.graphics.PointF);
+ method protected void onChildAttachedToWindow(android.view.View);
+ method protected abstract void onSeekTargetStep(int, int, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.SmoothScroller.Action);
+ method protected abstract void onStart();
+ method protected abstract void onStop();
+ method protected abstract void onTargetFound(android.view.View, android.support.v7.widget.RecyclerView.State, android.support.v7.widget.RecyclerView.SmoothScroller.Action);
+ method public void setTargetPosition(int);
+ method protected final void stop();
+ }
+
+ public static class RecyclerView.SmoothScroller.Action {
+ ctor public RecyclerView.SmoothScroller.Action(int, int);
+ ctor public RecyclerView.SmoothScroller.Action(int, int, int);
+ ctor public RecyclerView.SmoothScroller.Action(int, int, int, android.view.animation.Interpolator);
+ method public int getDuration();
+ method public int getDx();
+ method public int getDy();
+ method public android.view.animation.Interpolator getInterpolator();
+ method public void jumpTo(int);
+ method public void setDuration(int);
+ method public void setDx(int);
+ method public void setDy(int);
+ method public void setInterpolator(android.view.animation.Interpolator);
+ method public void update(int, int, int, android.view.animation.Interpolator);
+ field public static final int UNDEFINED_DURATION = -2147483648; // 0x80000000
+ }
+
+ public static class RecyclerView.State {
+ ctor public RecyclerView.State();
+ method public boolean didStructureChange();
+ method public T get(int);
+ method public int getItemCount();
+ method public int getTargetScrollPosition();
+ method public boolean hasTargetScrollPosition();
+ method public boolean isPreLayout();
+ method public void put(int, java.lang.Object);
+ method public void remove(int);
+ method public boolean willRunPredictiveAnimations();
+ method public boolean willRunSimpleAnimations();
+ }
+
+ public static abstract class RecyclerView.ViewCacheExtension {
+ ctor public RecyclerView.ViewCacheExtension();
+ method public abstract android.view.View getViewForPositionAndType(android.support.v7.widget.RecyclerView.Recycler, int, int);
+ }
+
+ public static abstract class RecyclerView.ViewHolder {
+ ctor public RecyclerView.ViewHolder(android.view.View);
+ method public final int getAdapterPosition();
+ method public final long getItemId();
+ method public final int getItemViewType();
+ method public final int getLayoutPosition();
+ method public final int getOldPosition();
+ method public final deprecated int getPosition();
+ method public final boolean isRecyclable();
+ method public final void setIsRecyclable(boolean);
+ field public final android.view.View itemView;
+ }
+
+ public class RecyclerViewAccessibilityDelegate extends android.support.v4.view.AccessibilityDelegateCompat {
+ ctor public RecyclerViewAccessibilityDelegate(android.support.v7.widget.RecyclerView);
+ }
+
+ public abstract class SimpleItemAnimator extends android.support.v7.widget.RecyclerView.ItemAnimator {
+ ctor public SimpleItemAnimator();
+ method public abstract boolean animateAdd(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public boolean animateAppearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateChange(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
+ method public boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateMove(android.support.v7.widget.RecyclerView.ViewHolder, int, int, int, int);
+ method public boolean animatePersistence(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
+ method public abstract boolean animateRemove(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+ method public final void dispatchChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+ method public final void dispatchMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public final void dispatchRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public boolean getSupportsChangeAnimations();
+ method public void onAddFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onAddStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onChangeFinished(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+ method public void onChangeStarting(android.support.v7.widget.RecyclerView.ViewHolder, boolean);
+ method public void onMoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onMoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onRemoveFinished(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onRemoveStarting(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void setSupportsChangeAnimations(boolean);
+ }
+
+ public class StaggeredGridLayoutManager extends android.support.v7.widget.RecyclerView.LayoutManager {
+ ctor public StaggeredGridLayoutManager(android.content.Context, android.util.AttributeSet, int, int);
+ ctor public StaggeredGridLayoutManager(int, int);
+ method public int[] findFirstCompletelyVisibleItemPositions(int[]);
+ method public int[] findFirstVisibleItemPositions(int[]);
+ method public int[] findLastCompletelyVisibleItemPositions(int[]);
+ method public int[] findLastVisibleItemPositions(int[]);
+ method public android.support.v7.widget.RecyclerView.LayoutParams generateDefaultLayoutParams();
+ method public int getGapStrategy();
+ method public int getOrientation();
+ method public boolean getReverseLayout();
+ method public int getSpanCount();
+ method public void invalidateSpanAssignments();
+ method public void scrollToPositionWithOffset(int, int);
+ method public void setGapStrategy(int);
+ method public void setOrientation(int);
+ method public void setReverseLayout(boolean);
+ method public void setSpanCount(int);
+ field public static final deprecated int GAP_HANDLING_LAZY = 1; // 0x1
+ field public static final int GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS = 2; // 0x2
+ field public static final int GAP_HANDLING_NONE = 0; // 0x0
+ field public static final int HORIZONTAL = 0; // 0x0
+ field public static final java.lang.String TAG = "StaggeredGridLayoutManager";
+ field public static final int VERTICAL = 1; // 0x1
+ }
+
+ public static class StaggeredGridLayoutManager.LayoutParams extends android.support.v7.widget.RecyclerView.LayoutParams {
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.content.Context, android.util.AttributeSet);
+ ctor public StaggeredGridLayoutManager.LayoutParams(int, int);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.MarginLayoutParams);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.view.ViewGroup.LayoutParams);
+ ctor public StaggeredGridLayoutManager.LayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+ method public final int getSpanIndex();
+ method public boolean isFullSpan();
+ method public void setFullSpan(boolean);
+ field public static final int INVALID_SPAN_ID = -1; // 0xffffffff
+ }
+
+}
+
+package android.support.v7.widget.helper {
+
+ public class ItemTouchHelper extends android.support.v7.widget.RecyclerView.ItemDecoration implements android.support.v7.widget.RecyclerView.OnChildAttachStateChangeListener {
+ ctor public ItemTouchHelper(android.support.v7.widget.helper.ItemTouchHelper.Callback);
+ method public void attachToRecyclerView(android.support.v7.widget.RecyclerView);
+ method public void onChildViewAttachedToWindow(android.view.View);
+ method public void onChildViewDetachedFromWindow(android.view.View);
+ method public void startDrag(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void startSwipe(android.support.v7.widget.RecyclerView.ViewHolder);
+ field public static final int ACTION_STATE_DRAG = 2; // 0x2
+ field public static final int ACTION_STATE_IDLE = 0; // 0x0
+ field public static final int ACTION_STATE_SWIPE = 1; // 0x1
+ field public static final int ANIMATION_TYPE_DRAG = 8; // 0x8
+ field public static final int ANIMATION_TYPE_SWIPE_CANCEL = 4; // 0x4
+ field public static final int ANIMATION_TYPE_SWIPE_SUCCESS = 2; // 0x2
+ field public static final int DOWN = 2; // 0x2
+ field public static final int END = 32; // 0x20
+ field public static final int LEFT = 4; // 0x4
+ field public static final int RIGHT = 8; // 0x8
+ field public static final int START = 16; // 0x10
+ field public static final int UP = 1; // 0x1
+ }
+
+ public static abstract class ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.Callback();
+ method public boolean canDropOver(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public android.support.v7.widget.RecyclerView.ViewHolder chooseDropTarget(android.support.v7.widget.RecyclerView.ViewHolder, java.util.List<android.support.v7.widget.RecyclerView.ViewHolder>, int, int);
+ method public void clearView(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public int convertToAbsoluteDirection(int, int);
+ method public static int convertToRelativeDirection(int, int);
+ method public long getAnimationDuration(android.support.v7.widget.RecyclerView, int, float, float);
+ method public int getBoundingBoxMargin();
+ method public static android.support.v7.widget.helper.ItemTouchUIUtil getDefaultUIUtil();
+ method public float getMoveThreshold(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public abstract int getMovementFlags(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public float getSwipeThreshold(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public int interpolateOutOfBoundsScroll(android.support.v7.widget.RecyclerView, int, int, int, long);
+ method public boolean isItemViewSwipeEnabled();
+ method public boolean isLongPressDragEnabled();
+ method public static int makeFlag(int, int);
+ method public static int makeMovementFlags(int, int);
+ method public void onChildDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, float, float, int, boolean);
+ method public void onChildDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, float, float, int, boolean);
+ method public abstract boolean onMove(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void onMoved(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder, int, android.support.v7.widget.RecyclerView.ViewHolder, int, int, int);
+ method public void onSelectedChanged(android.support.v7.widget.RecyclerView.ViewHolder, int);
+ method public abstract void onSwiped(android.support.v7.widget.RecyclerView.ViewHolder, int);
+ field public static final int DEFAULT_DRAG_ANIMATION_DURATION = 200; // 0xc8
+ field public static final int DEFAULT_SWIPE_ANIMATION_DURATION = 250; // 0xfa
+ }
+
+ public static abstract class ItemTouchHelper.SimpleCallback extends android.support.v7.widget.helper.ItemTouchHelper.Callback {
+ ctor public ItemTouchHelper.SimpleCallback(int, int);
+ method public int getDragDirs(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public int getMovementFlags(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public int getSwipeDirs(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public void setDefaultDragDirs(int);
+ method public void setDefaultSwipeDirs(int);
+ }
+
+ public static abstract interface ItemTouchHelper.ViewDropHandler {
+ method public abstract void prepareForDrop(android.view.View, android.view.View, int, int);
+ }
+
+ public abstract interface ItemTouchUIUtil {
+ method public abstract void clearView(android.view.View);
+ method public abstract void onDraw(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.view.View, float, float, int, boolean);
+ method public abstract void onDrawOver(android.graphics.Canvas, android.support.v7.widget.RecyclerView, android.view.View, float, float, int, boolean);
+ method public abstract void onSelected(android.view.View);
+ }
+
+}
+
+package android.support.v7.widget.util {
+
+ public abstract class SortedListAdapterCallback extends android.support.v7.util.SortedList.Callback {
+ ctor public SortedListAdapterCallback(android.support.v7.widget.RecyclerView.Adapter);
+ method public void onChanged(int, int);
+ method public void onInserted(int, int);
+ method public void onMoved(int, int);
+ method public void onRemoved(int, int);
+ }
+
+}
+
diff --git a/v7/recyclerview/api/current.txt b/v7/recyclerview/api/current.txt
index 1f91036..5314475 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -15,6 +15,8 @@
public static final class R.dimen {
ctor public R.dimen();
field public static int item_touch_helper_max_drag_scroll_per_frame;
+ field public static int item_touch_helper_swipe_escape_max_velocity;
+ field public static int item_touch_helper_swipe_escape_velocity;
}
public static final class R.id {
@@ -25,6 +27,7 @@
public static final class R.styleable {
ctor public R.styleable();
field public static final int[] RecyclerView;
+ field public static int RecyclerView_android_descendantFocusability;
field public static int RecyclerView_android_orientation;
field public static int RecyclerView_layoutManager;
field public static int RecyclerView_reverseLayout;
@@ -233,9 +236,13 @@
method public abstract int getEnd();
method public abstract int getEndAfterPadding();
method public abstract int getEndPadding();
+ method public abstract int getMode();
+ method public abstract int getModeInOther();
method public abstract int getStartAfterPadding();
method public abstract int getTotalSpace();
method public int getTotalSpaceChange();
+ method public abstract int getTransformedEndWithDecoration(android.view.View);
+ method public abstract int getTransformedStartWithDecoration(android.view.View);
method public abstract void offsetChild(android.view.View, int);
method public abstract void offsetChildren(int);
method public void onLayoutComplete();
@@ -263,6 +270,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);
@@ -279,6 +288,7 @@
method public android.support.v7.widget.RecyclerView.LayoutManager getLayoutManager();
method public int getMaxFlingVelocity();
method public int getMinFlingVelocity();
+ method public boolean getPreserveFocusAfterLayout();
method public android.support.v7.widget.RecyclerView.RecycledViewPool getRecycledViewPool();
method public int getScrollState();
method public boolean hasFixedSize();
@@ -309,6 +319,7 @@
method public void setLayoutFrozen(boolean);
method public void setLayoutManager(android.support.v7.widget.RecyclerView.LayoutManager);
method public deprecated void setOnScrollListener(android.support.v7.widget.RecyclerView.OnScrollListener);
+ method public void setPreserveFocusAfterLayout(boolean);
method public void setRecycledViewPool(android.support.v7.widget.RecyclerView.RecycledViewPool);
method public void setRecyclerListener(android.support.v7.widget.RecyclerView.RecyclerListener);
method public void setScrollingTouchSlop(int);
@@ -383,6 +394,7 @@
method public abstract boolean animateDisappearance(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
method public abstract boolean animatePersistence(android.support.v7.widget.RecyclerView.ViewHolder, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo, android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo);
method public boolean canReuseUpdatedViewHolder(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public boolean canReuseUpdatedViewHolder(android.support.v7.widget.RecyclerView.ViewHolder, java.util.List<java.lang.Object>);
method public final void dispatchAnimationFinished(android.support.v7.widget.RecyclerView.ViewHolder);
method public final void dispatchAnimationStarted(android.support.v7.widget.RecyclerView.ViewHolder);
method public final void dispatchAnimationsFinished();
@@ -454,6 +466,7 @@
method public boolean canScrollHorizontally();
method public boolean canScrollVertically();
method public boolean checkLayoutParams(android.support.v7.widget.RecyclerView.LayoutParams);
+ method public static int chooseSize(int, int, int);
method public int computeHorizontalScrollExtent(android.support.v7.widget.RecyclerView.State);
method public int computeHorizontalScrollOffset(android.support.v7.widget.RecyclerView.State);
method public int computeHorizontalScrollRange(android.support.v7.widget.RecyclerView.State);
@@ -466,6 +479,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);
@@ -474,10 +488,12 @@
method public int getBottomDecorationHeight(android.view.View);
method public android.view.View getChildAt(int);
method public int getChildCount();
- method public static int getChildMeasureSpec(int, int, int, boolean);
+ method public static deprecated int getChildMeasureSpec(int, int, int, boolean);
+ method public static int getChildMeasureSpec(int, int, int, int, boolean);
method public boolean getClipToPadding();
method public int getColumnCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
method public int getDecoratedBottom(android.view.View);
+ method public void getDecoratedBoundsWithMargins(android.view.View, android.graphics.Rect);
method public int getDecoratedLeft(android.view.View);
method public int getDecoratedMeasuredHeight(android.view.View);
method public int getDecoratedMeasuredWidth(android.view.View);
@@ -485,6 +501,7 @@
method public int getDecoratedTop(android.view.View);
method public android.view.View getFocusedChild();
method public int getHeight();
+ method public int getHeightMode();
method public int getItemCount();
method public int getItemViewType(android.view.View);
method public int getLayoutDirection();
@@ -503,14 +520,19 @@
method public int getRowCountForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
method public int getSelectionModeForAccessibility(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
method public int getTopDecorationHeight(android.view.View);
+ method public void getTransformedBoundingBox(android.view.View, boolean, android.graphics.Rect);
method public int getWidth();
+ method public int getWidthMode();
method public boolean hasFocus();
method public void ignoreView(android.view.View);
method public boolean isAttachedToWindow();
+ method public boolean isAutoMeasureEnabled();
method public boolean isFocused();
method public boolean isLayoutHierarchical(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public boolean isMeasurementCacheEnabled();
method public boolean isSmoothScrolling();
method public void layoutDecorated(android.view.View, int, int, int, int);
+ method public void layoutDecoratedWithMargins(android.view.View, int, int, int, int);
method public void measureChild(android.view.View, int, int);
method public void measureChildWithMargins(android.view.View, int, int);
method public void moveView(int, int);
@@ -534,6 +556,7 @@
method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int);
method public void onItemsUpdated(android.support.v7.widget.RecyclerView, int, int, java.lang.Object);
method public void onLayoutChildren(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void onLayoutCompleted(android.support.v7.widget.RecyclerView.State);
method public void onMeasure(android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State, int, int);
method public deprecated boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.view.View, android.view.View);
method public boolean onRequestChildFocus(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, android.view.View, android.view.View);
@@ -557,7 +580,10 @@
method public int scrollHorizontallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
method public void scrollToPosition(int);
method public int scrollVerticallyBy(int, android.support.v7.widget.RecyclerView.Recycler, android.support.v7.widget.RecyclerView.State);
+ method public void setAutoMeasureEnabled(boolean);
+ method public void setMeasuredDimension(android.graphics.Rect, int, int);
method public void setMeasuredDimension(int, int);
+ method public void setMeasurementCacheEnabled(boolean);
method public void smoothScrollToPosition(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.State, int);
method public void startSmoothScroll(android.support.v7.widget.RecyclerView.SmoothScroller);
method public void stopIgnoringView(android.view.View);
@@ -678,6 +704,7 @@
method public int getItemCount();
method public int getTargetScrollPosition();
method public boolean hasTargetScrollPosition();
+ method public boolean isMeasuring();
method public boolean isPreLayout();
method public void put(int, java.lang.Object);
method public void remove(int);
@@ -705,6 +732,7 @@
public class RecyclerViewAccessibilityDelegate extends android.support.v4.view.AccessibilityDelegateCompat {
ctor public RecyclerViewAccessibilityDelegate(android.support.v7.widget.RecyclerView);
+ method public android.support.v4.view.AccessibilityDelegateCompat getItemDelegate();
}
public abstract class SimpleItemAnimator extends android.support.v7.widget.RecyclerView.ItemAnimator {
@@ -812,7 +840,9 @@
method public static android.support.v7.widget.helper.ItemTouchUIUtil getDefaultUIUtil();
method public float getMoveThreshold(android.support.v7.widget.RecyclerView.ViewHolder);
method public abstract int getMovementFlags(android.support.v7.widget.RecyclerView, android.support.v7.widget.RecyclerView.ViewHolder);
+ method public float getSwipeEscapeVelocity(float);
method public float getSwipeThreshold(android.support.v7.widget.RecyclerView.ViewHolder);
+ method public float getSwipeVelocityThreshold(float);
method public int interpolateOutOfBoundsScroll(android.support.v7.widget.RecyclerView, int, int, int, long);
method public boolean isItemViewSwipeEnabled();
method public boolean isLongPressDragEnabled();
diff --git a/v7/recyclerview/build.gradle b/v7/recyclerview/build.gradle
index c9f1a22..386694d 100644
--- a/v7/recyclerview/build.gradle
+++ b/v7/recyclerview/build.gradle
@@ -1,18 +1,24 @@
-apply plugin: 'android-library'
+apply plugin: 'com.android.library'
archivesBaseName = 'recyclerview-v7'
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 {
- compileSdkVersion 21
- buildToolsVersion "19.1.0"
+ compileSdkVersion 23
defaultConfig {
minSdkVersion 7
@@ -22,7 +28,7 @@
sourceSets {
main.manifest.srcFile 'AndroidManifest.xml'
main.java.srcDir 'src'
- main.res.srcDir 'res'
+ main.res.srcDirs 'res', 'res-public'
androidTest.setRoot('tests')
test.java.srcDir 'jvm-tests'
@@ -31,6 +37,11 @@
androidTest.manifest.srcFile 'tests/AndroidManifest.xml'
}
+ compileOptions {
+ sourceCompatibility JavaVersion.VERSION_1_7
+ targetCompatibility JavaVersion.VERSION_1_7
+ }
+
lintOptions {
// TODO: fix errors and reenable.
abortOnError false
@@ -43,6 +54,10 @@
testOptions {
unitTests.returnDefaultValues = true
}
+
+ buildTypes.all {
+ consumerProguardFiles 'proguard-rules.pro'
+ }
}
android.libraryVariants.all { variant ->
@@ -78,17 +93,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/NO_DOCS b/v7/recyclerview/jvm-tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v7/recyclerview/jvm-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/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 03c1151b..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..a5a8e33 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;
@@ -244,6 +246,18 @@
}
@Test
+ public void testNotifyAfterPre() {
+ setupBasic(10, 2, 3);
+ add(2, 1);
+ mAdapterHelper.preProcess();
+ add(3, 1);
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mPreProcessClone.applyOps(mFirstPassUpdates, mTestAdapter);
+ mPreProcessClone.applyOps(mSecondPassUpdates, mTestAdapter);
+ assertAdaptersEqual(mTestAdapter, mPreProcessClone);
+ }
+
+ @Test
public void testSinglePass() {
setupBasic(10, 2, 3);
add(2, 1);
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..d8ff22a 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;
@@ -52,6 +54,37 @@
}
@Test
+ public void addOverridePre() {
+ RecyclerView.ViewHolder vh = new MockViewHolder();
+ MockInfo info = new MockInfo();
+ mStore.addToPreLayout(vh, info);
+ MockInfo info2 = new MockInfo();
+ mStore.addToPreLayout(vh, info2);
+ assertSame(info2, find(vh, FLAG_PRE));
+ }
+
+ @Test
+ public void addOverridePost() {
+ RecyclerView.ViewHolder vh = new MockViewHolder();
+ MockInfo info = new MockInfo();
+ mStore.addToPostLayout(vh, info);
+ MockInfo info2 = new MockInfo();
+ mStore.addToPostLayout(vh, info2);
+ assertSame(info2, find(vh, FLAG_POST));
+ }
+
+ @Test
+ public void addRemoveAndReAdd() {
+ RecyclerView.ViewHolder vh = new MockViewHolder();
+ MockInfo pre = new MockInfo();
+ mStore.addToPreLayout(vh, pre);
+ MockInfo post1 = new MockInfo();
+ mStore.addToPostLayout(vh, post1);
+ mStore.onViewDetached(vh);
+ mStore.addToDisappearedInLayout(vh);
+ }
+
+ @Test
public void addToPreLayout() {
RecyclerView.ViewHolder vh = new MockViewHolder();
MockInfo info = new MockInfo();
@@ -188,6 +221,19 @@
assertEquals(mCallback.persistent.get(vh), new Pair<>(pre, post));
}
+ @Test
+ public void processAppearAndDisappearInPostLayout() {
+ ViewHolder vh = new MockViewHolder();
+ MockInfo info1 = new MockInfo();
+ mStore.addToPostLayout(vh, info1);
+ mStore.addToDisappearedInLayout(vh);
+ mStore.process(mCallback);
+ assertTrue(mCallback.disappeared.isEmpty());
+ assertTrue(mCallback.appeared.isEmpty());
+ assertTrue(mCallback.persistent.isEmpty());
+ assertSame(mCallback.unused.get(0), vh);
+ }
+
static class MockViewHolder extends RecyclerView.ViewHolder {
public MockViewHolder() {
super(new View(null));
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/res-public/values/public_attrs.xml b/v7/recyclerview/res-public/values/public_attrs.xml
new file mode 100644
index 0000000..3da5fd7
--- /dev/null
+++ b/v7/recyclerview/res-public/values/public_attrs.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<resources>
+ <public type="attr" name="layoutManager"/>
+ <public type="attr" name="spanCount" format="integer"/>
+ <public type="attr" name="reverseLayout"/>
+ <public type="attr" name="stackFromEnd"/>
+</resources>
diff --git a/v7/recyclerview/res/values/attrs.xml b/v7/recyclerview/res/values/attrs.xml
index ad93ce6..a0d73c9 100644
--- a/v7/recyclerview/res/values/attrs.xml
+++ b/v7/recyclerview/res/values/attrs.xml
@@ -33,6 +33,7 @@
<eat-comment />
<attr name="android:orientation" />
+ <attr name="android:descendantFocusability" />
<attr name="spanCount" format="integer"/>
<attr name="reverseLayout" format="boolean" />
<attr name="stackFromEnd" format="boolean" />
diff --git a/v7/recyclerview/res/values/dimens.xml b/v7/recyclerview/res/values/dimens.xml
index 5928f36..90c41b9 100644
--- a/v7/recyclerview/res/values/dimens.xml
+++ b/v7/recyclerview/res/values/dimens.xml
@@ -19,4 +19,6 @@
<!-- The max amount of scroll ItemTouchHelper will trigger if dragged view is out of
RecyclerView's bounds.-->
<dimen name="item_touch_helper_max_drag_scroll_per_frame">20dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_velocity">120dp</dimen>
+ <dimen name="item_touch_helper_swipe_escape_max_velocity">800dp</dimen>
</resources>
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/util/SortedList.java b/v7/recyclerview/src/android/support/v7/util/SortedList.java
index d9e856f..c03af33 100644
--- a/v7/recyclerview/src/android/support/v7/util/SortedList.java
+++ b/v7/recyclerview/src/android/support/v7/util/SortedList.java
@@ -145,7 +145,7 @@
* </p>
* @param items Array of items to be added into the list.
* @param mayModifyInput If true, SortedList is allowed to modify the input.
- * @see {@link SortedList#addAll(T[] items)}.
+ * @see {@link SortedList#addAll(Object[] items)}.
*/
public void addAll(T[] items, boolean mayModifyInput) {
throwIfMerging();
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index 9220c5e..47e9722 100644
--- a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
@@ -502,6 +502,9 @@
* @return True if updates should be processed.
*/
boolean onItemRangeChanged(int positionStart, int itemCount, Object payload) {
+ if (itemCount < 1) {
+ return false;
+ }
mPendingUpdates.add(obtainUpdateOp(UpdateOp.UPDATE, positionStart, itemCount, payload));
mExistingUpdateTypes |= UpdateOp.UPDATE;
return mPendingUpdates.size() == 1;
@@ -511,6 +514,9 @@
* @return True if updates should be processed.
*/
boolean onItemRangeInserted(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
mPendingUpdates.add(obtainUpdateOp(UpdateOp.ADD, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.ADD;
return mPendingUpdates.size() == 1;
@@ -520,6 +526,9 @@
* @return True if updates should be processed.
*/
boolean onItemRangeRemoved(int positionStart, int itemCount) {
+ if (itemCount < 1) {
+ return false;
+ }
mPendingUpdates.add(obtainUpdateOp(UpdateOp.REMOVE, positionStart, itemCount, null));
mExistingUpdateTypes |= UpdateOp.REMOVE;
return mPendingUpdates.size() == 1;
@@ -530,7 +539,7 @@
*/
boolean onItemRangeMoved(int from, int to, int itemCount) {
if (from == to) {
- return false;//no-op
+ return false; // no-op
}
if (itemCount != 1) {
throw new IllegalArgumentException("Moving more than 1 item is not supported yet");
@@ -612,6 +621,10 @@
return position;
}
+ boolean hasUpdates() {
+ return !mPostponedList.isEmpty() && !mPendingUpdates.isEmpty();
+ }
+
/**
* Queued operation to happen when child views are updated.
*/
diff --git a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
index 0809efe..472b2e5 100644
--- a/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/DefaultItemAnimator.java
@@ -15,6 +15,7 @@
*/
package android.support.v7.widget;
+import android.support.annotation.NonNull;
import android.support.v4.animation.AnimatorCompatHelper;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.ViewPropertyAnimatorCompat;
@@ -632,6 +633,28 @@
}
}
+ /**
+ * {@inheritDoc}
+ * <p>
+ * If the payload list is not empty, DefaultItemAnimator returns <code>true</code>.
+ * When this is the case:
+ * <ul>
+ * <li>If you override {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)}, both
+ * ViewHolder arguments will be the same instance.
+ * </li>
+ * <li>
+ * If you are not overriding {@link #animateChange(ViewHolder, ViewHolder, int, int, int, int)},
+ * then DefaultItemAnimator will call {@link #animateMove(ViewHolder, int, int, int, int)} and
+ * run a move animation instead.
+ * </li>
+ * </ul>
+ */
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return !payloads.isEmpty() || super.canReuseUpdatedViewHolder(viewHolder, payloads);
+ }
+
private static class VpaListenerAdapter implements ViewPropertyAnimatorListener {
@Override
public void onAnimationStart(View view) {}
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index 7dcaea0..6a56418 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -38,11 +38,6 @@
private static final String TAG = "GridLayoutManager";
public static final int DEFAULT_SPAN_COUNT = -1;
/**
- * The measure spec for the scroll direction.
- */
- static final int MAIN_DIR_SPEC =
- View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- /**
* Span size have been changed but we've not done a new layout calculation.
*/
boolean mPendingSpanCountChange = false;
@@ -125,7 +120,9 @@
if (state.getItemCount() < 1) {
return 0;
}
- return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
+
+ // Row count is one more than the last item's row index.
+ return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
}
@Override
@@ -137,7 +134,9 @@
if (state.getItemCount() < 1) {
return 0;
}
- return getSpanGroupIndex(recycler, state, state.getItemCount() - 1);
+
+ // Column count is one more than the last item's column index.
+ return getSpanGroupIndex(recycler, state, state.getItemCount() - 1) + 1;
}
@Override
@@ -173,9 +172,12 @@
validateChildOrder();
}
clearPreLayoutSpanMappingCache();
- if (!state.isPreLayout()) {
- mPendingSpanCountChange = false;
- }
+ }
+
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingSpanCountChange = false;
}
private void clearPreLayoutSpanMappingCache() {
@@ -221,8 +223,13 @@
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mOrientation == HORIZONTAL) {
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.FILL_PARENT);
+ } else {
+ return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
}
@Override
@@ -273,35 +280,72 @@
calculateItemBorders(totalSpace);
}
- private void calculateItemBorders(int totalSpace) {
- if (mCachedBorders == null || mCachedBorders.length != mSpanCount + 1
- || mCachedBorders[mCachedBorders.length - 1] != totalSpace) {
- mCachedBorders = new int[mSpanCount + 1];
+ @Override
+ public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+ if (mCachedBorders == null) {
+ super.setMeasuredDimension(childrenBounds, wSpec, hSpec);
}
- mCachedBorders[0] = 0;
- int sizePerSpan = totalSpace / mSpanCount;
- int sizePerSpanRemainder = totalSpace % mSpanCount;
+ final int width, height;
+ final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ if (mOrientation == VERTICAL) {
+ final int usedHeight = childrenBounds.height() + verticalPadding;
+ height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+ width = chooseSize(wSpec, mCachedBorders[mCachedBorders.length - 1] + horizontalPadding,
+ getMinimumWidth());
+ } else {
+ final int usedWidth = childrenBounds.width() + horizontalPadding;
+ width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+ height = chooseSize(hSpec, mCachedBorders[mCachedBorders.length - 1] + verticalPadding,
+ getMinimumHeight());
+ }
+ setMeasuredDimension(width, height);
+ }
+
+ /**
+ * @param totalSpace Total available space after padding is removed
+ */
+ private void calculateItemBorders(int totalSpace) {
+ mCachedBorders = calculateItemBorders(mCachedBorders, mSpanCount, totalSpace);
+ }
+
+ /**
+ * @param cachedBorders The out array
+ * @param spanCount number of spans
+ * @param totalSpace total available space after padding is removed
+ * @return The updated array. Might be the same instance as the provided array if its size
+ * has not changed.
+ */
+ static int[] calculateItemBorders(int[] cachedBorders, int spanCount, int totalSpace) {
+ if (cachedBorders == null || cachedBorders.length != spanCount + 1
+ || cachedBorders[cachedBorders.length - 1] != totalSpace) {
+ cachedBorders = new int[spanCount + 1];
+ }
+ cachedBorders[0] = 0;
+ int sizePerSpan = totalSpace / spanCount;
+ int sizePerSpanRemainder = totalSpace % spanCount;
int consumedPixels = 0;
int additionalSize = 0;
- for (int i = 1; i <= mSpanCount; i++) {
+ for (int i = 1; i <= spanCount; i++) {
int itemSize = sizePerSpan;
additionalSize += sizePerSpanRemainder;
- if (additionalSize > 0 && (mSpanCount - additionalSize) < sizePerSpanRemainder) {
+ if (additionalSize > 0 && (spanCount - additionalSize) < sizePerSpanRemainder) {
itemSize += 1;
- additionalSize -= mSpanCount;
+ additionalSize -= spanCount;
}
consumedPixels += itemSize;
- mCachedBorders[i] = consumedPixels;
+ cachedBorders[i] = consumedPixels;
}
+ return cachedBorders;
}
@Override
void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
- AnchorInfo anchorInfo) {
- super.onAnchorReady(recycler, state, anchorInfo);
+ AnchorInfo anchorInfo, int itemDirection) {
+ super.onAnchorReady(recycler, state, anchorInfo, itemDirection);
updateMeasurements();
if (state.getItemCount() > 0 && !state.isPreLayout()) {
- ensureAnchorIsInFirstSpan(recycler, state, anchorInfo);
+ ensureAnchorIsInCorrectSpan(recycler, state, anchorInfo, itemDirection);
}
ensureViewSet();
}
@@ -328,12 +372,32 @@
return super.scrollVerticallyBy(dy, recycler, state);
}
- private void ensureAnchorIsInFirstSpan(RecyclerView.Recycler recycler, RecyclerView.State state,
- AnchorInfo anchorInfo) {
+ private void ensureAnchorIsInCorrectSpan(RecyclerView.Recycler recycler,
+ RecyclerView.State state, AnchorInfo anchorInfo, int itemDirection) {
+ final boolean layingOutInPrimaryDirection =
+ itemDirection == LayoutState.ITEM_DIRECTION_TAIL;
int span = getSpanIndex(recycler, state, anchorInfo.mPosition);
- while (span > 0 && anchorInfo.mPosition > 0) {
- anchorInfo.mPosition--;
- span = getSpanIndex(recycler, state, anchorInfo.mPosition);
+ if (layingOutInPrimaryDirection) {
+ // choose span 0
+ while (span > 0 && anchorInfo.mPosition > 0) {
+ anchorInfo.mPosition--;
+ span = getSpanIndex(recycler, state, anchorInfo.mPosition);
+ }
+ } else {
+ // choose the max span we can get. hopefully last one
+ final int indexLimit = state.getItemCount() - 1;
+ int pos = anchorInfo.mPosition;
+ int bestSpan = span;
+ while (pos < indexLimit) {
+ int next = getSpanIndex(recycler, state, pos + 1);
+ if (next > bestSpan) {
+ pos += 1;
+ bestSpan = next;
+ } else {
+ break;
+ }
+ }
+ anchorInfo.mPosition = pos;
}
}
@@ -433,6 +497,15 @@
@Override
void layoutChunk(RecyclerView.Recycler recycler, RecyclerView.State state,
LayoutState layoutState, LayoutChunkResult result) {
+ final int otherDirSpecMode = mOrientationHelper.getModeInOther();
+ final boolean flexibleInOtherDir = otherDirSpecMode != View.MeasureSpec.EXACTLY;
+ final int currentOtherDirSize = getChildCount() > 0 ? mCachedBorders[mSpanCount] : 0;
+ // if grid layout's dimensions are not specified, let the new row change the measurements
+ // This is not perfect since we not covering all rows but still solves an important case
+ // where they may have a header row which should be laid out according to children.
+ if (flexibleInOtherDir) {
+ updateMeasurements(); // reset measurements
+ }
final boolean layingOutInPrimaryDirection =
layoutState.mItemDirection == LayoutState.ITEM_DIRECTION_TAIL;
int count = 0;
@@ -470,6 +543,7 @@
}
int maxSize = 0;
+ float maxSizeInOther = 0; // use a float to get size per span
// we should assign spans before item decor offsets are calculated
assignSpans(recycler, state, count, consumedSpanCount, layingOutInPrimaryDirection);
@@ -490,35 +564,73 @@
}
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
- final int spec = View.MeasureSpec.makeMeasureSpec(
- mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
- mCachedBorders[lp.mSpanIndex],
- View.MeasureSpec.EXACTLY);
+ final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+ mCachedBorders[lp.mSpanIndex], otherDirSpecMode, 0,
+ mOrientation == HORIZONTAL ? lp.height : lp.width,
+ false);
+ final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(),
+ mOrientationHelper.getMode(), 0,
+ mOrientation == VERTICAL ? lp.height : lp.width, true);
+ // Unless the child has MATCH_PARENT, measure it from its specs before adding insets.
if (mOrientation == VERTICAL) {
- measureChildWithDecorationsAndMargin(view, spec, getMainDirSpec(lp.height), false);
+ @SuppressWarnings("deprecation")
+ final boolean applyInsets = lp.height == ViewGroup.LayoutParams.FILL_PARENT;
+ measureChildWithDecorationsAndMargin(view, spec, mainSpec, applyInsets, false);
} else {
- measureChildWithDecorationsAndMargin(view, getMainDirSpec(lp.width), spec, false);
+ //noinspection deprecation
+ final boolean applyInsets = lp.width == ViewGroup.LayoutParams.FILL_PARENT;
+ measureChildWithDecorationsAndMargin(view, mainSpec, spec, applyInsets, false);
}
final int size = mOrientationHelper.getDecoratedMeasurement(view);
if (size > maxSize) {
maxSize = size;
}
+ final float otherSize = 1f * mOrientationHelper.getDecoratedMeasurementInOther(view) /
+ lp.mSpanSize;
+ if (otherSize > maxSizeInOther) {
+ maxSizeInOther = otherSize;
+ }
}
-
- // views that did not measure the maxSize has to be re-measured
- final int maxMeasureSpec = getMainDirSpec(maxSize);
+ if (flexibleInOtherDir) {
+ // re-distribute columns
+ guessMeasurement(maxSizeInOther, currentOtherDirSize);
+ // now we should re-measure any item that was match parent.
+ maxSize = 0;
+ for (int i = 0; i < count; i++) {
+ View view = mSet[i];
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
+ mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0,
+ mOrientation == HORIZONTAL ? lp.height : lp.width, false);
+ final int mainSpec = getChildMeasureSpec(mOrientationHelper.getTotalSpace(),
+ mOrientationHelper.getMode(), 0,
+ mOrientation == VERTICAL ? lp.height : lp.width, true);
+ if (mOrientation == VERTICAL) {
+ measureChildWithDecorationsAndMargin(view, spec, mainSpec, false, true);
+ } else {
+ measureChildWithDecorationsAndMargin(view, mainSpec, spec, false, true);
+ }
+ final int size = mOrientationHelper.getDecoratedMeasurement(view);
+ if (size > maxSize) {
+ maxSize = size;
+ }
+ }
+ }
+ // Views that did not measure the maxSize has to be re-measured
+ // We will stop doing this once we introduce Gravity in the GLM layout params
+ final int maxMeasureSpec = View.MeasureSpec.makeMeasureSpec(maxSize,
+ View.MeasureSpec.EXACTLY);
for (int i = 0; i < count; i ++) {
final View view = mSet[i];
if (mOrientationHelper.getDecoratedMeasurement(view) != maxSize) {
final LayoutParams lp = (LayoutParams) view.getLayoutParams();
- final int spec = View.MeasureSpec.makeMeasureSpec(
- mCachedBorders[lp.mSpanIndex + lp.mSpanSize] -
- mCachedBorders[lp.mSpanIndex],
- View.MeasureSpec.EXACTLY);
+ final int spec = getChildMeasureSpec(mCachedBorders[lp.mSpanIndex + lp.mSpanSize]
+ - mCachedBorders[lp.mSpanIndex], View.MeasureSpec.EXACTLY, 0,
+ mOrientation == HORIZONTAL ? lp.height : lp.width, false);
if (mOrientation == VERTICAL) {
- measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true);
+ measureChildWithDecorationsAndMargin(view, spec, maxMeasureSpec, true, true);
} else {
- measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true);
+ measureChildWithDecorationsAndMargin(view, maxMeasureSpec, spec, true, true);
}
}
}
@@ -547,16 +659,20 @@
View view = mSet[i];
LayoutParams params = (LayoutParams) view.getLayoutParams();
if (mOrientation == VERTICAL) {
- left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
- right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+ if (isLayoutRTL()) {
+ right = getPaddingLeft() + mCachedBorders[params.mSpanIndex + params.mSpanSize];
+ left = right - mOrientationHelper.getDecoratedMeasurementInOther(view);
+ } else {
+ left = getPaddingLeft() + mCachedBorders[params.mSpanIndex];
+ right = left + mOrientationHelper.getDecoratedMeasurementInOther(view);
+ }
} else {
top = getPaddingTop() + mCachedBorders[params.mSpanIndex];
bottom = top + mOrientationHelper.getDecoratedMeasurementInOther(view);
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
- layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
- right - params.rightMargin, bottom - params.bottomMargin);
+ layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
@@ -572,16 +688,24 @@
Arrays.fill(mSet, null);
}
- private int getMainDirSpec(int dim) {
- if (dim < 0) {
- return MAIN_DIR_SPEC;
- } else {
- return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
- }
+ /**
+ * This is called after laying out a row (if vertical) or a column (if horizontal) when the
+ * RecyclerView does not have exact measurement specs.
+ * <p>
+ * Here we try to assign a best guess width or height and re-do the layout to update other
+ * views that wanted to FILL_PARENT in the non-scroll orientation.
+ *
+ * @param maxSizeInOther The maximum size per span ratio from the measurement of the children.
+ * @param currentOtherDirSize The size before this layout chunk. There is no reason to go below.
+ */
+ private void guessMeasurement(float maxSizeInOther, int currentOtherDirSize) {
+ final int contentSize = Math.round(maxSizeInOther * mSpanCount);
+ // always re-calculate because borders were stretched during the fill
+ calculateItemBorders(Math.max(contentSize, currentOtherDirSize));
}
private void measureChildWithDecorationsAndMargin(View child, int widthSpec, int heightSpec,
- boolean capBothSpecs) {
+ boolean capBothSpecs, boolean alreadyMeasured) {
calculateItemDecorationsForChild(child, mDecorInsets);
RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
if (capBothSpecs || mOrientation == VERTICAL) {
@@ -592,7 +716,16 @@
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mDecorInsets.top,
lp.bottomMargin + mDecorInsets.bottom);
}
- child.measure(widthSpec, heightSpec);
+ final boolean measure;
+ if (alreadyMeasured) {
+ measure = shouldReMeasureChild(child, widthSpec, heightSpec, lp);
+ } else {
+ measure = shouldMeasureChild(child, widthSpec, heightSpec, lp);
+ }
+ if (measure) {
+ child.measure(widthSpec, heightSpec);
+ }
+
}
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
@@ -602,7 +735,7 @@
final int mode = View.MeasureSpec.getMode(spec);
if (mode == View.MeasureSpec.AT_MOST || mode == View.MeasureSpec.EXACTLY) {
return View.MeasureSpec.makeMeasureSpec(
- View.MeasureSpec.getSize(spec) - startInset - endInset, mode);
+ Math.max(0, View.MeasureSpec.getSize(spec) - startInset - endInset), mode);
}
return spec;
}
@@ -670,6 +803,7 @@
}
mSpanCount = spanCount;
mSpanSizeLookup.invalidateSpanIndexCache();
+ requestLayout();
}
/**
@@ -842,6 +976,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..bf730ad 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LayoutState.java
@@ -15,6 +15,7 @@
*/
package android.support.v7.widget;
+
import android.view.View;
/**
@@ -35,8 +36,11 @@
final static int ITEM_DIRECTION_TAIL = 1;
- final static int SCOLLING_OFFSET_NaN = Integer.MIN_VALUE;
-
+ /**
+ * We may not want to recycle children in some cases (e.g. layout)
+ */
+ boolean mRecycle = true;
+
/**
* Number of pixels that we should fill, in the layout direction.
*/
@@ -70,6 +74,16 @@
int mEndLine = 0;
/**
+ * If true, layout should stop if a focusable view is added
+ */
+ boolean mStopInFocusable;
+
+ /**
+ * If the content is not wrapped with any value
+ */
+ boolean mInfinite;
+
+ /**
* @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..a36cf50 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -16,6 +16,8 @@
package android.support.v7.widget;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
import android.content.Context;
import android.graphics.PointF;
import android.os.Parcel;
@@ -23,18 +25,16 @@
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityEventCompat;
import android.support.v4.view.accessibility.AccessibilityRecordCompat;
-import android.util.AttributeSet;
+import android.support.v7.widget.RecyclerView.LayoutParams;
import android.support.v7.widget.helper.ItemTouchHelper;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
-import android.support.v7.widget.RecyclerView.LayoutParams;
import java.util.List;
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-
/**
* A {@link android.support.v7.widget.RecyclerView.LayoutManager} implementation which provides
* similar functionality to {@link android.widget.ListView}.
@@ -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;
/**
@@ -154,6 +154,7 @@
public LinearLayoutManager(Context context, int orientation, boolean reverseLayout) {
setOrientation(orientation);
setReverseLayout(reverseLayout);
+ setAutoMeasureEnabled(true);
}
/**
@@ -170,6 +171,7 @@
setOrientation(properties.orientation);
setReverseLayout(properties.reverseLayout);
setStackFromEnd(properties.stackFromEnd);
+ setAutoMeasureEnabled(true);
}
/**
@@ -476,10 +478,14 @@
// resolve layout direction
resolveShouldLayoutReverse();
- mAnchorInfo.reset();
- mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
- // calculate anchor position and coordinate
- updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
+ if (!mAnchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
+ mPendingSavedState != null) {
+ mAnchorInfo.reset();
+ mAnchorInfo.mLayoutFromEnd = mShouldReverseLayout ^ mStackFromEnd;
+ // calculate anchor position and coordinate
+ updateAnchorInfoForLayout(recycler, state, mAnchorInfo);
+ mAnchorInfo.mValid = true;
+ }
if (DEBUG) {
Log.d(TAG, "Anchor info:" + mAnchorInfo);
}
@@ -527,8 +533,18 @@
}
int startOffset;
int endOffset;
- onAnchorReady(recycler, state, mAnchorInfo);
+ final int firstLayoutDirection;
+ if (mAnchorInfo.mLayoutFromEnd) {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_TAIL :
+ LayoutState.ITEM_DIRECTION_HEAD;
+ } else {
+ firstLayoutDirection = mShouldReverseLayout ? LayoutState.ITEM_DIRECTION_HEAD :
+ LayoutState.ITEM_DIRECTION_TAIL;
+ }
+
+ onAnchorReady(recycler, state, mAnchorInfo, firstLayoutDirection);
detachAndScrapAttachedViews(recycler);
+ mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mIsPreLayout = state.isPreLayout();
if (mAnchorInfo.mLayoutFromEnd) {
// fill towards start
@@ -607,27 +623,36 @@
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
- mPendingScrollPosition = NO_POSITION;
- mPendingScrollPositionOffset = INVALID_OFFSET;
mOrientationHelper.onLayoutComplete();
+ } else {
+ mAnchorInfo.reset();
}
mLastStackFromEnd = mStackFromEnd;
- mPendingSavedState = null; // we don't need this anymore
if (DEBUG) {
validateChildOrder();
}
}
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingSavedState = null; // we don't need this anymore
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
+ mAnchorInfo.reset();
+ }
+
/**
* Method called when Anchor position is decided. Extending class can setup accordingly or
* even update anchor info if necessary.
- *
- * @param recycler
- * @param state
- * @param anchorInfo Simple data structure to keep anchor point information for the next layout
+ * @param recycler The recycler for the layout
+ * @param state The layout state
+ * @param anchorInfo The mutable POJO that keeps the position and offset.
+ * @param firstLayoutItemDirection The direction of the first layout filling in terms of adapter
+ * indices.
*/
void onAnchorReady(RecyclerView.Recycler recycler, RecyclerView.State state,
- AnchorInfo anchorInfo) {
+ AnchorInfo anchorInfo, int firstLayoutItemDirection) {
}
/**
@@ -1115,9 +1140,11 @@
private void updateLayoutState(int layoutDirection, int requiredSpace,
boolean canUseExistingSpace, RecyclerView.State state) {
+ // If parent provides a hint, don't measure unlimited.
+ mLayoutState.mInfinite = resolveIsInfinite();
mLayoutState.mExtra = getExtraLayoutSpace(state);
mLayoutState.mLayoutDirection = layoutDirection;
- int fastScrollSpace;
+ int scrollingOffset;
if (layoutDirection == LayoutState.LAYOUT_END) {
mLayoutState.mExtra += mOrientationHelper.getEndPadding();
// get the first child in the direction we are going
@@ -1128,7 +1155,7 @@
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedEnd(child);
// calculate how much we can scroll without adding new children (independent of layout)
- fastScrollSpace = mOrientationHelper.getDecoratedEnd(child)
+ scrollingOffset = mOrientationHelper.getDecoratedEnd(child)
- mOrientationHelper.getEndAfterPadding();
} else {
@@ -1138,14 +1165,19 @@
: LayoutState.ITEM_DIRECTION_HEAD;
mLayoutState.mCurrentPosition = getPosition(child) + mLayoutState.mItemDirection;
mLayoutState.mOffset = mOrientationHelper.getDecoratedStart(child);
- fastScrollSpace = -mOrientationHelper.getDecoratedStart(child)
+ scrollingOffset = -mOrientationHelper.getDecoratedStart(child)
+ mOrientationHelper.getStartAfterPadding();
}
mLayoutState.mAvailable = requiredSpace;
if (canUseExistingSpace) {
- mLayoutState.mAvailable -= fastScrollSpace;
+ mLayoutState.mAvailable -= scrollingOffset;
}
- mLayoutState.mScrollingOffset = fastScrollSpace;
+ mLayoutState.mScrollingOffset = scrollingOffset;
+ }
+
+ boolean resolveIsInfinite() {
+ return mOrientationHelper.getMode() == View.MeasureSpec.UNSPECIFIED
+ && mOrientationHelper.getEnd() == 0;
}
int scrollBy(int dy, RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -1157,8 +1189,8 @@
final int layoutDirection = dy > 0 ? LayoutState.LAYOUT_END : LayoutState.LAYOUT_START;
final int absDy = Math.abs(dy);
updateLayoutState(layoutDirection, absDy, true, state);
- final int freeScroll = mLayoutState.mScrollingOffset;
- final int consumed = freeScroll + fill(recycler, mLayoutState, state, false);
+ final int consumed = mLayoutState.mScrollingOffset
+ + fill(recycler, mLayoutState, state, false);
if (consumed < 0) {
if (DEBUG) {
Log.d(TAG, "Don't have any more elements to scroll");
@@ -1207,6 +1239,8 @@
/**
* Recycles views that went out of bounds after scrolling towards the end of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
*
* @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
* @param dt This can be used to add additional padding to the visible area. This is used
@@ -1227,7 +1261,9 @@
if (mShouldReverseLayout) {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
@@ -1235,7 +1271,9 @@
} else {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedEnd(child) > limit) {// stop here
+ if (mOrientationHelper.getDecoratedEnd(child) > limit
+ || mOrientationHelper.getTransformedEndWithDecoration(child) > limit) {
+ // stop here
recycleChildren(recycler, 0, i);
return;
}
@@ -1246,6 +1284,8 @@
/**
* Recycles views that went out of bounds after scrolling towards the start of the layout.
+ * <p>
+ * Checks both layout position and visible position to guarantee that the view is not visible.
*
* @param recycler Recycler instance of {@link android.support.v7.widget.RecyclerView}
* @param dt This can be used to add additional padding to the visible area. This is used
@@ -1265,7 +1305,9 @@
if (mShouldReverseLayout) {
for (int i = 0; i < childCount; i++) {
View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
recycleChildren(recycler, 0, i);
return;
}
@@ -1273,7 +1315,9 @@
} else {
for (int i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
- if (mOrientationHelper.getDecoratedStart(child) < limit) {// stop here
+ if (mOrientationHelper.getDecoratedStart(child) < limit
+ || mOrientationHelper.getTransformedStartWithDecoration(child) < limit) {
+ // stop here
recycleChildren(recycler, childCount - 1, i);
return;
}
@@ -1294,7 +1338,7 @@
* @see android.support.v7.widget.LinearLayoutManager.LayoutState#mLayoutDirection
*/
private void recycleByLayoutState(RecyclerView.Recycler recycler, LayoutState layoutState) {
- if (!layoutState.mRecycle) {
+ if (!layoutState.mRecycle || layoutState.mInfinite) {
return;
}
if (layoutState.mLayoutDirection == LayoutState.LAYOUT_START) {
@@ -1328,7 +1372,7 @@
}
int remainingSpace = layoutState.mAvailable + layoutState.mExtra;
LayoutChunkResult layoutChunkResult = new LayoutChunkResult();
- while (remainingSpace > 0 && layoutState.hasMore(state)) {
+ while ((layoutState.mInfinite || remainingSpace > 0) && layoutState.hasMore(state)) {
layoutChunkResult.resetInternal();
layoutChunk(recycler, state, layoutState, layoutChunkResult);
if (layoutChunkResult.mFinished) {
@@ -1425,8 +1469,7 @@
}
// We calculate everything with View's bounding box (which includes decor and margins)
// To calculate correct layout position, we subtract margins.
- layoutDecorated(view, left + params.leftMargin, top + params.topMargin,
- right - params.rightMargin, bottom - params.bottomMargin);
+ layoutDecoratedWithMargins(view, left, top, right, bottom);
if (DEBUG) {
Log.d(TAG, "laid out child at position " + getPosition(view) + ", with l:"
+ (left + params.leftMargin) + ", t:" + (top + params.topMargin) + ", r:"
@@ -1439,6 +1482,13 @@
result.mFocusable = view.isFocusable();
}
+ @Override
+ boolean shouldMeasureTwice() {
+ return getHeightMode() != View.MeasureSpec.EXACTLY
+ && getWidthMode() != View.MeasureSpec.EXACTLY
+ && hasFlexibleChildInBothOrientations();
+ }
+
/**
* Converts a focusDirection to orientation.
*
@@ -1449,12 +1499,24 @@
* @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;
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_START;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_END;
+ } else {
+ return LayoutState.LAYOUT_START;
+ }
case View.FOCUS_FORWARD:
- return LayoutState.LAYOUT_END;
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_END;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_START;
+ } else {
+ return LayoutState.LAYOUT_END;
+ }
case View.FOCUS_UP:
return mOrientation == VERTICAL ? LayoutState.LAYOUT_START
: LayoutState.INVALID_LAYOUT;
@@ -1863,7 +1925,7 @@
*/
static class LayoutState {
- final static String TAG = "LinearLayoutManager#LayoutState";
+ final static String TAG = "LLM#LayoutState";
final static int LAYOUT_START = -1;
@@ -1931,7 +1993,8 @@
boolean mIsPreLayout = false;
/**
- * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)} amount.
+ * The most recent {@link #scrollBy(int, RecyclerView.Recycler, RecyclerView.State)}
+ * amount.
*/
int mLastScrollDelta;
@@ -1942,6 +2005,11 @@
List<RecyclerView.ViewHolder> mScrapList = null;
/**
+ * Used when there is no limit in how many views can be laid out.
+ */
+ boolean mInfinite;
+
+ /**
* @return true if there are more items in the data adapter
*/
boolean hasMore(RecyclerView.State state) {
@@ -2103,10 +2171,17 @@
int mPosition;
int mCoordinate;
boolean mLayoutFromEnd;
+ boolean mValid;
+
+ AnchorInfo() {
+ reset();
+ }
+
void reset() {
mPosition = NO_POSITION;
mCoordinate = INVALID_OFFSET;
mLayoutFromEnd = false;
+ mValid = false;
}
/**
@@ -2125,6 +2200,7 @@
"mPosition=" + mPosition +
", mCoordinate=" + mCoordinate +
", mLayoutFromEnd=" + mLayoutFromEnd +
+ ", mValid=" + mValid +
'}';
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
index cda5cd1..0996ca4 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearSmoothScroller.java
@@ -122,6 +122,7 @@
stop();
return;
}
+ //noinspection PointlessBooleanExpression
if (DEBUG && mTargetVector != null
&& ((mTargetVector.x * dx < 0 || mTargetVector.y * dy < 0))) {
throw new IllegalStateException("Scroll happened in the opposite direction"
@@ -291,13 +292,13 @@
* @param view The view which we want to make fully visible
* @param snapPreference The edge which the view should snap to when entering the visible
* area. One of {@link #SNAP_TO_START}, {@link #SNAP_TO_END} or
- * {@link #SNAP_TO_END}.
+ * {@link #SNAP_TO_ANY}.
* @return The vertical scroll amount necessary to make the view visible with the given
* snap preference.
*/
public int calculateDyToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
- if (!layoutManager.canScrollVertically()) {
+ if (layoutManager == null || !layoutManager.canScrollVertically()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
@@ -322,7 +323,7 @@
*/
public int calculateDxToMakeVisible(View view, int snapPreference) {
final RecyclerView.LayoutManager layoutManager = getLayoutManager();
- if (!layoutManager.canScrollHorizontally()) {
+ if (layoutManager == null || !layoutManager.canScrollHorizontally()) {
return 0;
}
final RecyclerView.LayoutParams params = (RecyclerView.LayoutParams)
diff --git a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
index 8ca9851..8987b9c 100644
--- a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
@@ -16,6 +16,7 @@
package android.support.v7.widget;
+import android.graphics.Rect;
import android.view.View;
import android.widget.LinearLayout;
@@ -41,6 +42,8 @@
private int mLastTotalSpace = INVALID_SIZE;
+ final Rect mTmpRect = new Rect();
+
private OrientationHelper(RecyclerView.LayoutManager layoutManager) {
mLayoutManager = layoutManager;
}
@@ -93,6 +96,38 @@
public abstract int getDecoratedEnd(View view);
/**
+ * Returns the end of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed end will be returned
+ * @return The end of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedEndWithDecoration(View view);
+
+ /**
+ * Returns the start of the View after its matrix transformations are applied to its layout
+ * position.
+ * <p>
+ * This method is useful when trying to detect the visible edge of a View.
+ * <p>
+ * It includes the decorations but does not include the margins.
+ *
+ * @param view The view whose transformed start will be returned
+ * @return The start of the View after its decor insets and transformation matrix is applied to
+ * its position
+ *
+ * @see RecyclerView.LayoutManager#getTransformedBoundingBox(View, boolean, Rect)
+ */
+ public abstract int getTransformedStartWithDecoration(View view);
+
+ /**
* Returns the space occupied by this View in the current orientation including decorations and
* margins.
*
@@ -166,6 +201,28 @@
public abstract int getEndPadding();
/**
+ * Returns the MeasureSpec mode for the current orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getMode();
+
+ /**
+ * Returns the MeasureSpec mode for the perpendicular orientation from the LayoutManager.
+ *
+ * @return The current measure spec mode.
+ *
+ * @see View.MeasureSpec
+ * @see RecyclerView.LayoutManager#getWidthMode()
+ * @see RecyclerView.LayoutManager#getHeightMode()
+ */
+ public abstract int getModeInOther();
+
+ /**
* Creates an OrientationHelper for the given LayoutManager and orientation.
*
* @param layoutManager LayoutManager to attach to
@@ -243,6 +300,18 @@
}
@Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.right;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.left;
+ }
+
+ @Override
public int getTotalSpace() {
return mLayoutManager.getWidth() - mLayoutManager.getPaddingLeft()
- mLayoutManager.getPaddingRight();
@@ -257,6 +326,16 @@
public int getEndPadding() {
return mLayoutManager.getPaddingRight();
}
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getWidthMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getHeightMode();
+ }
};
}
@@ -319,6 +398,18 @@
}
@Override
+ public int getTransformedEndWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.bottom;
+ }
+
+ @Override
+ public int getTransformedStartWithDecoration(View view) {
+ mLayoutManager.getTransformedBoundingBox(view, true, mTmpRect);
+ return mTmpRect.top;
+ }
+
+ @Override
public int getTotalSpace() {
return mLayoutManager.getHeight() - mLayoutManager.getPaddingTop()
- mLayoutManager.getPaddingBottom();
@@ -333,6 +424,16 @@
public int getEndPadding() {
return mLayoutManager.getPaddingBottom();
}
+
+ @Override
+ public int getMode() {
+ return mLayoutManager.getHeightMode();
+ }
+
+ @Override
+ public int getModeInOther() {
+ return mLayoutManager.getWidthMode();
+ }
};
}
}
\ No newline at end of file
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d00730f..c837965 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -16,12 +16,15 @@
package android.support.v7.widget;
+
import android.content.Context;
import android.content.res.TypedArray;
import android.database.Observable;
import android.graphics.Canvas;
+import android.graphics.Matrix;
import android.graphics.PointF;
import android.graphics.Rect;
+import android.graphics.RectF;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
@@ -31,7 +34,11 @@
import android.support.annotation.IntDef;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.support.v4.os.ParcelableCompat;
+import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.os.TraceCompat;
+import android.support.v4.view.AbsSavedState;
import android.support.v4.view.InputDeviceCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.NestedScrollingChild;
@@ -46,6 +53,7 @@
import android.support.v4.widget.EdgeEffectCompat;
import android.support.v4.widget.ScrollerCompat;
import android.support.v7.recyclerview.R;
+import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseArray;
@@ -72,7 +80,6 @@
import static android.support.v7.widget.AdapterHelper.Callback;
import static android.support.v7.widget.AdapterHelper.UpdateOp;
-import android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
/**
* A flexible view for providing a limited window into a large data set.
@@ -147,6 +154,11 @@
private static final boolean DEBUG = false;
+ private static final int[] NESTED_SCROLLING_ATTRS
+ = {16843830 /* android.R.attr.nestedScrollingEnabled */};
+
+ private static final int[] CLIP_TO_PADDING_ATTR = {android.R.attr.clipToPadding};
+
/**
* On Kitkat and JB MR2, there is a bug which prevents DisplayList from being invalidated if
* a View is two levels deep(wrt to ViewHolder.itemView). DisplayList can be invalidated by
@@ -156,8 +168,14 @@
*/
private static final boolean FORCE_INVALIDATE_DISPLAY_LIST = Build.VERSION.SDK_INT == 18
|| Build.VERSION.SDK_INT == 19 || Build.VERSION.SDK_INT == 20;
+ /**
+ * On M+, an unspecified measure spec may include a hint which we can use. On older platforms,
+ * this value might be garbage. To save LayoutManagers from it, RecyclerView sets the size to
+ * 0 when mode is unspecified.
+ */
+ static final boolean ALLOW_SIZE_IN_UNSPECIFIED_SPEC = Build.VERSION.SDK_INT >= 23;
- private static final boolean DISPATCH_TEMP_DETACH = false;
+ static final boolean DISPATCH_TEMP_DETACH = false;
public static final int HORIZONTAL = 0;
public static final int VERTICAL = 1;
@@ -275,6 +293,11 @@
// a layout request will happen, we should not do layout here.
return;
}
+ if (!mIsAttached) {
+ requestLayout();
+ // if we are not attached yet, mark us as requiring layout and skip
+ return;
+ }
if (mLayoutFrozen) {
mLayoutRequestEaten = true;
return; //we'll process updates when ice age ends.
@@ -284,17 +307,22 @@
};
private final Rect mTempRect = new Rect();
+ private final Rect mTempRect2 = new Rect();
+ private final RectF mTempRectF = new RectF();
private Adapter mAdapter;
- private LayoutManager mLayout;
+ @VisibleForTesting LayoutManager mLayout;
private RecyclerListener mRecyclerListener;
- private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<ItemDecoration>();
+ private final ArrayList<ItemDecoration> mItemDecorations = new ArrayList<>();
private final ArrayList<OnItemTouchListener> mOnItemTouchListeners =
- new ArrayList<OnItemTouchListener>();
+ new ArrayList<>();
private OnItemTouchListener mActiveOnItemTouchListener;
private boolean mIsAttached;
private boolean mHasFixedSize;
- private boolean mFirstLayoutComplete;
- private boolean mEatRequestLayout;
+ @VisibleForTesting boolean mFirstLayoutComplete;
+
+ // Counting lock to control whether we should ignore requestLayout calls from children or not.
+ private int mEatRequestLayout = 0;
+
private boolean mLayoutRequestEaten;
private boolean mLayoutFrozen;
private boolean mIgnoreMotionEventTillDown;
@@ -361,6 +389,7 @@
private final int mMaxFlingVelocity;
// This value is used when handling generic motion events.
private float mScrollFactor = Float.MIN_VALUE;
+ private boolean mPreserveFocusAfterLayout = true;
private final ViewFlinger mViewFlinger = new ViewFlinger();
@@ -382,7 +411,7 @@
// preserved not to create a new one in each layout pass
private final int[] mMinMaxLayoutPositions = new int[2];
- private final NestedScrollingChildHelper mScrollingChildHelper;
+ private NestedScrollingChildHelper mScrollingChildHelper;
private final int[] mScrollOffset = new int[2];
private final int[] mScrollConsumed = new int[2];
private final int[] mNestedOffsets = new int[2];
@@ -410,8 +439,8 @@
private final ViewInfoStore.ProcessCallback mViewInfoProcessCallback =
new ViewInfoStore.ProcessCallback() {
@Override
- public void processDisappeared(ViewHolder viewHolder, ItemHolderInfo info,
- ItemHolderInfo postInfo) {
+ public void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo info,
+ @Nullable ItemHolderInfo postInfo) {
mRecycler.unscrapView(viewHolder);
animateDisappearance(viewHolder, info, postInfo);
}
@@ -452,6 +481,13 @@
public RecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
+ if (attrs != null) {
+ TypedArray a = context.obtainStyledAttributes(attrs, CLIP_TO_PADDING_ATTR, defStyle, 0);
+ mClipToPadding = a.getBoolean(0, true);
+ a.recycle();
+ } else {
+ mClipToPadding = true;
+ }
setScrollContainer(true);
setFocusableInTouchMode(true);
final int version = Build.VERSION.SDK_INT;
@@ -476,17 +512,34 @@
.getSystemService(Context.ACCESSIBILITY_SERVICE);
setAccessibilityDelegateCompat(new RecyclerViewAccessibilityDelegate(this));
// Create the layoutManager if specified.
+
+ boolean nestedScrollingEnabled = true;
+
if (attrs != null) {
int defStyleRes = 0;
TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.RecyclerView,
defStyle, defStyleRes);
String layoutManagerName = a.getString(R.styleable.RecyclerView_layoutManager);
+ int descendantFocusability = a.getInt(
+ R.styleable.RecyclerView_android_descendantFocusability, -1);
+ if (descendantFocusability == -1) {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
+ }
a.recycle();
createLayoutManager(context, layoutManagerName, attrs, defStyle, defStyleRes);
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ a = context.obtainStyledAttributes(attrs, NESTED_SCROLLING_ATTRS,
+ defStyle, defStyleRes);
+ nestedScrollingEnabled = a.getBoolean(0, true);
+ a.recycle();
+ }
+ } else {
+ setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
}
- mScrollingChildHelper = new NestedScrollingChildHelper(this);
- setNestedScrollingEnabled(true);
+ // Re-set whether nested scrolling is enabled so that it is set on all API levels
+ setNestedScrollingEnabled(nestedScrollingEnabled);
}
/**
@@ -755,9 +808,13 @@
}
/**
- * RecyclerView can perform several optimizations if it can know in advance that changes in
- * adapter content cannot change the size of the RecyclerView itself.
- * If your use of RecyclerView falls into this category, set this to true.
+ * RecyclerView can perform several optimizations if it can know in advance that RecyclerView's
+ * size is not affected by the adapter contents. RecyclerView can still change its size based
+ * on other factors (e.g. its parent's size) but this size calculation cannot depend on the
+ * size of its children or contents of its adapter (except the number of items in the adapter).
+ * <p>
+ * If your use of RecyclerView falls into this category, set this to {@code true}. It will allow
+ * RecyclerView to avoid invalidating the whole layout when its adapter contents change.
*
* @param hasFixedSize true if adapter changes cannot affect the size of the RecyclerView.
*/
@@ -948,7 +1005,7 @@
*/
public void addOnChildAttachStateChangeListener(OnChildAttachStateChangeListener listener) {
if (mOnChildAttachStateListeners == null) {
- mOnChildAttachStateListeners = new ArrayList<OnChildAttachStateChangeListener>();
+ mOnChildAttachStateListeners = new ArrayList<>();
}
mOnChildAttachStateListeners.add(listener);
}
@@ -991,6 +1048,7 @@
if (layout == mLayout) {
return;
}
+ stopScroll();
// TODO We should do this switch a dispachLayout pass and animate children. There is a good
// chance that LayoutManagers will re-use views.
if (mLayout != null) {
@@ -1031,6 +1089,11 @@
@Override
protected void onRestoreInstanceState(Parcelable state) {
+ if (!(state instanceof SavedState)) {
+ super.onRestoreInstanceState(state);
+ return;
+ }
+
mPendingSavedState = (SavedState) state;
super.onRestoreInstanceState(mPendingSavedState.getSuperState());
if (mLayout != null && mPendingSavedState.mLayoutState != null) {
@@ -1093,7 +1156,8 @@
Log.d(TAG, "after removing animated view: " + view + ", " + this);
}
}
- resumeRequestLayout(false);
+ // only clear request eaten flag if we removed the view.
+ resumeRequestLayout(!removed);
return removed;
}
@@ -1295,7 +1359,7 @@
*/
public void addOnScrollListener(OnScrollListener listener) {
if (mScrollListeners == null) {
- mScrollListeners = new ArrayList<OnScrollListener>();
+ mScrollListeners = new ArrayList<>();
}
mScrollListeners.add(listener);
}
@@ -1409,11 +1473,7 @@
* This method consumes all deferred changes to avoid that case.
*/
private void consumePendingUpdateOperations() {
- if (!mFirstLayoutComplete) {
- // a layout request will happen, we should not do layout here.
- return;
- }
- if (mDataSetHasChangedAfterLayout) {
+ if (!mFirstLayoutComplete || mDataSetHasChangedAfterLayout) {
TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
@@ -1545,8 +1605,10 @@
*/
@Override
public int computeHorizontalScrollOffset() {
- return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState)
- : 0;
+ if (mLayout == null) {
+ return 0;
+ }
+ return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollOffset(mState) : 0;
}
/**
@@ -1568,6 +1630,9 @@
*/
@Override
public int computeHorizontalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollExtent(mState) : 0;
}
@@ -1588,6 +1653,9 @@
*/
@Override
public int computeHorizontalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
return mLayout.canScrollHorizontally() ? mLayout.computeHorizontalScrollRange(mState) : 0;
}
@@ -1610,6 +1678,9 @@
*/
@Override
public int computeVerticalScrollOffset() {
+ if (mLayout == null) {
+ return 0;
+ }
return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollOffset(mState) : 0;
}
@@ -1631,6 +1702,9 @@
*/
@Override
public int computeVerticalScrollExtent() {
+ if (mLayout == null) {
+ return 0;
+ }
return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollExtent(mState) : 0;
}
@@ -1651,31 +1725,50 @@
*/
@Override
public int computeVerticalScrollRange() {
+ if (mLayout == null) {
+ return 0;
+ }
return mLayout.canScrollVertically() ? mLayout.computeVerticalScrollRange(mState) : 0;
}
void eatRequestLayout() {
- if (!mEatRequestLayout) {
- mEatRequestLayout = true;
- if (!mLayoutFrozen) {
- mLayoutRequestEaten = false;
- }
+ mEatRequestLayout++;
+ if (mEatRequestLayout == 1 && !mLayoutFrozen) {
+ mLayoutRequestEaten = false;
}
}
void resumeRequestLayout(boolean performLayoutChildren) {
- if (mEatRequestLayout) {
+ if (mEatRequestLayout < 1) {
+ //noinspection PointlessBooleanExpression
+ if (DEBUG) {
+ throw new IllegalStateException("invalid eat request layout count");
+ }
+ mEatRequestLayout = 1;
+ }
+ if (!performLayoutChildren) {
+ // Reset the layout request eaten counter.
+ // This is necessary since eatRequest calls can be nested in which case the outher
+ // call will override the inner one.
+ // for instance:
+ // eat layout for process adapter updates
+ // eat layout for dispatchLayout
+ // a bunch of req layout calls arrive
+
+ mLayoutRequestEaten = false;
+ }
+ if (mEatRequestLayout == 1) {
// when layout is frozen we should delay dispatchLayout()
if (performLayoutChildren && mLayoutRequestEaten && !mLayoutFrozen &&
mLayout != null && mAdapter != null) {
dispatchLayout();
}
- mEatRequestLayout = false;
if (!mLayoutFrozen) {
mLayoutRequestEaten = false;
}
}
+ mEatRequestLayout--;
}
/**
@@ -1704,7 +1797,7 @@
if (frozen != mLayoutFrozen) {
assertNotInLayoutOrScroll("Do not setLayoutFrozen in layout or scroll");
if (!frozen) {
- mLayoutFrozen = frozen;
+ mLayoutFrozen = false;
if (mLayoutRequestEaten && mLayout != null && mAdapter != null) {
requestLayout();
}
@@ -1714,7 +1807,7 @@
MotionEvent cancelEvent = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
onTouchEvent(cancelEvent);
- mLayoutFrozen = frozen;
+ mLayoutFrozen = true;
mIgnoreMotionEventTillDown = true;
stopScroll();
}
@@ -1989,23 +2082,155 @@
mLeftGlow = mRightGlow = mTopGlow = mBottomGlow = null;
}
- // Focus handling
-
+ /**
+ * Since RecyclerView is a collection ViewGroup that includes virtual children (items that are
+ * in the Adapter but not visible in the UI), it employs a more involved focus search strategy
+ * that differs from other ViewGroups.
+ * <p>
+ * It first does a focus search within the RecyclerView. If this search finds a View that is in
+ * the focus direction with respect to the currently focused View, RecyclerView returns that
+ * child as the next focus target. When it cannot find such child, it calls
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} to layout more Views
+ * in the focus search direction. If LayoutManager adds a View that matches the
+ * focus search criteria, it will be returned as the focus search result. Otherwise,
+ * RecyclerView will call parent to handle the focus search like a regular ViewGroup.
+ * <p>
+ * When the direction is {@link View#FOCUS_FORWARD} or {@link View#FOCUS_BACKWARD}, a View that
+ * is not in the focus direction is still valid focus target which may not be the desired
+ * behavior if the Adapter has more children in the focus direction. To handle this case,
+ * RecyclerView converts the focus direction to an absolute direction and makes a preliminary
+ * focus search in that direction. If there are no Views to gain focus, it will call
+ * {@link LayoutManager#onFocusSearchFailed(View, int, Recycler, State)} before running a
+ * focus search with the original (relative) direction. This allows RecyclerView to provide
+ * better candidates to the focus search while still allowing the view system to take focus from
+ * the RecyclerView and give it to a more suitable child if such child exists.
+ *
+ * @param focused The view that currently has focus
+ * @param direction One of {@link View#FOCUS_UP}, {@link View#FOCUS_DOWN},
+ * {@link View#FOCUS_LEFT}, {@link View#FOCUS_RIGHT}, {@link View#FOCUS_FORWARD},
+ * {@link View#FOCUS_BACKWARD} or 0 for not applicable.
+ *
+ * @return A new View that can be the next focus after the focused View
+ */
@Override
public View focusSearch(View focused, int direction) {
View result = mLayout.onInterceptFocusSearch(focused, direction);
if (result != null) {
return result;
}
+ final boolean canRunFocusFailure = mAdapter != null && mLayout != null
+ && !isComputingLayout() && !mLayoutFrozen;
+
final FocusFinder ff = FocusFinder.getInstance();
- result = ff.findNextFocus(this, focused, direction);
- if (result == null && mAdapter != null && mLayout != null && !isComputingLayout()
- && !mLayoutFrozen) {
- eatRequestLayout();
- result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
- resumeRequestLayout(false);
+ if (canRunFocusFailure
+ && (direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD)) {
+ // convert direction to absolute direction and see if we have a view there and if not
+ // tell LayoutManager to add if it can.
+ boolean needsFocusFailureLayout = false;
+ if (mLayout.canScrollVertically()) {
+ final int absDir =
+ direction == View.FOCUS_FORWARD ? View.FOCUS_DOWN : View.FOCUS_UP;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ }
+ if (!needsFocusFailureLayout && mLayout.canScrollHorizontally()) {
+ boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+ final int absDir = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ final View found = ff.findNextFocus(this, focused, absDir);
+ needsFocusFailureLayout = found == null;
+ }
+ if (needsFocusFailureLayout) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
+ result = ff.findNextFocus(this, focused, direction);
+ } else {
+ result = ff.findNextFocus(this, focused, direction);
+ if (result == null && canRunFocusFailure) {
+ consumePendingUpdateOperations();
+ final View focusedItemView = findContainingItemView(focused);
+ if (focusedItemView == null) {
+ // panic, focused view is not a child anymore, cannot call super.
+ return null;
+ }
+ eatRequestLayout();
+ result = mLayout.onFocusSearchFailed(focused, direction, mRecycler, mState);
+ resumeRequestLayout(false);
+ }
}
- return result != null ? result : super.focusSearch(focused, direction);
+ return isPreferredNextFocus(focused, result, direction)
+ ? result : super.focusSearch(focused, direction);
+ }
+
+ /**
+ * Checks if the new focus candidate is a good enough candidate such that RecyclerView will
+ * assign it as the next focus View instead of letting view hierarchy decide.
+ * A good candidate means a View that is aligned in the focus direction wrt the focused View
+ * and is not the RecyclerView itself.
+ * When this method returns false, RecyclerView will let the parent make the decision so the
+ * same View may still get the focus as a result of that search.
+ */
+ private boolean isPreferredNextFocus(View focused, View next, int direction) {
+ if (next == null || next == this) {
+ return false;
+ }
+ if (focused == null) {
+ return true;
+ }
+
+ if(direction == View.FOCUS_FORWARD || direction == View.FOCUS_BACKWARD) {
+ final boolean rtl = mLayout.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+ final int absHorizontal = (direction == View.FOCUS_FORWARD) ^ rtl
+ ? View.FOCUS_RIGHT : View.FOCUS_LEFT;
+ if (isPreferredNextFocusAbsolute(focused, next, absHorizontal)) {
+ return true;
+ }
+ if (direction == View.FOCUS_FORWARD) {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_DOWN);
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, View.FOCUS_UP);
+ }
+ } else {
+ return isPreferredNextFocusAbsolute(focused, next, direction);
+ }
+
+ }
+
+ /**
+ * Logic taken from FocusSarch#isCandidate
+ */
+ private boolean isPreferredNextFocusAbsolute(View focused, View next, int direction) {
+ mTempRect.set(0, 0, focused.getWidth(), focused.getHeight());
+ mTempRect2.set(0, 0, next.getWidth(), next.getHeight());
+ offsetDescendantRectToMyCoords(focused, mTempRect);
+ offsetDescendantRectToMyCoords(next, mTempRect2);
+ switch (direction) {
+ case View.FOCUS_LEFT:
+ return (mTempRect.right > mTempRect2.right
+ || mTempRect.left >= mTempRect2.right)
+ && mTempRect.left > mTempRect2.left;
+ case View.FOCUS_RIGHT:
+ return (mTempRect.left < mTempRect2.left
+ || mTempRect.right <= mTempRect2.left)
+ && mTempRect.right < mTempRect2.right;
+ case View.FOCUS_UP:
+ return (mTempRect.bottom > mTempRect2.bottom
+ || mTempRect.top >= mTempRect2.bottom)
+ && mTempRect.top > mTempRect2.top;
+ case View.FOCUS_DOWN:
+ return (mTempRect.top < mTempRect2.top
+ || mTempRect.bottom <= mTempRect2.top)
+ && mTempRect.bottom < mTempRect2.bottom;
+ }
+ throw new IllegalArgumentException("direction must be absolute. received:" + direction);
}
@Override
@@ -2049,11 +2274,21 @@
}
@Override
+ protected boolean onRequestFocusInDescendants(int direction, Rect previouslyFocusedRect) {
+ if (isComputingLayout()) {
+ // if we are in the middle of a layout calculation, don't let any child take focus.
+ // RV will handle it after layout calculation is finished.
+ return false;
+ }
+ return super.onRequestFocusInDescendants(direction, previouslyFocusedRect);
+ }
+
+ @Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mLayoutOrScrollCounter = 0;
mIsAttached = true;
- mFirstLayoutComplete = false;
+ mFirstLayoutComplete = mFirstLayoutComplete && !isLayoutRequested();
if (mLayout != null) {
mLayout.dispatchAttachedToWindow(this);
}
@@ -2066,8 +2301,6 @@
if (mItemAnimator != null) {
mItemAnimator.endAnimations();
}
- mFirstLayoutComplete = false;
-
stopScroll();
mIsAttached = false;
if (mLayout != null) {
@@ -2526,79 +2759,93 @@
} else {
return 0; //listPreferredItemHeight is not defined, no generic scrolling
}
-
}
return mScrollFactor;
}
@Override
protected void onMeasure(int widthSpec, int heightSpec) {
- if (mAdapterUpdateDuringMeasure) {
- eatRequestLayout();
- processAdapterUpdatesAndSetAnimationFlags();
-
- if (mState.mRunPredictiveAnimations) {
- // TODO: try to provide a better approach.
- // When RV decides to run predictive animations, we need to measure in pre-layout
- // state so that pre-layout pass results in correct layout.
- // On the other hand, this will prevent the layout manager from resizing properly.
- mState.mInPreLayout = true;
- } else {
- // consume remaining updates to provide a consistent state with the layout pass.
- mAdapterHelper.consumeUpdatesInOnePass();
- mState.mInPreLayout = false;
- }
- mAdapterUpdateDuringMeasure = false;
- resumeRequestLayout(false);
- }
-
- if (mAdapter != null) {
- mState.mItemCount = mAdapter.getItemCount();
- } else {
- mState.mItemCount = 0;
- }
if (mLayout == null) {
defaultOnMeasure(widthSpec, heightSpec);
- } else {
- mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ return;
}
+ if (mLayout.mAutoMeasure) {
+ final int widthMode = MeasureSpec.getMode(widthSpec);
+ final int heightMode = MeasureSpec.getMode(heightSpec);
+ final boolean skipMeasure = widthMode == MeasureSpec.EXACTLY
+ && heightMode == MeasureSpec.EXACTLY;
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ if (skipMeasure || mAdapter == null) {
+ return;
+ }
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ }
+ // set dimensions in 2nd step. Pre-layout should happen with old dimensions for
+ // consistency
+ mLayout.setMeasureSpecs(widthSpec, heightSpec);
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
- mState.mInPreLayout = false; // clear
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+
+ // if RecyclerView has non-exact width and height and if there is at least one child
+ // which also has non-exact width & height, we have to re-measure.
+ if (mLayout.shouldMeasureTwice()) {
+ mLayout.setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(getMeasuredWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(getMeasuredHeight(), MeasureSpec.EXACTLY));
+ mState.mIsMeasuring = true;
+ dispatchLayoutStep2();
+ // now we can get the width and height from the children.
+ mLayout.setMeasuredDimensionFromChildren(widthSpec, heightSpec);
+ }
+ } else {
+ if (mHasFixedSize) {
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ return;
+ }
+ // custom onMeasure
+ if (mAdapterUpdateDuringMeasure) {
+ eatRequestLayout();
+ processAdapterUpdatesAndSetAnimationFlags();
+
+ if (mState.mRunPredictiveAnimations) {
+ mState.mInPreLayout = true;
+ } else {
+ // consume remaining updates to provide a consistent state with the layout pass.
+ mAdapterHelper.consumeUpdatesInOnePass();
+ mState.mInPreLayout = false;
+ }
+ mAdapterUpdateDuringMeasure = false;
+ resumeRequestLayout(false);
+ }
+
+ if (mAdapter != null) {
+ mState.mItemCount = mAdapter.getItemCount();
+ } else {
+ mState.mItemCount = 0;
+ }
+ eatRequestLayout();
+ mLayout.onMeasure(mRecycler, mState, widthSpec, heightSpec);
+ resumeRequestLayout(false);
+ mState.mInPreLayout = false; // clear
+ }
}
/**
* Used when onMeasure is called before layout manager is set
*/
- private void defaultOnMeasure(int widthSpec, int heightSpec) {
- final int widthMode = MeasureSpec.getMode(widthSpec);
- final int heightMode = MeasureSpec.getMode(heightSpec);
- final int widthSize = MeasureSpec.getSize(widthSpec);
- final int heightSize = MeasureSpec.getSize(heightSpec);
-
- int width = 0;
- int height = 0;
-
- switch (widthMode) {
- case MeasureSpec.EXACTLY:
- case MeasureSpec.AT_MOST:
- width = widthSize;
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- width = ViewCompat.getMinimumWidth(this);
- break;
- }
-
- switch (heightMode) {
- case MeasureSpec.EXACTLY:
- case MeasureSpec.AT_MOST:
- height = heightSize;
- break;
- case MeasureSpec.UNSPECIFIED:
- default:
- height = ViewCompat.getMinimumHeight(this);
- break;
- }
+ void defaultOnMeasure(int widthSpec, int heightSpec) {
+ // calling LayoutManager here is not pretty but that API is already public and it is better
+ // than creating another method since this is internal.
+ final int width = LayoutManager.chooseSize(widthSpec,
+ getPaddingLeft() + getPaddingRight(),
+ ViewCompat.getMinimumWidth(this));
+ final int height = LayoutManager.chooseSize(heightSpec,
+ getPaddingTop() + getPaddingBottom(),
+ ViewCompat.getMinimumHeight(this));
setMeasuredDimension(width, height);
}
@@ -2608,6 +2855,7 @@
super.onSizeChanged(w, h, oldw, oldh);
if (w != oldw || h != oldh) {
invalidateGlows();
+ // layout's w/h are updated during measure/layout steps.
}
}
@@ -2763,7 +3011,7 @@
// simple animations are a subset of advanced animations (which will cause a
// pre-layout step)
// If layout supports predictive animations, pre-process to decide if we want to run them
- if (mItemAnimator != null && mLayout.supportsPredictiveItemAnimations()) {
+ if (predictiveItemAnimationsEnabled()) {
mAdapterHelper.preProcess();
} else {
mAdapterHelper.consumeUpdatesInOnePass();
@@ -2806,16 +3054,115 @@
void dispatchLayout() {
if (mAdapter == null) {
Log.e(TAG, "No adapter attached; skipping layout");
+ // leave the state in START
return;
}
if (mLayout == null) {
Log.e(TAG, "No layout manager attached; skipping layout");
+ // leave the state in START
return;
}
- mViewInfoStore.clear();
- eatRequestLayout();
- onEnterLayoutOrScroll();
+ mState.mIsMeasuring = false;
+ if (mState.mLayoutStep == State.STEP_START) {
+ dispatchLayoutStep1();
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else if (mAdapterHelper.hasUpdates() || mLayout.getWidth() != getWidth() ||
+ mLayout.getHeight() != getHeight()) {
+ // First 2 steps are done in onMeasure but looks like we have to run again due to
+ // changed size.
+ mLayout.setExactMeasureSpecsFrom(this);
+ dispatchLayoutStep2();
+ } else {
+ // always make sure we sync them (to ensure mode is exact)
+ mLayout.setExactMeasureSpecsFrom(this);
+ }
+ dispatchLayoutStep3();
+ }
+ private void saveFocusInfo() {
+ View child = null;
+ if (mPreserveFocusAfterLayout && hasFocus() && mAdapter != null) {
+ child = getFocusedChild();
+ }
+
+ final ViewHolder focusedVh = child == null ? null : findContainingViewHolder(child);
+ if (focusedVh == null) {
+ resetFocusInfo();
+ } else {
+ mState.mFocusedItemId = mAdapter.hasStableIds() ? focusedVh.getItemId() : NO_ID;
+ mState.mFocusedItemPosition = mDataSetHasChangedAfterLayout ? NO_POSITION :
+ focusedVh.getAdapterPosition();
+ mState.mFocusedSubChildId = getDeepestFocusedViewWithId(focusedVh.itemView);
+ }
+ }
+
+ private void resetFocusInfo() {
+ mState.mFocusedItemId = NO_ID;
+ mState.mFocusedItemPosition = NO_POSITION;
+ mState.mFocusedSubChildId = View.NO_ID;
+ }
+
+ private void recoverFocusFromState() {
+ if (!mPreserveFocusAfterLayout || mAdapter == null || !hasFocus()) {
+ return;
+ }
+ // only recover focus if RV itself has the focus or the focused view is hidden
+ if (!isFocused()) {
+ final View focusedChild = getFocusedChild();
+ if (focusedChild == null || !mChildHelper.isHidden(focusedChild)) {
+ return;
+ }
+ }
+ ViewHolder focusTarget = null;
+ if (mState.mFocusedItemPosition != NO_POSITION) {
+ focusTarget = findViewHolderForAdapterPosition(mState.mFocusedItemPosition);
+ }
+ if (focusTarget == null && mState.mFocusedItemId != NO_ID && mAdapter.hasStableIds()) {
+ focusTarget = findViewHolderForItemId(mState.mFocusedItemId);
+ }
+ if (focusTarget == null || focusTarget.itemView.hasFocus() ||
+ !focusTarget.itemView.hasFocusable()) {
+ return;
+ }
+ // looks like the focused item has been replaced with another view that represents the
+ // same item in the adapter. Request focus on that.
+ View viewToFocus = focusTarget.itemView;
+ if (mState.mFocusedSubChildId != NO_ID) {
+ View child = focusTarget.itemView.findViewById(mState.mFocusedSubChildId);
+ if (child != null && child.isFocusable()) {
+ viewToFocus = child;
+ }
+ }
+ viewToFocus.requestFocus();
+ }
+
+ private int getDeepestFocusedViewWithId(View view) {
+ int lastKnownId = view.getId();
+ while (!view.isFocused() && view instanceof ViewGroup && view.hasFocus()) {
+ view = ((ViewGroup) view).getFocusedChild();
+ final int id = view.getId();
+ if (id != View.NO_ID) {
+ lastKnownId = view.getId();
+ }
+ }
+ return lastKnownId;
+ }
+
+ /**
+ * The first step of a layout where we;
+ * - process adapter updates
+ * - decide which animation should run
+ * - save information about current views
+ * - If necessary, run predictive layout and save its information
+ */
+ private void dispatchLayoutStep1() {
+ mState.assertLayoutStep(State.STEP_START);
+ mState.mIsMeasuring = false;
+ eatRequestLayout();
+ mViewInfoStore.clear();
+ onEnterLayoutOrScroll();
+ saveFocusInfo();
processAdapterUpdatesAndSetAnimationFlags();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
@@ -2888,10 +3235,23 @@
}
// we don't process disappearing list because they may re-appear in post layout pass.
clearOldPositions();
- mAdapterHelper.consumePostponedUpdates();
} else {
clearOldPositions();
}
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ mState.mLayoutStep = State.STEP_LAYOUT;
+ }
+
+ /**
+ * The second layout step where we do the actual layout of the views for the final state.
+ * This step might be run multiple times if necessary (e.g. measure).
+ */
+ private void dispatchLayoutStep2() {
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.assertLayoutStep(State.STEP_LAYOUT | State.STEP_ANIMATIONS);
+ mAdapterHelper.consumeUpdatesInOnePass();
mState.mItemCount = mAdapter.getItemCount();
mState.mDeletedInvisibleItemCountSincePreviousLayout = 0;
@@ -2904,11 +3264,25 @@
// onLayoutChildren may have caused client code to disable item animations; re-check
mState.mRunSimpleAnimations = mState.mRunSimpleAnimations && mItemAnimator != null;
+ mState.mLayoutStep = State.STEP_ANIMATIONS;
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
+ }
+ /**
+ * The final step of the layout where we save the information about views for animations,
+ * trigger animations and do any necessary cleanup.
+ */
+ private void dispatchLayoutStep3() {
+ mState.assertLayoutStep(State.STEP_ANIMATIONS);
+ eatRequestLayout();
+ onEnterLayoutOrScroll();
+ mState.mLayoutStep = State.STEP_START;
if (mState.mRunSimpleAnimations) {
// Step 3: Find out where things are now, and process change animations.
- int count = mChildHelper.getChildCount();
- for (int i = 0; i < count; ++i) {
+ // traverse list in reverse because we may call animateChange in the loop which may
+ // remove the target view holder.
+ for (int i = mChildHelper.getChildCount() - 1; i >= 0; i--) {
ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
if (holder.shouldIgnore()) {
continue;
@@ -2919,9 +3293,34 @@
ViewHolder oldChangeViewHolder = mViewInfoStore.getFromOldChangeHolders(key);
if (oldChangeViewHolder != null && !oldChangeViewHolder.shouldIgnore()) {
// run a change animation
- final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
+
+ // If an Item is CHANGED but the updated version is disappearing, it creates
+ // a conflicting case.
+ // Since a view that is marked as disappearing is likely to be going out of
+ // bounds, we run a change animation. Both views will be cleaned automatically
+ // once their animations finish.
+ // On the other hand, if it is the same view holder instance, we run a
+ // disappearing animation instead because we are not going to rebind the updated
+ // VH unless it is enforced by the layout manager.
+ final boolean oldDisappearing = mViewInfoStore.isDisappearing(
oldChangeViewHolder);
- animateChange(oldChangeViewHolder, holder, preInfo, animationInfo);
+ final boolean newDisappearing = mViewInfoStore.isDisappearing(holder);
+ if (oldDisappearing && oldChangeViewHolder == holder) {
+ // run disappear animation instead of change
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ } else {
+ final ItemHolderInfo preInfo = mViewInfoStore.popFromPreLayout(
+ oldChangeViewHolder);
+ // we add and remove so that any post info is merged.
+ mViewInfoStore.addToPostLayout(holder, animationInfo);
+ ItemHolderInfo postInfo = mViewInfoStore.popFromPostLayout(holder);
+ if (preInfo == null) {
+ handleMissingPreInfoForChangeError(key, holder, oldChangeViewHolder);
+ } else {
+ animateChange(oldChangeViewHolder, holder, preInfo, postInfo,
+ oldDisappearing, newDisappearing);
+ }
+ }
} else {
mViewInfoStore.addToPostLayout(holder, animationInfo);
}
@@ -2930,22 +3329,71 @@
// Step 4: Process view info lists and trigger animations
mViewInfoStore.process(mViewInfoProcessCallback);
}
- resumeRequestLayout(false);
+
mLayout.removeAndRecycleScrapInt(mRecycler);
mState.mPreviousLayoutItemCount = mState.mItemCount;
mDataSetHasChangedAfterLayout = false;
mState.mRunSimpleAnimations = false;
mState.mRunPredictiveAnimations = false;
- onExitLayoutOrScroll();
mLayout.mRequestedSimpleAnimations = false;
if (mRecycler.mChangedScrap != null) {
mRecycler.mChangedScrap.clear();
}
+ mLayout.onLayoutCompleted(mState);
+ onExitLayoutOrScroll();
+ resumeRequestLayout(false);
mViewInfoStore.clear();
if (didChildRangeChange(mMinMaxLayoutPositions[0], mMinMaxLayoutPositions[1])) {
dispatchOnScrolled(0, 0);
}
+ recoverFocusFromState();
+ resetFocusInfo();
+ }
+
+ /**
+ * This handles the case where there is an unexpected VH missing in the pre-layout map.
+ * <p>
+ * We might be able to detect the error in the application which will help the developer to
+ * resolve the issue.
+ * <p>
+ * If it is not an expected error, we at least print an error to notify the developer and ignore
+ * the animation.
+ *
+ * https://code.google.com/p/android/issues/detail?id=193958
+ *
+ * @param key The change key
+ * @param holder Current ViewHolder
+ * @param oldChangeViewHolder Changed ViewHolder
+ */
+ private void handleMissingPreInfoForChangeError(long key,
+ ViewHolder holder, ViewHolder oldChangeViewHolder) {
+ // check if two VH have the same key, if so, print that as an error
+ final int childCount = mChildHelper.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View view = mChildHelper.getChildAt(i);
+ ViewHolder other = getChildViewHolderInt(view);
+ if (other == holder) {
+ continue;
+ }
+ final long otherKey = getChangedHolderKey(other);
+ if (otherKey == key) {
+ if (mAdapter != null && mAdapter.hasStableIds()) {
+ throw new IllegalStateException("Two different ViewHolders have the same stable"
+ + " ID. Stable IDs in your adapter MUST BE unique and SHOULD NOT"
+ + " change.\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ } else {
+ throw new IllegalStateException("Two different ViewHolders have the same change"
+ + " ID. This might happen due to inconsistent Adapter update events or"
+ + " if the LayoutManager lays out the same View multiple times."
+ + "\n ViewHolder 1:" + other + " \n View Holder 2:" + holder);
+ }
+ }
+ }
+ // Very unlikely to happen but if it does, notify the developer.
+ Log.e(TAG, "Problem while matching changed view holders with the new"
+ + "ones. The pre-layout information for the change holder " + oldChangeViewHolder
+ + " cannot be found but it is necessary for " + holder);
}
/**
@@ -2995,17 +3443,10 @@
if (count == 0) {
return minPositionPreLayout != 0 || maxPositionPreLayout != 0;
}
- for (int i = 0; i < count; ++i) {
- final ViewHolder holder = getChildViewHolderInt(mChildHelper.getChildAt(i));
- if (holder.shouldIgnore()) {
- continue;
- }
- final int pos = holder.getLayoutPosition();
- if (pos < minPositionPreLayout || pos > maxPositionPreLayout) {
- return true;
- }
- }
- return false;
+ // get the new min max
+ findMinMaxChildLayoutPositions(mMinMaxLayoutPositions);
+ return mMinMaxLayoutPositions[0] != minPositionPreLayout ||
+ mMinMaxLayoutPositions[1] != maxPositionPreLayout;
}
@Override
@@ -3049,9 +3490,16 @@
}
private void animateChange(@NonNull ViewHolder oldHolder, @NonNull ViewHolder newHolder,
- @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo) {
+ @NonNull ItemHolderInfo preInfo, @NonNull ItemHolderInfo postInfo,
+ boolean oldHolderDisappearing, boolean newHolderDisappearing) {
oldHolder.setIsRecyclable(false);
+ if (oldHolderDisappearing) {
+ addAnimatingView(oldHolder);
+ }
if (oldHolder != newHolder) {
+ if (newHolderDisappearing) {
+ addAnimatingView(newHolder);
+ }
oldHolder.mShadowedHolder = newHolder;
// old holder should disappear after animation ends
addAnimatingView(oldHolder);
@@ -3066,17 +3514,15 @@
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
- eatRequestLayout();
TraceCompat.beginSection(TRACE_ON_LAYOUT_TAG);
dispatchLayout();
TraceCompat.endSection();
- resumeRequestLayout(false);
mFirstLayoutComplete = true;
}
@Override
public void requestLayout() {
- if (!mEatRequestLayout && !mLayoutFrozen) {
+ if (mEatRequestLayout == 0 && !mLayoutFrozen) {
super.requestLayout();
} else {
mLayoutRequestEaten = true;
@@ -3339,7 +3785,8 @@
}
private boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
- return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder);
+ return mItemAnimator == null || mItemAnimator.canReuseUpdatedViewHolder(viewHolder,
+ viewHolder.getUnmodifiedPayloads());
}
private void setDataSetChangedAfterLayout() {
@@ -3390,6 +3837,39 @@
}
/**
+ * Returns true if the RecyclerView should attempt to preserve currently focused Adapter Item's
+ * focus even if the View representing the Item is replaced during a layout calculation.
+ * <p>
+ * By default, this value is {@code true}.
+ *
+ * @return True if the RecyclerView will try to preserve focused Item after a layout if it loses
+ * focus.
+ *
+ * @see #setPreserveFocusAfterLayout(boolean)
+ */
+ public boolean getPreserveFocusAfterLayout() {
+ return mPreserveFocusAfterLayout;
+ }
+
+ /**
+ * Set whether the RecyclerView should try to keep the same Item focused after a layout
+ * calculation or not.
+ * <p>
+ * Usually, LayoutManagers keep focused views visible before and after layout but sometimes,
+ * views may lose focus during a layout calculation as their state changes or they are replaced
+ * with another view due to type change or animation. In these cases, RecyclerView can request
+ * focus on the new view automatically.
+ *
+ * @param preserveFocusAfterLayout Whether RecyclerView should preserve focused Item during a
+ * layout calculations. Defaults to true.
+ *
+ * @see #getPreserveFocusAfterLayout()
+ */
+ public void setPreserveFocusAfterLayout(boolean preserveFocusAfterLayout) {
+ mPreserveFocusAfterLayout = preserveFocusAfterLayout;
+ }
+
+ /**
* Retrieve the {@link ViewHolder} for the given child view.
*
* @param child Child of this RecyclerView to query for its ViewHolder
@@ -3404,6 +3884,44 @@
return getChildViewHolderInt(child);
}
+ /**
+ * Traverses the ancestors 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;
@@ -3480,6 +3998,10 @@
* next layout calculation. If there are pending adapter updates, the return value of this
* method may not match your adapter contents. You can use
* #{@link ViewHolder#getAdapterPosition()} to get the current adapter position of a ViewHolder.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * with the same layout position representing the same Item. In this case, the updated
+ * ViewHolder will be returned.
*
* @param position The position of the item in the data set of the adapter
* @return The ViewHolder at <code>position</code> or null if there is no such item
@@ -3498,6 +4020,9 @@
* <p>
* This method checks only the children of RecyclerView. If the item at the given
* <code>position</code> is not laid out, it <em>will not</em> create a new one.
+ * <p>
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders
+ * representing the same Item. In this case, the updated ViewHolder will be returned.
*
* @param position The position of the item in the data set of the adapter
* @return The ViewHolder at <code>position</code> or null if there is no such item
@@ -3507,25 +4032,37 @@
return null;
}
final int childCount = mChildHelper.getUnfilteredChildCount();
+ // hidden VHs are not preferred but if that is the only one we find, we rather return it
+ ViewHolder hidden = null;
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.isRemoved() && getAdapterPositionFor(holder) == position) {
- return holder;
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
}
}
- return null;
+ return hidden;
}
ViewHolder findViewHolderForPosition(int position, boolean checkNewPosition) {
final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
if (holder != null && !holder.isRemoved()) {
if (checkNewPosition) {
- if (holder.mPosition == position) {
- return holder;
+ if (holder.mPosition != position) {
+ continue;
}
- } else if (holder.getLayoutPosition() == position) {
+ } else if (holder.getLayoutPosition() != position) {
+ continue;
+ }
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
return holder;
}
}
@@ -3533,7 +4070,7 @@
// This method should not query cached views. It creates a problem during adapter updates
// when we are dealing with already laid out views. Also, for the public method, it is more
// reasonable to return null if position is not laid out.
- return null;
+ return hidden;
}
/**
@@ -3544,20 +4081,29 @@
* This method checks only the children of RecyclerView. If the item with the given
* <code>id</code> is not laid out, it <em>will not</em> create a new one.
*
+ * When the ItemAnimator is running a change animation, there might be 2 ViewHolders with the
+ * same id. In this case, the updated ViewHolder will be returned.
+ *
* @param id The id for the requested item
* @return The ViewHolder with the given <code>id</code> or null if there is no such item
*/
public ViewHolder findViewHolderForItemId(long id) {
+ if (mAdapter == null || !mAdapter.hasStableIds()) {
+ return null;
+ }
final int childCount = mChildHelper.getUnfilteredChildCount();
+ ViewHolder hidden = null;
for (int i = 0; i < childCount; i++) {
final ViewHolder holder = getChildViewHolderInt(mChildHelper.getUnfilteredChildAt(i));
- if (holder != null && holder.getItemId() == id) {
- return holder;
+ if (holder != null && !holder.isRemoved() && holder.getItemId() == id) {
+ if (mChildHelper.isHidden(holder.itemView)) {
+ hidden = holder;
+ } else {
+ return holder;
+ }
}
}
- // this method should not query cached views. They are not children so they
- // should not be returned in this public method
- return null;
+ return hidden;
}
/**
@@ -3779,6 +4325,10 @@
@Override
public void run() {
+ if (mLayout == null) {
+ stop();
+ return; // no layout, cannot scroll.
+ }
disableRunOnAnimationRequests();
consumePendingUpdateOperations();
// keep a local reference so that if it is changed during onAnimation method, it won't
@@ -4158,7 +4708,7 @@
private ArrayList<ViewHolder> getScrapHeapForType(int viewType) {
ArrayList<ViewHolder> scrap = mScrap.get(viewType);
if (scrap == null) {
- scrap = new ArrayList<ViewHolder>();
+ scrap = new ArrayList<>();
mScrap.put(viewType, scrap);
if (mMaxScrap.indexOfKey(viewType) < 0) {
mMaxScrap.put(viewType, DEFAULT_MAX_SCRAP);
@@ -4182,7 +4732,7 @@
* may be repositioned by a LayoutManager without remeasurement.</p>
*/
public final class Recycler {
- final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<ViewHolder>();
+ final ArrayList<ViewHolder> mAttachedScrap = new ArrayList<>();
private ArrayList<ViewHolder> mChangedScrap = null;
final ArrayList<ViewHolder> mCachedViews = new ArrayList<ViewHolder>();
@@ -4652,9 +5202,10 @@
if (!holder.hasAnyOfTheFlags(ViewHolder.FLAG_INVALID | ViewHolder.FLAG_REMOVED
| ViewHolder.FLAG_UPDATE)) {
// Retire oldest cached view
- final int cachedViewSize = mCachedViews.size();
- if (cachedViewSize == mViewCacheMax && cachedViewSize > 0) {
+ int cachedViewSize = mCachedViews.size();
+ if (cachedViewSize >= mViewCacheMax && cachedViewSize > 0) {
recycleCachedViewAt(0);
+ cachedViewSize --;
}
if (cachedViewSize < mViewCacheMax) {
mCachedViews.add(holder);
@@ -4961,7 +5512,7 @@
final int cachedCount = mCachedViews.size();
for (int i = 0; i < cachedCount; i++) {
final ViewHolder holder = mCachedViews.get(i);
- if (holder != null && holder.getLayoutPosition() >= insertedAt) {
+ if (holder != null && holder.mPosition >= insertedAt) {
if (DEBUG) {
Log.d(TAG, "offsetPositionRecordsForInsert cached " + i + " holder " +
holder + " now at position " + (holder.mPosition + count));
@@ -4983,14 +5534,14 @@
for (int i = cachedCount - 1; i >= 0; i--) {
final ViewHolder holder = mCachedViews.get(i);
if (holder != null) {
- if (holder.getLayoutPosition() >= removedEnd) {
+ if (holder.mPosition >= removedEnd) {
if (DEBUG) {
Log.d(TAG, "offsetPositionRecordsForRemove cached " + i +
" holder " + holder + " now at position " +
(holder.mPosition - count));
}
holder.offsetPosition(-count, applyToPreLayout);
- } else if (holder.getLayoutPosition() >= removedFrom) {
+ } else if (holder.mPosition >= removedFrom) {
// Item for this view was removed. Dump it from the cache.
holder.addFlags(ViewHolder.FLAG_REMOVED);
recycleCachedViewAt(i);
@@ -5697,7 +6248,6 @@
mOnChildAttachStateListeners.get(i).onChildViewAttachedToWindow(child);
}
}
-
}
/**
@@ -5725,17 +6275,132 @@
private boolean mRequestedSimpleAnimations = false;
- private boolean mIsAttachedToWindow = false;
+ boolean mIsAttachedToWindow = false;
+
+ private boolean mAutoMeasure = false;
+
+ /**
+ * LayoutManager has its own more strict measurement cache to avoid re-measuring a child
+ * if the space that will be given to it is already larger than what it has measured before.
+ */
+ private boolean mMeasurementCacheEnabled = true;
+
+
+ /**
+ * These measure specs might be the measure specs that were passed into RecyclerView's
+ * onMeasure method OR fake measure specs created by the RecyclerView.
+ * For example, when a layout is run, RecyclerView always sets these specs to be
+ * EXACTLY because a LayoutManager cannot resize RecyclerView during a layout pass.
+ * <p>
+ * Also, to be able to use the hint in unspecified measure specs, RecyclerView checks the
+ * API level and sets the size to 0 pre-M to avoid any issue that might be caused by
+ * corrupt values. Older platforms have no responsibility to provide a size if they set
+ * mode to unspecified.
+ */
+ private int mWidthMode, mHeightMode;
+ private int mWidth, mHeight;
void setRecyclerView(RecyclerView recyclerView) {
if (recyclerView == null) {
mRecyclerView = null;
mChildHelper = null;
+ mWidth = 0;
+ mHeight = 0;
} else {
mRecyclerView = recyclerView;
mChildHelper = recyclerView.mChildHelper;
+ mWidth = recyclerView.getWidth();
+ mHeight = recyclerView.getHeight();
+ }
+ mWidthMode = MeasureSpec.EXACTLY;
+ mHeightMode = MeasureSpec.EXACTLY;
+ }
+
+ void setMeasureSpecs(int wSpec, int hSpec) {
+ mWidth = MeasureSpec.getSize(wSpec);
+ mWidthMode = MeasureSpec.getMode(wSpec);
+ if (mWidthMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mWidth = 0;
}
+ mHeight = MeasureSpec.getSize(hSpec);
+ mHeightMode = MeasureSpec.getMode(hSpec);
+ if (mHeightMode == MeasureSpec.UNSPECIFIED && !ALLOW_SIZE_IN_UNSPECIFIED_SPEC) {
+ mHeight = 0;
+ }
+ }
+
+ /**
+ * Called after a layout is calculated during a measure pass when using auto-measure.
+ * <p>
+ * It simply traverses all children to calculate a bounding box then calls
+ * {@link #setMeasuredDimension(Rect, int, int)}. LayoutManagers can override that method
+ * if they need to handle the bounding box differently.
+ * <p>
+ * For example, GridLayoutManager override that method to ensure that even if a column is
+ * empty, the GridLayoutManager still measures wide enough to include it.
+ *
+ * @param widthSpec The widthSpec that was passing into RecyclerView's onMeasure
+ * @param heightSpec The heightSpec that was passing into RecyclerView's onMeasure
+ */
+ void setMeasuredDimensionFromChildren(int widthSpec, int heightSpec) {
+ final int count = getChildCount();
+ if (count == 0) {
+ mRecyclerView.defaultOnMeasure(widthSpec, heightSpec);
+ return;
+ }
+ int minX = Integer.MAX_VALUE;
+ int minY = Integer.MAX_VALUE;
+ int maxX = Integer.MIN_VALUE;
+ int maxY = Integer.MIN_VALUE;
+
+ for (int i = 0; i < count; i++) {
+ View child = getChildAt(i);
+ LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final Rect bounds = mRecyclerView.mTempRect;
+ getDecoratedBoundsWithMargins(child, bounds);
+ if (bounds.left < minX) {
+ minX = bounds.left;
+ }
+ if (bounds.right > maxX) {
+ maxX = bounds.right;
+ }
+ if (bounds.top < minY) {
+ minY = bounds.top;
+ }
+ if (bounds.bottom > maxY) {
+ maxY = bounds.bottom;
+ }
+ }
+ mRecyclerView.mTempRect.set(minX, minY, maxX, maxY);
+ setMeasuredDimension(mRecyclerView.mTempRect, widthSpec, heightSpec);
+ }
+
+ /**
+ * Sets the measured dimensions from the given bounding box of the children and the
+ * measurement specs that were passed into {@link RecyclerView#onMeasure(int, int)}. It is
+ * called after the RecyclerView calls
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} during a measurement pass.
+ * <p>
+ * This method should call {@link #setMeasuredDimension(int, int)}.
+ * <p>
+ * The default implementation adds the RecyclerView's padding to the given bounding box
+ * then caps the value to be within the given measurement specs.
+ * <p>
+ * This method is only called if the LayoutManager opted into the auto measurement API.
+ *
+ * @param childrenBounds The bounding box of all children
+ * @param wSpec The widthMeasureSpec that was passed into the RecyclerView.
+ * @param hSpec The heightMeasureSpec that was passed into the RecyclerView.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+ int usedWidth = childrenBounds.width() + getPaddingLeft() + getPaddingRight();
+ int usedHeight = childrenBounds.height() + getPaddingTop() + getPaddingBottom();
+ int width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+ int height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+ setMeasuredDimension(width, height);
}
/**
@@ -5761,6 +6426,30 @@
}
/**
+ * Chooses a size from the given specs and parameters that is closest to the desired size
+ * and also complies with the spec.
+ *
+ * @param spec The measureSpec
+ * @param desired The preferred measurement
+ * @param min The minimum value
+ *
+ * @return A size that fits to the given specs
+ */
+ public static int chooseSize(int spec, int desired, int min) {
+ final int mode = View.MeasureSpec.getMode(spec);
+ final int size = View.MeasureSpec.getSize(spec);
+ switch (mode) {
+ case View.MeasureSpec.EXACTLY:
+ return size;
+ case View.MeasureSpec.AT_MOST:
+ return Math.min(size, Math.max(desired, min));
+ case View.MeasureSpec.UNSPECIFIED:
+ default:
+ return Math.max(desired, min);
+ }
+ }
+
+ /**
* Checks if RecyclerView is in the middle of a layout or scroll and throws an
* {@link IllegalStateException} if it <b>is</b>.
*
@@ -5774,6 +6463,86 @@
}
/**
+ * Defines whether the layout should be measured by the RecyclerView or the LayoutManager
+ * wants to handle the layout measurements itself.
+ * <p>
+ * This method is usually called by the LayoutManager with value {@code true} if it wants
+ * to support WRAP_CONTENT. If you are using a public LayoutManager but want to customize
+ * the measurement logic, you can call this method with {@code false} and override
+ * {@link LayoutManager#onMeasure(int, int)} to implement your custom measurement logic.
+ * <p>
+ * AutoMeasure is a convenience mechanism for LayoutManagers to easily wrap their content or
+ * handle various specs provided by the RecyclerView's parent.
+ * It works by calling {@link LayoutManager#onLayoutChildren(Recycler, State)} during an
+ * {@link RecyclerView#onMeasure(int, int)} call, then calculating desired dimensions based
+ * on children's positions. It does this while supporting all existing animation
+ * capabilities of the RecyclerView.
+ * <p>
+ * AutoMeasure works as follows:
+ * <ol>
+ * <li>LayoutManager should call {@code setAutoMeasureEnabled(true)} to enable it. All of
+ * the framework LayoutManagers use {@code auto-measure}.</li>
+ * <li>When {@link RecyclerView#onMeasure(int, int)} is called, if the provided specs are
+ * exact, RecyclerView will only call LayoutManager's {@code onMeasure} and return without
+ * doing any layout calculation.</li>
+ * <li>If one of the layout specs is not {@code EXACT}, the RecyclerView will start the
+ * layout process in {@code onMeasure} call. It will process all pending Adapter updates and
+ * decide whether to run a predictive layout or not. If it decides to do so, it will first
+ * call {@link #onLayoutChildren(Recycler, State)} with {@link State#isPreLayout()} set to
+ * {@code true}. At this stage, {@link #getWidth()} and {@link #getHeight()} will still
+ * return the width and height of the RecyclerView as of the last layout calculation.
+ * <p>
+ * After handling the predictive case, RecyclerView will call
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. The LayoutManager can
+ * access the measurement specs via {@link #getHeight()}, {@link #getHeightMode()},
+ * {@link #getWidth()} and {@link #getWidthMode()}.</li>
+ * <li>After the layout calculation, RecyclerView sets the measured width & height by
+ * calculating the bounding box for the children (+ RecyclerView's padding). The
+ * LayoutManagers can override {@link #setMeasuredDimension(Rect, int, int)} to choose
+ * different values. For instance, GridLayoutManager overrides this value to handle the case
+ * where if it is vertical and has 3 columns but only 2 items, it should still measure its
+ * width to fit 3 items, not 2.</li>
+ * <li>Any following on measure call to the RecyclerView will run
+ * {@link #onLayoutChildren(Recycler, State)} with {@link State#isMeasuring()} set to
+ * {@code true} and {@link State#isPreLayout()} set to {@code false}. RecyclerView will
+ * take care of which views are actually added / removed / moved / changed for animations so
+ * that the LayoutManager should not worry about them and handle each
+ * {@link #onLayoutChildren(Recycler, State)} call as if it is the last one.
+ * </li>
+ * <li>When measure is complete and RecyclerView's
+ * {@link #onLayout(boolean, int, int, int, int)} method is called, RecyclerView checks
+ * whether it already did layout calculations during the measure pass and if so, it re-uses
+ * that information. It may still decide to call {@link #onLayoutChildren(Recycler, State)}
+ * if the last measure spec was different from the final dimensions or adapter contents
+ * have changed between the measure call and the layout call.</li>
+ * <li>Finally, animations are calculated and run as usual.</li>
+ * </ol>
+ *
+ * @param enabled <code>True</code> if the Layout should be measured by the
+ * RecyclerView, <code>false</code> if the LayoutManager wants
+ * to measure itself.
+ *
+ * @see #setMeasuredDimension(Rect, int, int)
+ * @see #isAutoMeasureEnabled()
+ */
+ public void setAutoMeasureEnabled(boolean enabled) {
+ mAutoMeasure = enabled;
+ }
+
+ /**
+ * Returns whether the LayoutManager uses the automatic measurement API or not.
+ *
+ * @return <code>True</code> if the LayoutManager is measured by the RecyclerView or
+ * <code>false</code> if it measures itself.
+ *
+ * @see #setAutoMeasureEnabled(boolean)
+ */
+ public boolean isAutoMeasureEnabled() {
+ return mAutoMeasure;
+ }
+
+ /**
* Returns whether this LayoutManager supports automatic item animations.
* A LayoutManager wishing to support item animations should obey certain
* rules as outlined in {@link #onLayoutChildren(Recycler, State)}.
@@ -5857,11 +6626,16 @@
/**
* Called when this LayoutManager is both attached to a RecyclerView and that RecyclerView
* is attached to a window.
- *
- * <p>Subclass implementations should always call through to the superclass implementation.
- * </p>
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
*
* @param view The RecyclerView this LayoutManager is bound to
+ *
+ * @see #onDetachedFromWindow(RecyclerView, Recycler)
*/
@CallSuper
public void onAttachedToWindow(RecyclerView view) {
@@ -5879,13 +6653,25 @@
/**
* Called when this LayoutManager is detached from its parent RecyclerView or when
* its parent RecyclerView is detached from its window.
- *
- * <p>Subclass implementations should always call through to the superclass implementation.
- * </p>
+ * <p>
+ * LayoutManager should clear all of its View references as another LayoutManager might be
+ * assigned to the RecyclerView.
+ * <p>
+ * If the RecyclerView is re-attached with the same LayoutManager and Adapter, it may not
+ * call {@link #onLayoutChildren(Recycler, State)} if nothing has changed and a layout was
+ * not requested on the RecyclerView while it was detached.
+ * <p>
+ * If your LayoutManager has View references that it cleans in on-detach, it should also
+ * call {@link RecyclerView#requestLayout()} to ensure that it is re-laid out when
+ * RecyclerView is re-attached.
+ * <p>
+ * Subclass implementations should always call through to the superclass implementation.
*
* @param view The RecyclerView this LayoutManager is bound to
* @param recycler The recycler to use if you prefer to recycle your children instead of
* keeping them around.
+ *
+ * @see #onAttachedToWindow(RecyclerView)
*/
@CallSuper
public void onDetachedFromWindow(RecyclerView view, Recycler recycler) {
@@ -5957,6 +6743,20 @@
}
/**
+ * Called after a full layout calculation is finished. The layout calculation may include
+ * multiple {@link #onLayoutChildren(Recycler, State)} calls due to animations or
+ * layout measurement but it will include only one {@link #onLayoutCompleted(State)} call.
+ * This method will be called at the end of {@link View#layout(int, int, int, int)} call.
+ * <p>
+ * This is a good place for the LayoutManager to do some cleanup like pending scroll
+ * position, saved state etc.
+ *
+ * @param state Transient state of RecyclerView
+ */
+ public void onLayoutCompleted(State state) {
+ }
+
+ /**
* Create a default <code>LayoutParams</code> object for a child of the RecyclerView.
*
* <p>LayoutManagers will often want to use a custom <code>LayoutParams</code> type
@@ -6337,6 +7137,36 @@
}
/**
+ * Traverses the ancestors 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.
@@ -6555,12 +7385,48 @@
}
/**
+ * Return the width measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Width measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getWidthMode() {
+ return mWidthMode;
+ }
+
+ /**
+ * Return the height measurement spec mode of the RecyclerView.
+ * <p>
+ * This value is set only if the LayoutManager opts into the auto measure api via
+ * {@link #setAutoMeasureEnabled(boolean)}.
+ * <p>
+ * When RecyclerView is running a layout, this value is always set to
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ *
+ * @return Height measure spec mode.
+ *
+ * @see View.MeasureSpec#getMode(int)
+ * @see View#onMeasure(int, int)
+ */
+ public int getHeightMode() {
+ return mHeightMode;
+ }
+
+ /**
* Return the width of the parent RecyclerView
*
* @return Width in pixels
*/
public int getWidth() {
- return mRecyclerView != null ? mRecyclerView.getWidth() : 0;
+ return mWidth;
}
/**
@@ -6569,7 +7435,7 @@
* @return Height in pixels
*/
public int getHeight() {
- return mRecyclerView != null ? mRecyclerView.getHeight() : 0;
+ return mHeight;
}
/**
@@ -6774,6 +7640,7 @@
} else {
detachViewAt(index);
recycler.scrapView(view);
+ mRecyclerView.mViewInfoStore.onViewDetached(viewHolder);
}
}
@@ -6834,14 +7701,85 @@
final Rect insets = mRecyclerView.getItemDecorInsetsForChild(child);
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
-
- final int widthSpec = getChildMeasureSpec(getWidth(),
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() + widthUsed, lp.width,
canScrollHorizontally());
- final int heightSpec = getChildMeasureSpec(getHeight(),
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() + heightUsed, lp.height,
canScrollVertically());
- child.measure(widthSpec, heightSpec);
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
+ }
+
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is already measured once in this layout pass.
+ */
+ boolean shouldReMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getMeasuredWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getMeasuredHeight(), heightSpec, lp.height);
+ }
+
+ // we may consider making this public
+ /**
+ * RecyclerView internally does its own View measurement caching which should help with
+ * WRAP_CONTENT.
+ * <p>
+ * Use this method if the View is not yet measured and you need to decide whether to
+ * measure this View or not.
+ */
+ boolean shouldMeasureChild(View child, int widthSpec, int heightSpec, LayoutParams lp) {
+ return child.isLayoutRequested()
+ || !mMeasurementCacheEnabled
+ || !isMeasurementUpToDate(child.getWidth(), widthSpec, lp.width)
+ || !isMeasurementUpToDate(child.getHeight(), heightSpec, lp.height);
+ }
+
+ /**
+ * In addition to the View Framework's measurement cache, RecyclerView uses its own
+ * additional measurement cache for its children to avoid re-measuring them when not
+ * necessary. It is on by default but it can be turned off via
+ * {@link #setMeasurementCacheEnabled(boolean)}.
+ *
+ * @return True if measurement cache is enabled, false otherwise.
+ *
+ * @see #setMeasurementCacheEnabled(boolean)
+ */
+ public boolean isMeasurementCacheEnabled() {
+ return mMeasurementCacheEnabled;
+ }
+
+ /**
+ * Sets whether RecyclerView should use its own measurement cache for the children. This is
+ * a more aggressive cache than the framework uses.
+ *
+ * @param measurementCacheEnabled True to enable the measurement cache, false otherwise.
+ *
+ * @see #isMeasurementCacheEnabled()
+ */
+ public void setMeasurementCacheEnabled(boolean measurementCacheEnabled) {
+ mMeasurementCacheEnabled = measurementCacheEnabled;
+ }
+
+ private static boolean isMeasurementUpToDate(int childSize, int spec, int dimension) {
+ final int specMode = MeasureSpec.getMode(spec);
+ final int specSize = MeasureSpec.getSize(spec);
+ if (dimension > 0 && childSize != dimension) {
+ return false;
+ }
+ switch (specMode) {
+ case MeasureSpec.UNSPECIFIED:
+ return true;
+ case MeasureSpec.AT_MOST:
+ return specSize >= childSize;
+ case MeasureSpec.EXACTLY:
+ return specSize == childSize;
+ }
+ return false;
}
/**
@@ -6863,40 +7801,43 @@
widthUsed += insets.left + insets.right;
heightUsed += insets.top + insets.bottom;
- final int widthSpec = getChildMeasureSpec(getWidth(),
+ final int widthSpec = getChildMeasureSpec(getWidth(), getWidthMode(),
getPaddingLeft() + getPaddingRight() +
lp.leftMargin + lp.rightMargin + widthUsed, lp.width,
canScrollHorizontally());
- final int heightSpec = getChildMeasureSpec(getHeight(),
+ final int heightSpec = getChildMeasureSpec(getHeight(), getHeightMode(),
getPaddingTop() + getPaddingBottom() +
lp.topMargin + lp.bottomMargin + heightUsed, lp.height,
canScrollVertically());
- child.measure(widthSpec, heightSpec);
+ if (shouldMeasureChild(child, widthSpec, heightSpec, lp)) {
+ child.measure(widthSpec, heightSpec);
+ }
}
/**
* Calculate a MeasureSpec value for measuring a child view in one dimension.
*
* @param parentSize Size of the parent view where the child will be placed
- * @param padding Total space currently consumed by other elements of parent
- * @param childDimension Desired size of the child view, or MATCH_PARENT/WRAP_CONTENT.
+ * @param padding Total space currently consumed by other elements of the parent
+ * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT.
* Generally obtained from the child view's LayoutParams
* @param canScroll true if the parent RecyclerView can scroll in this dimension
*
* @return a MeasureSpec value for the child view
+ * @deprecated use {@link #getChildMeasureSpec(int, int, int, int, boolean)}
*/
+ @Deprecated
public static int getChildMeasureSpec(int parentSize, int padding, int childDimension,
boolean canScroll) {
int size = Math.max(0, parentSize - padding);
int resultSize = 0;
int resultMode = 0;
-
if (canScroll) {
if (childDimension >= 0) {
resultSize = childDimension;
resultMode = MeasureSpec.EXACTLY;
} else {
- // MATCH_PARENT can't be applied since we can scroll in this dimension, wrap
+ // FILL_PARENT can't be applied since we can scroll in this dimension, wrap
// instead using UNSPECIFIED.
resultSize = 0;
resultMode = MeasureSpec.UNSPECIFIED;
@@ -6907,6 +7848,7 @@
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.FILL_PARENT) {
resultSize = size;
+ // TODO this should be my spec.
resultMode = MeasureSpec.EXACTLY;
} else if (childDimension == LayoutParams.WRAP_CONTENT) {
resultSize = size;
@@ -6917,6 +7859,64 @@
}
/**
+ * Calculate a MeasureSpec value for measuring a child view in one dimension.
+ *
+ * @param parentSize Size of the parent view where the child will be placed
+ * @param parentMode The measurement spec mode of the parent
+ * @param padding Total space currently consumed by other elements of parent
+ * @param childDimension Desired size of the child view, or FILL_PARENT/WRAP_CONTENT.
+ * Generally obtained from the child view's LayoutParams
+ * @param canScroll true if the parent RecyclerView can scroll in this dimension
+ *
+ * @return a MeasureSpec value for the child view
+ */
+ public static int getChildMeasureSpec(int parentSize, int parentMode, int padding,
+ int childDimension, boolean canScroll) {
+ int size = Math.max(0, parentSize - padding);
+ int resultSize = 0;
+ int resultMode = 0;
+ if (canScroll) {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.FILL_PARENT){
+ switch (parentMode) {
+ case MeasureSpec.AT_MOST:
+ case MeasureSpec.EXACTLY:
+ resultSize = size;
+ resultMode = parentMode;
+ break;
+ case MeasureSpec.UNSPECIFIED:
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ break;
+ }
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = 0;
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+ } else {
+ if (childDimension >= 0) {
+ resultSize = childDimension;
+ resultMode = MeasureSpec.EXACTLY;
+ } else if (childDimension == LayoutParams.FILL_PARENT) {
+ resultSize = size;
+ resultMode = parentMode;
+ } else if (childDimension == LayoutParams.WRAP_CONTENT) {
+ resultSize = size;
+ if (parentMode == MeasureSpec.AT_MOST || parentMode == MeasureSpec.EXACTLY) {
+ resultMode = MeasureSpec.AT_MOST;
+ } else {
+ resultMode = MeasureSpec.UNSPECIFIED;
+ }
+
+ }
+ }
+ //noinspection WrongConstant
+ return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
+ }
+
+ /**
* Returns the measured width of the given child, plus the additional size of
* any insets applied by {@link ItemDecoration ItemDecorations}.
*
@@ -6953,6 +7953,8 @@
* ignore decoration insets within measurement and layout code. See the following
* methods:</p>
* <ul>
+ * <li>{@link #layoutDecoratedWithMargins(View, int, int, int, int)}</li>
+ * <li>{@link #getDecoratedBoundsWithMargins(View, Rect)}</li>
* <li>{@link #measureChild(View, int, int)}</li>
* <li>{@link #measureChildWithMargins(View, int, int)}</li>
* <li>{@link #getDecoratedLeft(View)}</li>
@@ -6970,6 +7972,7 @@
* @param bottom Bottom edge, with item decoration insets included
*
* @see View#layout(int, int, int, int)
+ * @see #layoutDecoratedWithMargins(View, int, int, int, int)
*/
public void layoutDecorated(View child, int left, int top, int right, int bottom) {
final Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
@@ -6978,6 +7981,97 @@
}
/**
+ * Lay out the given child view within the RecyclerView using coordinates that
+ * include any current {@link ItemDecoration ItemDecorations} and margins.
+ *
+ * <p>LayoutManagers should prefer working in sizes and coordinates that include
+ * item decoration insets whenever possible. This allows the LayoutManager to effectively
+ * ignore decoration insets within measurement and layout code. See the following
+ * methods:</p>
+ * <ul>
+ * <li>{@link #layoutDecorated(View, int, int, int, int)}</li>
+ * <li>{@link #measureChild(View, int, int)}</li>
+ * <li>{@link #measureChildWithMargins(View, int, int)}</li>
+ * <li>{@link #getDecoratedLeft(View)}</li>
+ * <li>{@link #getDecoratedTop(View)}</li>
+ * <li>{@link #getDecoratedRight(View)}</li>
+ * <li>{@link #getDecoratedBottom(View)}</li>
+ * <li>{@link #getDecoratedMeasuredWidth(View)}</li>
+ * <li>{@link #getDecoratedMeasuredHeight(View)}</li>
+ * </ul>
+ *
+ * @param child Child to lay out
+ * @param left Left edge, with item decoration insets and left margin included
+ * @param top Top edge, with item decoration insets and top margin included
+ * @param right Right edge, with item decoration insets and right margin included
+ * @param bottom Bottom edge, with item decoration insets and bottom margin included
+ *
+ * @see View#layout(int, int, int, int)
+ * @see #layoutDecorated(View, int, int, int, int)
+ */
+ public void layoutDecoratedWithMargins(View child, int left, int top, int right,
+ int bottom) {
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ child.layout(left + insets.left + lp.leftMargin, top + insets.top + lp.topMargin,
+ right - insets.right - lp.rightMargin,
+ bottom - insets.bottom - lp.bottomMargin);
+ }
+
+ /**
+ * Calculates the bounding box of the View while taking into account its matrix changes
+ * (translation, scale etc) with respect to the RecyclerView.
+ * <p>
+ * If {@code includeDecorInsets} is {@code true}, they are applied first before applying
+ * the View's matrix so that the decor offsets also go through the same transformation.
+ *
+ * @param child The ItemView whose bounding box should be calculated.
+ * @param includeDecorInsets True if the decor insets should be included in the bounding box
+ * @param out The rectangle into which the output will be written.
+ */
+ public void getTransformedBoundingBox(View child, boolean includeDecorInsets, Rect out) {
+ if (includeDecorInsets) {
+ Rect insets = ((LayoutParams) child.getLayoutParams()).mDecorInsets;
+ out.set(-insets.left, -insets.top,
+ child.getWidth() + insets.right, child.getHeight() + insets.bottom);
+ } else {
+ out.set(0, 0, child.getWidth(), child.getHeight());
+ }
+
+ if (mRecyclerView != null) {
+ final Matrix childMatrix = ViewCompat.getMatrix(child);
+ if (childMatrix != null && !childMatrix.isIdentity()) {
+ final RectF tempRectF = mRecyclerView.mTempRectF;
+ tempRectF.set(out);
+ childMatrix.mapRect(tempRectF);
+ out.set(
+ (int) Math.floor(tempRectF.left),
+ (int) Math.floor(tempRectF.top),
+ (int) Math.ceil(tempRectF.right),
+ (int) Math.ceil(tempRectF.bottom)
+ );
+ }
+ }
+ out.offset(child.getLeft(), child.getTop());
+ }
+
+ /**
+ * Returns the bounds of the view including its decoration and margins.
+ *
+ * @param view The view element to check
+ * @param outBounds A rect that will receive the bounds of the element including its
+ * decoration and margins.
+ */
+ public void getDecoratedBoundsWithMargins(View view, Rect outBounds) {
+ final LayoutParams lp = (LayoutParams) view.getLayoutParams();
+ final Rect insets = lp.mDecorInsets;
+ outBounds.set(view.getLeft() - insets.left - lp.leftMargin,
+ view.getTop() - insets.top - lp.topMargin,
+ view.getRight() + insets.right + lp.rightMargin,
+ view.getBottom() + insets.bottom + lp.bottomMargin);
+ }
+
+ /**
* Returns the left edge of the given child view within its parent, offset by any applied
* {@link ItemDecoration ItemDecorations}.
*
@@ -7175,8 +8269,8 @@
final int parentTop = getPaddingTop();
final int parentRight = getWidth() - getPaddingRight();
final int parentBottom = getHeight() - getPaddingBottom();
- final int childLeft = child.getLeft() + rect.left;
- final int childTop = child.getTop() + rect.top;
+ final int childLeft = child.getLeft() + rect.left - child.getScrollX();
+ final int childTop = child.getTop() + rect.top - child.getScrollY();
final int childRight = childLeft + rect.width();
final int childBottom = childTop + rect.height();
@@ -7853,6 +8947,39 @@
return properties;
}
+ void setExactMeasureSpecsFrom(RecyclerView recyclerView) {
+ setMeasureSpecs(
+ MeasureSpec.makeMeasureSpec(recyclerView.getWidth(), MeasureSpec.EXACTLY),
+ MeasureSpec.makeMeasureSpec(recyclerView.getHeight(), MeasureSpec.EXACTLY)
+ );
+ }
+
+ /**
+ * Internal API to allow LayoutManagers to be measured twice.
+ * <p>
+ * This is not public because LayoutManagers should be able to handle their layouts in one
+ * pass but it is very convenient to make existing LayoutManagers support wrapping content
+ * when both orientations are undefined.
+ * <p>
+ * This API will be removed after default LayoutManagers properly implement wrap content in
+ * non-scroll orientation.
+ */
+ boolean shouldMeasureTwice() {
+ return false;
+ }
+
+ boolean hasFlexibleChildInBothOrientations() {
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ final View child = getChildAt(i);
+ final ViewGroup.LayoutParams lp = child.getLayoutParams();
+ if (lp.width < 0 && lp.height < 0) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Some general properties that a LayoutManager may want to use.
*/
@@ -8033,16 +9160,12 @@
/**
- * An OnScrollListener can be set on a RecyclerView to receive messages
- * when a scrolling event has occurred on that RecyclerView.
+ * An OnScrollListener can be added to a RecyclerView to receive messages when a scrolling event
+ * has occurred on that RecyclerView.
+ * <p>
+ * @see RecyclerView#addOnScrollListener(OnScrollListener)
+ * @see RecyclerView#clearOnChildAttachStateChangeListeners()
*
- * @see RecyclerView#setOnScrollListener(OnScrollListener) and
- * RecyclerView#addOnScrollListener(OnScrollListener)
- *
- * If you are planning to have several listeners at the same time, use
- * RecyclerView#addOnScrollListener. If there will be only one listener at the time and you
- * want your components to be able to easily replace the listener use
- * RecyclerView#setOnScrollListener.
*/
public abstract static class OnScrollListener {
/**
@@ -8630,49 +9753,49 @@
@Override
public void setNestedScrollingEnabled(boolean enabled) {
- mScrollingChildHelper.setNestedScrollingEnabled(enabled);
+ getScrollingChildHelper().setNestedScrollingEnabled(enabled);
}
@Override
public boolean isNestedScrollingEnabled() {
- return mScrollingChildHelper.isNestedScrollingEnabled();
+ return getScrollingChildHelper().isNestedScrollingEnabled();
}
@Override
public boolean startNestedScroll(int axes) {
- return mScrollingChildHelper.startNestedScroll(axes);
+ return getScrollingChildHelper().startNestedScroll(axes);
}
@Override
public void stopNestedScroll() {
- mScrollingChildHelper.stopNestedScroll();
+ getScrollingChildHelper().stopNestedScroll();
}
@Override
public boolean hasNestedScrollingParent() {
- return mScrollingChildHelper.hasNestedScrollingParent();
+ return getScrollingChildHelper().hasNestedScrollingParent();
}
@Override
public boolean dispatchNestedScroll(int dxConsumed, int dyConsumed, int dxUnconsumed,
int dyUnconsumed, int[] offsetInWindow) {
- return mScrollingChildHelper.dispatchNestedScroll(dxConsumed, dyConsumed,
+ return getScrollingChildHelper().dispatchNestedScroll(dxConsumed, dyConsumed,
dxUnconsumed, dyUnconsumed, offsetInWindow);
}
@Override
public boolean dispatchNestedPreScroll(int dx, int dy, int[] consumed, int[] offsetInWindow) {
- return mScrollingChildHelper.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
+ return getScrollingChildHelper().dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow);
}
@Override
public boolean dispatchNestedFling(float velocityX, float velocityY, boolean consumed) {
- return mScrollingChildHelper.dispatchNestedFling(velocityX, velocityY, consumed);
+ return getScrollingChildHelper().dispatchNestedFling(velocityX, velocityY, consumed);
}
@Override
public boolean dispatchNestedPreFling(float velocityX, float velocityY) {
- return mScrollingChildHelper.dispatchNestedPreFling(velocityX, velocityY);
+ return getScrollingChildHelper().dispatchNestedPreFling(velocityX, velocityY);
}
/**
@@ -8755,6 +9878,7 @@
/**
* @deprecated use {@link #getViewLayoutPosition()} or {@link #getViewAdapterPosition()}
*/
+ @Deprecated
public int getViewPosition() {
return mViewHolder.getPosition();
}
@@ -9288,16 +10412,17 @@
* This is public so that the CREATOR can be access on cold launch.
* @hide
*/
- public static class SavedState extends android.view.View.BaseSavedState {
+ public static class SavedState extends AbsSavedState {
Parcelable mLayoutState;
/**
* called by CREATOR
*/
- SavedState(Parcel in) {
- super(in);
- mLayoutState = in.readParcelable(LayoutManager.class.getClassLoader());
+ SavedState(Parcel in, ClassLoader loader) {
+ super(in, loader);
+ mLayoutState = in.readParcelable(
+ loader != null ? loader : LayoutManager.class.getClassLoader());
}
/**
@@ -9317,18 +10442,18 @@
mLayoutState = other.mLayoutState;
}
- public static final Parcelable.Creator<SavedState> CREATOR
- = new Parcelable.Creator<SavedState>() {
- @Override
- public SavedState createFromParcel(Parcel in) {
- return new SavedState(in);
- }
+ public static final Creator<SavedState> CREATOR = ParcelableCompat.newCreator(
+ new ParcelableCompatCreatorCallbacks<SavedState>() {
+ @Override
+ public SavedState createFromParcel(Parcel in, ClassLoader loader) {
+ return new SavedState(in, loader);
+ }
- @Override
- public SavedState[] newArray(int size) {
- return new SavedState[size];
- }
- };
+ @Override
+ public SavedState[] newArray(int size) {
+ return new SavedState[size];
+ }
+ });
}
/**
* <p>Contains useful information about the current RecyclerView state like target scroll
@@ -9341,9 +10466,29 @@
* data between your components without needing to manage their lifecycles.</p>
*/
public static class State {
+ static final int STEP_START = 1;
+ static final int STEP_LAYOUT = 1 << 1;
+ static final int STEP_ANIMATIONS = 1 << 2;
+
+ void assertLayoutStep(int accepted) {
+ if ((accepted & mLayoutStep) == 0) {
+ throw new IllegalStateException("Layout state should be one of "
+ + Integer.toBinaryString(accepted) + " but it is "
+ + Integer.toBinaryString(mLayoutStep));
+ }
+ }
+
+ @IntDef(flag = true, value = {
+ STEP_START, STEP_LAYOUT, STEP_ANIMATIONS
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ @interface LayoutState {}
private int mTargetPosition = RecyclerView.NO_POSITION;
+ @LayoutState
+ private int mLayoutStep = STEP_START;
+
private SparseArray<Object> mData;
/**
@@ -9372,6 +10517,19 @@
private boolean mTrackOldChangeHolders = false;
+ private boolean mIsMeasuring = false;
+
+ /**
+ * This data is saved before a layout calculation happens. After the layout is finished,
+ * if the previously focused view has been replaced with another view for the same item, we
+ * move the focus to the new item automatically.
+ */
+ int mFocusedItemPosition;
+ long mFocusedItemId;
+ // when a sub child has focus, record its id and see if we can directly request focus on
+ // that one instead
+ int mFocusedSubChildId;
+
State reset() {
mTargetPosition = RecyclerView.NO_POSITION;
if (mData != null) {
@@ -9379,9 +10537,32 @@
}
mItemCount = 0;
mStructureChanged = false;
+ mIsMeasuring = false;
return this;
}
+ /**
+ * Returns true if the RecyclerView is currently measuring the layout. This value is
+ * {@code true} only if the LayoutManager opted into the auto measure API and RecyclerView
+ * has non-exact measurement specs.
+ * <p>
+ * Note that if the LayoutManager supports predictive animations and it is calculating the
+ * pre-layout step, this value will be {@code false} even if the RecyclerView is in
+ * {@code onMeasure} call. This is because pre-layout means the previous state of the
+ * RecyclerView and measurements made for that state cannot change the RecyclerView's size.
+ * LayoutManager is always guaranteed to receive another call to
+ * {@link LayoutManager#onLayoutChildren(Recycler, State)} when this happens.
+ *
+ * @return True if the RecyclerView is currently calculating its bounds, false otherwise.
+ */
+ public boolean isMeasuring() {
+ return mIsMeasuring;
+ }
+
+ /**
+ * Returns true if
+ * @return
+ */
public boolean isPreLayout() {
return mInPreLayout;
}
@@ -9750,7 +10931,7 @@
*
* @see #recordPostLayoutInformation(State, ViewHolder)
* @see #animateAppearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
- * @see #animateDisappearance(ViewHolder, ItemHolderInfo)
+ * @see #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
* @see #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
* @see #animatePersistence(ViewHolder, ItemHolderInfo, ItemHolderInfo)
*/
@@ -9798,6 +10979,18 @@
* the change flags that were passed to
* {@link #recordPreLayoutInformation(State, ViewHolder, int, List)}.
* <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateChange} instead of {@code animateDisappearance} if and only if the ItemAnimator
+ * returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
+ * <p>
* If LayoutManager supports predictive animations, it might provide a target disappear
* location for the View by laying it out in that location. When that happens,
* RecyclerView will call {@link #recordPostLayoutInformation(State, ViewHolder)} and the
@@ -9894,7 +11087,7 @@
* <p>
* When an item is updated, ItemAnimator has a chance to ask RecyclerView to keep the
* previous presentation of the item as-is and supply a new ViewHolder for the updated
- * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder)}.
+ * presentation (see: {@link #canReuseUpdatedViewHolder(ViewHolder, List)}.
* This is useful if you don't know the contents of the Item and would like
* to cross-fade the old and the new one ({@link DefaultItemAnimator} uses this technique).
* <p>
@@ -9903,7 +11096,7 @@
* <p>
* When {@link Adapter#notifyItemChanged(int)} is called, the Item's view type may change.
* If the Item's view type has changed or ItemAnimator returned <code>false</code> for
- * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder)} was called, the
+ * this ViewHolder when {@link #canReuseUpdatedViewHolder(ViewHolder, List)} was called, the
* <code>oldHolder</code> and <code>newHolder</code> will be different ViewHolder instances
* which represent the same Item. In that case, only the new ViewHolder is visible
* to the LayoutManager but RecyclerView keeps old ViewHolder attached for animations.
@@ -9915,6 +11108,18 @@
* <p>
* If oldHolder and newHolder are the same instance, you should call
* {@link #dispatchAnimationFinished(ViewHolder)} <b>only once</b>.
+ * <p>
+ * Note that when a ViewHolder both changes and disappears in the same layout pass, the
+ * animation callback method which will be called by the RecyclerView depends on the
+ * ItemAnimator's decision whether to re-use the same ViewHolder or not, and also the
+ * LayoutManager's decision whether to layout the changed version of a disappearing
+ * ViewHolder or not. RecyclerView will call
+ * {@code animateChange} instead of
+ * {@link #animateDisappearance(ViewHolder, ItemHolderInfo, ItemHolderInfo)
+ * animateDisappearance} if and only if the ItemAnimator returns {@code false} from
+ * {@link #canReuseUpdatedViewHolder(ViewHolder) canReuseUpdatedViewHolder} and the
+ * LayoutManager lays out a new disappearing view that holds the updated information.
+ * Built-in LayoutManagers try to avoid laying out updated versions of disappearing views.
*
* @param oldHolder The ViewHolder before the layout is started, might be the same
* instance with newHolder.
@@ -10109,18 +11314,53 @@
* type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
* both {@link ViewHolder}s in the
* {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ * <p>
+ * If your application is using change payloads, you can override
+ * {@link #canReuseUpdatedViewHolder(ViewHolder, List)} to decide based on payloads.
*
* @param viewHolder The ViewHolder which represents the changed item's old content.
*
* @return True if RecyclerView should just rebind to the same ViewHolder or false if
* RecyclerView should create a new ViewHolder and pass this ViewHolder to the
* ItemAnimator to animate. Default implementation returns <code>true</code>.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder, List)
*/
- public boolean canReuseUpdatedViewHolder(ViewHolder viewHolder) {
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder) {
return true;
}
/**
+ * When an item is changed, ItemAnimator can decide whether it wants to re-use
+ * the same ViewHolder for animations or RecyclerView should create a copy of the
+ * item and ItemAnimator will use both to run the animation (e.g. cross-fade).
+ * <p>
+ * Note that this method will only be called if the {@link ViewHolder} still has the same
+ * type ({@link Adapter#getItemViewType(int)}). Otherwise, ItemAnimator will always receive
+ * both {@link ViewHolder}s in the
+ * {@link #animateChange(ViewHolder, ViewHolder, ItemHolderInfo, ItemHolderInfo)} method.
+ *
+ * @param viewHolder The ViewHolder which represents the changed item's old content.
+ * @param payloads A non-null list of merged payloads that were sent with change
+ * notifications. Can be empty if the adapter is invalidated via
+ * {@link RecyclerView.Adapter#notifyDataSetChanged()}. The same list of
+ * payloads will be passed into
+ * {@link RecyclerView.Adapter#onBindViewHolder(ViewHolder, int, List)}
+ * method <b>if</b> this method returns <code>true</code>.
+ *
+ * @return True if RecyclerView should just rebind to the same ViewHolder or false if
+ * RecyclerView should create a new ViewHolder and pass this ViewHolder to the
+ * ItemAnimator to animate. Default implementation calls
+ * {@link #canReuseUpdatedViewHolder(ViewHolder)}.
+ *
+ * @see #canReuseUpdatedViewHolder(ViewHolder)
+ */
+ public boolean canReuseUpdatedViewHolder(@NonNull ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return canReuseUpdatedViewHolder(viewHolder);
+ }
+
+ /**
* This method should be called by ItemAnimator implementations to notify
* any listeners that all pending and active item animations are finished.
*/
@@ -10272,4 +11512,11 @@
*/
int onGetChildDrawingOrder(int childCount, int i);
}
+
+ private NestedScrollingChildHelper getScrollingChildHelper() {
+ if (mScrollingChildHelper == null) {
+ mScrollingChildHelper = new NestedScrollingChildHelper(this);
+ }
+ return mScrollingChildHelper;
+ }
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
index 3fe9abc..1283f03 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerViewAccessibilityDelegate.java
@@ -72,7 +72,12 @@
}
}
- AccessibilityDelegateCompat getItemDelegate() {
+ /**
+ * Gets the AccessibilityDelegate for an individual item in the RecyclerView.
+ * A basic item delegate is provided by default, but you can override this
+ * method to provide a custom per-item delegate.
+ */
+ public AccessibilityDelegateCompat getItemDelegate() {
return mItemDelegate;
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
index 1266236..2db7541 100644
--- a/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
+++ b/v7/recyclerview/src/android/support/v7/widget/SimpleItemAnimator.java
@@ -8,6 +8,8 @@
import android.util.Log;
import android.view.View;
+import java.util.List;
+
/**
* A wrapper class for ItemAnimator that records View bounds and decides whether it should run
* move, change, add or remove animations. This class also replicates the original ItemAnimator
@@ -18,8 +20,7 @@
* extend this class, you can override {@link #obtainHolderInfo()} method to provide your own info
* class that extends {@link ItemHolderInfo}.
*/
-abstract public class
- SimpleItemAnimator extends RecyclerView.ItemAnimator {
+abstract public class SimpleItemAnimator extends RecyclerView.ItemAnimator {
private static final boolean DEBUG = false;
@@ -58,8 +59,16 @@
mSupportsChangeAnimations = supportsChangeAnimations;
}
+ /**
+ * {@inheritDoc}
+ *
+ * @return True if change animations are not supported or the ViewHolder is invalid,
+ * false otherwise.
+ *
+ * @see #setSupportsChangeAnimations(boolean)
+ */
@Override
- public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder) {
return !mSupportsChangeAnimations || viewHolder.isInvalid();
}
@@ -217,8 +226,12 @@
* {@link Adapter#notifyItemRangeChanged(int, int)}.
* <p>
* Implementers can choose whether and how to animate changes, but must always call
- * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null ViewHolder,
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} for each non-null distinct ViewHolder,
* either immediately (if no animation will occur) or after the animation actually finishes.
+ * If the {@code oldHolder} is the same ViewHolder as the {@code newHolder}, you must call
+ * {@link #dispatchChangeFinished(ViewHolder, boolean)} once and only once. In that case, the
+ * second parameter of {@code dispatchChangeFinished} is ignored.
+ * <p>
* The return value indicates whether an animation has been set up and whether the
* ItemAnimator's {@link #runPendingAnimations()} method should be called at the
* next opportunity. This mechanism allows ItemAnimator to set up individual animations
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 071bb4e..9207caa 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -16,11 +16,19 @@
package android.support.v7.widget;
+import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
+import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
+import static android.support.v7.widget.LayoutState.LAYOUT_END;
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
+import static android.support.v7.widget.RecyclerView.NO_POSITION;
+
import android.content.Context;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
+import android.support.annotation.NonNull;
+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;
@@ -36,12 +44,6 @@
import java.util.BitSet;
import java.util.List;
-import static android.support.v7.widget.LayoutState.LAYOUT_START;
-import static android.support.v7.widget.LayoutState.LAYOUT_END;
-import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_HEAD;
-import static android.support.v7.widget.LayoutState.ITEM_DIRECTION_TAIL;
-import static android.support.v7.widget.RecyclerView.NO_POSITION;
-
/**
* A LayoutManager that lays out children in a staggered grid formation.
* It supports horizontal & vertical layout as well as an ability to layout children in reverse.
@@ -65,6 +67,10 @@
*/
public static final int GAP_HANDLING_NONE = 0;
+ /**
+ * @deprecated No longer supported.
+ */
+ @SuppressWarnings("unused")
@Deprecated
public static final int GAP_HANDLING_LAZY = 1;
@@ -90,6 +96,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
@@ -102,7 +114,9 @@
* Primary orientation is the layout's orientation, secondary orientation is the orientation
* for spans. Having both makes code much cleaner for calculations.
*/
+ @NonNull
OrientationHelper mPrimaryOrientation;
+ @NonNull
OrientationHelper mSecondaryOrientation;
private int mOrientation;
@@ -112,7 +126,8 @@
*/
private int mSizePerSpan;
- private LayoutState mLayoutState;
+ @NonNull
+ private final LayoutState mLayoutState;
private boolean mReverseLayout = false;
@@ -168,7 +183,7 @@
/**
* Re-used measurement specs. updated by onLayout.
*/
- private int mFullSizeSpec, mWidthSpec, mHeightSpec;
+ private int mFullSizeSpec;
/**
* Re-used rectangle to get child decor offsets.
@@ -205,12 +220,16 @@
* 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);
setOrientation(properties.orientation);
setSpanCount(properties.spanCount);
setReverseLayout(properties.reverseLayout);
+ setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
+ mLayoutState = new LayoutState();
+ createOrientationHelpers();
}
/**
@@ -223,6 +242,15 @@
public StaggeredGridLayoutManager(int spanCount, int orientation) {
mOrientation = orientation;
setSpanCount(spanCount);
+ setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
+ mLayoutState = new LayoutState();
+ createOrientationHelpers();
+ }
+
+ private void createOrientationHelpers() {
+ mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
+ mSecondaryOrientation = OrientationHelper
+ .createOrientationHelper(this, 1 - mOrientation);
}
/**
@@ -289,6 +317,8 @@
for (int i = 0; i < mSpanCount; i++) {
mSpans[i].clear();
}
+ // SGLM will require fresh layout call to recover state after detach
+ view.requestLayout();
}
/**
@@ -363,10 +393,16 @@
private boolean checkSpanForGap(Span span) {
if (mShouldReverseLayout) {
if (span.getEndLine() < mPrimaryOrientation.getEndAfterPadding()) {
- return true;
+ // if it is full span, it is OK
+ final View endView = span.mViews.get(span.mViews.size() - 1);
+ final LayoutParams lp = span.getLayoutParams(endView);
+ return !lp.mFullSpan;
}
} else if (span.getStartLine() > mPrimaryOrientation.getStartAfterPadding()) {
- return true;
+ // if it is full span, it is OK
+ final View startView = span.mViews.get(0);
+ final LayoutParams lp = span.getLayoutParams(startView);
+ return !lp.mFullSpan;
}
return false;
}
@@ -409,12 +445,9 @@
return;
}
mOrientation = orientation;
- if (mPrimaryOrientation != null && mSecondaryOrientation != null) {
- // swap
- OrientationHelper tmp = mPrimaryOrientation;
- mPrimaryOrientation = mSecondaryOrientation;
- mSecondaryOrientation = tmp;
- }
+ OrientationHelper tmp = mPrimaryOrientation;
+ mPrimaryOrientation = mSecondaryOrientation;
+ mSecondaryOrientation = tmp;
requestLayout();
}
@@ -478,6 +511,7 @@
+ "or GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS");
}
mGapStrategy = gapStrategy;
+ setAutoMeasureEnabled(mGapStrategy != GAP_HANDLING_NONE);
requestLayout();
}
@@ -508,15 +542,6 @@
requestLayout();
}
- private void ensureOrientationHelper() {
- if (mPrimaryOrientation == null) {
- mPrimaryOrientation = OrientationHelper.createOrientationHelper(this, mOrientation);
- mSecondaryOrientation = OrientationHelper
- .createOrientationHelper(this, 1 - mOrientation);
- mLayoutState = new LayoutState();
- }
- }
-
/**
* Calculates the views' layout order. (e.g. from end to start or start to end)
* RTL layout support is applied automatically. So if layout is RTL and
@@ -546,29 +571,58 @@
public boolean getReverseLayout() {
return mReverseLayout;
}
+
+ @Override
+ public void setMeasuredDimension(Rect childrenBounds, int wSpec, int hSpec) {
+ // we don't like it to wrap content in our non-scroll direction.
+ final int width, height;
+ final int horizontalPadding = getPaddingLeft() + getPaddingRight();
+ final int verticalPadding = getPaddingTop() + getPaddingBottom();
+ if (mOrientation == VERTICAL) {
+ final int usedHeight = childrenBounds.height() + verticalPadding;
+ height = chooseSize(hSpec, usedHeight, getMinimumHeight());
+ width = chooseSize(wSpec, mSizePerSpan * mSpanCount + horizontalPadding,
+ getMinimumWidth());
+ } else {
+ final int usedWidth = childrenBounds.width() + horizontalPadding;
+ width = chooseSize(wSpec, usedWidth, getMinimumWidth());
+ height = chooseSize(hSpec, mSizePerSpan * mSpanCount + verticalPadding,
+ getMinimumHeight());
+ }
+ setMeasuredDimension(width, height);
+ }
+
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- ensureOrientationHelper();
- final AnchorInfo anchorInfo = mAnchorInfo;
- anchorInfo.reset();
+ onLayoutChildren(recycler, state, true);
+ }
+
+ private void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state,
+ boolean shouldCheckForGaps) {
+ final AnchorInfo anchorInfo = mAnchorInfo;
if (mPendingSavedState != null || mPendingScrollPosition != NO_POSITION) {
if (state.getItemCount() == 0) {
removeAndRecycleAllViews(recycler);
+ anchorInfo.reset();
return;
}
}
- if (mPendingSavedState != null) {
- applyPendingSavedState(anchorInfo);
- } else {
- resolveShouldLayoutReverse();
- anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+ if (!anchorInfo.mValid || mPendingScrollPosition != NO_POSITION ||
+ mPendingSavedState != null) {
+ anchorInfo.reset();
+ if (mPendingSavedState != null) {
+ applyPendingSavedState(anchorInfo);
+ } else {
+ resolveShouldLayoutReverse();
+ anchorInfo.mLayoutFromEnd = mShouldReverseLayout;
+ }
+
+ updateAnchorInfoForLayout(state, anchorInfo);
+ anchorInfo.mValid = true;
}
-
- updateAnchorInfoForLayout(state, anchorInfo);
-
- if (mPendingSavedState == null) {
+ if (mPendingSavedState == null && mPendingScrollPosition == NO_POSITION) {
if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
isLayoutRTL() != mLastLayoutRTL) {
mLazySpanLookup.clear();
@@ -593,8 +647,9 @@
}
}
detachAndScrapAttachedViews(recycler);
+ mLayoutState.mRecycle = false;
mLaidOutInvalidFullSpan = false;
- updateMeasureSpecs();
+ updateMeasureSpecs(mSecondaryOrientation.getTotalSpace());
updateLayoutState(anchorInfo.mPosition, state);
if (anchorInfo.mLayoutFromEnd) {
// Layout start.
@@ -614,6 +669,8 @@
fill(recycler, mLayoutState, state);
}
+ repositionToWrapContentIfNecessary();
+
if (getChildCount() > 0) {
if (mShouldReverseLayout) {
fixEndGap(recycler, state, true);
@@ -623,21 +680,85 @@
fixEndGap(recycler, state, false);
}
}
-
- if (!state.isPreLayout()) {
+ boolean hasGaps = false;
+ if (shouldCheckForGaps && !state.isPreLayout()) {
final boolean needToCheckForGaps = mGapStrategy != GAP_HANDLING_NONE
&& getChildCount() > 0
&& (mLaidOutInvalidFullSpan || hasGapsToFix() != null);
if (needToCheckForGaps) {
removeCallbacks(mCheckForGapsRunnable);
- postOnAnimation(mCheckForGapsRunnable);
+ if (checkForGaps()) {
+ hasGaps = true;
+ }
}
- mPendingScrollPosition = NO_POSITION;
- mPendingScrollPositionOffset = INVALID_OFFSET;
+ }
+ if (state.isPreLayout()) {
+ mAnchorInfo.reset();
}
mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
mLastLayoutRTL = isLayoutRTL();
+ if (hasGaps) {
+ mAnchorInfo.reset();
+ onLayoutChildren(recycler, state, false);
+ }
+ }
+
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ mPendingScrollPosition = NO_POSITION;
+ mPendingScrollPositionOffset = INVALID_OFFSET;
mPendingSavedState = null; // we don't need this anymore
+ mAnchorInfo.reset();
+ }
+
+ private void repositionToWrapContentIfNecessary() {
+ if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
+ return; // nothing to do
+ }
+ float maxSize = 0;
+ final int childCount = getChildCount();
+ for (int i = 0; i < childCount; i ++) {
+ View child = getChildAt(i);
+ float size = mSecondaryOrientation.getDecoratedMeasurement(child);
+ if (size < maxSize) {
+ continue;
+ }
+ LayoutParams layoutParams = (LayoutParams) child.getLayoutParams();
+ if (layoutParams.isFullSpan()) {
+ size = 1f * size / mSpanCount;
+ }
+ maxSize = Math.max(maxSize, size);
+ }
+ int before = mSizePerSpan;
+ int desired = Math.round(maxSize * mSpanCount);
+ if (mSecondaryOrientation.getMode() == View.MeasureSpec.AT_MOST) {
+ desired = Math.min(desired, mSecondaryOrientation.getTotalSpace());
+ }
+ updateMeasureSpecs(desired);
+ if (mSizePerSpan == before) {
+ return; // nothing has changed
+ }
+ for (int i = 0; i < childCount; i ++) {
+ View child = getChildAt(i);
+ final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+ if (lp.mFullSpan) {
+ continue;
+ }
+ if (isLayoutRTL() && mOrientation == VERTICAL) {
+ int newOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * mSizePerSpan;
+ int prevOffset = -(mSpanCount - 1 - lp.mSpan.mIndex) * before;
+ child.offsetLeftAndRight(newOffset - prevOffset);
+ } else {
+ int newOffset = lp.mSpan.mIndex * mSizePerSpan;
+ int prevOffset = lp.mSpan.mIndex * before;
+ if (mOrientation == VERTICAL) {
+ child.offsetLeftAndRight(newOffset - prevOffset);
+ } else {
+ child.offsetTopAndBottom(newOffset - prevOffset);
+ }
+ }
+ }
}
private void applyPendingSavedState(AnchorInfo anchorInfo) {
@@ -726,7 +847,6 @@
// child
anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
: getFirstChildPosition();
-
if (mPendingScrollPositionOffset != INVALID_OFFSET) {
if (anchorInfo.mLayoutFromEnd) {
final int target = mPrimaryOrientation.getEndAfterPadding() -
@@ -785,17 +905,11 @@
return true;
}
- void updateMeasureSpecs() {
- mSizePerSpan = mSecondaryOrientation.getTotalSpace() / mSpanCount;
+ void updateMeasureSpecs(int totalSpace) {
+ mSizePerSpan = totalSpace / mSpanCount;
+ //noinspection ResourceType
mFullSizeSpec = View.MeasureSpec.makeMeasureSpec(
- mSecondaryOrientation.getTotalSpace(), View.MeasureSpec.EXACTLY);
- if (mOrientation == VERTICAL) {
- mWidthSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
- mHeightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- } else {
- mHeightSpec = View.MeasureSpec.makeMeasureSpec(mSizePerSpan, View.MeasureSpec.EXACTLY);
- mWidthSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
- }
+ totalSpace, mSecondaryOrientation.getMode());
}
@Override
@@ -940,7 +1054,6 @@
if (getChildCount() == 0) {
return 0;
}
- ensureOrientationHelper();
return ScrollbarHelper.computeScrollOffset(state, mPrimaryOrientation,
findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
, findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
@@ -961,7 +1074,6 @@
if (getChildCount() == 0) {
return 0;
}
- ensureOrientationHelper();
return ScrollbarHelper.computeScrollExtent(state, mPrimaryOrientation,
findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
, findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
@@ -982,7 +1094,6 @@
if (getChildCount() == 0) {
return 0;
}
- ensureOrientationHelper();
return ScrollbarHelper.computeScrollRange(state, mPrimaryOrientation,
findFirstVisibleItemClosestToStart(!mSmoothScrollbarEnabled, true)
, findFirstVisibleItemClosestToEnd(!mSmoothScrollbarEnabled, true),
@@ -994,43 +1105,48 @@
return computeScrollRange(state);
}
- private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp) {
+ private void measureChildWithDecorationsAndMargin(View child, LayoutParams lp,
+ boolean alreadyMeasured) {
if (lp.mFullSpan) {
if (mOrientation == VERTICAL) {
measureChildWithDecorationsAndMargin(child, mFullSizeSpec,
- getSpecForDimension(lp.height, mHeightSpec));
+ getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
+ alreadyMeasured);
} else {
measureChildWithDecorationsAndMargin(child,
- getSpecForDimension(lp.width, mWidthSpec), mFullSizeSpec);
+ getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
+ mFullSizeSpec, alreadyMeasured);
}
} else {
if (mOrientation == VERTICAL) {
- measureChildWithDecorationsAndMargin(child, mWidthSpec,
- getSpecForDimension(lp.height, mHeightSpec));
+ measureChildWithDecorationsAndMargin(child,
+ getChildMeasureSpec(mSizePerSpan, getWidthMode(), 0, lp.width, false),
+ getChildMeasureSpec(getHeight(), getHeightMode(), 0, lp.height, true),
+ alreadyMeasured);
} else {
measureChildWithDecorationsAndMargin(child,
- getSpecForDimension(lp.width, mWidthSpec), mHeightSpec);
+ getChildMeasureSpec(getWidth(), getWidthMode(), 0, lp.width, true),
+ getChildMeasureSpec(mSizePerSpan, getHeightMode(), 0, lp.height, false),
+ alreadyMeasured);
}
}
}
- private int getSpecForDimension(int dim, int defaultSpec) {
- if (dim < 0) {
- return defaultSpec;
- } else {
- return View.MeasureSpec.makeMeasureSpec(dim, View.MeasureSpec.EXACTLY);
- }
- }
-
private void measureChildWithDecorationsAndMargin(View child, int widthSpec,
- int heightSpec) {
+ int heightSpec, boolean alreadyMeasured) {
calculateItemDecorationsForChild(child, mTmpRect);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
widthSpec = updateSpecWithExtra(widthSpec, lp.leftMargin + mTmpRect.left,
lp.rightMargin + mTmpRect.right);
heightSpec = updateSpecWithExtra(heightSpec, lp.topMargin + mTmpRect.top,
lp.bottomMargin + mTmpRect.bottom);
- child.measure(widthSpec, heightSpec);
+ final boolean measure = alreadyMeasured
+ ? shouldReMeasureChild(child, widthSpec, heightSpec, lp)
+ : shouldMeasureChild(child, widthSpec, heightSpec, lp);
+ if (measure) {
+ child.measure(widthSpec, heightSpec);
+ }
+
}
private int updateSpecWithExtra(int spec, int startInset, int endInset) {
@@ -1074,7 +1190,6 @@
}
if (getChildCount() > 0) {
- ensureOrientationHelper();
state.mAnchorPosition = mLastLayoutFromEnd ? getLastChildPosition()
: getFirstChildPosition();
state.mVisibleAnchorPosition = findFirstVisibleItemPositionInt();
@@ -1187,7 +1302,6 @@
* children order.
*/
View findFirstVisibleItemClosestToStart(boolean fullyVisible, boolean acceptPartiallyVisible) {
- ensureOrientationHelper();
final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
final int limit = getChildCount();
@@ -1218,7 +1332,6 @@
* children order.
*/
View findFirstVisibleItemClosestToEnd(boolean fullyVisible, boolean acceptPartiallyVisible) {
- ensureOrientationHelper();
final int boundsStart = mPrimaryOrientation.getStartAfterPadding();
final int boundsEnd = mPrimaryOrientation.getEndAfterPadding();
View partiallyVisible = null;
@@ -1243,7 +1356,10 @@
private void fixEndGap(RecyclerView.Recycler recycler, RecyclerView.State state,
boolean canOffsetChildren) {
- final int maxEndLine = getMaxEnd(mPrimaryOrientation.getEndAfterPadding());
+ final int maxEndLine = getMaxEnd(Integer.MIN_VALUE);
+ if (maxEndLine == Integer.MIN_VALUE) {
+ return;
+ }
int gap = mPrimaryOrientation.getEndAfterPadding() - maxEndLine;
int fixOffset;
if (gap > 0) {
@@ -1259,7 +1375,10 @@
private void fixStartGap(RecyclerView.Recycler recycler, RecyclerView.State state,
boolean canOffsetChildren) {
- final int minStartLine = getMinStart(mPrimaryOrientation.getStartAfterPadding());
+ final int minStartLine = getMinStart(Integer.MAX_VALUE);
+ if (minStartLine == Integer.MAX_VALUE) {
+ return;
+ }
int gap = minStartLine - mPrimaryOrientation.getStartAfterPadding();
int fixOffset;
if (gap > 0) {
@@ -1298,6 +1417,10 @@
mLayoutState.mEndLine = mPrimaryOrientation.getEnd() + endExtra;
mLayoutState.mStartLine = -startExtra;
}
+ mLayoutState.mStopInFocusable = false;
+ mLayoutState.mRecycle = true;
+ mLayoutState.mInfinite = mPrimaryOrientation.getMode() == View.MeasureSpec.UNSPECIFIED &&
+ mPrimaryOrientation.getEnd() == 0;
}
private void setLayoutStateDirection(int direction) {
@@ -1402,10 +1525,18 @@
final int targetLine;
// Line of the furthest row.
- if (layoutState.mLayoutDirection == LAYOUT_END) {
- targetLine = layoutState.mEndLine + layoutState.mAvailable;
- } else { // LAYOUT_START
- targetLine = layoutState.mStartLine - layoutState.mAvailable;
+ if (mLayoutState.mInfinite) {
+ if (layoutState.mLayoutDirection == LAYOUT_END) {
+ targetLine = Integer.MAX_VALUE;
+ } else { // LAYOUT_START
+ targetLine = Integer.MIN_VALUE;
+ }
+ } else {
+ if (layoutState.mLayoutDirection == LAYOUT_END) {
+ targetLine = layoutState.mEndLine + layoutState.mAvailable;
+ } else { // LAYOUT_START
+ targetLine = layoutState.mStartLine - layoutState.mAvailable;
+ }
}
updateAllRemainingSpans(layoutState.mLayoutDirection, targetLine);
@@ -1419,7 +1550,8 @@
? mPrimaryOrientation.getEndAfterPadding()
: mPrimaryOrientation.getStartAfterPadding();
boolean added = false;
- while (layoutState.hasMore(state) && !mRemainingSpans.isEmpty()) {
+ while (layoutState.hasMore(state)
+ && (mLayoutState.mInfinite || !mRemainingSpans.isEmpty())) {
View view = layoutState.next(recycler);
LayoutParams lp = ((LayoutParams) view.getLayoutParams());
final int position = lp.getViewLayoutPosition();
@@ -1445,7 +1577,7 @@
} else {
addView(view, 0);
}
- measureChildWithDecorationsAndMargin(view, lp);
+ measureChildWithDecorationsAndMargin(view, lp, false);
final int start;
final int end;
@@ -1493,13 +1625,22 @@
mLaidOutInvalidFullSpan = true;
}
}
-
}
attachViewToSpans(view, lp, layoutState);
- final int otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
- : currentSpan.mIndex * mSizePerSpan +
- mSecondaryOrientation.getStartAfterPadding();
- final int otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
+ final int otherStart;
+ final int otherEnd;
+ if (isLayoutRTL() && mOrientation == VERTICAL) {
+ otherEnd = lp.mFullSpan ? mSecondaryOrientation.getEndAfterPadding() :
+ mSecondaryOrientation.getEndAfterPadding()
+ - (mSpanCount - 1 - currentSpan.mIndex) * mSizePerSpan;
+ otherStart = otherEnd - mSecondaryOrientation.getDecoratedMeasurement(view);
+ } else {
+ otherStart = lp.mFullSpan ? mSecondaryOrientation.getStartAfterPadding()
+ : currentSpan.mIndex * mSizePerSpan +
+ mSecondaryOrientation.getStartAfterPadding();
+ otherEnd = otherStart + mSecondaryOrientation.getDecoratedMeasurement(view);
+ }
+
if (mOrientation == VERTICAL) {
layoutDecoratedWithMargins(view, otherStart, start, otherEnd, end);
} else {
@@ -1512,6 +1653,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 +1711,9 @@
}
private void recycle(RecyclerView.Recycler recycler, LayoutState layoutState) {
+ if (!layoutState.mRecycle || layoutState.mInfinite) {
+ return;
+ }
if (layoutState.mAvailable == 0) {
// easy, recycle line is still valid
if (layoutState.mLayoutDirection == LAYOUT_START) {
@@ -1612,18 +1763,6 @@
}
}
- private void layoutDecoratedWithMargins(View child, int left, int top, int right, int bottom) {
- LayoutParams lp = (LayoutParams) child.getLayoutParams();
- if (DEBUG) {
- Log.d(TAG, "layout decorated pos: " + lp.getViewLayoutPosition() + ", span:"
- + lp.getSpanIndex() + ", fullspan:" + lp.mFullSpan
- + ". l:" + left + ",t:" + top
- + ", r:" + right + ", b:" + bottom);
- }
- layoutDecorated(child, left + lp.leftMargin, top + lp.topMargin, right - lp.rightMargin
- , bottom - lp.bottomMargin);
- }
-
private void updateAllRemainingSpans(int layoutDir, int targetLine) {
for (int i = 0; i < mSpanCount; i++) {
if (mSpans[i].mViews.isEmpty()) {
@@ -1715,7 +1854,8 @@
private void recycleFromStart(RecyclerView.Recycler recycler, int line) {
while (getChildCount() > 0) {
View child = getChildAt(0);
- if (mPrimaryOrientation.getDecoratedEnd(child) <= line) {
+ if (mPrimaryOrientation.getDecoratedEnd(child) <= line &&
+ mPrimaryOrientation.getTransformedEndWithDecoration(child) <= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
@@ -1745,7 +1885,8 @@
int i;
for (i = childCount - 1; i >= 0; i--) {
View child = getChildAt(i);
- if (mPrimaryOrientation.getDecoratedStart(child) >= line) {
+ if (mPrimaryOrientation.getDecoratedStart(child) >= line &&
+ mPrimaryOrientation.getTransformedStartWithDecoration(child) >= line) {
LayoutParams lp = (LayoutParams) child.getLayoutParams();
// Don't recycle the last View in a span not to lose span's start/end lines
if (lp.mFullSpan) {
@@ -1908,7 +2049,6 @@
}
int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
- ensureOrientationHelper();
final int referenceChildPosition;
final int layoutDir;
if (dt > 0) { // layout towards end
@@ -1918,6 +2058,7 @@
layoutDir = LAYOUT_START;
referenceChildPosition = getFirstChildPosition();
}
+ mLayoutState.mRecycle = true;
updateLayoutState(referenceChildPosition, state);
setLayoutStateDirection(layoutDir);
mLayoutState.mCurrentPosition = referenceChildPosition + mLayoutState.mItemDirection;
@@ -1985,10 +2126,16 @@
return 0;
}
+ @SuppressWarnings("deprecation")
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
- return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mOrientation == HORIZONTAL) {
+ return new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.FILL_PARENT);
+ } else {
+ return new LayoutParams(ViewGroup.LayoutParams.FILL_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
}
@Override
@@ -2014,6 +2161,116 @@
return mOrientation;
}
+ @Nullable
+ @Override
+ public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ if (getChildCount() == 0) {
+ return null;
+ }
+
+ final View directChild = findContainingItemView(focused);
+ if (directChild == null) {
+ return null;
+ }
+
+ resolveShouldLayoutReverse();
+ final int layoutDir = convertFocusDirectionToLayoutDirection(direction);
+ if (layoutDir == LayoutState.INVALID_LAYOUT) {
+ return null;
+ }
+ LayoutParams prevFocusLayoutParams = (LayoutParams) directChild.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;
+ mLayoutState.mRecycle = false;
+ fill(recycler, mLayoutState, state);
+ mLastLayoutFromEnd = mShouldReverseLayout;
+ if (!prevFocusFullSpan) {
+ View view = prevFocusSpan.getFocusableViewAfter(referenceChildPosition, layoutDir);
+ if (view != null && view != directChild) {
+ 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 != directChild) {
+ return view;
+ }
+ }
+ } else {
+ for (int i = 0; i < mSpanCount; i ++) {
+ View view = mSpans[i].getFocusableViewAfter(referenceChildPosition, layoutDir);
+ if (view != null && view != directChild) {
+ 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:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_START;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_END;
+ } else {
+ return LayoutState.LAYOUT_START;
+ }
+ case View.FOCUS_FORWARD:
+ if (mOrientation == VERTICAL) {
+ return LayoutState.LAYOUT_END;
+ } else if (isLayoutRTL()) {
+ return LayoutState.LAYOUT_START;
+ } else {
+ 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 +2351,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 +2535,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 +2579,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 +2785,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 +2877,6 @@
return mGapPerSpan == null ? 0 : mGapPerSpan[spanIndex];
}
- public void invalidateSpanGaps() {
- mGapPerSpan = null;
- }
-
@Override
public int describeContents() {
return 0;
@@ -2712,6 +2956,7 @@
mReverseLayout = in.readInt() == 1;
mAnchorLayoutFromEnd = in.readInt() == 1;
mLastLayoutRTL = in.readInt() == 1;
+ //noinspection unchecked
mFullSpanItems = in.readArrayList(
LazySpanLookup.FullSpanItem.class.getClassLoader());
}
@@ -2784,18 +3029,24 @@
/**
* Data class to hold the information about an anchor position which is used in onLayout call.
*/
- private class AnchorInfo {
+ class AnchorInfo {
int mPosition;
int mOffset;
boolean mLayoutFromEnd;
boolean mInvalidateOffsets;
+ boolean mValid;
+
+ public AnchorInfo() {
+ reset();
+ }
void reset() {
mPosition = NO_POSITION;
mOffset = INVALID_OFFSET;
mLayoutFromEnd = false;
mInvalidateOffsets = false;
+ mValid = false;
}
void assignCoordinateFromPadding() {
diff --git a/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
index 0af8dfb..f01a382 100644
--- a/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
+++ b/v7/recyclerview/src/android/support/v7/widget/ViewInfoStore.java
@@ -21,6 +21,7 @@
import android.support.v4.util.ArrayMap;
import android.support.v4.util.LongSparseArray;
import android.support.v4.util.Pools;
+import android.view.View;
import static android.support.v7.widget.RecyclerView.ViewHolder;
import static android.support.v7.widget.RecyclerView.ItemAnimator.ItemHolderInfo;
@@ -73,22 +74,51 @@
record.flags |= FLAG_PRE;
}
+ boolean isDisappearing(ViewHolder holder) {
+ final InfoRecord record = mLayoutHolderMap.get(holder);
+ return record != null && ((record.flags & FLAG_DISAPPEARED) != 0);
+ }
+
/**
* Finds the ItemHolderInfo for the given ViewHolder in preLayout list and removes it.
+ *
* @param vh The ViewHolder whose information is being queried
* @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
*/
@Nullable
ItemHolderInfo popFromPreLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_PRE);
+ }
+
+ /**
+ * Finds the ItemHolderInfo for the given ViewHolder in postLayout list and removes it.
+ *
+ * @param vh The ViewHolder whose information is being queried
+ * @return The ItemHolderInfo for the given ViewHolder or null if it does not exist
+ */
+ @Nullable
+ ItemHolderInfo popFromPostLayout(ViewHolder vh) {
+ return popFromLayoutStep(vh, FLAG_POST);
+ }
+
+ private ItemHolderInfo popFromLayoutStep(ViewHolder vh, int flag) {
int index = mLayoutHolderMap.indexOfKey(vh);
if (index < 0) {
return null;
}
final InfoRecord record = mLayoutHolderMap.valueAt(index);
- if (record != null && (record.flags & FLAG_PRE) != 0) {
- record.flags &= ~FLAG_PRE;
- final ItemHolderInfo info = record.preInfo;
- if (record.flags == 0) {
+ if (record != null && (record.flags & flag) != 0) {
+ record.flags &= ~flag;
+ final ItemHolderInfo info;
+ if (flag == FLAG_PRE) {
+ info = record.preInfo;
+ } else if (flag == FLAG_POST) {
+ info = record.postInfo;
+ } else {
+ throw new IllegalArgumentException("Must provide flag PRE or POST");
+ }
+ // if not pre-post flag is left, clear.
+ if ((record.flags & (FLAG_PRE | FLAG_POST)) == 0) {
mLayoutHolderMap.removeAt(index);
InfoRecord.recycle(record);
}
@@ -198,7 +228,13 @@
callback.unused(viewHolder);
} else if ((record.flags & FLAG_DISAPPEARED) != 0) {
// Set as "disappeared" by the LayoutManager (addDisappearingView)
- callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
+ if (record.preInfo == null) {
+ // similar to appear disappear but happened between different layout passes.
+ // this can happen when the layout manager is using auto-measure
+ callback.unused(viewHolder);
+ } else {
+ callback.processDisappeared(viewHolder, record.preInfo, record.postInfo);
+ }
} else if ((record.flags & FLAG_APPEAR_PRE_AND_POST) == FLAG_APPEAR_PRE_AND_POST) {
// Appeared in the layout but not in the adapter (e.g. entered the viewport)
callback.processAppeared(viewHolder, record.preInfo, record.postInfo);
@@ -241,8 +277,12 @@
InfoRecord.drainCache();
}
+ public void onViewDetached(ViewHolder viewHolder) {
+ removeFromDisappearedInLayout(viewHolder);
+ }
+
interface ProcessCallback {
- void processDisappeared(ViewHolder viewHolder, ItemHolderInfo preInfo,
+ void processDisappeared(ViewHolder viewHolder, @NonNull ItemHolderInfo preInfo,
@Nullable ItemHolderInfo postInfo);
void processAppeared(ViewHolder viewHolder, @Nullable ItemHolderInfo preInfo,
ItemHolderInfo postInfo);
diff --git a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
index 72d3e8c..b64c753 100644
--- a/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
+++ b/v7/recyclerview/src/android/support/v7/widget/helper/ItemTouchHelper.java
@@ -16,19 +16,24 @@
package android.support.v7.widget.helper;
+import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.os.Build;
-import android.support.v4.animation.ValueAnimatorCompat;
+import android.support.annotation.Nullable;
import android.support.v4.animation.AnimatorCompatHelper;
import android.support.v4.animation.AnimatorListenerCompat;
import android.support.v4.animation.AnimatorUpdateListenerCompat;
+import android.support.v4.animation.ValueAnimatorCompat;
import android.support.v4.view.GestureDetectorCompat;
import android.support.v4.view.MotionEventCompat;
import android.support.v4.view.VelocityTrackerCompat;
import android.support.v4.view.ViewCompat;
+import android.support.v7.recyclerview.R;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.RecyclerView.OnItemTouchListener;
+import android.support.v7.widget.RecyclerView.ViewHolder;
import android.util.Log;
import android.view.GestureDetector;
import android.view.HapticFeedbackConstants;
@@ -37,15 +42,11 @@
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewParent;
-import android.support.v7.recyclerview.R;
+import android.view.animation.Interpolator;
import java.util.ArrayList;
import java.util.List;
-import android.support.v7.widget.RecyclerView.OnItemTouchListener;
-import android.support.v7.widget.RecyclerView.ViewHolder;
-import android.view.animation.Interpolator;
-
/**
* This is a utility class to add swipe to dismiss and drag & drop support to RecyclerView.
* <p>
@@ -156,6 +157,11 @@
private static final int ACTION_MODE_DRAG_MASK = ACTION_MODE_SWIPE_MASK << DIRECTION_FLAG_COUNT;
/**
+ * The unit we are using to track velocity
+ */
+ private static final int PIXELS_PER_SECOND = 1000;
+
+ /**
* Views, whose state should be cleared after they are detached from RecyclerView.
* This is necessary after swipe dismissing an item. We wait until animator finishes its job
* to clean these views.
@@ -181,6 +187,16 @@
float mInitialTouchY;
/**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mSwipeEscapeVelocity;
+
+ /**
+ * Set when ItemTouchHelper is assigned to a RecyclerView.
+ */
+ float mMaxSwipeVelocity;
+
+ /**
* The diff between the last event and initial touch.
*/
float mDx;
@@ -366,11 +382,11 @@
break;
}
case MotionEvent.ACTION_CANCEL:
- case MotionEvent.ACTION_UP:
if (mVelocityTracker != null) {
- mVelocityTracker
- .computeCurrentVelocity(1000, mRecyclerView.getMaxFlingVelocity());
+ mVelocityTracker.clear();
}
+ // fall through
+ case MotionEvent.ACTION_UP:
select(null, ACTION_STATE_IDLE);
mActivePointerId = ACTIVE_POINTER_ID_NONE;
break;
@@ -378,11 +394,6 @@
final int pointerIndex = MotionEventCompat.getActionIndex(event);
final int pointerId = MotionEventCompat.getPointerId(event, pointerIndex);
if (pointerId == mActivePointerId) {
- if (mVelocityTracker != null) {
- mVelocityTracker
- .computeCurrentVelocity(1000,
- mRecyclerView.getMaxFlingVelocity());
- }
// This was our active pointer going up. Choose a new
// active pointer and adjust accordingly.
final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
@@ -435,12 +446,14 @@
/**
* Attaches the ItemTouchHelper to the provided RecyclerView. If TouchHelper is already
- * attached
- * to a RecyclerView, it will first detach from the previous one.
+ * attached to a RecyclerView, it will first detach from the previous one. You can call this
+ * method with {@code null} to detach it from the current RecyclerView.
*
- * @param recyclerView The RecyclerView instance to which you want to add this helper.
+ * @param recyclerView The RecyclerView instance to which you want to add this helper or
+ * {@code null} if you want to remove ItemTouchHelper from the current
+ * RecyclerView.
*/
- public void attachToRecyclerView(RecyclerView recyclerView) {
+ public void attachToRecyclerView(@Nullable RecyclerView recyclerView) {
if (mRecyclerView == recyclerView) {
return; // nothing to do
}
@@ -449,6 +462,11 @@
}
mRecyclerView = recyclerView;
if (mRecyclerView != null) {
+ final Resources resources = recyclerView.getResources();
+ mSwipeEscapeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_velocity);
+ mMaxSwipeVelocity = resources
+ .getDimension(R.dimen.item_touch_helper_swipe_escape_max_velocity);
setupCallbacks();
}
}
@@ -1008,7 +1026,7 @@
/**
* Starts dragging the provided ViewHolder. By default, ItemTouchHelper starts a drag when a
- * View is long pressed. You can disable that behavior via
+ * View is long pressed. You can disable that behavior by overriding
* {@link ItemTouchHelper.Callback#isLongPressDragEnabled()}.
* <p>
* For this method to work:
@@ -1187,11 +1205,17 @@
if ((flags & (LEFT | RIGHT)) != 0) {
final int dirFlag = mDx > 0 ? RIGHT : LEFT;
if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
final float xVelocity = VelocityTrackerCompat
.getXVelocity(mVelocityTracker, mActivePointerId);
+ final float yVelocity = VelocityTrackerCompat
+ .getYVelocity(mVelocityTracker, mActivePointerId);
final int velDirFlag = xVelocity > 0f ? RIGHT : LEFT;
+ final float absXVelocity = Math.abs(xVelocity);
if ((velDirFlag & flags) != 0 && dirFlag == velDirFlag &&
- Math.abs(xVelocity) >= mRecyclerView.getMinFlingVelocity()) {
+ absXVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
+ absXVelocity > Math.abs(yVelocity)) {
return velDirFlag;
}
}
@@ -1210,11 +1234,17 @@
if ((flags & (UP | DOWN)) != 0) {
final int dirFlag = mDy > 0 ? DOWN : UP;
if (mVelocityTracker != null && mActivePointerId > -1) {
+ mVelocityTracker.computeCurrentVelocity(PIXELS_PER_SECOND,
+ mCallback.getSwipeVelocityThreshold(mMaxSwipeVelocity));
+ final float xVelocity = VelocityTrackerCompat
+ .getXVelocity(mVelocityTracker, mActivePointerId);
final float yVelocity = VelocityTrackerCompat
.getYVelocity(mVelocityTracker, mActivePointerId);
final int velDirFlag = yVelocity > 0f ? DOWN : UP;
+ final float absYVelocity = Math.abs(yVelocity);
if ((velDirFlag & flags) != 0 && velDirFlag == dirFlag &&
- Math.abs(yVelocity) >= mRecyclerView.getMinFlingVelocity()) {
+ absYVelocity >= mCallback.getSwipeEscapeVelocity(mSwipeEscapeVelocity) &&
+ absYVelocity > Math.abs(xVelocity)) {
return velDirFlag;
}
}
@@ -1370,7 +1400,8 @@
}
/**
- * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for visual
+ * Returns the {@link ItemTouchUIUtil} that is used by the {@link Callback} class for
+ * visual
* changes on Views in response to user interactions. {@link ItemTouchUIUtil} has different
* implementations for different platform versions.
* <p>
@@ -1660,6 +1691,54 @@
}
/**
+ * Defines the minimum velocity which will be considered as a swipe action by the user.
+ * <p>
+ * You can increase this value to make it harder to swipe or decrease it to make it easier.
+ * Keep in mind that ItemTouchHelper also checks the perpendicular velocity and makes sure
+ * current direction velocity is larger then the perpendicular one. Otherwise, user's
+ * movement is ambiguous. You can change the threshold by overriding
+ * {@link #getSwipeVelocityThreshold(float)}.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * The default framework value is passed as a parameter so that you can modify it with a
+ * multiplier.
+ *
+ * @param defaultValue The default value (in pixels per second) used by the
+ * ItemTouchHelper.
+ * @return The minimum swipe velocity. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeVelocityThreshold(float)
+ * @see #getSwipeThreshold(ViewHolder)
+ */
+ public float getSwipeEscapeVelocity(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
+ * Defines the maximum velocity ItemTouchHelper will ever calculate for pointer movements.
+ * <p>
+ * To consider a movement as swipe, ItemTouchHelper requires it to be larger than the
+ * perpendicular movement. If both directions reach to the max threshold, none of them will
+ * be considered as a swipe because it is usually an indication that user rather tried to
+ * scroll then swipe.
+ * <p>
+ * The velocity is calculated in pixels per second.
+ * <p>
+ * You can customize this behavior by changing this method. If you increase the value, it
+ * will be easier for the user to swipe diagonally and if you decrease the value, user will
+ * need to make a rather straight finger movement to trigger a swipe.
+ *
+ * @param defaultValue The default value(in pixels per second) used by the ItemTouchHelper.
+ * @return The velocity cap for pointer movements. The default implementation returns the
+ * <code>defaultValue</code> parameter.
+ * @see #getSwipeEscapeVelocity(float)
+ */
+ public float getSwipeVelocityThreshold(float defaultValue) {
+ return defaultValue;
+ }
+
+ /**
* Called by ItemTouchHelper to select a drop target from the list of ViewHolders that
* are under the dragged View.
* <p>
@@ -1777,7 +1856,6 @@
* @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
* {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
* {@link ItemTouchHelper#ACTION_STATE_DRAG}.
- *
* @see #clearView(RecyclerView, RecyclerView.ViewHolder)
*/
public void onSelectedChanged(ViewHolder viewHolder, int actionState) {
@@ -2036,15 +2114,14 @@
* the faster the list will scroll. Similarly, the larger portion of the View is out of
* bounds, the faster the RecyclerView will scroll.
*
- * @param recyclerView The RecyclerView instance to which ItemTouchHelper is attached
- * to.
+ * @param recyclerView The RecyclerView instance to which ItemTouchHelper is
+ * attached to.
* @param viewSize The total size of the View in scroll direction, excluding
* item decorations.
* @param viewSizeOutOfBounds The total size of the View that is out of bounds. This value
* is negative if the View is dragged towards left or top edge.
* @param totalSize The total size of RecyclerView in the scroll direction.
* @param msSinceStartScroll The time passed since View is kept out of bounds.
- *
* @return The amount that RecyclerView should scroll. Keep in mind that this value will
* be passed to {@link RecyclerView#scrollBy(int, int)} method.
*/
diff --git a/v7/recyclerview/tests/Android.mk b/v7/recyclerview/tests/Android.mk
index 3aba114..9c523b7 100644
--- a/v7/recyclerview/tests/Android.mk
+++ b/v7/recyclerview/tests/Android.mk
@@ -18,7 +18,7 @@
LOCAL_MODULE_TAGS := tests
-LOCAL_SDK_VERSION := current
+LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, src)
diff --git a/v7/recyclerview/tests/AndroidManifest.xml b/v7/recyclerview/tests/AndroidManifest.xml
index 5047517..0661526 100644
--- a/v7/recyclerview/tests/AndroidManifest.xml
+++ b/v7/recyclerview/tests/AndroidManifest.xml
@@ -17,16 +17,20 @@
<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"
+ android:targetSdkVersion="23"
+ 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" />
- <activity android:name="android.support.v7.widget.test.RecyclerViewTestActivity"/>
- <activity android:name="android.support.v7.widget.TestActivity"/>
+ <application android:supportsRtl="true">
+ <uses-library android:name="android.test.runner"/>
+ <activity android:name="android.support.v7.widget.test.RecyclerViewTestActivity"
+ android:theme="@style/noAnimTheme"/>
+ <activity android:name="android.support.v7.widget.TestActivity" android:theme="@style/noAnimTheme"/>
</application>
<instrumentation android:name="android.test.InstrumentationTestRunner"
android:targetPackage="android.support.v7.recyclerview.test"
- />
+ />
</manifest>
diff --git a/v7/recyclerview/tests/NO_DOCS b/v7/recyclerview/tests/NO_DOCS
new file mode 100644
index 0000000..0c81e4a
--- /dev/null
+++ b/v7/recyclerview/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/v7/recyclerview/tests/res/drawable/item_bg.xml b/v7/recyclerview/tests/res/drawable/item_bg.xml
new file mode 100644
index 0000000..840005a
--- /dev/null
+++ b/v7/recyclerview/tests/res/drawable/item_bg.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<selector xmlns:android="http://schemas.android.com/apk/res/android">
+ <item android:state_focused="true">
+ <color android:color="#FF0000"/>
+ </item>
+ <item>
+ <color android:color="#0000FF"/>
+ </item>
+</selector>
\ No newline at end of file
diff --git a/v7/recyclerview/tests/res/layout/focus_search_activity.xml b/v7/recyclerview/tests/res/layout/focus_search_activity.xml
new file mode 100644
index 0000000..8ab7046
--- /dev/null
+++ b/v7/recyclerview/tests/res/layout/focus_search_activity.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<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:id="@+id/root"
+ android:orientation="vertical">
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="I'm before" android:focusableInTouchMode="true"
+ android:background="@drawable/item_bg"
+ android:focusable="true"
+ android:tag="before view"
+ android:id="@+id/before"
+ android:layout_weight="0"/>
+ <android.support.v7.widget.RecyclerView android:layout_width="match_parent"
+ app:spanCount="3"
+ android:id="@+id/recycler_view"
+ android:layout_weight="1"
+ android:layout_height="0dp">
+
+ </android.support.v7.widget.RecyclerView>
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:text="I'm after rv" android:focusableInTouchMode="true"
+ android:background="@drawable/item_bg"
+ android:focusable="true"
+ android:id="@+id/after"
+ android:tag="after view"
+ android:layout_weight="0"/>
+</LinearLayout>
diff --git a/v7/recyclerview/tests/res/layout/focus_test_item_view.xml b/v7/recyclerview/tests/res/layout/focus_test_item_view.xml
new file mode 100644
index 0000000..8f81872
--- /dev/null
+++ b/v7/recyclerview/tests/res/layout/focus_test_item_view.xml
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:orientation="vertical"
+ android:id="@+id/item_root"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+ <FrameLayout android:layout_width="wrap_content"
+ android:id="@+id/parent2"
+ android:layout_height="wrap_content">
+ <FrameLayout android:layout_width="wrap_content"
+ android:id="@+id/parent1"
+ android:layout_height="wrap_content">
+ <TextView android:layout_width="wrap_content" android:layout_height="wrap_content"
+ android:id="@+id/text_view"/>
+ </FrameLayout>
+ </FrameLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/v7/recyclerview/tests/res/layout/inflation_test.xml b/v7/recyclerview/tests/res/layout/inflation_test.xml
index 8a13c33..b34a1a3 100644
--- a/v7/recyclerview/tests/res/layout/inflation_test.xml
+++ b/v7/recyclerview/tests/res/layout/inflation_test.xml
@@ -15,37 +15,92 @@
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:app="http://schemas.android.com/apk/res-auto"
- android:orientation="vertical"
- android:layout_width="fill_parent"
- android:layout_height="fill_parent">
+ xmlns:app="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="fill_parent"
+ android:layout_height="fill_parent">
<android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerView"
- android:layout_width="fill_parent"
- android:layout_height="100dp"
- app:layoutManager="GridLayoutManager"
- android:orientation="horizontal"
- app:spanCount="3"
- app:reverseLayout="true" />
+ android:id="@+id/clipToPaddingNo"
+ android:layout_width="fill_parent"
+ app:layoutManager="LinearLayoutManager"
+ android:clipToPadding="false"
+ android:layout_height="100dp"/>
<android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerView2"
- android:layout_width="fill_parent"
- android:layout_height="100dp"
- app:layoutManager=".CustomLayoutManager"
- android:orientation="vertical"
- app:stackFromEnd="true" />
+ android:id="@+id/clipToPaddingUndefined"
+ android:layout_width="fill_parent"
+ app:layoutManager="LinearLayoutManager"
+ android:layout_height="100dp"/>
<android.support.v7.widget.RecyclerView
- android:id="@+id/recyclerView3"
- android:layout_width="fill_parent"
- android:layout_height="100dp"
- app:layoutManager=".CustomLayoutManager$LayoutManager" />
+ android:id="@+id/clipToPaddingYes"
+ android:layout_width="fill_parent"
+ app:layoutManager="LinearLayoutManager"
+ android:clipToPadding="true"
+ android:layout_height="100dp"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ app:layoutManager="GridLayoutManager"
+ android:orientation="horizontal"
+ app:spanCount="3"
+ app:reverseLayout="true"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView2"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ app:layoutManager=".CustomLayoutManager"
+ android:orientation="vertical"
+ app:stackFromEnd="true"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView3"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ app:layoutManager=".CustomLayoutManager$LayoutManager"/>
<android.support.v7.widget.RecyclerView
android:id="@+id/recyclerView4"
android:layout_width="fill_parent"
android:layout_height="100dp"
- app:layoutManager=".PrivateLayoutManager" />
+ app:layoutManager=".PrivateLayoutManager"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView5"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/recyclerView6"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ android:nestedScrollingEnabled="false"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/focusability_undefined"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/focusability_after"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ android:descendantFocusability="afterDescendants"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/focusability_before"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ android:descendantFocusability="beforeDescendants"/>
+
+ <android.support.v7.widget.RecyclerView
+ android:id="@+id/focusability_block"
+ android:layout_width="fill_parent"
+ android:layout_height="100dp"
+ android:descendantFocusability="blocksDescendants"/>
+
</LinearLayout>
\ No newline at end of file
diff --git a/v7/recyclerview/tests/res/layout/item_view.xml b/v7/recyclerview/tests/res/layout/item_view.xml
new file mode 100644
index 0000000..ffb2153
--- /dev/null
+++ b/v7/recyclerview/tests/res/layout/item_view.xml
@@ -0,0 +1,25 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2016 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<View xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:background="@drawable/item_bg"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:orientation="vertical">
+
+</View>
\ No newline at end of file
diff --git a/v7/recyclerview/tests/res/values/styles.xml b/v7/recyclerview/tests/res/values/styles.xml
new file mode 100644
index 0000000..f7aad81
--- /dev/null
+++ b/v7/recyclerview/tests/res/values/styles.xml
@@ -0,0 +1,6 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources>
+ <style name="noAnimTheme" parent="android:Theme">
+ <item name="android:windowAnimationStyle">@null</item>
+ </style>
+</resources>
diff --git a/v7/recyclerview/tests/src/android/support/v7/recyclerview/test/SameActivityTestRule.java b/v7/recyclerview/tests/src/android/support/v7/recyclerview/test/SameActivityTestRule.java
new file mode 100644
index 0000000..66f3134
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/recyclerview/test/SameActivityTestRule.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.recyclerview.test;
+
+import android.support.test.rule.ActivityTestRule;
+import android.support.v7.widget.TestActivity;
+
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+/**
+ * Like ActivityTestRule but re-uses the same activity
+ */
+public class SameActivityTestRule extends ActivityTestRule<TestActivity> {
+ static TestActivity sTestActivity;
+ public SameActivityTestRule() {
+ super(TestActivity.class, false, false);
+ }
+
+ public boolean canReUseActivity() {
+ return true;
+ }
+
+ @Override
+ public Statement apply(final Statement base, Description description) {
+ return new ReUsedActivityStatement(base);
+ }
+
+ @Override
+ public TestActivity getActivity() {
+ if (sTestActivity != null) {
+ return sTestActivity;
+ }
+ return super.getActivity();
+ }
+
+ private class ReUsedActivityStatement extends Statement {
+
+ private final Statement mBase;
+
+ public ReUsedActivityStatement(Statement base) {
+ mBase = base;
+ }
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ if (sTestActivity == null || !sTestActivity.canBeReUsed() || !canReUseActivity()) {
+ launchActivity(getActivityIntent());
+ sTestActivity = getActivity();
+ sTestActivity.setAllowFinish(!canReUseActivity());
+ if (!canReUseActivity()) {
+ sTestActivity = null;
+ }
+ } else {
+ sTestActivity.resetContent();
+ }
+ mBase.evaluate();
+ } finally {
+ afterActivityFinished();
+ }
+ }
+ }
+}
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..11fd3ed 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/AsyncListUtilTest.java
@@ -16,12 +16,22 @@
package android.support.v7.util;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
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;
+import static org.junit.Assert.*;
+@MediumTest
+@RunWith(JUnit4.class)
public class AsyncListUtilTest extends BaseThreadedTest {
private static final int TILE_SIZE = 10;
@@ -31,11 +41,10 @@
AsyncListUtil<String> mAsyncListUtil;
- @Override
- public void setUp() throws Exception {
+ @Before
+ public final void setupCallbacks() throws Exception {
mDataCallback = new TestDataCallback();
mViewCallback = new TestViewCallback();
-
mDataCallback.expectTiles(0, 10, 20);
super.setUp();
mDataCallback.waitForTiles("initial load");
@@ -48,20 +57,22 @@
String.class, TILE_SIZE, mDataCallback, mViewCallback);
}
- @Override
+ @After
public void tearDown() throws Exception {
/// Wait a little extra to catch spurious messages.
new CountDownLatch(1).await(500, TimeUnit.MILLISECONDS);
}
- public void testWithNoPreload() throws Throwable {
+ @Test
+ public void withNoPreload() throws Throwable {
scrollAndExpectTiles(10, "scroll to 10", 30);
scrollAndExpectTiles(25, "scroll to 25", 40);
scrollAndExpectTiles(45, "scroll to 45", 50, 60);
scrollAndExpectTiles(70, "scroll to 70", 70, 80, 90);
}
- public void testWithPreload() throws Throwable {
+ @Test
+ public void withPreload() throws Throwable {
mViewCallback.mStartPreload = 5;
mViewCallback.mEndPreload = 15;
scrollAndExpectTiles(50, "scroll down a lot", 40, 50, 60, 70, 80);
@@ -72,7 +83,8 @@
scrollAndExpectTiles(40, "scroll up a little, no new tiles loaded");
}
- public void testTileCaching() throws Throwable {
+ @Test
+ public void tileCaching() throws Throwable {
scrollAndExpectTiles(25, "next screen", 30, 40);
scrollAndExpectTiles(0, "back at top, no new page loads");
@@ -83,7 +95,8 @@
scrollAndExpectTiles(0, "scroll back to top, all pages should reload", 0, 10, 20);
}
- public void testDataRefresh() throws Throwable {
+ @Test
+ public void dataRefresh() throws Throwable {
mViewCallback.expectDataSetChanged(40);
mDataCallback.expectTiles(0, 10, 20);
refreshOnUiThread();
@@ -97,7 +110,8 @@
mDataCallback.waitForTiles("decreasing item count");
}
- public void testItemChanged() throws Throwable {
+ @Test
+ public void itemChanged() throws Throwable {
final int position = 30;
final int count = 20;
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/BaseThreadedTest.java b/v7/recyclerview/tests/src/android/support/v7/util/BaseThreadedTest.java
index 73df9a7..deb6054 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/BaseThreadedTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/BaseThreadedTest.java
@@ -16,29 +16,54 @@
package android.support.v7.util;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Rule;
+
+import android.app.Instrumentation;
import android.support.annotation.UiThread;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
import android.support.v7.widget.TestActivity;
import android.test.ActivityInstrumentationTestCase2;
-abstract public class BaseThreadedTest extends ActivityInstrumentationTestCase2<TestActivity> {
- public BaseThreadedTest() {
- super(TestActivity.class);
- }
+abstract public class BaseThreadedTest {
+ @Rule
+ public ActivityTestRule<TestActivity> mActivityRule = new ActivityTestRule<>(
+ TestActivity.class);
- @Override
- public void setUp() throws Exception{
+ public final void setUp() throws Exception{
try {
- runTestOnUiThread(new Runnable() {
+ getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
setUpUi();
}
});
} catch (Throwable throwable) {
- fail(throwable.getMessage());
+ Assert.fail(throwable.getMessage());
}
}
+ public Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ public void runTestOnUiThread(final Runnable test) {
+ final Throwable[] error = new Throwable[1];
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ test.run();
+ } catch (Throwable t) {
+ error[0] = t;
+ }
+ }
+ });
+ Assert.assertNull(error[0]);
+ }
+
@UiThread
protected abstract void setUpUi();
}
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 28c14d01..9aa9d11 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;
@@ -37,7 +39,7 @@
}
@Test
- public void testAllArguments() {
+ public void allArguments() {
String data = "data";
mQueue.sendMessage(MessageThreadUtil.SyncQueueItem.obtainMessage(
0, 1, 2, 3, 4, 5, data));
@@ -53,7 +55,7 @@
}
@Test
- public void testSendInOrder() {
+ public void sendInOrder() {
mQueue.sendMessage(obtainMessage(1, 2));
mQueue.sendMessage(obtainMessage(3, 4));
mQueue.sendMessage(obtainMessage(5, 6));
@@ -75,7 +77,7 @@
}
@Test
- public void testSendAtFront() {
+ public void sendAtFront() {
mQueue.sendMessage(obtainMessage(1, 2));
mQueue.sendMessageAtFrontOfQueue(obtainMessage(3, 4));
mQueue.sendMessage(obtainMessage(5, 6));
@@ -97,7 +99,7 @@
}
@Test
- public void testRemove() {
+ public void remove() {
mQueue.sendMessage(obtainMessage(1, 0));
mQueue.sendMessage(obtainMessage(2, 0));
mQueue.sendMessage(obtainMessage(1, 0));
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..8091f29 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/ThreadUtilTest.java
@@ -16,23 +16,33 @@
package android.support.v7.util;
+import org.junit.Before;
+import org.junit.Test;
+
import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.CoreMatchers.*;
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<>();
ThreadUtil.MainThreadCallback<Integer> mMainThreadProxy;
ThreadUtil.BackgroundCallback<Integer> mBackgroundProxy;
+ @Before
+ public void init() throws Exception {
+ super.setUp();
+ }
+
@Override
@UiThread
public void setUpUi() {
@@ -89,7 +99,8 @@
});
}
- public void testUpdateItemCount() throws InterruptedException {
+ @Test
+ public void updateItemCount() throws InterruptedException {
initWait("updateItemCount");
// In this test and below the calls to mMainThreadProxy are not really made from the UI
// thread. That's OK since the message queue inside mMainThreadProxy is synchronized.
@@ -98,7 +109,8 @@
assertThat(data, is(new Object[]{7, 9}));
}
- public void testAddTile() throws InterruptedException {
+ @Test
+ public void addTile() throws InterruptedException {
initWait("addTile");
TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10);
mMainThreadProxy.addTile(3, tile);
@@ -106,14 +118,16 @@
assertThat(data, is(new Object[]{3, tile}));
}
- public void testRemoveTile() throws InterruptedException {
+ @Test
+ public void removeTile() throws InterruptedException {
initWait("removeTile");
mMainThreadProxy.removeTile(1, 2);
Object[] data = waitFor("removeTile");
assertThat(data, is(new Object[]{1, 2}));
}
- public void testRefresh() throws InterruptedException {
+ @Test
+ public void refresh() throws InterruptedException {
initWait("refresh");
// In this test and below the calls to mBackgroundProxy are not really made from the worker
// thread. That's OK since the message queue inside mBackgroundProxy is synchronized.
@@ -122,21 +136,24 @@
assertThat(data, is(new Object[]{2}));
}
- public void testRangeUpdate() throws InterruptedException {
+ @Test
+ public void rangeUpdate() throws InterruptedException {
initWait("updateRange");
mBackgroundProxy.updateRange(10, 20, 5, 25, 1);
Object[] data = waitFor("updateRange");
assertThat(data, is(new Object[] {10, 20, 5, 25, 1}));
}
- public void testLoadTile() throws InterruptedException {
+ @Test
+ public void loadTile() throws InterruptedException {
initWait("loadTile");
mBackgroundProxy.loadTile(2, 1);
Object[] data = waitFor("loadTile");
assertThat(data, is(new Object[]{2, 1}));
}
- public void testRecycleTile() throws InterruptedException {
+ @Test
+ public void recycleTile() throws InterruptedException {
initWait("recycleTile");
TileList.Tile<Integer> tile = new TileList.Tile<Integer>(Integer.class, 10);
mBackgroundProxy.recycleTile(tile);
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..308d01b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/util/TileListTest.java
@@ -14,10 +14,12 @@
* limitations under the License.
*/
+
package android.support.v7.util;
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 +28,7 @@
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
+@SmallTest
public class TileListTest {
int mTileSize = 3;
TileList<Integer> mTileList;
@@ -36,22 +39,22 @@
}
@Test
- public void testEmptyGet() {
+ public void emptyGet() {
assertThat(mTileList.getItemAt(3), nullValue());
assertThat(mTileList.getItemAt(100), nullValue());
}
@Test
- public void testGetItemAt() {
+ public void getItemAt() {
assertThat(mTileList.addOrReplace(createTile(0, 1, 2, 3)), nullValue());
- assertThat(mTileList.getItemAt(0).intValue(), is(1));
- assertThat(mTileList.getItemAt(1).intValue(), is(2));
- assertThat(mTileList.getItemAt(2).intValue(), is(3));
+ assertThat(mTileList.getItemAt(0), is(1));
+ assertThat(mTileList.getItemAt(1), is(2));
+ assertThat(mTileList.getItemAt(2), is(3));
assertThat(mTileList.getItemAt(3), nullValue());
}
@Test
- public void testSize() {
+ public void size() {
assertThat(mTileList.size(), is(0));
assertThat(mTileList.addOrReplace(createTile(0, 1, 2, 3)), nullValue());
assertThat(mTileList.size(), is(1));
@@ -65,7 +68,7 @@
}
@Test
- public void testGetAtIndex() {
+ public void getAtIndex() {
assertThat(mTileList.addOrReplace(createTile(0, 1, 2, 3)), nullValue());
assertThat(mTileList.addOrReplace(createTile(3, 1, 2, 3)), nullValue());
assertThat(mTileList.addOrReplace(createTile(6, 1, 2, 3)), nullValue());
@@ -76,45 +79,46 @@
assertThat(mTileList.getAtIndex(3), nullValue());
}
- public void testAddShortTileAndGet() {
+ @Test
+ public void addShortTileAndGet() {
assertThat(mTileList.addOrReplace(createTile(0, 1)), nullValue());
- assertThat(mTileList.getItemAt(0).intValue(), is(1));
- assertThat(mTileList.getItemAt(1).intValue(), nullValue());
- assertThat(mTileList.getItemAt(2).intValue(), nullValue());
+ assertThat(mTileList.getItemAt(0), is(1));
+ assertThat(mTileList.getItemAt(1), nullValue());
+ assertThat(mTileList.getItemAt(2), nullValue());
}
@Test
- public void testAddToReplaceAndGet() {
+ public void addToReplaceAndGet() {
TileList.Tile<Integer> prev = createTile(0, 1, 2, 3);
mTileList.addOrReplace(prev);
assertThat(mTileList.addOrReplace(createTile(0, 4, 5, 6)), sameInstance(prev));
- assertThat(mTileList.getItemAt(0).intValue(), is(4));
- assertThat(mTileList.getItemAt(1).intValue(), is(5));
- assertThat(mTileList.getItemAt(2).intValue(), is(6));
+ assertThat(mTileList.getItemAt(0), is(4));
+ assertThat(mTileList.getItemAt(1), is(5));
+ assertThat(mTileList.getItemAt(2), is(6));
assertThat(mTileList.getItemAt(3), nullValue());
}
@Test
- public void testAddRangeWithGapAndGet() {
+ public void addRangeWithGapAndGet() {
mTileList.addOrReplace(createTile(0, 1, 2, 3));
assertThat(mTileList.addOrReplace(createTile(mTileSize * 2, 4, 5, 6)), nullValue());
- assertThat(mTileList.getItemAt(0).intValue(), is(1));
- assertThat(mTileList.getItemAt(1).intValue(), is(2));
- assertThat(mTileList.getItemAt(2).intValue(), is(3));
+ assertThat(mTileList.getItemAt(0), is(1));
+ assertThat(mTileList.getItemAt(1), is(2));
+ assertThat(mTileList.getItemAt(2), is(3));
assertThat(mTileList.getItemAt(mTileSize), nullValue());
assertThat(mTileList.getItemAt(mTileSize + 1), nullValue());
assertThat(mTileList.getItemAt(mTileSize + 2), nullValue());
- assertThat(mTileList.getItemAt(mTileSize * 2).intValue(), is(4));
- assertThat(mTileList.getItemAt(mTileSize * 2 + 1).intValue(), is(5));
- assertThat(mTileList.getItemAt(mTileSize * 2 + 2).intValue(), is(6));
+ assertThat(mTileList.getItemAt(mTileSize * 2), is(4));
+ assertThat(mTileList.getItemAt(mTileSize * 2 + 1), is(5));
+ assertThat(mTileList.getItemAt(mTileSize * 2 + 2), is(6));
assertThat(mTileList.addOrReplace(createTile(mTileSize, 7, 8, 9)), nullValue());
- assertThat(mTileList.getItemAt(mTileSize).intValue(), is(7));
- assertThat(mTileList.getItemAt(mTileSize + 1).intValue(), is(8));
- assertThat(mTileList.getItemAt(mTileSize + 2).intValue(), is(9));
+ assertThat(mTileList.getItemAt(mTileSize), is(7));
+ assertThat(mTileList.getItemAt(mTileSize + 1), is(8));
+ assertThat(mTileList.getItemAt(mTileSize + 2), is(9));
}
@Test
- public void testRemove() {
+ public void remove() {
mTileList.addOrReplace(createTile(0, 1, 2, 3));
mTileList.addOrReplace(createTile(3, 4, 5, 6));
mTileList.addOrReplace(createTile(6, 7, 8, 9));
@@ -139,4 +143,4 @@
}
return window;
}
-}
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java b/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java
new file mode 100644
index 0000000..d033004
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/util/TouchUtils.java
@@ -0,0 +1,296 @@
+/*
+ * 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.util;
+
+import android.app.Instrumentation;
+import android.os.SystemClock;
+import android.support.v7.widget.RecyclerView;
+import android.test.InstrumentationTestCase;
+import android.view.Gravity;
+import android.view.MotionEvent;
+import android.view.View;
+import android.view.ViewConfiguration;
+
+/**
+ * RV specific layout tests.
+ */
+public class TouchUtils {
+ public static void tapView(Instrumentation inst, RecyclerView recyclerView,
+ View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ public static void touchAndCancelView(Instrumentation inst, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ }
+
+ public static void clickView(Instrumentation inst, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep(1000);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static void longClickView(Instrumentation inst, View v) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float y = xy[1] + (viewHeight / 2.0f);
+
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ eventTime = SystemClock.uptimeMillis();
+ final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
+ x + touchSlop / 2, y + touchSlop / 2, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+
+ try {
+ Thread.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f));
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ public static void dragViewToTop(Instrumentation inst, View v) {
+ dragViewToTop(inst, v, calculateStepsForDistance(v.getTop()));
+ }
+
+ public static void dragViewToTop(Instrumentation inst, View v, int stepCount) {
+ int[] xy = new int[2];
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ final float x = xy[0] + (viewWidth / 2.0f);
+ float fromY = xy[1] + (viewHeight / 2.0f);
+ float toY = 0;
+
+ drag(inst, x, x, fromY, toY, stepCount);
+ }
+
+ private static void getStartLocation(View v, int gravity, int[] xy) {
+ v.getLocationOnScreen(xy);
+
+ final int viewWidth = v.getWidth();
+ final int viewHeight = v.getHeight();
+
+ switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
+ case Gravity.TOP:
+ break;
+ case Gravity.CENTER_VERTICAL:
+ xy[1] += viewHeight / 2;
+ break;
+ case Gravity.BOTTOM:
+ xy[1] += viewHeight - 1;
+ break;
+ default:
+ // Same as top -- do nothing
+ }
+
+ switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
+ case Gravity.LEFT:
+ break;
+ case Gravity.CENTER_HORIZONTAL:
+ xy[0] += viewWidth / 2;
+ break;
+ case Gravity.RIGHT:
+ xy[0] += viewWidth - 1;
+ break;
+ default:
+ // Same as left -- do nothing
+ }
+ }
+
+ public static int dragViewTo(Instrumentation inst, View v, int gravity, int toX,
+ int toY) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaX = fromX - toX;
+ int deltaY = fromY - toY;
+
+ int distance = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
+ drag(inst, fromX, toX, fromY, toY, calculateStepsForDistance(distance));
+
+ return distance;
+ }
+
+ public static int dragViewToX(Instrumentation inst, View v, int gravity, int toX) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaX = fromX - toX;
+
+ drag(inst, fromX, toX, fromY, fromY, calculateStepsForDistance(deltaX));
+
+ return deltaX;
+ }
+
+ public static int dragViewToY(Instrumentation inst, View v, int gravity, int toY) {
+ int[] xy = new int[2];
+
+ getStartLocation(v, gravity, xy);
+
+ final int fromX = xy[0];
+ final int fromY = xy[1];
+
+ int deltaY = fromY - toY;
+
+ drag(inst, fromX, fromX, fromY, toY, calculateStepsForDistance(deltaY));
+
+ return deltaY;
+ }
+
+
+ public static void drag(Instrumentation inst, float fromX, float toX, float fromY,
+ float toY, int stepCount) {
+ long downTime = SystemClock.uptimeMillis();
+ long eventTime = SystemClock.uptimeMillis();
+
+ float y = fromY;
+ float x = fromX;
+
+ float yStep = (toY - fromY) / stepCount;
+ float xStep = (toX - fromX) / stepCount;
+
+ MotionEvent event = MotionEvent.obtain(downTime, eventTime,
+ MotionEvent.ACTION_DOWN, x, y, 0);
+ inst.sendPointerSync(event);
+ for (int i = 0; i < stepCount; ++i) {
+ y += yStep;
+ x += xStep;
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
+ inst.sendPointerSync(event);
+ }
+
+ eventTime = SystemClock.uptimeMillis();
+ event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
+ inst.sendPointerSync(event);
+ inst.waitForIdleSync();
+ }
+
+ private static int calculateStepsForDistance(int distance) {
+ return 1 + Math.abs(distance) / 10;
+ }
+}
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..8bd003d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
@@ -16,8 +16,15 @@
package android.support.v7.widget;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.content.Context;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v7.util.AsyncListUtil;
+import android.test.suitebuilder.annotation.MediumTest;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
@@ -25,7 +32,12 @@
import java.util.BitSet;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import static org.junit.Assert.*;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+@MediumTest
+@RunWith(AndroidJUnit4.class)
public class AsyncListUtilLayoutTest extends BaseRecyclerViewInstrumentationTest {
private static final boolean DEBUG = false;
@@ -46,7 +58,8 @@
public int mStartPrefetch = 0;
public int mEndPrefetch = 0;
- public void testAsyncListUtil() throws Throwable {
+ @Test
+ public void asyncListUtil() throws Throwable {
mRecyclerView = inflateWrappedRV();
mRecyclerView.setHasFixedSize(true);
@@ -304,11 +317,17 @@
mLayoutLatch = new CountDownLatch(count);
}
- public void waitForLayout(long timeout) throws InterruptedException {
- mLayoutLatch.await(timeout * (DEBUG ? 100 : 1), TimeUnit.SECONDS);
- assertEquals("all expected layouts should be executed at the expected time",
- 0, mLayoutLatch.getCount());
- getInstrumentation().waitForIdleSync();
+ public void waitForLayout(int seconds) throws Throwable {
+ mLayoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("all layouts should complete on time",
+ mLayoutLatch.getCount(), CoreMatchers.is(0L));
+ // use a runnable to ensure RV layout is finished
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
}
@Override
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java b/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java
new file mode 100644
index 0000000..12281ae
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/AttachDetachCollector.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Simple class that can collect list of view attach and detach events so that we can assert on them
+ */
+public class AttachDetachCollector implements RecyclerView.OnChildAttachStateChangeListener {
+ private final List<View> mAttached = new ArrayList<>();
+ private final List<View> mDetached = new ArrayList<>();
+
+ public AttachDetachCollector(RecyclerView recyclerView) {
+ recyclerView.addOnChildAttachStateChangeListener(this);
+ }
+
+ @Override
+ public void onChildViewAttachedToWindow(View view) {
+ mAttached.add(view);
+ }
+
+ @Override
+ public void onChildViewDetachedFromWindow(View view) {
+ mDetached.add(view);
+ }
+
+ public void reset() {
+ mAttached.clear();
+ mDetached.clear();
+ }
+
+ public List<View> getAttached() {
+ return mAttached;
+ }
+
+ public List<View> getDetached() {
+ return mDetached;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
new file mode 100644
index 0000000..aa1be7b
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import android.content.Context;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.CountDownLatch;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
+public class BaseGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+ static final String TAG = "GridLayoutManagerTest";
+ static final boolean DEBUG = false;
+
+ WrappedGridLayoutManager mGlm;
+ GridTestAdapter mAdapter;
+
+ public RecyclerView setupBasic(Config config) throws Throwable {
+ return setupBasic(config, new GridTestAdapter(config.mItemCount));
+ }
+
+ public RecyclerView setupBasic(Config config, GridTestAdapter testAdapter) throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ mAdapter = testAdapter;
+ mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
+ config.mReverseLayout);
+ mAdapter.assignSpanSizeLookup(mGlm);
+ recyclerView.setAdapter(mAdapter);
+ recyclerView.setLayoutManager(mGlm);
+ return recyclerView;
+ }
+
+ public static List<Config> createBaseVariations() {
+ List<Config> variations = new ArrayList<>();
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (int spanCount : new int[]{1, 3, 4}) {
+ variations.add(new Config(spanCount, orientation, reverseLayout));
+ }
+ }
+ }
+ return variations;
+ }
+
+ public void waitForFirstLayout(RecyclerView recyclerView) throws Throwable {
+ mGlm.expectLayout(1);
+ setRecyclerView(recyclerView);
+ mGlm.waitForLayout(2);
+ }
+
+ protected int getSize(View view) {
+ if (mGlm.getOrientation() == GridLayoutManager.HORIZONTAL) {
+ return view.getWidth();
+ }
+ return view.getHeight();
+ }
+
+ GridLayoutManager.LayoutParams getLp(View view) {
+ return (GridLayoutManager.LayoutParams) view.getLayoutParams();
+ }
+
+ static class Config implements Cloneable {
+
+ int mSpanCount;
+ int mOrientation = GridLayoutManager.VERTICAL;
+ int mItemCount = 1000;
+ int mSpanPerItem = 1;
+ boolean mReverseLayout = false;
+
+ Config(int spanCount, int itemCount) {
+ mSpanCount = spanCount;
+ mItemCount = itemCount;
+ }
+
+ public Config(int spanCount, int orientation, boolean reverseLayout) {
+ mSpanCount = spanCount;
+ mOrientation = orientation;
+ mReverseLayout = reverseLayout;
+ }
+
+ Config orientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "Config{" +
+ "mSpanCount=" + mSpanCount +
+ ", mOrientation=" + (mOrientation == GridLayoutManager.HORIZONTAL ? "h" : "v") +
+ ", mItemCount=" + mItemCount +
+ ", mReverseLayout=" + mReverseLayout +
+ '}';
+ }
+
+ public Config reverseLayout(boolean reverseLayout) {
+ mReverseLayout = reverseLayout;
+ return this;
+ }
+
+ @Override
+ protected Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+ }
+
+ class WrappedGridLayoutManager extends GridLayoutManager {
+
+ CountDownLatch mLayoutLatch;
+
+ List<GridLayoutManagerTest.Callback>
+ mCallbacks = new ArrayList<GridLayoutManagerTest.Callback>();
+
+ Boolean mFakeRTL;
+
+ public WrappedGridLayoutManager(Context context, int spanCount) {
+ super(context, spanCount);
+ }
+
+ public WrappedGridLayoutManager(Context context, int spanCount, int orientation,
+ boolean reverseLayout) {
+ super(context, spanCount, orientation, reverseLayout);
+ }
+
+ @Override
+ protected boolean isLayoutRTL() {
+ return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
+ }
+
+ public void setFakeRtl(Boolean fakeRtl) {
+ mFakeRTL = fakeRtl;
+ try {
+ requestLayoutOnUIThread(mRecyclerView);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ for (GridLayoutManagerTest.Callback callback : mCallbacks) {
+ callback.onBeforeLayout(recycler, state);
+ }
+ super.onLayoutChildren(recycler, state);
+ for (GridLayoutManagerTest.Callback callback : mCallbacks) {
+ callback.onAfterLayout(recycler, state);
+ }
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ mLayoutLatch.countDown();
+ }
+
+ @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 void expectLayout(int layoutCount) {
+ mLayoutLatch = new CountDownLatch(layoutCount);
+ }
+
+ public void waitForLayout(int seconds) throws Throwable {
+ mLayoutLatch.await(seconds * (DEBUG ? 1000 : 1), SECONDS);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("all layouts should complete on time",
+ mLayoutLatch.getCount(), CoreMatchers.is(0L));
+ // use a runnable to ensure RV layout is finished
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
+ }
+ }
+
+ class GridTestAdapter extends TestAdapter {
+
+ Set<Integer> mFullSpanItems = new HashSet<Integer>();
+ int mSpanPerItem = 1;
+
+ GridTestAdapter(int count) {
+ super(count);
+ }
+
+ GridTestAdapter(int count, int spanPerItem) {
+ super(count);
+ mSpanPerItem = spanPerItem;
+ }
+
+ void setFullSpan(int... items) {
+ for (int i : items) {
+ mFullSpanItems.add(i);
+ }
+ }
+
+ void assignSpanSizeLookup(final GridLayoutManager glm) {
+ glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
+ }
+ });
+ }
+ }
+
+ class Callback {
+
+ public void onBeforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+
+ public void onAfterLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+ }
+}
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..305fa66
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -0,0 +1,548 @@
+/*
+ * 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 android.view.ViewGroup;
+
+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;
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static org.junit.Assert.*;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
+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}) {
+ for (boolean wrap : new boolean[]{false, true}) {
+ variations.add(
+ new Config(orientation, reverseLayout, stackFromBottom).wrap(wrap));
+ }
+
+ }
+ }
+ }
+ return variations;
+ }
+
+ WrappedLinearLayoutManager mLayoutManager;
+ TestAdapter mTestAdapter;
+
+ 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 (config.mWrap) {
+ mRecyclerView.setLayoutParams(
+ new ViewGroup.LayoutParams(
+ config.mOrientation == HORIZONTAL ? WRAP_CONTENT : FILL_PARENT,
+ config.mOrientation == VERTICAL ? WRAP_CONTENT : FILL_PARENT
+ )
+ );
+ }
+ 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;
+
+ boolean mWrap = false;
+
+ 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 +
+ ", wrap=" + mWrap +
+ '}';
+ }
+
+ public Config wrap(boolean wrap) {
+ mWrap = wrap;
+ return this;
+ }
+ }
+
+ 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(int seconds) throws Throwable {
+ layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("all layouts should complete on time",
+ layoutLatch.getCount(), CoreMatchers.is(0L));
+ // use a runnable to ensure RV layout is finished
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
+ }
+
+ @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());
+ }
+ }
+
+ @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/BaseRecyclerViewAnimationsTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
index 4764c00..57c09fa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -28,6 +28,7 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import static org.junit.Assert.*;
/**
* Base class for animation related tests.
@@ -46,11 +47,6 @@
super(DEBUG);
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- }
-
RecyclerView setupBasic(int itemCount) throws Throwable {
return setupBasic(itemCount, 0, itemCount);
}
@@ -211,12 +207,6 @@
public void onPostDispatchLayout() {
mOnLayoutCallbacks.postDispatchLayout();
}
-
- @Override
- public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
- super.waitForLayout(timeout, timeUnit);
- checkForMainThreadException();
- }
}
abstract class OnLayoutCallbacks {
@@ -649,6 +639,14 @@
}
setFrom(viewHolder);
}
+
+ @Override
+ public String toString() {
+ return "LoggingInfo{" +
+ "changeFlags=" + changeFlags +
+ ", payloads=" + payloads +
+ '}';
+ }
}
static class AnimateChange extends AnimateLogBase {
@@ -685,9 +683,9 @@
}
static class AnimateLogBase {
- final RecyclerView.ViewHolder viewHolder;
- final LoggingInfo preInfo;
- final LoggingInfo postInfo;
+ public final RecyclerView.ViewHolder viewHolder;
+ public final LoggingInfo preInfo;
+ public final LoggingInfo postInfo;
public AnimateLogBase(RecyclerView.ViewHolder viewHolder, LoggingInfo pre,
LoggingInfo postInfo) {
@@ -696,6 +694,14 @@
this.postInfo = postInfo;
}
+ public String log() {
+ return getClass().getSimpleName() + "[" + log(preInfo) + " - " + log(postInfo) + "]";
+ }
+
+ public String log(LoggingInfo info) {
+ return info == null ? "null" : info.toString();
+ }
+
@Override
public boolean equals(Object o) {
if (this == o) {
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 c5f3408..7938a0d 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -16,12 +16,21 @@
package android.support.v7.widget;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Rule;
+
import android.app.Instrumentation;
import android.graphics.Rect;
-import android.os.Handler;
import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
import android.support.v4.view.ViewCompat;
-import android.test.ActivityInstrumentationTestCase2;
+import android.support.v7.recyclerview.test.SameActivityTestRule;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -37,12 +46,16 @@
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.ReentrantLock;
import android.support.v7.recyclerview.test.R;
-abstract public class BaseRecyclerViewInstrumentationTest extends
- ActivityInstrumentationTestCase2<TestActivity> {
+import static org.junit.Assert.*;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+abstract public class BaseRecyclerViewInstrumentationTest {
private static final String TAG = "RecyclerViewTest";
@@ -52,28 +65,48 @@
protected AdapterHelper mAdapterHelper;
- Throwable mainThreadException;
+ private Throwable mMainThreadException;
+
+ private boolean mIgnoreMainThreadException = false;
Thread mInstrumentationThread;
+ @Rule
+ public ActivityTestRule<TestActivity> mActivityRule = new SameActivityTestRule() {
+ @Override
+ public boolean canReUseActivity() {
+ return BaseRecyclerViewInstrumentationTest.this.canReUseActivity();
+ }
+ };
+
public BaseRecyclerViewInstrumentationTest() {
this(false);
}
public BaseRecyclerViewInstrumentationTest(boolean debug) {
- super("android.support.v7.recyclerview", TestActivity.class);
mDebug = debug;
}
void checkForMainThreadException() throws Throwable {
- if (mainThreadException != null) {
- throw mainThreadException;
+ if (!mIgnoreMainThreadException && mMainThreadException != null) {
+ throw mMainThreadException;
}
}
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ public void setIgnoreMainThreadException(boolean ignoreMainThreadException) {
+ mIgnoreMainThreadException = ignoreMainThreadException;
+ }
+
+ public Throwable getMainThreadException() {
+ return mMainThreadException;
+ }
+
+ protected TestActivity getActivity() {
+ return mActivityRule.getActivity();
+ }
+
+ @Before
+ public final void setUpInsThread() throws Exception {
mInstrumentationThread = Thread.currentThread();
}
@@ -90,10 +123,14 @@
}
}
+ public boolean canReUseActivity() {
+ return true;
+ }
+
protected void enableAccessibility()
throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Method getUIAutomation = Instrumentation.class.getMethod("getUiAutomation");
- getUIAutomation.invoke(getInstrumentation());
+ getUIAutomation.invoke(InstrumentationRegistry.getInstrumentation());
}
void setAdapter(final RecyclerView.Adapter adapter) throws Throwable {
@@ -105,6 +142,22 @@
});
}
+ public View focusSearch(final View focused, final int direction)
+ throws Throwable {
+ final View[] result = new View[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ View view = focused.focusSearch(direction);
+ if (view != null && view != focused) {
+ view.requestFocus();
+ }
+ result[0] = view;
+ }
+ });
+ return result[0];
+ }
+
protected WrappedRecyclerView inflateWrappedRV() {
return (WrappedRecyclerView)
LayoutInflater.from(getActivity()).inflate(R.layout.wrapped_test_rv,
@@ -130,25 +183,31 @@
if (mInstrumentationThread == Thread.currentThread()) {
throw new RuntimeException(t);
}
- if (mainThreadException != null) {
+ if (mMainThreadException != null) {
Log.e(TAG, "receiving another main thread exception. dropping.", t);
} else {
Log.e(TAG, "captured exception on main thread", t);
- mainThreadException = t;
+ mMainThreadException = t;
}
if (mRecyclerView != null && mRecyclerView
.getLayoutManager() instanceof TestLayoutManager) {
TestLayoutManager lm = (TestLayoutManager) mRecyclerView.getLayoutManager();
// finish all layouts so that we get the correct exception
- while (lm.layoutLatch.getCount() > 0) {
- lm.layoutLatch.countDown();
+ if (lm.layoutLatch != null) {
+ while (lm.layoutLatch.getCount() > 0) {
+ lm.layoutLatch.countDown();
+ }
}
}
}
- @Override
- protected void tearDown() throws Exception {
+ public Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
+ }
+
+ @After
+ public final void tearDown() throws Exception {
if (mRecyclerView != null) {
try {
removeRecyclerView();
@@ -157,7 +216,6 @@
}
}
getInstrumentation().waitForIdleSync();
- super.tearDown();
try {
checkForMainThreadException();
@@ -188,12 +246,15 @@
@Override
public void run() {
try {
- final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
- if (adapter instanceof AttachDetachCountingAdapter) {
- ((AttachDetachCountingAdapter) adapter).getCounter()
- .validateRemaining(mRecyclerView);
+ // do not run validation if we already have an error
+ if (mMainThreadException == null) {
+ final RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
+ if (adapter instanceof AttachDetachCountingAdapter) {
+ ((AttachDetachCountingAdapter) adapter).getCounter()
+ .validateRemaining(mRecyclerView);
+ }
}
- getActivity().mContainer.removeAllViews();
+ getActivity().getContainer().removeAllViews();
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
@@ -221,14 +282,45 @@
latch.await(seconds, TimeUnit.SECONDS));
}
- public boolean requestFocus(final View view) {
- final boolean[] result = new boolean[1];
- getActivity().runOnUiThread(new Runnable() {
+ public void waitForIdleScroll(final RecyclerView recyclerView) throws Throwable {
+ final CountDownLatch latch = new CountDownLatch(1);
+ runTestOnUiThread(new Runnable() {
@Override
public void run() {
- result[0] = view.requestFocus();
+ RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == SCROLL_STATE_IDLE) {
+ latch.countDown();
+ recyclerView.removeOnScrollListener(this);
+ }
+ }
+ };
+ if (recyclerView.getScrollState() == SCROLL_STATE_IDLE) {
+ latch.countDown();
+ } else {
+ recyclerView.addOnScrollListener(listener);
+ }
}
});
+ assertTrue("should go idle in 10 seconds", latch.await(10, TimeUnit.SECONDS));
+ }
+
+ public boolean requestFocus(final View view, boolean waitForScroll) throws Throwable {
+ final boolean[] result = new boolean[1];
+ try {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ result[0] = view.requestFocus();
+ }
+ });
+ } catch (Throwable throwable) {
+ fail(throwable.getMessage());
+ }
+ if (waitForScroll && result[0]) {
+ waitForIdleScroll(mRecyclerView);
+ }
return result[0];
}
@@ -274,8 +366,8 @@
RecyclerView.ViewHolder vh = parent.getChildViewHolder(view);
if (!vh.isRemoved()) {
assertNotSame("If getItemOffsets is called, child should have a valid"
- + " adapter position unless it is removed : " + vh,
- vh.getAdapterPosition(), RecyclerView.NO_POSITION);
+ + " adapter position unless it is removed : " + vh,
+ vh.getAdapterPosition(), RecyclerView.NO_POSITION);
}
}
});
@@ -284,13 +376,13 @@
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- getActivity().mContainer.addView(recyclerView);
+ getActivity().getContainer().addView(recyclerView);
}
});
}
protected FrameLayout getRecyclerViewContainer() {
- return getActivity().mContainer;
+ return getActivity().getContainer();
}
public void requestLayoutOnUIThread(final View view) {
@@ -333,11 +425,42 @@
});
}
- void smoothScrollToPosition(final int position)
- throws Throwable {
+ void smoothScrollToPosition(final int position) throws Throwable {
+ smoothScrollToPosition(position, true);
+ }
+
+ void smoothScrollToPosition(final int position, boolean assertArrival) throws Throwable {
if (mDebug) {
Log.d(TAG, "SMOOTH scrolling to " + position);
}
+ final CountDownLatch viewAdded = new CountDownLatch(1);
+ final RecyclerView.OnChildAttachStateChangeListener listener =
+ new RecyclerView.OnChildAttachStateChangeListener() {
+ @Override
+ public void onChildViewAttachedToWindow(View view) {
+ if (position == mRecyclerView.getChildAdapterPosition(view)) {
+ viewAdded.countDown();
+ }
+ }
+ @Override
+ public void onChildViewDetachedFromWindow(View view) {
+ }
+ };
+ final AtomicBoolean addedListener = new AtomicBoolean(false);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RecyclerView.ViewHolder viewHolderForAdapterPosition =
+ mRecyclerView.findViewHolderForAdapterPosition(position);
+ if (viewHolderForAdapterPosition != null) {
+ viewAdded.countDown();
+ } else {
+ mRecyclerView.addOnChildAttachStateChangeListener(listener);
+ addedListener.set(true);
+ }
+
+ }
+ });
runTestOnUiThread(new Runnable() {
@Override
public void run() {
@@ -345,17 +468,21 @@
}
});
getInstrumentation().waitForIdleSync();
- Thread.sleep(200); //give scroller some time so start
- while (mRecyclerView.getLayoutManager().isSmoothScrolling() ||
- mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
- if (mDebug) {
- Log.d(TAG, "SMOOTH scrolling step");
- }
- Thread.sleep(200);
- }
+ assertThat("should be able to scroll in 10 seconds", !assertArrival ||
+ viewAdded.await(10, TimeUnit.SECONDS),
+ CoreMatchers.is(true));
+ waitForIdleScroll(mRecyclerView);
if (mDebug) {
Log.d(TAG, "SMOOTH scrolling done");
}
+ if (addedListener.get()) {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.removeOnChildAttachStateChangeListener(listener);
+ }
+ });
+ }
getInstrumentation().waitForIdleSync();
}
@@ -368,7 +495,16 @@
});
}
- class TestViewHolder extends RecyclerView.ViewHolder {
+ public void setVisibility(final View view, final int visibility) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.setVisibility(visibility);
+ }
+ });
+ }
+
+ public class TestViewHolder extends RecyclerView.ViewHolder {
Item mBoundItem;
@@ -383,47 +519,49 @@
}
}
class DumbLayoutManager extends TestLayoutManager {
- ReentrantLock mLayoutLock = new ReentrantLock();
- public void blockLayout() {
- mLayoutLock.lock();
- }
-
- public void unblockLayout() {
- mLayoutLock.unlock();
- }
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- mLayoutLock.lock();
detachAndScrapAttachedViews(recycler);
layoutRange(recycler, 0, state.getItemCount());
if (layoutLatch != null) {
layoutLatch.countDown();
}
- mLayoutLock.unlock();
}
}
public class TestLayoutManager extends RecyclerView.LayoutManager {
int mScrollVerticallyAmount;
int mScrollHorizontallyAmount;
protected CountDownLatch layoutLatch;
+ private boolean mSupportsPredictive = false;
public void expectLayouts(int count) {
layoutLatch = new CountDownLatch(count);
}
- public void waitForLayout(long timeout, TimeUnit timeUnit, boolean waitForIdle)
- throws Throwable {
- layoutLatch.await(timeout * (mDebug ? 100 : 1), timeUnit);
- assertEquals("all expected layouts should be executed at the expected time",
- 0, layoutLatch.getCount());
- if (waitForIdle) {
- getInstrumentation().waitForIdleSync();
- }
+ public void waitForLayout(int seconds) throws Throwable {
+ layoutLatch.await(seconds * (mDebug ? 1000 : 1), SECONDS);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("all layouts should complete on time",
+ layoutLatch.getCount(), CoreMatchers.is(0L));
+ // use a runnable to ensure RV layout is finished
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
}
- public void waitForLayout(long timeout, TimeUnit timeUnit)
- throws Throwable {
- waitForLayout(timeout, timeUnit, true);
+ public boolean isSupportsPredictive() {
+ return mSupportsPredictive;
+ }
+
+ public void setSupportsPredictive(boolean supportsPredictive) {
+ mSupportsPredictive = supportsPredictive;
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return mSupportsPredictive;
}
public void assertLayoutCount(int count, String msg, long timeout) throws Throwable {
@@ -436,14 +574,6 @@
assertFalse(msg, layoutLatch.getCount() == 0);
}
- public void waitForLayout(long timeout) throws Throwable {
- waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, true);
- }
-
- public void waitForLayout(long timeout, boolean waitForIdle) throws Throwable {
- waitForLayout(timeout * (mDebug ? 10000 : 1), TimeUnit.SECONDS, waitForIdle);
- }
-
@Override
public RecyclerView.LayoutParams generateDefaultLayoutParams() {
return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
@@ -497,7 +627,6 @@
assertEquals("getViewForPosition should return correct position",
i, getPosition(view));
addView(view);
-
measureChildWithMargins(view, 0, 0);
if (getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL) {
layoutDecorated(view, getWidth() - getDecoratedMeasuredWidth(view), top,
@@ -620,7 +749,10 @@
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent,
int viewType) {
- return new TestViewHolder(new TextView(parent.getContext()));
+ TextView itemView = new TextView(parent.getContext());
+ itemView.setFocusableInTouchMode(true);
+ itemView.setFocusable(true);
+ return new TestViewHolder(itemView);
}
@Override
@@ -628,7 +760,7 @@
assertNotNull(holder.mOwnerRecyclerView);
assertEquals(position, holder.getAdapterPosition());
final Item item = mItems.get(position);
- ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mAdapterIndex + ")");
+ ((TextView) (holder.itemView)).setText(item.mText + "(" + item.mId + ")");
holder.mBoundItem = item;
}
@@ -697,6 +829,19 @@
new AddRemoveRunnable(DEFAULT_ITEM_PREFIX, new int[]{0, count}).runOnMainThread();
}
+ public void resetItemsTo(final List<Item> testItems) throws Throwable {
+ if (!mItems.isEmpty()) {
+ deleteAndNotify(0, mItems.size());
+ }
+ mItems = testItems;
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ notifyItemRangeInserted(0, testItems.size());
+ }
+ });
+ }
+
public void addAndNotify(final int start, final int count) throws Throwable {
addAndNotify(new int[]{start, count});
}
@@ -822,7 +967,6 @@
return mAttachmentCounter;
}
-
private class AddRemoveRunnable implements Runnable {
final String mNewItemPrefix;
final int[][] mStartCountTuples;
@@ -877,12 +1021,11 @@
return Looper.myLooper() == Looper.getMainLooper();
}
- @Override
public void runTestOnUiThread(Runnable r) throws Throwable {
if (Looper.myLooper() == Looper.getMainLooper()) {
r.run();
} else {
- super.runTestOnUiThread(r);
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(r);
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
new file mode 100644
index 0000000..0fea8d6
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
@@ -0,0 +1,839 @@
+package android.support.v7.widget;
+
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.HashSet;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+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.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 org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
+public class BaseStaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+
+ protected static final boolean DEBUG = false;
+ protected static final int AVG_ITEM_PER_VIEW = 3;
+ protected static final String TAG = "StaggeredGridLayoutManagerTest";
+ volatile WrappedLayoutManager mLayoutManager;
+ GridTestAdapter mAdapter;
+
+ 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 (int spanCount : new int[]{1, 3}) {
+ for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
+ GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
+ for (boolean wrap : new boolean[]{true, false}) {
+ variations.add(new Config(orientation, reverseLayout, spanCount,
+ gapStrategy).wrap(wrap));
+ }
+
+ }
+ }
+ }
+ }
+ return variations;
+ }
+
+ 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) throws Throwable {
+ 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);
+ mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
+ config.mOrientation);
+ mLayoutManager.setGapStrategy(config.mGapStrategy);
+ mLayoutManager.setReverseLayout(config.mReverseLayout);
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ try {
+ StaggeredGridLayoutManager.LayoutParams
+ lp = (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
+ assertNotNull("view should have layout params assigned", lp);
+ assertNotNull("when item offsets are requested, view should have a valid span",
+ lp.mSpan);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ }
+
+ StaggeredGridLayoutManager.LayoutParams getLp(View view) {
+ return (StaggeredGridLayoutManager.LayoutParams) view.getLayoutParams();
+ }
+
+ void waitFirstLayout() throws Throwable {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(mRecyclerView);
+ mLayoutManager.waitForLayout(3);
+ getInstrumentation().waitForIdleSync();
+ }
+
+ /**
+ * enqueues an empty runnable to main thread so that we can be assured it did run
+ *
+ * @param count Number of times to run
+ */
+ protected void waitForMainThread(int count) throws Throwable {
+ final AtomicInteger i = new AtomicInteger(count);
+ while (i.get() > 0) {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ i.decrementAndGet();
+ }
+ });
+ }
+ }
+
+ public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
+ Map<Item, Rect> after) {
+ Throwable throwable = null;
+ try {
+ assertRectSetsEqual("NOT " + message, before, after);
+ } catch (Throwable t) {
+ throwable = t;
+ }
+ assertNotNull(message + " two 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 log = new StringBuilder();
+ if (DEBUG) {
+ log.append("checking rectangle equality.\n");
+ log.append("total space:" + mLayoutManager.mPrimaryOrientation.getTotalSpace());
+ log.append("before:");
+ for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+ log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
+ .append(entry.getValue());
+ }
+ log.append("\nafter:");
+ for (Map.Entry<Item, Rect> entry : after.entrySet()) {
+ log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
+ .append(entry.getValue());
+ }
+ message += "\n\n" + log.toString();
+ }
+ assertEquals(message + ": item 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 + ": Same 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 + ": Item with same adapter index should be visible " +
+ "after simple re-layout",
+ afterRect);
+ }
+ assertEquals(message + ": Item should be laid out at the same coordinates",
+ entry.getValue(),
+ afterRect);
+ }
+ }
+
+ protected void assertViewPositions(Config config) {
+ ArrayList<ArrayList<View>> viewsBySpan = mLayoutManager.collectChildrenBySpan();
+ OrientationHelper orientationHelper = OrientationHelper
+ .createOrientationHelper(mLayoutManager, config.mOrientation);
+ for (ArrayList<View> span : viewsBySpan) {
+ // validate all children's order. first child should have min start mPosition
+ final int count = span.size();
+ for (int i = 0, j = 1; j < count; i++, j++) {
+ View prev = span.get(i);
+ View next = span.get(j);
+ assertTrue(config + " prev item should be above next item",
+ orientationHelper.getDecoratedEnd(prev) <= orientationHelper
+ .getDecoratedStart(next)
+ );
+
+ }
+ }
+ }
+
+ protected 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 + (mAdapter.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);
+ }
+
+ protected void scrollToPositionWithOffset(final int position, final int offset)
+ throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mLayoutManager.scrollToPositionWithOffset(position, offset);
+ }
+ });
+ }
+
+ static class OnLayoutListener {
+
+ void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+
+ void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ }
+ }
+
+ static class VisibleChildren {
+
+ int[] firstVisiblePositions;
+
+ int[] firstFullyVisiblePositions;
+
+ int[] lastVisiblePositions;
+
+ int[] lastFullyVisiblePositions;
+
+ View findFirstPartialVisibleClosestToStart;
+ View findFirstPartialVisibleClosestToEnd;
+
+ VisibleChildren(int spanCount) {
+ firstFullyVisiblePositions = new int[spanCount];
+ firstVisiblePositions = new int[spanCount];
+ lastVisiblePositions = new int[spanCount];
+ lastFullyVisiblePositions = new int[spanCount];
+ for (int i = 0; i < spanCount; i++) {
+ firstFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
+ firstVisiblePositions[i] = RecyclerView.NO_POSITION;
+ lastVisiblePositions[i] = RecyclerView.NO_POSITION;
+ lastFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
+ }
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ VisibleChildren that = (VisibleChildren) o;
+
+ if (!Arrays.equals(firstFullyVisiblePositions, that.firstFullyVisiblePositions)) {
+ return false;
+ }
+ if (findFirstPartialVisibleClosestToStart
+ != null ? !findFirstPartialVisibleClosestToStart
+ .equals(that.findFirstPartialVisibleClosestToStart)
+ : that.findFirstPartialVisibleClosestToStart != null) {
+ return false;
+ }
+ if (!Arrays.equals(firstVisiblePositions, that.firstVisiblePositions)) {
+ return false;
+ }
+ if (!Arrays.equals(lastFullyVisiblePositions, that.lastFullyVisiblePositions)) {
+ return false;
+ }
+ if (findFirstPartialVisibleClosestToEnd != null ? !findFirstPartialVisibleClosestToEnd
+ .equals(that.findFirstPartialVisibleClosestToEnd)
+ : that.findFirstPartialVisibleClosestToEnd
+ != null) {
+ return false;
+ }
+ if (!Arrays.equals(lastVisiblePositions, that.lastVisiblePositions)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = Arrays.hashCode(firstVisiblePositions);
+ result = 31 * result + Arrays.hashCode(firstFullyVisiblePositions);
+ result = 31 * result + Arrays.hashCode(lastVisiblePositions);
+ result = 31 * result + Arrays.hashCode(lastFullyVisiblePositions);
+ result = 31 * result + (findFirstPartialVisibleClosestToStart != null
+ ? findFirstPartialVisibleClosestToStart
+ .hashCode() : 0);
+ result = 31 * result + (findFirstPartialVisibleClosestToEnd != null
+ ? findFirstPartialVisibleClosestToEnd
+ .hashCode()
+ : 0);
+ return result;
+ }
+
+ @Override
+ public String toString() {
+ return "VisibleChildren{" +
+ "firstVisiblePositions=" + Arrays.toString(firstVisiblePositions) +
+ ", firstFullyVisiblePositions=" + Arrays.toString(firstFullyVisiblePositions) +
+ ", lastVisiblePositions=" + Arrays.toString(lastVisiblePositions) +
+ ", lastFullyVisiblePositions=" + Arrays.toString(lastFullyVisiblePositions) +
+ ", findFirstPartialVisibleClosestToStart=" +
+ viewToString(findFirstPartialVisibleClosestToStart) +
+ ", findFirstPartialVisibleClosestToEnd=" +
+ viewToString(findFirstPartialVisibleClosestToEnd) +
+ '}';
+ }
+
+ private String viewToString(View view) {
+ if (view == null) {
+ return null;
+ }
+ ViewGroup.LayoutParams lp = view.getLayoutParams();
+ if (lp instanceof RecyclerView.LayoutParams == false) {
+ return System.identityHashCode(view) + "(?)";
+ }
+ RecyclerView.LayoutParams rvlp = (RecyclerView.LayoutParams) lp;
+ return System.identityHashCode(view) + "(" + rvlp.getViewAdapterPosition() + ")";
+ }
+ }
+
+ abstract static class OnBindCallback {
+
+ abstract void onBoundItem(TestViewHolder vh, int position);
+
+ boolean assignRandomSize() {
+ return true;
+ }
+
+ void onCreatedViewHolder(TestViewHolder vh) {
+ }
+ }
+
+ static class Config implements Cloneable {
+
+ static final int DEFAULT_ITEM_COUNT = 300;
+
+ int mOrientation = OrientationHelper.VERTICAL;
+
+ boolean mReverseLayout = false;
+
+ int mSpanCount = 3;
+
+ int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
+
+ int mItemCount = DEFAULT_ITEM_COUNT;
+
+ boolean mWrap = false;
+
+ Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
+ mOrientation = orientation;
+ mReverseLayout = reverseLayout;
+ mSpanCount = spanCount;
+ mGapStrategy = gapStrategy;
+ }
+
+ public Config() {
+
+ }
+
+ Config orientation(int orientation) {
+ mOrientation = orientation;
+ return this;
+ }
+
+ Config reverseLayout(boolean reverseLayout) {
+ mReverseLayout = reverseLayout;
+ return this;
+ }
+
+ Config spanCount(int spanCount) {
+ mSpanCount = spanCount;
+ return this;
+ }
+
+ Config gapStrategy(int gapStrategy) {
+ mGapStrategy = gapStrategy;
+ return this;
+ }
+
+ public Config itemCount(int itemCount) {
+ mItemCount = itemCount;
+ return this;
+ }
+
+ public Config wrap(boolean wrap) {
+ mWrap = wrap;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "[CONFIG:" +
+ " span:" + mSpanCount + "," +
+ " orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
+ " reverse:" + (mReverseLayout ? "T" : "F") +
+ " itemCount:" + mItemCount +
+ " wrapContent:" + mWrap +
+ " gap strategy: " + gapStrategyName(mGapStrategy);
+ }
+
+ protected static String gapStrategyName(int gapStrategy) {
+ switch (gapStrategy) {
+ case GAP_HANDLING_NONE:
+ return "none";
+ case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
+ return "move spans";
+ }
+ return "gap strategy: unknown";
+ }
+
+ @Override
+ public Object clone() throws CloneNotSupportedException {
+ return super.clone();
+ }
+ }
+
+ class WrappedLayoutManager extends StaggeredGridLayoutManager {
+
+ CountDownLatch layoutLatch;
+ OnLayoutListener mOnLayoutListener;
+ // gradle does not yet let us customize manifest for tests which is necessary to test RTL.
+ // until bug is fixed, we'll fake it.
+ // public issue id: 57819
+ Boolean mFakeRTL;
+
+ @Override
+ boolean isLayoutRTL() {
+ return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
+ }
+
+ public void expectLayouts(int count) {
+ layoutLatch = new CountDownLatch(count);
+ }
+
+ public void waitForLayout(int seconds) throws Throwable {
+ layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("all layouts should complete on time",
+ layoutLatch.getCount(), CoreMatchers.is(0L));
+ // use a runnable to ensure RV layout is finished
+ getInstrumentation().runOnMainSync(new Runnable() {
+ @Override
+ public void run() {
+ }
+ });
+ }
+
+ public void assertNoLayout(String msg, long timeout) throws Throwable {
+ layoutLatch.await(timeout, TimeUnit.SECONDS);
+ assertFalse(msg, layoutLatch.getCount() == 0);
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ String before;
+ if (DEBUG) {
+ before = layoutToString("before");
+ } else {
+ before = "enable DEBUG";
+ }
+ try {
+ if (mOnLayoutListener != null) {
+ mOnLayoutListener.before(recycler, state);
+ }
+ super.onLayoutChildren(recycler, state);
+ if (mOnLayoutListener != null) {
+ mOnLayoutListener.after(recycler, state);
+ }
+ validateChildren(before);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+
+ layoutLatch.countDown();
+ }
+
+ @Override
+ int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ int result = super.scrollBy(dt, recycler, state);
+ validateChildren();
+ return result;
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+
+ return 0;
+ }
+
+ public WrappedLayoutManager(int spanCount, int orientation) {
+ super(spanCount, orientation);
+ }
+
+ ArrayList<ArrayList<View>> collectChildrenBySpan() {
+ ArrayList<ArrayList<View>> viewsBySpan = new ArrayList<ArrayList<View>>();
+ for (int i = 0; i < getSpanCount(); i++) {
+ viewsBySpan.add(new ArrayList<View>());
+ }
+ for (int i = 0; i < getChildCount(); i++) {
+ View view = getChildAt(i);
+ LayoutParams lp
+ = (LayoutParams) view
+ .getLayoutParams();
+ viewsBySpan.get(lp.mSpan.mIndex).add(view);
+ }
+ return viewsBySpan;
+ }
+
+ @Nullable
+ @Override
+ public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ View result = null;
+ try {
+ result = super.onFocusSearchFailed(focused, direction, recycler, state);
+ validateChildren();
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ return result;
+ }
+
+ Rect getViewBounds(View view) {
+ if (getOrientation() == HORIZONTAL) {
+ return new Rect(
+ mPrimaryOrientation.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedStart(view),
+ mPrimaryOrientation.getDecoratedEnd(view),
+ mSecondaryOrientation.getDecoratedEnd(view));
+ } else {
+ return new Rect(
+ mSecondaryOrientation.getDecoratedStart(view),
+ mPrimaryOrientation.getDecoratedStart(view),
+ mSecondaryOrientation.getDecoratedEnd(view),
+ mPrimaryOrientation.getDecoratedEnd(view));
+ }
+ }
+
+ public String getBoundsLog() {
+ StringBuilder sb = new StringBuilder();
+ sb.append("view bounds:[start:").append(mPrimaryOrientation.getStartAfterPadding())
+ .append(",").append(" end").append(mPrimaryOrientation.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(
+ mPrimaryOrientation.getDecoratedStart(child)).append(", end:")
+ .append(mPrimaryOrientation.getDecoratedEnd(child)).append("]\n");
+ }
+ return sb.toString();
+ }
+
+ public VisibleChildren traverseAndFindVisibleChildren() {
+ int childCount = getChildCount();
+ final VisibleChildren visibleChildren = new VisibleChildren(getSpanCount());
+ final int start = mPrimaryOrientation.getStartAfterPadding();
+ final int end = mPrimaryOrientation.getEndAfterPadding();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ final int childStart = mPrimaryOrientation.getDecoratedStart(child);
+ final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
+ final boolean fullyVisible = childStart >= start && childEnd <= end;
+ final boolean hidden = childEnd <= start || childStart >= end;
+ if (hidden) {
+ continue;
+ }
+ final int position = getPosition(child);
+ final int span = getLp(child).getSpanIndex();
+ if (fullyVisible) {
+ if (position < visibleChildren.firstFullyVisiblePositions[span] ||
+ visibleChildren.firstFullyVisiblePositions[span]
+ == RecyclerView.NO_POSITION) {
+ visibleChildren.firstFullyVisiblePositions[span] = position;
+ }
+
+ if (position > visibleChildren.lastFullyVisiblePositions[span]) {
+ visibleChildren.lastFullyVisiblePositions[span] = position;
+ }
+ }
+
+ if (position < visibleChildren.firstVisiblePositions[span] ||
+ visibleChildren.firstVisiblePositions[span] == RecyclerView.NO_POSITION) {
+ visibleChildren.firstVisiblePositions[span] = position;
+ }
+
+ if (position > visibleChildren.lastVisiblePositions[span]) {
+ visibleChildren.lastVisiblePositions[span] = position;
+ }
+ if (visibleChildren.findFirstPartialVisibleClosestToStart == null) {
+ visibleChildren.findFirstPartialVisibleClosestToStart = child;
+ }
+ visibleChildren.findFirstPartialVisibleClosestToEnd = child;
+ }
+ return visibleChildren;
+ }
+
+ 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();
+ for (int i = 0; i < childCount; i++) {
+ View child = getChildAt(i);
+ // do it if and only if child is visible
+ if (child.getRight() < 0 || child.getBottom() < 0 ||
+ child.getLeft() >= getWidth() || child.getTop() >= getHeight()) {
+ // invisible children may be drawn in cases like scrolling so we should
+ // ignore them
+ continue;
+ }
+ LayoutParams lp = (LayoutParams) child
+ .getLayoutParams();
+ TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
+ items.put(vh.mBoundItem, getViewBounds(child));
+ }
+ }
+ });
+ return items;
+ }
+
+
+ public void setFakeRtl(Boolean fakeRtl) {
+ mFakeRTL = fakeRtl;
+ try {
+ requestLayoutOnUIThread(mRecyclerView);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+
+ String layoutToString(String hint) {
+ StringBuilder sb = new StringBuilder();
+ sb.append("LAYOUT POSITIONS AND INDICES ").append(hint).append("\n");
+ for (int i = 0; i < getChildCount(); i++) {
+ final View view = getChildAt(i);
+ final LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
+ sb.append(String.format("index: %d pos: %d top: %d bottom: %d span: %d isFull:%s",
+ i, getPosition(view),
+ mPrimaryOrientation.getDecoratedStart(view),
+ mPrimaryOrientation.getDecoratedEnd(view),
+ layoutParams.getSpanIndex(), layoutParams.isFullSpan())).append("\n");
+ }
+ return sb.toString();
+ }
+
+ protected void validateChildren() {
+ validateChildren(null);
+ }
+
+ private void validateChildren(String msg) {
+ if (getChildCount() == 0 || mRecyclerView.mState.isPreLayout()) {
+ return;
+ }
+ final int dir = mShouldReverseLayout ? -1 : 1;
+ int i = 0;
+ int pos = -1;
+ while (i < getChildCount()) {
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ if (lp.isItemRemoved()) {
+ i++;
+ continue;
+ }
+ pos = getPosition(getChildAt(i));
+ break;
+ }
+ if (pos == -1) {
+ return;
+ }
+ while (++i < getChildCount()) {
+ LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
+ if (lp.isItemRemoved()) {
+ continue;
+ }
+ pos += dir;
+ if (getPosition(getChildAt(i)) != pos) {
+ throw new RuntimeException("INVALID POSITION FOR CHILD " + i + "\n" +
+ layoutToString("ERROR") + "\n msg:" + msg);
+ }
+ }
+ }
+ }
+
+ class GridTestAdapter extends TestAdapter {
+
+ int mOrientation;
+ int mRecyclerViewWidth;
+ int mRecyclerViewHeight;
+ Integer mSizeReference = null;
+
+ // original ids of items that should be full span
+ HashSet<Integer> mFullSpanItems = new HashSet<Integer>();
+
+ protected boolean mViewsHaveEqualSize = false; // size in the scrollable direction
+
+ protected OnBindCallback mOnBindCallback;
+
+ GridTestAdapter(int count, int orientation) {
+ super(count);
+ mOrientation = orientation;
+ }
+
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ mRecyclerViewWidth = parent.getWidth();
+ mRecyclerViewHeight = parent.getHeight();
+ TestViewHolder vh = super.onCreateViewHolder(parent, viewType);
+ if (mOnBindCallback != null) {
+ mOnBindCallback.onCreatedViewHolder(vh);
+ }
+ return vh;
+ }
+
+ @Override
+ public void offsetOriginalIndices(int start, int offset) {
+ if (mFullSpanItems.size() > 0) {
+ HashSet<Integer> old = mFullSpanItems;
+ mFullSpanItems = new HashSet<Integer>();
+ for (Integer i : old) {
+ if (i < start) {
+ mFullSpanItems.add(i);
+ } else if (offset > 0 || (start + Math.abs(offset)) <= i) {
+ mFullSpanItems.add(i + offset);
+ } else if (DEBUG) {
+ Log.d(TAG, "removed full span item " + i);
+ }
+ }
+ }
+ super.offsetOriginalIndices(start, offset);
+ }
+
+ @Override
+ protected void moveInUIThread(int from, int to) {
+ boolean setAsFullSpanAgain = mFullSpanItems.contains(from);
+ super.moveInUIThread(from, to);
+ if (setAsFullSpanAgain) {
+ mFullSpanItems.add(to);
+ }
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ if (mSizeReference == null) {
+ mSizeReference = mOrientation == OrientationHelper.HORIZONTAL ? mRecyclerViewWidth
+ / AVG_ITEM_PER_VIEW : mRecyclerViewHeight / AVG_ITEM_PER_VIEW;
+ }
+ super.onBindViewHolder(holder, position);
+ Item item = mItems.get(position);
+ RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
+ .getLayoutParams();
+ if (lp instanceof StaggeredGridLayoutManager.LayoutParams) {
+ ((StaggeredGridLayoutManager.LayoutParams) lp)
+ .setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
+ } else {
+ StaggeredGridLayoutManager.LayoutParams slp
+ = (StaggeredGridLayoutManager.LayoutParams) mLayoutManager
+ .generateDefaultLayoutParams();
+ holder.itemView.setLayoutParams(slp);
+ slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
+ lp = slp;
+ }
+
+ if (mOnBindCallback == null || mOnBindCallback.assignRandomSize()) {
+ final int minSize = mViewsHaveEqualSize ? mSizeReference :
+ mSizeReference + 20 * (item.mId % 10);
+ if (mOrientation == OrientationHelper.HORIZONTAL) {
+ holder.itemView.setMinimumWidth(minSize);
+ } else {
+ holder.itemView.setMinimumHeight(minSize);
+ }
+ lp.topMargin = 3;
+ lp.leftMargin = 5;
+ lp.rightMargin = 7;
+ lp.bottomMargin = 9;
+ }
+
+ if (mOnBindCallback != null) {
+ mOnBindCallback.onBoundItem(holder, position);
+ }
+ }
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java
new file mode 100644
index 0000000..8f29750
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentTest.java
@@ -0,0 +1,599 @@
+/*
+ * 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 static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.app.Activity;
+import android.content.Context;
+import android.graphics.Color;
+import android.graphics.Rect;
+import android.support.annotation.Nullable;
+import android.support.v4.util.LongSparseArray;
+import android.support.v7.widget.TestedFrameLayout.FullControlLayoutParams;
+import android.util.Log;
+import android.view.Gravity;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * Class to test any generic wrap content behavior.
+ * It does so by running the same view scenario twice. Once with match parent setup to record all
+ * dimensions and once with wrap_content setup. Then compares all child locations & ids +
+ * RecyclerView size.
+ */
+abstract public class BaseWrapContentTest extends BaseRecyclerViewInstrumentationTest {
+
+ static final boolean DEBUG = false;
+ static final String TAG = "WrapContentTest";
+ RecyclerView.LayoutManager mLayoutManager;
+
+ TestAdapter mTestAdapter;
+
+ LoggingItemAnimator mLoggingItemAnimator;
+
+ boolean mIsWrapContent;
+
+ protected final WrapContentConfig mWrapContentConfig;
+
+ public BaseWrapContentTest(WrapContentConfig config) {
+ mWrapContentConfig = config;
+ }
+
+ abstract RecyclerView.LayoutManager createLayoutManager();
+
+ void unspecifiedWithHintTest(boolean horizontal) throws Throwable {
+ final int itemHeight = 20;
+ final int itemWidth = 15;
+ RecyclerView.LayoutManager layoutManager = createLayoutManager();
+ WrappedRecyclerView rv = createRecyclerView(getActivity());
+ TestAdapter testAdapter = new TestAdapter(20) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setLayoutParams(new ViewGroup.LayoutParams(itemWidth, itemHeight));
+ }
+ };
+ rv.setLayoutManager(layoutManager);
+ rv.setAdapter(testAdapter);
+ TestedFrameLayout.FullControlLayoutParams lp =
+ new TestedFrameLayout.FullControlLayoutParams(0, 0);
+ if (horizontal) {
+ lp.wSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED);
+ lp.hSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
+ } else {
+ lp.hSpec = View.MeasureSpec.makeMeasureSpec(25, View.MeasureSpec.UNSPECIFIED);
+ lp.wSpec = View.MeasureSpec.makeMeasureSpec(50, View.MeasureSpec.AT_MOST);
+ }
+ rv.setLayoutParams(lp);
+ setRecyclerView(rv);
+ rv.waitUntilLayout();
+
+ // we don't assert against the given size hint because LM will still ask for more if it
+ // lays out more children. This is the correct behavior because the spec is not AT_MOST,
+ // it is UNSPECIFIED.
+ if (horizontal) {
+ int expectedWidth = rv.getPaddingLeft() + rv.getPaddingRight() + itemWidth;
+ while (expectedWidth < 25) {
+ expectedWidth += itemWidth;
+ }
+ assertThat(rv.getWidth(), CoreMatchers.is(expectedWidth));
+ } else {
+ int expectedHeight = rv.getPaddingTop() + rv.getPaddingBottom() + itemHeight;
+ while (expectedHeight < 25) {
+ expectedHeight += itemHeight;
+ }
+ assertThat(rv.getHeight(), CoreMatchers.is(expectedHeight));
+ }
+ }
+
+ protected void testScenerio(Scenario scenario) throws Throwable {
+ FullControlLayoutParams matchParent = new FullControlLayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.MATCH_PARENT);
+ FullControlLayoutParams wrapContent = new FullControlLayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ if (mWrapContentConfig.isUnlimitedHeight()) {
+ wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ if (mWrapContentConfig.isUnlimitedWidth()) {
+ wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+
+ mIsWrapContent = false;
+ List<Snapshot> s1 = runScenario(scenario, matchParent, null);
+ mIsWrapContent = true;
+
+ List<Snapshot> s2 = runScenario(scenario, wrapContent, s1);
+ assertEquals("test sanity", s1.size(), s2.size());
+
+ for (int i = 0; i < s1.size(); i++) {
+ Snapshot step1 = s1.get(i);
+ Snapshot step2 = s2.get(i);
+ step1.assertSame(step2, i);
+ }
+ }
+
+ public List<Snapshot> runScenario(Scenario scenario, ViewGroup.LayoutParams lp,
+ @Nullable List<Snapshot> compareWith)
+ throws Throwable {
+ removeRecyclerView();
+ Item.idCounter.set(0);
+ List<Snapshot> result = new ArrayList<>();
+ RecyclerView.LayoutManager layoutManager = scenario.createLayoutManager();
+ WrappedRecyclerView recyclerView = new WrappedRecyclerView(getActivity());
+ recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+ recyclerView.setLayoutManager(layoutManager);
+ recyclerView.setLayoutParams(lp);
+ mLayoutManager = layoutManager;
+ mTestAdapter = new TestAdapter(scenario.getSeedAdapterSize());
+ recyclerView.setAdapter(mTestAdapter);
+ mLoggingItemAnimator = new LoggingItemAnimator();
+ recyclerView.setItemAnimator(mLoggingItemAnimator);
+ setRecyclerView(recyclerView);
+ recyclerView.waitUntilLayout();
+ int stepIndex = 0;
+ for (Step step : scenario.mStepList) {
+ mLoggingItemAnimator.reset();
+ step.onRun();
+ recyclerView.waitUntilLayout();
+ recyclerView.waitUntilAnimations();
+ Snapshot snapshot = takeSnapshot();
+ if (mIsWrapContent) {
+ snapshot.assertRvSize();
+ }
+ result.add(snapshot);
+ if (compareWith != null) {
+ compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
+ }
+ stepIndex++;
+ }
+ recyclerView.waitUntilLayout();
+ recyclerView.waitUntilAnimations();
+ Snapshot snapshot = takeSnapshot();
+ if (mIsWrapContent) {
+ snapshot.assertRvSize();
+ }
+ result.add(snapshot);
+ if (compareWith != null) {
+ compareWith.get(stepIndex).assertSame(snapshot, stepIndex);
+ }
+ return result;
+ }
+
+ protected WrappedRecyclerView createRecyclerView(Activity activity) {
+ return new WrappedRecyclerView(getActivity());
+ }
+
+ void layoutAndCheck(TestedFrameLayout.FullControlLayoutParams lp,
+ BaseWrapContentWithAspectRatioTest.WrapContentAdapter adapter, Rect[] expected,
+ int width, int height) throws Throwable {
+ WrappedRecyclerView recyclerView = createRecyclerView(getActivity());
+ recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+ recyclerView.setLayoutManager(createLayoutManager());
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutParams(lp);
+ Rect padding = mWrapContentConfig.padding;
+ recyclerView.setPadding(padding.left, padding.top,
+ padding.right, padding.bottom);
+ setRecyclerView(recyclerView);
+ recyclerView.waitUntilLayout();
+ Snapshot snapshot = takeSnapshot();
+ int index = 0;
+ Rect tmp = new Rect();
+ for (BaseWrapContentWithAspectRatioTest.MeasureBehavior behavior : adapter.behaviors) {
+ tmp.set(expected[index]);
+ tmp.offset(padding.left, padding.top);
+ assertThat("behavior " + index, snapshot.mChildCoordinates.get(behavior.getId()),
+ is(tmp));
+ index ++;
+ }
+ Rect boundingBox = new Rect(0, 0, 0, 0);
+ for (Rect rect : expected) {
+ boundingBox.union(rect);
+ }
+ assertThat(recyclerView.getWidth(), is(width + padding.left + padding.right));
+ assertThat(recyclerView.getHeight(), is(height + padding.top + padding.bottom));
+ }
+
+
+ abstract protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager);
+
+ abstract protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager);
+
+ protected Snapshot takeSnapshot() throws Throwable {
+ Snapshot snapshot = new Snapshot(mRecyclerView, mLoggingItemAnimator,
+ getHorizontalGravity(mLayoutManager), getVerticalGravity(mLayoutManager));
+ return snapshot;
+ }
+
+ abstract class Scenario {
+
+ ArrayList<Step> mStepList = new ArrayList<>();
+
+ public Scenario(Step... steps) {
+ Collections.addAll(mStepList, steps);
+ }
+
+ public int getSeedAdapterSize() {
+ return 10;
+ }
+
+ public RecyclerView.LayoutManager createLayoutManager() {
+ return BaseWrapContentTest.this.createLayoutManager();
+ }
+ }
+
+ abstract static class Step {
+
+ abstract void onRun() throws Throwable;
+ }
+
+ class Snapshot {
+
+ Rect mRawChildrenBox = new Rect();
+
+ Rect mRvSize = new Rect();
+
+ Rect mRvPadding = new Rect();
+
+ Rect mRvParentSize = new Rect();
+
+ LongSparseArray<Rect> mChildCoordinates = new LongSparseArray<>();
+
+ LongSparseArray<String> mAppear = new LongSparseArray<>();
+
+ LongSparseArray<String> mDisappear = new LongSparseArray<>();
+
+ LongSparseArray<String> mPersistent = new LongSparseArray<>();
+
+ LongSparseArray<String> mChanged = new LongSparseArray<>();
+
+ int mVerticalGravity;
+
+ int mHorizontalGravity;
+
+ int mOffsetX, mOffsetY;// how much we should offset children
+
+ public Snapshot(RecyclerView recyclerView, LoggingItemAnimator loggingItemAnimator,
+ int horizontalGravity, int verticalGravity)
+ throws Throwable {
+ mRvSize = getViewBounds(recyclerView);
+ mRvParentSize = getViewBounds((View) recyclerView.getParent());
+ mRvPadding = new Rect(recyclerView.getPaddingLeft(), recyclerView.getPaddingTop(),
+ recyclerView.getPaddingRight(), recyclerView.getPaddingBottom());
+ mVerticalGravity = verticalGravity;
+ mHorizontalGravity = horizontalGravity;
+ if (mVerticalGravity == Gravity.TOP) {
+ mOffsetY = 0;
+ } else {
+ mOffsetY = mRvParentSize.bottom - mRvSize.bottom;
+ }
+
+ if (mHorizontalGravity == Gravity.LEFT) {
+ mOffsetX = 0;
+ } else {
+ mOffsetX = mRvParentSize.right - mRvSize.right;
+ }
+ collectChildCoordinates(recyclerView);
+ if (loggingItemAnimator != null) {
+ collectInto(mAppear, loggingItemAnimator.mAnimateAppearanceList);
+ collectInto(mDisappear, loggingItemAnimator.mAnimateDisappearanceList);
+ collectInto(mPersistent, loggingItemAnimator.mAnimatePersistenceList);
+ collectInto(mChanged, loggingItemAnimator.mAnimateChangeList);
+ }
+ }
+
+ public boolean doesChildrenFitVertically() {
+ return mRawChildrenBox.top >= mRvPadding.top
+ && mRawChildrenBox.bottom <= mRvSize.bottom - mRvPadding.bottom;
+ }
+
+ public boolean doesChildrenFitHorizontally() {
+ return mRawChildrenBox.left >= mRvPadding.left
+ && mRawChildrenBox.right <= mRvSize.right - mRvPadding.right;
+ }
+
+ public void assertSame(Snapshot other, int step) {
+ if (mWrapContentConfig.isUnlimitedHeight() &&
+ (!doesChildrenFitVertically() || !other.doesChildrenFitVertically())) {
+ if (DEBUG) {
+ Log.d(TAG, "cannot assert coordinates because it does not fit vertically");
+ }
+ return;
+ }
+ if (mWrapContentConfig.isUnlimitedWidth() &&
+ (!doesChildrenFitHorizontally() || !other.doesChildrenFitHorizontally())) {
+ if (DEBUG) {
+ Log.d(TAG, "cannot assert coordinates because it does not fit horizontally");
+ }
+ return;
+ }
+ assertMap("child coordinates. step:" + step, mChildCoordinates,
+ other.mChildCoordinates);
+ if (mWrapContentConfig.isUnlimitedHeight() || mWrapContentConfig.isUnlimitedWidth()) {
+ return;//cannot assert animatinos in unlimited size
+ }
+ assertMap("appearing step:" + step, mAppear, other.mAppear);
+ assertMap("disappearing step:" + step, mDisappear, other.mDisappear);
+ assertMap("persistent step:" + step, mPersistent, other.mPersistent);
+ assertMap("changed step:" + step, mChanged, other.mChanged);
+ }
+
+ private void assertMap(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2) {
+ StringBuilder logBuilder = new StringBuilder();
+ logBuilder.append(prefix).append("\n");
+ logBuilder.append("map1").append("\n");
+ logInto(map1, logBuilder);
+ logBuilder.append("map2").append("\n");
+ logInto(map2, logBuilder);
+ final String log = logBuilder.toString();
+ assertEquals(log + " same size", map1.size(), map2.size());
+ for (int i = 0; i < map1.size(); i++) {
+ assertAtIndex(log, map1, map2, i);
+ }
+ }
+
+ private void assertAtIndex(String prefix, LongSparseArray<?> map1, LongSparseArray<?> map2,
+ int index) {
+ long key1 = map1.keyAt(index);
+ long key2 = map2.keyAt(index);
+ assertEquals(prefix + "key mismatch at index " + index, key1, key2);
+ Object value1 = map1.valueAt(index);
+ Object value2 = map2.valueAt(index);
+ assertEquals(prefix + " value mismatch at index " + index, value1, value2);
+ }
+
+ private void logInto(LongSparseArray<?> map, StringBuilder sb) {
+ for (int i = 0; i < map.size(); i++) {
+ long key = map.keyAt(i);
+ Object value = map.valueAt(i);
+ sb.append(key).append(" : ").append(value).append("\n");
+ }
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder("Snapshot{\n");
+ sb.append("child coordinates:\n");
+ logInto(mChildCoordinates, sb);
+ sb.append("appear animations:\n");
+ logInto(mAppear, sb);
+ sb.append("disappear animations:\n");
+ logInto(mDisappear, sb);
+ sb.append("change animations:\n");
+ logInto(mChanged, sb);
+ sb.append("persistent animations:\n");
+ logInto(mPersistent, sb);
+ sb.append("}");
+ return sb.toString();
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mChildCoordinates.hashCode();
+ result = 31 * result + mAppear.hashCode();
+ result = 31 * result + mDisappear.hashCode();
+ result = 31 * result + mPersistent.hashCode();
+ result = 31 * result + mChanged.hashCode();
+ return result;
+ }
+
+ private void collectInto(
+ LongSparseArray<String> target,
+ List<? extends BaseRecyclerViewAnimationsTest.AnimateLogBase> list) {
+ for (BaseRecyclerViewAnimationsTest.AnimateLogBase base : list) {
+ long id = getItemId(base.viewHolder);
+ assertNull(target.get(id));
+ target.put(id, log(base));
+ }
+ }
+
+ private String log(BaseRecyclerViewAnimationsTest.AnimateLogBase base) {
+ return base.getClass().getSimpleName() +
+ ((TextView) base.viewHolder.itemView).getText() + ": " +
+ "[pre:" + log(base.postInfo) +
+ ", post:" + log(base.postInfo) + "]";
+ }
+
+ private String log(BaseRecyclerViewAnimationsTest.LoggingInfo postInfo) {
+ if (postInfo == null) {
+ return "?";
+ }
+ return "PI[flags: " + postInfo.changeFlags
+ + ",l:" + (postInfo.left + mOffsetX)
+ + ",t:" + (postInfo.top + mOffsetY)
+ + ",r:" + (postInfo.right + mOffsetX)
+ + ",b:" + (postInfo.bottom + mOffsetY) + "]";
+ }
+
+ void collectChildCoordinates(RecyclerView recyclerView) throws Throwable {
+ mRawChildrenBox = new Rect(0, 0, 0, 0);
+ final int childCount = recyclerView.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ View child = recyclerView.getChildAt(i);
+ Rect childBounds = getChildBounds(recyclerView, child, true);
+ mRawChildrenBox.union(getChildBounds(recyclerView, child, false));
+ RecyclerView.ViewHolder childViewHolder = recyclerView.getChildViewHolder(child);
+ mChildCoordinates.put(getItemId(childViewHolder), childBounds);
+ }
+ }
+
+ private Rect getViewBounds(View view) {
+ return new Rect(view.getLeft(), view.getTop(), view.getRight(), view.getBottom());
+ }
+
+ private Rect getChildBounds(RecyclerView recyclerView, View child, boolean offset) {
+ RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
+ RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) child.getLayoutParams();
+ Rect rect = new Rect(layoutManager.getDecoratedLeft(child) - lp.leftMargin,
+ layoutManager.getDecoratedTop(child) - lp.topMargin,
+ layoutManager.getDecoratedRight(child) + lp.rightMargin,
+ layoutManager.getDecoratedBottom(child) + lp.bottomMargin);
+ if (offset) {
+ rect.offset(mOffsetX, mOffsetY);
+ }
+ return rect;
+ }
+
+ private long getItemId(RecyclerView.ViewHolder vh) {
+ if (vh instanceof TestViewHolder) {
+ return ((TestViewHolder) vh).mBoundItem.mId;
+ } else if (vh instanceof BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) {
+ BaseWrapContentWithAspectRatioTest.WrapContentViewHolder casted =
+ (BaseWrapContentWithAspectRatioTest.WrapContentViewHolder) vh;
+ return casted.mView.mBehavior.getId();
+ } else {
+ throw new IllegalArgumentException("i don't support any VH");
+ }
+ }
+
+ public void assertRvSize() {
+ if (shouldWrapContentHorizontally()) {
+ int expectedW = mRawChildrenBox.width() + mRvPadding.left + mRvPadding.right;
+ assertTrue(mRvSize.width() + " <= " + expectedW, mRvSize.width() <= expectedW);
+ }
+ if (shouldWrapContentVertically()) {
+ int expectedH = mRawChildrenBox.height() + mRvPadding.top + mRvPadding.bottom;
+ assertTrue(mRvSize.height() + "<=" + expectedH, mRvSize.height() <= expectedH);
+ }
+ }
+ }
+
+ protected boolean shouldWrapContentHorizontally() {
+ return true;
+ }
+
+ protected boolean shouldWrapContentVertically() {
+ return true;
+ }
+
+ static class WrappedRecyclerView extends RecyclerView {
+
+ public WrappedRecyclerView(Context context) {
+ super(context);
+ }
+
+ public void waitUntilLayout() {
+ while (isLayoutRequested()) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ public void waitUntilAnimations() throws InterruptedException {
+ final CountDownLatch latch = new CountDownLatch(1);
+ if (mItemAnimator == null || !mItemAnimator.isRunning(
+ new ItemAnimator.ItemAnimatorFinishedListener() {
+ @Override
+ public void onAnimationsFinished() {
+ latch.countDown();
+ }
+ })) {
+ latch.countDown();
+ }
+ MatcherAssert.assertThat("waiting too long for animations",
+ latch.await(60, TimeUnit.SECONDS), CoreMatchers.is(true));
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int l, int t, int r, int b) {
+ super.onLayout(changed, l, t, r, b);
+ }
+ }
+
+ static class WrapContentConfig {
+
+ public boolean unlimitedWidth;
+ public boolean unlimitedHeight;
+ public Rect padding = new Rect(0, 0, 0, 0);
+
+ public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight) {
+ this.unlimitedWidth = unlimitedWidth;
+ this.unlimitedHeight = unlimitedHeight;
+ }
+
+ public WrapContentConfig(boolean unlimitedWidth, boolean unlimitedHeight, Rect padding) {
+ this.unlimitedWidth = unlimitedWidth;
+ this.unlimitedHeight = unlimitedHeight;
+ this.padding.set(padding);
+ }
+
+ public boolean isUnlimitedWidth() {
+ return unlimitedWidth;
+ }
+
+ public WrapContentConfig setUnlimitedWidth(boolean unlimitedWidth) {
+ this.unlimitedWidth = unlimitedWidth;
+ return this;
+ }
+
+ public boolean isUnlimitedHeight() {
+ return unlimitedHeight;
+ }
+
+ public WrapContentConfig setUnlimitedHeight(boolean unlimitedHeight) {
+ this.unlimitedHeight = unlimitedHeight;
+ return this;
+ }
+
+ @Override
+ public String toString() {
+ return "WrapContentConfig{"
+ + "unlimitedWidth=" + unlimitedWidth
+ + ", unlimitedHeight=" + unlimitedHeight
+ + ", padding=" + padding
+ + '}';
+ }
+
+ public TestedFrameLayout.FullControlLayoutParams toLayoutParams(int wDim, int hDim) {
+ TestedFrameLayout.FullControlLayoutParams
+ lp = new TestedFrameLayout.FullControlLayoutParams(
+ wDim, hDim);
+ if (unlimitedWidth) {
+ lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ if (unlimitedHeight) {
+ lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ }
+ return lp;
+ }
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..f022eee
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseWrapContentWithAspectRatioTest.java
@@ -0,0 +1,300 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.Description;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v4.util.Pair;
+import android.util.AttributeSet;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+abstract public class BaseWrapContentWithAspectRatioTest extends BaseRecyclerViewInstrumentationTest {
+ final BaseWrapContentTest.WrapContentConfig mWrapContentConfig;
+
+ protected BaseWrapContentWithAspectRatioTest(
+ BaseWrapContentTest.WrapContentConfig wrapContentConfig) {
+ mWrapContentConfig = wrapContentConfig;
+ }
+
+ int getSize(View view, int orientation) {
+ if (orientation == VERTICAL) {
+ return view.getHeight();
+ }
+ return view.getWidth();
+ }
+
+ static class LoggingView extends View {
+
+ MeasureBehavior mBehavior;
+
+ public void setBehavior(MeasureBehavior behavior) {
+ mBehavior = behavior;
+ }
+
+ public LoggingView(Context context) {
+ super(context);
+ }
+
+ public LoggingView(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public LoggingView(Context context, AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public LoggingView(Context context, AttributeSet attrs, int defStyleAttr,
+ int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ @Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ mBehavior.onMeasure(this, widthMeasureSpec, heightMeasureSpec);
+ }
+
+ @Override
+ protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ super.onLayout(changed, left, top, right, bottom);
+ mBehavior.onLayout(changed, left, top, right, bottom);
+ }
+
+ public void setMeasured(int w, int h) {
+ setMeasuredDimension(w, h);
+ }
+
+ public void prepareLayoutParams() {
+ mBehavior.setLayoutParams(this);
+ }
+ }
+
+ static class AspectRatioMeasureBehavior extends MeasureBehavior {
+
+ Float ratio;
+ int control;
+
+ public AspectRatioMeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
+ super(desiredW, desiredH, wMode, hMode);
+ }
+
+ public AspectRatioMeasureBehavior aspectRatio(int control, float ratio) {
+ this.control = control;
+ this.ratio = ratio;
+ return this;
+ }
+
+ @Override
+ public void onMeasure(LoggingView view, int wSpec,
+ int hSpec) {
+ super.onMeasure(view, wSpec, hSpec);
+ if (control == VERTICAL) {
+ view.setMeasured(getSecondary(view.getMeasuredHeight()),
+ view.getMeasuredHeight());
+ } else if (control == HORIZONTAL) {
+ view.setMeasured(view.getMeasuredWidth(),
+ getSecondary(view.getMeasuredWidth()));
+ }
+ }
+
+ public int getSecondary(int controlSize) {
+ return (int) (controlSize * ratio);
+ }
+ }
+
+ static class MeasureBehavior {
+ private static final AtomicLong idCounter = new AtomicLong(0);
+ public List<Pair<Integer, Integer>> measureSpecs = new ArrayList<>();
+ public List<Pair<Integer, Integer>> layouts = new ArrayList<>();
+ int desiredW, desiredH;
+ final long mId = idCounter.incrementAndGet();
+
+ ViewGroup.LayoutParams layoutParams;
+
+ public MeasureBehavior(int desiredW, int desiredH, int wMode, int hMode) {
+ this.desiredW = desiredW;
+ this.desiredH = desiredH;
+ layoutParams = new ViewGroup.LayoutParams(
+ wMode, hMode
+ );
+ }
+
+ public long getId() {
+ return mId;
+ }
+
+ public void onMeasure(LoggingView view, int wSpec, int hSpec) {
+ measureSpecs.add(new Pair<>(wSpec, hSpec));
+ view.setMeasured(
+ RecyclerView.LayoutManager.chooseSize(wSpec, desiredW, 0),
+ RecyclerView.LayoutManager.chooseSize(hSpec, desiredH, 0));
+ }
+
+ public int getSpec(int position, int orientation) {
+ if (orientation == VERTICAL) {
+ return measureSpecs.get(position).second;
+ } else {
+ return measureSpecs.get(position).first;
+ }
+ }
+
+ public void setLayoutParams(LoggingView view) {
+ view.setLayoutParams(layoutParams);
+ }
+
+ public void onLayout(boolean changed, int left, int top, int right, int bottom) {
+ if (changed) {
+ layouts.add(new Pair<>(right - left, bottom - top));
+ }
+ }
+ }
+
+
+ static class WrapContentViewHolder extends RecyclerView.ViewHolder {
+
+ LoggingView mView;
+
+ public WrapContentViewHolder(ViewGroup parent) {
+ super(new LoggingView(parent.getContext()));
+ mView = (LoggingView) itemView;
+ mView.setBackgroundColor(Color.GREEN);
+ }
+ }
+
+ static class WrapContentAdapter extends RecyclerView.Adapter<WrapContentViewHolder> {
+
+ List<MeasureBehavior> behaviors = new ArrayList<>();
+
+ public WrapContentAdapter(MeasureBehavior... behaviors) {
+ Collections.addAll(this.behaviors, behaviors);
+ }
+
+ @Override
+ public WrapContentViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return new WrapContentViewHolder(parent);
+ }
+
+ @Override
+ public void onBindViewHolder(WrapContentViewHolder holder, int position) {
+ holder.mView.setBehavior(behaviors.get(position));
+ holder.mView.prepareLayoutParams();
+ }
+
+ @Override
+ public int getItemCount() {
+ return behaviors.size();
+ }
+ }
+
+ static class MeasureSpecMatcher extends BaseMatcher<Integer> {
+
+ private boolean checkSize = false;
+ private boolean checkMode = false;
+ private int mSize;
+ private int mMode;
+
+ public static MeasureSpecMatcher is(int size, int mode) {
+ MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, mode);
+ matcher.checkSize = true;
+ matcher.checkMode = true;
+ return matcher;
+ }
+
+ public static MeasureSpecMatcher size(int size) {
+ MeasureSpecMatcher matcher = new MeasureSpecMatcher(size, 0);
+ matcher.checkSize = true;
+ matcher.checkMode = false;
+ return matcher;
+ }
+
+ public static MeasureSpecMatcher mode(int mode) {
+ MeasureSpecMatcher matcher = new MeasureSpecMatcher(0, mode);
+ matcher.checkSize = false;
+ matcher.checkMode = true;
+ return matcher;
+ }
+
+ private MeasureSpecMatcher(int size, int mode) {
+ mSize = size;
+ mMode = mode;
+
+ }
+
+ @Override
+ public boolean matches(Object item) {
+ if (item == null) {
+ return false;
+ }
+ Integer intValue = (Integer) item;
+ final int size = View.MeasureSpec.getSize(intValue);
+ final int mode = View.MeasureSpec.getMode(intValue);
+ if (checkSize && size != mSize) {
+ return false;
+ }
+ if (checkMode && mode != mMode) {
+ return false;
+ }
+ return true;
+ }
+
+ @Override
+ public void describeMismatch(Object item, Description description) {
+ Integer intValue = (Integer) item;
+ final int size = View.MeasureSpec.getSize(intValue);
+ final int mode = View.MeasureSpec.getMode(intValue);
+ if (checkSize && size != mSize) {
+ description.appendText(" Expected size was ").appendValue(mSize)
+ .appendText(" but received size is ").appendValue(size);
+ }
+ if (checkMode && mode != mMode) {
+ description.appendText(" Expected mode was ").appendValue(modeName(mMode))
+ .appendText(" but received mode is ").appendValue(modeName(mode));
+ }
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ if (checkSize) {
+ description.appendText(" Measure spec size:").appendValue(mSize);
+ }
+ if (checkMode) {
+ description.appendText(" Measure spec mode:").appendValue(modeName(mMode));
+ }
+ }
+
+ private static String modeName(int mode) {
+ switch (mode) {
+ case View.MeasureSpec.AT_MOST:
+ return "at most";
+ case View.MeasureSpec.EXACTLY:
+ return "exactly";
+ default:
+ return "unspecified";
+ }
+ }
+ }
+}
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..0db69f1 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BucketTest.java
@@ -16,14 +16,24 @@
package android.support.v7.widget;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import android.support.test.runner.AndroidJUnit4;
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;
+import static org.junit.Assert.*;
-public class BucketTest extends AndroidTestCase {
+@SmallTest
+@RunWith(JUnit4.class)
+public class BucketTest {
ChildHelper.Bucket mBucket;
@@ -33,11 +43,10 @@
int max = 0;
- @Override
+ @Before
public void setUp() throws Exception {
- super.setUp();
mBucket = new ChildHelper.Bucket();
- mArr = new ArrayList<Integer>();
+ mArr = new ArrayList<>();
Collections.addAll(mArr, 0, 1, 2, 3, 4, 5, 6, 10, 12, 13, 21, 22, 122, 14, 44, 29, 205, 19);
for (int i = 1; i < 4; i++) {
mArr.add(i * (ChildHelper.Bucket.BITS_PER_WORD - 1));
@@ -48,7 +57,7 @@
mArr.add(i * ChildHelper.Bucket.BITS_PER_WORD + 1);
}
- mSet = new HashSet<Integer>();
+ mSet = new HashSet<>();
max = 0;
for (int i = mArr.size() - 1; i >= 0; i--) {
if (!mSet.add(mArr.get(i))) {
@@ -59,7 +68,8 @@
}
- public void testSetClear() {
+ @Test
+ public void setClear() {
for (int i : mArr) {
mBucket.set(i);
assertTrue(mBucket.get(i));
@@ -79,7 +89,8 @@
}
- public void testRemove() {
+ @Test
+ public void remove() {
for (int i : mArr) {
mBucket.reset();
for (int j : mArr) {
@@ -99,7 +110,8 @@
}
}
- public void testInsert() {
+ @Test
+ public void insert() {
for (int i : mArr) {
for (boolean val : new boolean[]{true, false}) {
mBucket.reset();
@@ -123,7 +135,8 @@
}
- public void testCountOnesBefore() {
+ @Test
+ public void countOnesBefore() {
assertEquals(mBucket.countOnesBefore(0), 0);
for (int i : mArr) {
mBucket.set(i);
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..47a34db 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;
@@ -54,7 +56,7 @@
}
@Test
- public void testAddChild() {
+ public void addChild() {
RecyclerView.ViewHolder vh = vh();
mChildHelper.addView(vh.itemView, false);
assertEquals(1, mLoggingCallback.getChildCount());
@@ -63,7 +65,7 @@
}
@Test
- public void testAddChildHidden() {
+ public void addChildHidden() {
RecyclerView.ViewHolder vh = vh();
mChildHelper.addView(vh.itemView, true);
assertEquals(1, mLoggingCallback.getChildCount());
@@ -72,7 +74,7 @@
}
@Test
- public void testAddChildAndHide() {
+ public void addChildAndHide() {
RecyclerView.ViewHolder vh = vh();
mChildHelper.addView(vh.itemView, false);
mChildHelper.hide(vh.itemView);
@@ -82,7 +84,7 @@
}
@Test
- public void testFindHiddenNonRemoved() {
+ public void findHiddenNonRemoved() {
RecyclerView.ViewHolder vh = vh();
vh.mPosition = 12;
mChildHelper.addView(vh.itemView, true);
@@ -91,7 +93,7 @@
}
@Test
- public void testFindHiddenRemoved() {
+ public void findHiddenRemoved() {
RecyclerView.ViewHolder vh = vh();
vh.mPosition = 12;
vh.addFlags(RecyclerView.ViewHolder.FLAG_REMOVED);
@@ -168,10 +170,5 @@
public void onLeftHiddenState(View child) {
mOnExitedHiddenState.add(child);
}
-
- public void clearHiddenStateLog() {
- mOnExitedHiddenState.clear();
- mOnEnteredHiddenState.clear();
- }
}
}
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 5010be9..4ace4ca 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultItemAnimatorTest.java
@@ -16,8 +16,15 @@
package android.support.v7.widget;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.os.Looper;
+import android.support.test.runner.AndroidJUnit4;
import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -33,8 +40,11 @@
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
+import static org.junit.Assert.*;
-public class DefaultItemAnimatorTest extends ActivityInstrumentationTestCase2<TestActivity> {
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class DefaultItemAnimatorTest extends BaseRecyclerViewInstrumentationTest {
private static final String TAG = "DefaultItemAnimatorTest";
Throwable mainThreadException;
@@ -51,13 +61,8 @@
Semaphore mExpectedItemCount = new Semaphore(0);
- public DefaultItemAnimatorTest() {
- super("android.support.v7.recyclerview", TestActivity.class);
- }
-
- @Override
- protected void setUp() throws Exception {
- super.setUp();
+ @Before
+ public void setUp() throws Exception {
mAnimator = new DefaultItemAnimator() {
@Override
public void onRemoveFinished(RecyclerView.ViewHolder item) {
@@ -105,7 +110,7 @@
}
};
mAdapter = new Adapter(20);
- mDummyParent = getActivity().mContainer;
+ mDummyParent = getActivity().getContainer();
}
void checkForMainThreadException() throws Throwable {
@@ -114,17 +119,11 @@
}
}
- @Override
- protected void tearDown() throws Exception {
- getInstrumentation().waitForIdleSync();
- super.tearDown();
- try {
- checkForMainThreadException();
- } catch (Exception e) {
- throw e;
- } catch (Throwable throwable) {
- throw new Exception(throwable);
- }
+ @Test
+ public void reUseWithPayload() {
+ RecyclerView.ViewHolder vh = new ViewHolder(new TextView(getActivity()));
+ assertFalse(mAnimator.canReuseUpdatedViewHolder(vh, new ArrayList<>()));
+ assertTrue(mAnimator.canReuseUpdatedViewHolder(vh, Arrays.asList((Object) "a")));
}
void expectItems(RecyclerView.ViewHolder... viewHolders) {
@@ -162,7 +161,8 @@
mExpectedItemCount.tryAcquire(1, 2, TimeUnit.SECONDS));
}
- public void testAnimateAdd() throws Throwable {
+ @Test
+ public void animateAdd() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateAdd(vh));
@@ -170,7 +170,8 @@
runAndWait(1, 1);
}
- public void testAnimateRemove() throws Throwable {
+ @Test
+ public void animateRemove() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateRemove(vh));
@@ -178,7 +179,8 @@
runAndWait(1, 1);
}
- public void testAnimateMove() throws Throwable {
+ @Test
+ public void animateMove() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateMove(vh, 0, 0, 100, 100));
@@ -186,7 +188,8 @@
runAndWait(1, 1);
}
- public void testAnimateChange() throws Throwable {
+ @Test
+ public void animateChange() throws Throwable {
ViewHolder vh = createViewHolder(1);
ViewHolder vh2 = createViewHolder(2);
expectItems(vh, vh2);
@@ -219,35 +222,40 @@
}
}
- public void testCancelAddBefore() throws Throwable {
+ @Test
+ public void cancelAddBefore() throws Throwable {
final ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateAdd(vh));
cancelBefore(1, vh);
}
- public void testCancelAddAfter() throws Throwable {
+ @Test
+ public void cancelAddAfter() throws Throwable {
final ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateAdd(vh));
cancelAfter(1, vh);
}
- public void testCancelMoveBefore() throws Throwable {
+ @Test
+ public void cancelMoveBefore() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateMove(vh, 10, 10, 100, 100));
cancelBefore(1, vh);
}
- public void testCancelMoveAfter() throws Throwable {
+ @Test
+ public void cancelMoveAfter() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateMove(vh, 10, 10, 100, 100));
cancelAfter(1, vh);
}
- public void testCancelRemove() throws Throwable {
+ @Test
+ public void cancelRemove() throws Throwable {
ViewHolder vh = createViewHolder(1);
expectItems(vh);
assertTrue(animateRemove(vh));
@@ -255,10 +263,12 @@
runAndWait(1, 1);
}
- public void testCancelChangeOldBefore() throws Throwable {
+ @Test
+ public void cancelChangeOldBefore() throws Throwable {
cancelChangeOldTest(true);
}
- public void testCancelChangeOldAfter() throws Throwable {
+ @Test
+ public void cancelChangeOldAfter() throws Throwable {
cancelChangeOldTest(false);
}
@@ -270,11 +280,13 @@
cancelTest(before, 2, vh);
}
- public void testCancelChangeNewBefore() throws Throwable {
+ @Test
+ public void cancelChangeNewBefore() throws Throwable {
cancelChangeNewTest(true);
}
- public void testCancelChangeNewAfter() throws Throwable {
+ @Test
+ public void cancelChangeNewAfter() throws Throwable {
cancelChangeNewTest(false);
}
@@ -286,11 +298,13 @@
cancelTest(before, 2, vh2);
}
- public void testCancelChangeBothBefore() throws Throwable {
+ @Test
+ public void cancelChangeBothBefore() throws Throwable {
cancelChangeBothTest(true);
}
- public void testCancelChangeBothAfter() throws Throwable {
+ @Test
+ public void cancelChangeBothAfter() throws Throwable {
cancelChangeBothTest(false);
}
@@ -387,7 +401,7 @@
List<String> mItems;
private Adapter(int count) {
- mItems = new ArrayList<String>();
+ mItems = new ArrayList<>();
for (int i = 0; i < count; i++) {
mItems.add("item-" + i);
}
@@ -424,7 +438,7 @@
}
private interface ThrowingRunnable {
- public void run() throws Throwable;
+ void run() throws Throwable;
}
@Override
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/DefaultMeasureSpecTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultMeasureSpecTest.java
new file mode 100644
index 0000000..a24eb83
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/DefaultMeasureSpecTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import android.graphics.Rect;
+import android.support.test.InstrumentationRegistry;
+import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.View;
+
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@SmallTest
+public class DefaultMeasureSpecTest extends AndroidTestCase {
+ private final int mWSpec;
+ private final int mHSpec;
+ private int mExpectedW;
+ private int mExpectedH;
+ private final Rect mPadding;
+ RecyclerView mRecyclerView;
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setContext(InstrumentationRegistry.getContext());
+ mRecyclerView = new RecyclerView(getContext());
+ if (mPadding != null) {
+ mRecyclerView.setPadding(mPadding.left, mPadding.top, mPadding.right, mPadding.bottom);
+ }
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Object[]> param() {
+ List<Object[]> params = Arrays.asList(
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(20, EXACTLY), 10,
+ 20, new Rect(0, 0, 0, 0)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(100, AT_MOST), 10,
+ 0, new Rect(0, 0, 0, 0)},
+ new Object[]{null, makeMeasureSpec(10, AT_MOST), makeMeasureSpec(100, EXACTLY), 0,
+ 100, new Rect(0, 0, 0, 0)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(100, UNSPECIFIED),
+ 10, 0, new Rect(0, 0, 0, 0)},
+ new Object[]{null, makeMeasureSpec(10, UNSPECIFIED), makeMeasureSpec(100, EXACTLY),
+ 0, 100, new Rect(0, 0, 0, 0)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(20, EXACTLY), 10,
+ 20, new Rect(39, 50, 34, 23)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(100, AT_MOST), 10,
+ 50, new Rect(3, 35, 3, 15)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(100, AT_MOST), 10,
+ 100, new Rect(3, 350, 3, 15)},
+
+ new Object[]{null, makeMeasureSpec(10, AT_MOST), makeMeasureSpec(100, EXACTLY), 10,
+ 100, new Rect(15, 500, 5, 30)},
+ new Object[]{null, makeMeasureSpec(10, AT_MOST), makeMeasureSpec(100, EXACTLY), 5,
+ 100, new Rect(3, 500, 2, 30)},
+ new Object[]{null, makeMeasureSpec(10, EXACTLY), makeMeasureSpec(100, UNSPECIFIED),
+ 10, 20, new Rect(500, 15, 30, 5)},
+ new Object[]{null, makeMeasureSpec(10, UNSPECIFIED), makeMeasureSpec(100, EXACTLY),
+ 45, 100, new Rect(15, 400, 30, 5)}
+ );
+ for (Object[] param : params) {
+ param[0] = "width: " + log((Integer) param[1]) + ", height:" + log((Integer) param[2]);
+ param[0] = param[0] + ", padding:" + param[5];
+ }
+ return params;
+ }
+
+ public DefaultMeasureSpecTest(@SuppressWarnings("UnusedParameters") String ignored,
+ int wSpec, int hSpec, int expectedW, int expectedH, Rect padding) {
+ mWSpec = wSpec;
+ mHSpec = hSpec;
+ mExpectedW = expectedW;
+ mExpectedH = expectedH;
+ this.mPadding = padding;
+ }
+
+ @Test
+ public void testWithSmallerMinWidth() {
+ mRecyclerView.setMinimumWidth(Math.max(0, mExpectedW - 5));
+ runTest();
+ }
+
+ @Test
+ public void testWithSmallerMinHeight() {
+ mRecyclerView.setMinimumHeight(Math.max(0, mExpectedH - 5));
+ runTest();
+ }
+
+ @Test
+ public void testWithLargerMinHeight() {
+ mRecyclerView.setMinimumHeight(mExpectedH + 5);
+ int mode = View.MeasureSpec.getMode(mHSpec);
+ switch (mode) {
+ case UNSPECIFIED:
+ mExpectedH += 5;
+ break;
+ case AT_MOST:
+ mExpectedH = Math.min(View.MeasureSpec.getSize(mHSpec), mExpectedH + 5);
+ break;
+ }
+ runTest();
+ }
+
+ @Test
+ public void testWithLargerMinWidth() {
+ mRecyclerView.setMinimumWidth(mExpectedW + 5);
+ int mode = View.MeasureSpec.getMode(mWSpec);
+ switch (mode) {
+ case UNSPECIFIED:
+ mExpectedW += 5;
+ break;
+ case AT_MOST:
+ mExpectedW = Math.min(View.MeasureSpec.getSize(mWSpec), mExpectedW + 5);
+ break;
+ }
+ runTest();
+ }
+
+ @Test
+ public void runTest() {
+ mRecyclerView.defaultOnMeasure(mWSpec, mHSpec);
+ MatcherAssert.assertThat("measured width", mRecyclerView.getMeasuredWidth(),
+ CoreMatchers.is(mExpectedW));
+ MatcherAssert.assertThat("measured height", mRecyclerView.getMeasuredHeight(),
+ CoreMatchers.is(mExpectedH));
+ }
+
+ private static String log(int spec) {
+ final int size = View.MeasureSpec.getSize(spec);
+ int mode = View.MeasureSpec.getMode(spec);
+ if (mode == View.MeasureSpec.AT_MOST) {
+ return "at most " + size;
+ }
+ if (mode == View.MeasureSpec.UNSPECIFIED) {
+ return "unspecified " + size;
+ }
+ if (mode == View.MeasureSpec.EXACTLY) {
+ return "exactly " + size;
+ }
+ return "?? " + size;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/FocusSearchNavigationTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/FocusSearchNavigationTest.java
new file mode 100644
index 0000000..48bd6c5
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/FocusSearchNavigationTest.java
@@ -0,0 +1,317 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+
+import static android.support.v7.widget.RecyclerView.HORIZONTAL;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
+import static android.support.v7.widget.RecyclerView.VERTICAL;
+
+import android.app.Activity;
+import android.content.Context;
+import android.os.Looper;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.v4.view.ViewCompat;
+import android.support.v7.recyclerview.test.R;
+import android.support.v7.widget.test.RecyclerViewTestActivity;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+import android.widget.LinearLayout;
+
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.hamcrest.CoreMatchers.is;
+
+import org.hamcrest.BaseMatcher;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.Description;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class tests RecyclerView focus search failure handling by using a real LayoutManager.
+ */
+@RunWith(Parameterized.class)
+public class FocusSearchNavigationTest {
+ @Rule
+ public ActivityTestRule<RecyclerViewTestActivity> mActivityRule
+ = new ActivityTestRule<>(RecyclerViewTestActivity.class);
+
+ private final int mOrientation;
+ private final int mLayoutDir;
+
+ public FocusSearchNavigationTest(int orientation, int layoutDir) {
+ mOrientation = orientation;
+ mLayoutDir = layoutDir;
+ }
+
+ @Parameterized.Parameters(name = "orientation:{0} layoutDir:{1}")
+ public static List<Object[]> params() {
+ return Arrays.asList(
+ new Object[]{VERTICAL, ViewCompat.LAYOUT_DIRECTION_LTR},
+ new Object[]{HORIZONTAL, ViewCompat.LAYOUT_DIRECTION_LTR},
+ new Object[]{HORIZONTAL, ViewCompat.LAYOUT_DIRECTION_RTL}
+ );
+ }
+
+ private Activity mActivity;
+ private RecyclerView mRecyclerView;
+ private View mBefore;
+ private View mAfter;
+
+ private void setup(final int itemCount) throws Throwable {
+ mActivity = mActivityRule.getActivity();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mActivity.setContentView(R.layout.focus_search_activity);
+ mActivity.getWindow().getDecorView().setLayoutDirection(mLayoutDir);
+ LinearLayout linearLayout = (LinearLayout) mActivity.findViewById(R.id.root);
+ linearLayout.setOrientation(mOrientation);
+ mRecyclerView = (RecyclerView) mActivity.findViewById(R.id.recycler_view);
+ mRecyclerView.setLayoutDirection(mLayoutDir);
+ LinearLayoutManager layout = new LinearLayoutManager(mActivity.getBaseContext());
+ layout.setOrientation(mOrientation);
+ mRecyclerView.setLayoutManager(layout);
+ mRecyclerView.setAdapter(new FocusSearchAdapter(itemCount, mOrientation));
+ if (mOrientation == VERTICAL) {
+ mRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(
+ ViewGroup.LayoutParams.MATCH_PARENT, 250));
+ } else {
+ mRecyclerView.setLayoutParams(new LinearLayout.LayoutParams(
+ 250, ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+
+ mBefore = mActivity.findViewById(R.id.before);
+ mAfter = mActivity.findViewById(R.id.after);
+ }
+ });
+ waitForIdleSync();
+ assertThat("test sanity", mRecyclerView.getLayoutManager().getLayoutDirection(),
+ is(mLayoutDir));
+ assertThat("test sanity", mRecyclerView.getLayoutDirection(), is(mLayoutDir));
+ }
+
+ @Test
+ public void focusSearchForward() throws Throwable {
+ setup(20);
+ requestFocus(mBefore);
+ assertThat(mBefore, hasFocus());
+ View focused = mBefore;
+ for (int i = 0; i < 20; i++) {
+ focusSearchAndGive(focused, View.FOCUS_FORWARD);
+ RecyclerView.ViewHolder viewHolder = mRecyclerView.findViewHolderForAdapterPosition(i);
+ assertThat("vh at " + i, viewHolder, hasFocus());
+ focused = viewHolder.itemView;
+ }
+ focusSearchAndGive(focused, View.FOCUS_FORWARD);
+ assertThat(mAfter, hasFocus());
+ focusSearchAndGive(mAfter, View.FOCUS_FORWARD);
+ assertThat(mBefore, hasFocus());
+ focusSearchAndGive(mBefore, View.FOCUS_FORWARD);
+ focused = mActivity.getCurrentFocus();
+ //noinspection ConstantConditions
+ assertThat(focused.getParent(), CoreMatchers.<ViewParent>sameInstance(mRecyclerView));
+ }
+
+ @Test
+ public void focusSearchBackwards() throws Throwable {
+ setup(20);
+ requestFocus(mAfter);
+ assertThat(mAfter, hasFocus());
+ View focused = mAfter;
+ RecyclerView.ViewHolder lastViewHolder = null;
+ int i = 20;
+ while(lastViewHolder == null) {
+ lastViewHolder = mRecyclerView.findViewHolderForAdapterPosition(--i);
+ }
+ assertThat(lastViewHolder, notNullValue());
+
+ while(i >= 0) {
+ focusSearchAndGive(focused, View.FOCUS_BACKWARD);
+ RecyclerView.ViewHolder viewHolder = mRecyclerView.findViewHolderForAdapterPosition(i);
+ assertThat("vh at " + i, viewHolder, hasFocus());
+ focused = viewHolder.itemView;
+ i--;
+ }
+ focusSearchAndGive(focused, View.FOCUS_BACKWARD);
+ assertThat(mBefore, hasFocus());
+ focusSearchAndGive(mBefore, View.FOCUS_BACKWARD);
+ assertThat(mAfter, hasFocus());
+ }
+
+ private View focusSearchAndGive(final View view, final int focusDir) throws Throwable {
+ View next = focusSearch(view, focusDir);
+ if (next != null && next != view) {
+ requestFocus(next);
+ return next;
+ }
+ return null;
+ }
+
+ private View focusSearch(final View view, final int focusDir) throws Throwable {
+ final View[] result = new View[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ result[0] = view.focusSearch(focusDir);
+ }
+ });
+ waitForIdleSync();
+ return result[0];
+ }
+
+ private void waitForIdleSync() throws Throwable {
+ waitForIdleScroll(mRecyclerView);
+ InstrumentationRegistry.getInstrumentation().waitForIdleSync();
+ }
+
+ private void requestFocus(final View view) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ view.requestFocus();
+ }
+ });
+ waitForIdleSync();
+ }
+
+ public void waitForIdleScroll(final RecyclerView recyclerView) throws Throwable {
+ final CountDownLatch latch = new CountDownLatch(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RecyclerView.OnScrollListener listener = new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrollStateChanged(RecyclerView recyclerView, int newState) {
+ if (newState == SCROLL_STATE_IDLE) {
+ latch.countDown();
+ recyclerView.removeOnScrollListener(this);
+ }
+ }
+ };
+ if (recyclerView.getScrollState() == SCROLL_STATE_IDLE) {
+ latch.countDown();
+ } else {
+ recyclerView.addOnScrollListener(listener);
+ }
+ }
+ });
+ assertTrue("should go idle in 10 seconds", latch.await(10, TimeUnit.SECONDS));
+ }
+
+ private void runTestOnUiThread(Runnable r) throws Throwable {
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ r.run();
+ } else {
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(r);
+ }
+ }
+
+ static class FocusSearchAdapter extends RecyclerView.Adapter {
+ private int mItemCount;
+ private int mOrientation;
+ public FocusSearchAdapter(int itemCount, int orientation) {
+ mItemCount = itemCount;
+ mOrientation = orientation;
+ }
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_view,
+ parent, false);
+ if (mOrientation == VERTICAL) {
+ view.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ 50));
+ } else {
+ view.setLayoutParams(new ViewGroup.LayoutParams(50,
+ ViewGroup.LayoutParams.MATCH_PARENT));
+ }
+ return new RecyclerView.ViewHolder(view) {};
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+ holder.itemView.setTag("pos " + position);
+ }
+
+ @Override
+ public int getItemCount() {
+ return mItemCount;
+ }
+ }
+
+ static HasFocusMatcher hasFocus() {
+ return new HasFocusMatcher();
+ }
+
+ static class HasFocusMatcher extends BaseMatcher<Object> {
+ @Override
+ public boolean matches(Object item) {
+ if (item instanceof RecyclerView.ViewHolder) {
+ item = ((RecyclerView.ViewHolder) item).itemView;
+ }
+ return item instanceof View && ((View) item).hasFocus();
+ }
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText("view has focus");
+ }
+
+ private String objectToLog(Object item) {
+ if (item instanceof RecyclerView.ViewHolder) {
+ RecyclerView.ViewHolder vh = (RecyclerView.ViewHolder) item;
+ return vh.toString();
+ }
+ if (item instanceof View) {
+ final Object tag = ((View) item).getTag();
+ return tag == null ? item.toString() : tag.toString();
+ }
+ final String classLog = item == null ? "null" : item.getClass().getSimpleName();
+ return classLog;
+ }
+
+ @Override
+ public void describeMismatch(Object item, Description description) {
+ String noun = objectToLog(item);
+ description.appendText(noun + " does not have focus");
+ Context context = null;
+ if (item instanceof RecyclerView.ViewHolder) {
+ context = ((RecyclerView.ViewHolder)item).itemView.getContext();
+ } else if (item instanceof View) {
+ context = ((View) item).getContext();
+ }
+ if (context instanceof Activity) {
+ View currentFocus = ((Activity) context).getWindow().getCurrentFocus();
+ description.appendText(". Current focus is in " + objectToLog(currentFocus));
+ }
+ }
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java
new file mode 100644
index 0000000..99ec4ed
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerBaseConfigSetTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.util.Log;
+import android.view.View;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerBaseConfigSetTest extends BaseGridLayoutManagerTest {
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Config> params() {
+ return createBaseVariations();
+ }
+
+ private final Config mConfig;
+
+ public GridLayoutManagerBaseConfigSetTest(Config config) {
+ mConfig = config;
+ }
+
+ @Test
+ public void scrollBackAndPreservePositions() throws Throwable {
+ Config config = (Config) mConfig.clone();
+ config.mItemCount = 150;
+ scrollBackAndPreservePositionsTest(config);
+ }
+
+ public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable {
+ final RecyclerView rv = setupBasic(config);
+ for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) {
+ mAdapter.setFullSpan(i);
+ }
+ waitForFirstLayout(rv);
+ final int[] globalPositions = new int[mAdapter.getItemCount()];
+ Arrays.fill(globalPositions, Integer.MIN_VALUE);
+ final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20)
+ * (config.mReverseLayout ? -1 : 1);
+ final String logPrefix = config.toString();
+ final int[] globalPos = new int[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertSame("test sanity", mRecyclerView, rv);
+ int globalScrollPosition = 0;
+ int visited = 0;
+ while (visited < mAdapter.getItemCount()) {
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ final int pos = mRecyclerView.getChildLayoutPosition(child);
+ if (globalPositions[pos] != Integer.MIN_VALUE) {
+ continue;
+ }
+ visited++;
+ GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+ child.getLayoutParams();
+ if (config.mReverseLayout) {
+ globalPositions[pos] = globalScrollPosition +
+ mGlm.mOrientationHelper.getDecoratedEnd(child);
+ } else {
+ globalPositions[pos] = globalScrollPosition +
+ mGlm.mOrientationHelper.getDecoratedStart(child);
+ }
+ assertEquals(logPrefix + " span index should match",
+ mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
+ lp.getSpanIndex());
+ }
+ int scrolled = mGlm.scrollBy(scrollStep,
+ mRecyclerView.mRecycler, mRecyclerView.mState);
+ globalScrollPosition += scrolled;
+ if (scrolled == 0) {
+ assertEquals(
+ logPrefix + " If scroll is complete, all views should be visited",
+ visited, mAdapter.getItemCount());
+ }
+ }
+ if (DEBUG) {
+ Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
+ }
+ globalPos[0] = globalScrollPosition;
+ }
+ });
+ checkForMainThreadException();
+ // test sanity, ensure scroll happened
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int childCount = mGlm.getChildCount();
+ final BitSet expectedPositions = new BitSet();
+ for (int i = 0; i < childCount; i ++) {
+ expectedPositions.set(mAdapter.getItemCount() - i - 1);
+ }
+ for (int i = 0; i <childCount; i ++) {
+ final View view = mGlm.getChildAt(i);
+ int position = mGlm.getPosition(view);
+ assertTrue("child position should be in last page", expectedPositions.get(position));
+ }
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ int globalScrollPosition = globalPos[0];
+ // now scroll back and make sure global positions match
+ BitSet shouldTest = new BitSet(mAdapter.getItemCount());
+ shouldTest.set(0, mAdapter.getItemCount() - 1, true);
+ String assertPrefix = config
+ + " global pos must match when scrolling in reverse for position ";
+ int scrollAmount = Integer.MAX_VALUE;
+ while (!shouldTest.isEmpty() && scrollAmount != 0) {
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ int pos = mRecyclerView.getChildLayoutPosition(child);
+ if (!shouldTest.get(pos)) {
+ continue;
+ }
+ GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
+ child.getLayoutParams();
+ shouldTest.clear(pos);
+ int globalPos;
+ if (config.mReverseLayout) {
+ globalPos = globalScrollPosition +
+ mGlm.mOrientationHelper.getDecoratedEnd(child);
+ } else {
+ globalPos = globalScrollPosition +
+ mGlm.mOrientationHelper.getDecoratedStart(child);
+ }
+ assertEquals(assertPrefix + pos,
+ globalPositions[pos], globalPos);
+ assertEquals("span index should match",
+ mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
+ lp.getSpanIndex());
+ }
+ scrollAmount = mGlm.scrollBy(-scrollStep,
+ mRecyclerView.mRecycler, mRecyclerView.mState);
+ globalScrollPosition += scrollAmount;
+ }
+ assertTrue("all views should be seen", shouldTest.isEmpty());
+ }
+ });
+ checkForMainThreadException();
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
new file mode 100644
index 0000000..2fc5015
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCachedBordersTest.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerCachedBordersTest extends BaseGridLayoutManagerTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Config> params() {
+ List<Config> testConfigurations = createBaseVariations();
+ testConfigurations.addAll(cachedBordersTestConfigs());
+ return testConfigurations;
+ }
+
+ private final Config mConfig;
+
+ public GridLayoutManagerCachedBordersTest(Config config) {
+ mConfig = config;
+ }
+
+
+ @Test
+ public void gridCachedBorderstTest() throws Throwable {
+ RecyclerView recyclerView = setupBasic(mConfig);
+ waitForFirstLayout(recyclerView);
+ final boolean vertical = mConfig.mOrientation == GridLayoutManager.VERTICAL;
+ final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
+ final int lastVisible = mGlm.findLastVisibleItemPosition();
+ for (int i = 0; i < lastVisible; i += mConfig.mSpanCount) {
+ if ((i + 1) * mConfig.mSpanCount - 1 < lastVisible) {
+ int childrenSizeSum = 0;
+ for (int j = 0; j < mConfig.mSpanCount; j++) {
+ View child = recyclerView.getChildAt(i * mConfig.mSpanCount + j);
+ childrenSizeSum += vertical ? child.getWidth() : child.getHeight();
+ }
+ assertEquals(expectedSizeSum, childrenSizeSum);
+ }
+ }
+ }
+
+ private static List<Config> cachedBordersTestConfigs() {
+ ArrayList<Config> configs = new ArrayList<Config>();
+ final int[] spanCounts = new int[]{88, 279, 741};
+ final int[] spanPerItem = new int[]{11, 9, 13};
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (int i = 0; i < spanCounts.length; i++) {
+ Config config = new Config(spanCounts[i], orientation, reverseLayout);
+ config.mSpanPerItem = spanPerItem[i];
+ configs.add(config);
+ }
+ }
+ }
+ return configs;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCustomSizeInScrollDirectionTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCustomSizeInScrollDirectionTest.java
new file mode 100644
index 0000000..77a0723
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerCustomSizeInScrollDirectionTest.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Rect;
+import android.view.View;
+import android.view.ViewGroup;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerCustomSizeInScrollDirectionTest extends BaseGridLayoutManagerTest {
+ @Parameterized.Parameters(name = "addDecorOffsets:{1} addMargins:{2} config:{0}")
+ public static List<Object[]> getParams() {
+ List<Object[]> params = new ArrayList<>();
+ Boolean[] options = new Boolean[]{true, false};
+ for (boolean addMargins : options) {
+ for (boolean addDecorOffsets : options) {
+ params.add(new Object[] {
+ new Config(3, HORIZONTAL, false), addDecorOffsets, addMargins});
+ params.add(new Object[] {
+ new Config(3, VERTICAL, false), addDecorOffsets, addMargins});
+ }
+ }
+ return params;
+ }
+
+ private final boolean mAddDecorOffsets;
+ private final boolean mAddMargins;
+ private final Config mConfig;
+
+ public GridLayoutManagerCustomSizeInScrollDirectionTest(Config config, boolean addDecorOffsets,
+ boolean addMargins) {
+ mConfig = config;
+ mAddDecorOffsets = addDecorOffsets;
+ mAddMargins = addMargins;
+ }
+
+ @Test
+ public void customSizeInScrollDirectionTest() throws Throwable {
+ final int decorOffset = mAddDecorOffsets ? 7 : 0;
+ final int margin = mAddMargins ? 11 : 0;
+ final int[] sizePerPosition = new int[]{3, 5, 9, 21, 3, 5, 9, 6, 9, 1};
+ final int[] expectedSizePerPosition = new int[]{9, 9, 9, 21, 3, 5, 9, 9, 9, 1};
+
+ final GridTestAdapter testAdapter = new GridTestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)
+ holder.itemView.getLayoutParams();
+ if (layoutParams == null) {
+ layoutParams = new ViewGroup.MarginLayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ holder.itemView.setLayoutParams(layoutParams);
+ }
+ final int size = sizePerPosition[position];
+ if (mConfig.mOrientation == HORIZONTAL) {
+ layoutParams.width = size;
+ layoutParams.leftMargin = margin;
+ layoutParams.rightMargin = margin;
+ } else {
+ layoutParams.height = size;
+ layoutParams.topMargin = margin;
+ layoutParams.bottomMargin = margin;
+ }
+ }
+ };
+ testAdapter.setFullSpan(3, 5);
+ final RecyclerView rv = setupBasic(mConfig, testAdapter);
+ if (mAddDecorOffsets) {
+ rv.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ outRect.set(decorOffset, 0, decorOffset, 0);
+ } else {
+ outRect.set(0, decorOffset, 0, decorOffset);
+ }
+ }
+ });
+ }
+ waitForFirstLayout(rv);
+
+ assertTrue("[test sanity] some views should be laid out",
+ mRecyclerView.getChildCount() > 0);
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ final int size = mConfig.mOrientation == HORIZONTAL ? child.getWidth()
+ : child.getHeight();
+ assertEquals("child " + i + " should have the size specified in its layout params",
+ expectedSizePerPosition[i], size);
+ }
+ checkForMainThreadException();
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.java
new file mode 100644
index 0000000..78f1576
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerRtlTest.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerRtlTest extends BaseGridLayoutManagerTest {
+
+ public GridLayoutManagerRtlTest(Config config, boolean changeRtlAfter, boolean oneLine,
+ boolean itemsWrapContent) {
+ mConfig = config;
+ mChangeRtlAfter = changeRtlAfter;
+ mOneLine = oneLine;
+ mItemsWrapContent = itemsWrapContent;
+ }
+
+ @Parameterized.Parameters(name = "conf: {0} changeRl:{1} oneLine: {2} itemsWrap: {3}")
+ public static List<Object[]> params() {
+ List<Object[]> result = new ArrayList<>();
+ for (boolean changeRtlAfter : new boolean[]{false, true}) {
+ for (boolean oneLine : new boolean[]{false, true}) {
+ for (boolean itemsWrapContent : new boolean[]{false, true}) {
+ for (Config config : createBaseVariations()) {
+ result.add(new Object[] {
+ config,
+ changeRtlAfter,
+ oneLine,
+ itemsWrapContent
+ });
+ }
+ }
+ }
+ }
+ return result;
+ }
+ final Config mConfig;
+ final boolean mChangeRtlAfter;
+ final boolean mOneLine;
+ final boolean mItemsWrapContent;
+
+
+ @Test
+ public void rtlTest() throws Throwable {
+ if (mOneLine && mConfig.mOrientation != VERTICAL) {
+ return;// nothing to test
+ }
+ if (mConfig.mSpanCount == 1) {
+ mConfig.mSpanCount = 2;
+ }
+ String logPrefix = mConfig + ", changeRtlAfterLayout:" + mChangeRtlAfter + ","
+ + "oneLine:" + mOneLine + " itemsWrap:" + mItemsWrapContent;
+ mConfig.mItemCount = 5;
+ if (mOneLine) {
+ mConfig.mSpanCount = mConfig.mItemCount + 1;
+ } else {
+ mConfig.mSpanCount = Math.min(mConfig.mItemCount - 1, mConfig.mSpanCount);
+ }
+
+ RecyclerView rv = setupBasic(mConfig, new GridTestAdapter(mConfig.mItemCount) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (mItemsWrapContent) {
+ ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ if (lp == null) {
+ lp = mGlm.generateDefaultLayoutParams();
+ }
+ if (mConfig.mOrientation == HORIZONTAL) {
+ lp.height = ViewGroup.LayoutParams.WRAP_CONTENT;
+ } else {
+ lp.width = ViewGroup.LayoutParams.WRAP_CONTENT;
+ }
+ }
+ }
+ });
+ if (mChangeRtlAfter) {
+ waitForFirstLayout(rv);
+ mGlm.expectLayout(1);
+ mGlm.setFakeRtl(true);
+ mGlm.waitForLayout(2);
+ } else {
+ mGlm.mFakeRTL = true;
+ waitForFirstLayout(rv);
+ }
+
+ assertEquals("view should become rtl", true, mGlm.isLayoutRTL());
+ OrientationHelper helper = OrientationHelper.createHorizontalHelper(mGlm);
+ View child0 = mGlm.findViewByPosition(0);
+ final int secondChildPos = mConfig.mOrientation == VERTICAL ? 1
+ : mConfig.mSpanCount;
+ View child1 = mGlm.findViewByPosition(secondChildPos);
+ assertNotNull(logPrefix + " child position 0 should be laid out", child0);
+ assertNotNull(
+ logPrefix + " second child position " + (secondChildPos) + " should be laid out",
+ child1);
+ if (mConfig.mOrientation == VERTICAL || !mConfig.mReverseLayout) {
+ assertTrue(logPrefix + " second child should be to the left of first child",
+ helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
+ assertEquals(logPrefix + " first child should be right aligned",
+ helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
+ } else {
+ assertTrue(logPrefix + " first child should be to the left of second child",
+ helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
+ assertEquals(logPrefix + " first child should be left aligned",
+ helper.getDecoratedStart(child0), helper.getStartAfterPadding());
+ }
+ checkForMainThreadException();
+ }
+}
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..b38c551 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -16,78 +16,157 @@
package android.support.v7.widget;
-import android.content.Context;
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.graphics.Color;
import android.graphics.Rect;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.StateListDrawable;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.AccessibilityDelegateCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
import android.test.UiThreadTest;
-import android.util.Log;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.SparseIntArray;
+import android.util.StateSet;
import android.view.View;
import android.view.ViewGroup;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean;
import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
-import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
-public class GridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class GridLayoutManagerTest extends BaseGridLayoutManagerTest {
- static final String TAG = "GridLayoutManagerTest";
+ @Test
+ public void focusSearchFailureUp() throws Throwable {
+ focusSearchFailure(false);
+ }
- static final boolean DEBUG = false;
+ @Test
+ public void focusSearchFailureDown() throws Throwable {
+ focusSearchFailure(true);
+ }
- WrappedGridLayoutManager mGlm;
+ @Test
+ public void scrollToBadOffset() throws Throwable {
+ scrollToBadOffset(false);
+ }
- GridTestAdapter mAdapter;
+ @Test
+ public void scrollToBadOffsetReverse() throws Throwable {
+ scrollToBadOffset(true);
+ }
- final List<Config> mBaseVariations = new ArrayList<Config>();
+ private void scrollToBadOffset(boolean reverseLayout) throws Throwable {
+ final int w = 500;
+ final int h = 1000;
+ RecyclerView recyclerView = setupBasic(new Config(2, 100).reverseLayout(reverseLayout),
+ new GridTestAdapter(100) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
+ if (lp == null) {
+ lp = new ViewGroup.LayoutParams(w / 2, h / 2);
+ holder.itemView.setLayoutParams(lp);
+ } else {
+ lp.width = w / 2;
+ lp.height = h / 2;
+ holder.itemView.setLayoutParams(lp);
+ }
+ }
+ });
+ TestedFrameLayout.FullControlLayoutParams lp
+ = new TestedFrameLayout.FullControlLayoutParams(w, h);
+ recyclerView.setLayoutParams(lp);
+ waitForFirstLayout(recyclerView);
+ mGlm.expectLayout(1);
+ scrollToPosition(11);
+ mGlm.waitForLayout(2);
+ // assert spans and position etc
+ for (int i = 0; i < mGlm.getChildCount(); i++) {
+ View child = mGlm.getChildAt(i);
+ GridLayoutManager.LayoutParams params = (GridLayoutManager.LayoutParams) child
+ .getLayoutParams();
+ assertThat("span index for child at " + i + " with position " + params
+ .getViewAdapterPosition(),
+ params.getSpanIndex(), CoreMatchers.is(params.getViewAdapterPosition() % 2));
+ }
+ // assert spans and positions etc.
+ int lastVisible = mGlm.findLastVisibleItemPosition();
+ // this should be the scrolled child
+ assertThat(lastVisible, CoreMatchers.is(11));
+ }
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
- for (boolean reverseLayout : new boolean[]{false, true}) {
- for (int spanCount : new int[]{1, 3, 4}) {
- mBaseVariations.add(new Config(spanCount, orientation, reverseLayout));
- }
- }
+ private 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, true));
+ assertSame(viewToFocus, recyclerView.getFocusedChild());
+ int pos = 1;
+ View focusedView = viewToFocus;
+ while (pos < 31) {
+ focusSearch(focusedView, scrollDown ? View.FOCUS_DOWN : View.FOCUS_UP);
+ waitForIdleScroll(recyclerView);
+ focusedView = recyclerView.getFocusedChild();
+ assertEquals(Math.min(pos + 3, mAdapter.getItemCount() - 1),
+ recyclerView.getChildViewHolder(focusedView).getAdapterPosition());
+ pos += 3;
}
}
- public RecyclerView setupBasic(Config config) throws Throwable {
- return setupBasic(config, new GridTestAdapter(config.mItemCount));
- }
-
- public RecyclerView setupBasic(Config config, GridTestAdapter testAdapter) throws Throwable {
- RecyclerView recyclerView = new RecyclerView(getActivity());
- mAdapter = testAdapter;
- mGlm = new WrappedGridLayoutManager(getActivity(), config.mSpanCount, config.mOrientation,
- config.mReverseLayout);
- mAdapter.assignSpanSizeLookup(mGlm);
- recyclerView.setAdapter(mAdapter);
- recyclerView.setLayoutManager(mGlm);
- return recyclerView;
- }
-
- public void waitForFirstLayout(RecyclerView recyclerView) throws Throwable {
- mGlm.expectLayout(1);
- setRecyclerView(recyclerView);
- mGlm.waitForLayout(2);
- }
-
@UiThreadTest
- public void testScrollWithoutLayout() throws Throwable {
+ @Test
+ public void scrollWithoutLayout() throws Throwable {
final RecyclerView recyclerView = setupBasic(new Config(3, 100));
mGlm.expectLayout(1);
setRecyclerView(recyclerView);
@@ -95,7 +174,8 @@
recyclerView.scrollBy(0, 10);
}
- public void testScrollWithoutLayoutAfterInvalidate() throws Throwable {
+ @Test
+ public void scrollWithoutLayoutAfterInvalidate() throws Throwable {
final RecyclerView recyclerView = setupBasic(new Config(3, 100));
waitForFirstLayout(recyclerView);
runTestOnUiThread(new Runnable() {
@@ -107,19 +187,23 @@
});
}
- public void testPredictiveSpanLookup1() throws Throwable {
+ @Test
+ public void predictiveSpanLookup1() throws Throwable {
predictiveSpanLookupTest(0, false);
}
- public void testPredictiveSpanLookup2() throws Throwable {
+ @Test
+ public void predictiveSpanLookup2() throws Throwable {
predictiveSpanLookupTest(0, true);
}
- public void testPredictiveSpanLookup3() throws Throwable {
+ @Test
+ public void predictiveSpanLookup3() throws Throwable {
predictiveSpanLookupTest(1, false);
}
- public void testPredictiveSpanLookup4() throws Throwable {
+ @Test
+ public void predictiveSpanLookup4() throws Throwable {
predictiveSpanLookupTest(1, true);
}
@@ -157,145 +241,8 @@
checkForMainThreadException();
}
- public void testCustomWidthInHorizontal() throws Throwable {
- customSizeInScrollDirectionTest(new Config(3, HORIZONTAL, false));
- }
-
- public void testCustomHeightInVertical() throws Throwable {
- customSizeInScrollDirectionTest(new Config(3, VERTICAL, false));
- }
-
- public void customSizeInScrollDirectionTest(final Config config) throws Throwable {
- Boolean[] options = new Boolean[]{true, false};
- for (boolean addMargins : options) {
- for (boolean addDecorOffsets : options) {
- customSizeInScrollDirectionTest(config, addDecorOffsets, addMargins);
- }
- }
- }
-
- public void customSizeInScrollDirectionTest(final Config config, boolean addDecorOffsets,
- boolean addMarigns) throws Throwable {
- final int decorOffset = addDecorOffsets ? 7 : 0;
- final int margin = addMarigns ? 11 : 0;
- final int[] sizePerPosition = new int[]{3, 5, 9, 21, 3, 5, 9, 6, 9, 1};
- final int[] expectedSizePerPosition = new int[]{9, 9, 9, 21, 3, 5, 9, 9, 9, 1};
-
- final GridTestAdapter testAdapter = new GridTestAdapter(10) {
- @Override
- public void onBindViewHolder(TestViewHolder holder,
- int position) {
- super.onBindViewHolder(holder, position);
- ViewGroup.MarginLayoutParams layoutParams = (ViewGroup.MarginLayoutParams)
- holder.itemView.getLayoutParams();
- if (layoutParams == null) {
- layoutParams = new ViewGroup.MarginLayoutParams(
- ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- holder.itemView.setLayoutParams(layoutParams);
- }
- final int size = sizePerPosition[position];
- if (config.mOrientation == HORIZONTAL) {
- layoutParams.width = size;
- layoutParams.leftMargin = margin;
- layoutParams.rightMargin = margin;
- } else {
- layoutParams.height = size;
- layoutParams.topMargin = margin;
- layoutParams.bottomMargin = margin;
- }
- }
- };
- testAdapter.setFullSpan(3, 5);
- final RecyclerView rv = setupBasic(config, testAdapter);
- if (addDecorOffsets) {
- rv.addItemDecoration(new RecyclerView.ItemDecoration() {
- @Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
- RecyclerView.State state) {
- if (config.mOrientation == HORIZONTAL) {
- outRect.set(decorOffset, 0, decorOffset, 0);
- } else {
- outRect.set(0, decorOffset, 0, decorOffset);
- }
- }
- });
- }
- waitForFirstLayout(rv);
-
- assertTrue("[test sanity] some views should be laid out", mRecyclerView.getChildCount() > 0);
- for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
- View child = mRecyclerView.getChildAt(i);
- final int size = config.mOrientation == HORIZONTAL ? child.getWidth()
- : child.getHeight();
- assertEquals("child " + i + " should have the size specified in its layout params",
- expectedSizePerPosition[i], size);
- }
- checkForMainThreadException();
- }
-
- public void testRTL() throws Throwable {
- for (boolean changeRtlAfter : new boolean[]{false, true}) {
- for (boolean oneLine : new boolean[]{false, true}) {
- for (Config config : mBaseVariations) {
- rtlTest(config, changeRtlAfter, oneLine);
- removeRecyclerView();
- }
- }
- }
- }
-
- void rtlTest(Config config, boolean changeRtlAfter, boolean oneLine) throws Throwable {
- if (oneLine && config.mOrientation != VERTICAL) {
- return;// nothing to test
- }
- if (config.mSpanCount == 1) {
- config.mSpanCount = 2;
- }
- String logPrefix = config + ", changeRtlAfterLayout:" + changeRtlAfter + ", oneLine:" + oneLine;
- config.mItemCount = 5;
- if (oneLine) {
- config.mSpanCount = config.mItemCount + 1;
- } else {
- config.mSpanCount = Math.min(config.mItemCount - 1, config.mSpanCount);
- }
-
- RecyclerView rv = setupBasic(config);
- if (changeRtlAfter) {
- waitForFirstLayout(rv);
- mGlm.expectLayout(1);
- mGlm.setFakeRtl(true);
- mGlm.waitForLayout(2);
- } else {
- mGlm.mFakeRTL = true;
- waitForFirstLayout(rv);
- }
-
- assertEquals("view should become rtl", true, mGlm.isLayoutRTL());
- OrientationHelper helper = OrientationHelper.createHorizontalHelper(mGlm);
- View child0 = mGlm.findViewByPosition(0);
- final int secondChildPos = config.mOrientation == VERTICAL ? 1
- : config.mSpanCount;
- View child1 = mGlm.findViewByPosition(secondChildPos);
- assertNotNull(logPrefix + " child position 0 should be laid out", child0);
- assertNotNull(
- logPrefix + " second child position " + (secondChildPos) + " should be laid out",
- child1);
- if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
- assertTrue(logPrefix + " second child should be to the left of first child",
- helper.getDecoratedStart(child0) >= helper.getDecoratedEnd(child1));
- assertEquals(logPrefix + " first child should be right aligned",
- helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
- } else {
- assertTrue(logPrefix + " first child should be to the left of second child",
- helper.getDecoratedStart(child1) >= helper.getDecoratedEnd(child0));
- assertEquals(logPrefix + " first child should be left aligned",
- helper.getDecoratedStart(child0), helper.getStartAfterPadding());
- }
- checkForMainThreadException();
- }
-
- public void testMovingAGroupOffScreenForAddedItems() throws Throwable {
+ @Test
+ public void movingAGroupOffScreenForAddedItems() throws Throwable {
final RecyclerView rv = setupBasic(new Config(3, 100));
final int[] maxId = new int[1];
maxId[0] = -1;
@@ -312,7 +259,7 @@
return 3;
}
});
- ((SimpleItemAnimator)rv.getItemAnimator()).setSupportsChangeAnimations(true);
+ ((SimpleItemAnimator) rv.getItemAnimator()).setSupportsChangeAnimations(true);
waitForFirstLayout(rv);
View lastView = rv.getChildAt(rv.getChildCount() - 1);
final int lastPos = rv.getChildAdapterPosition(lastView);
@@ -329,60 +276,20 @@
}
- public void testCachedBorders() throws Throwable {
- List<Config> testConfigurations = new ArrayList<Config>(mBaseVariations);
- testConfigurations.addAll(cachedBordersTestConfigs());
- for (Config config : testConfigurations) {
- gridCachedBorderstTest(config);
- }
- }
-
- private void gridCachedBorderstTest(Config config) throws Throwable {
- RecyclerView recyclerView = setupBasic(config);
- waitForFirstLayout(recyclerView);
- final boolean vertical = config.mOrientation == GridLayoutManager.VERTICAL;
- final int expectedSizeSum = vertical ? recyclerView.getWidth() : recyclerView.getHeight();
- final int lastVisible = mGlm.findLastVisibleItemPosition();
- for (int i = 0; i < lastVisible; i += config.mSpanCount) {
- if ((i+1)*config.mSpanCount - 1 < lastVisible) {
- int childrenSizeSum = 0;
- for (int j = 0; j < config.mSpanCount; j++) {
- View child = recyclerView.getChildAt(i * config.mSpanCount + j);
- childrenSizeSum += vertical ? child.getWidth() : child.getHeight();
- }
- assertEquals(expectedSizeSum, childrenSizeSum);
- }
- }
- removeRecyclerView();
- }
-
- private List<Config> cachedBordersTestConfigs() {
- ArrayList<Config> configs = new ArrayList<Config>();
- final int [] spanCounts = new int[]{88, 279, 741};
- final int [] spanPerItem = new int[]{11, 9, 13};
- for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
- for (boolean reverseLayout : new boolean[]{false, true}) {
- for (int i = 0 ; i < spanCounts.length; i++) {
- Config config = new Config(spanCounts[i], orientation, reverseLayout);
- config.mSpanPerItem = spanPerItem[i];
- configs.add(config);
- }
- }
- }
- return configs;
- }
-
- public void testLayoutParams() throws Throwable {
+ @Test
+ public void layoutParams() throws Throwable {
layoutParamsTest(GridLayoutManager.HORIZONTAL);
removeRecyclerView();
layoutParamsTest(GridLayoutManager.VERTICAL);
}
- public void testHorizontalAccessibilitySpanIndices() throws Throwable {
+ @Test
+ public void horizontalAccessibilitySpanIndices() throws Throwable {
accessibilitySpanIndicesTest(HORIZONTAL);
}
- public void testVerticalAccessibilitySpanIndices() throws Throwable {
+ @Test
+ public void verticalAccessibilitySpanIndices() throws Throwable {
accessibilitySpanIndicesTest(VERTICAL);
}
@@ -409,10 +316,10 @@
orientation == HORIZONTAL ? itemInfo.getColumnIndex() : itemInfo.getRowIndex());
assertEquals("result should have span index",
ssl.getSpanIndex(position, mGlm.getSpanCount()),
- orientation == HORIZONTAL ? itemInfo.getRowIndex() : itemInfo.getColumnIndex());
+ orientation == HORIZONTAL ? itemInfo.getRowIndex() : itemInfo.getColumnIndex());
assertEquals("result should have span size",
ssl.getSpanSize(position),
- orientation == HORIZONTAL ? itemInfo.getRowSpan() : itemInfo.getColumnSpan());
+ orientation == HORIZONTAL ? itemInfo.getRowSpan() : itemInfo.getColumnSpan());
}
public GridLayoutManager.LayoutParams ensureGridLp(View view) {
@@ -490,14 +397,8 @@
assertEquals(secondRowSize, getSize(mGlm.findViewByPosition(5)));
}
- private int getSize(View view) {
- if (mGlm.getOrientation() == GridLayoutManager.HORIZONTAL) {
- return view.getWidth();
- }
- return view.getHeight();
- }
-
- public void testAnchorUpdate() throws InterruptedException {
+ @Test
+ public void anchorUpdate() throws InterruptedException {
GridLayoutManager glm = new GridLayoutManager(getActivity(), 11);
final GridLayoutManager.SpanSizeLookup spanSizeLookup
= new GridLayoutManager.SpanSizeLookup() {
@@ -517,31 +418,63 @@
RecyclerView.State state = new RecyclerView.State();
mRecyclerView = new RecyclerView(getActivity());
state.mItemCount = 1000;
- glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
assertEquals("gm should keep anchor in first span", 11, glm.mAnchorInfo.mPosition);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+ assertEquals("gm should keep anchor in last span in the row", 20,
+ glm.mAnchorInfo.mPosition);
+
+ glm.mAnchorInfo.mPosition = 5;
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+ assertEquals("gm should keep anchor in last span in the row", 10,
+ glm.mAnchorInfo.mPosition);
+
glm.mAnchorInfo.mPosition = 13;
- glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
assertEquals("gm should move anchor to first span", 11, glm.mAnchorInfo.mPosition);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+ assertEquals("gm should keep anchor in last span in the row", 20,
+ glm.mAnchorInfo.mPosition);
+
glm.mAnchorInfo.mPosition = 23;
- glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
assertEquals("gm should move anchor to first span", 21, glm.mAnchorInfo.mPosition);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+ assertEquals("gm should keep anchor in last span in the row", 25,
+ glm.mAnchorInfo.mPosition);
+
glm.mAnchorInfo.mPosition = 35;
- glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_TAIL);
assertEquals("gm should move anchor to first span", 31, glm.mAnchorInfo.mPosition);
+ glm.onAnchorReady(mRecyclerView.mRecycler, state, glm.mAnchorInfo,
+ LinearLayoutManager.LayoutState.ITEM_DIRECTION_HEAD);
+ assertEquals("gm should keep anchor in last span in the row", 35,
+ glm.mAnchorInfo.mPosition);
}
- public void testSpanLookup() {
+ @Test
+ public void spanLookup() {
spanLookupTest(false);
}
- public void testSpanLookupWithCache() {
+ @Test
+ public void spanLookupWithCache() {
spanLookupTest(true);
}
- public void testSpanLookupCache() {
+ @Test
+ public void spanLookupCache() {
final GridLayoutManager.SpanSizeLookup ssl
= new GridLayoutManager.SpanSizeLookup() {
@Override
@@ -603,24 +536,28 @@
assertEquals(0, ssl.getCachedSpanIndex(8, 5));
}
- public void testRemoveAnchorItem() throws Throwable {
+ @Test
+ public void removeAnchorItem() throws Throwable {
removeAnchorItemTest(
new Config(3, 0).orientation(VERTICAL).reverseLayout(false), 100, 0);
}
- public void testRemoveAnchorItemReverse() throws Throwable {
+ @Test
+ public void removeAnchorItemReverse() throws Throwable {
removeAnchorItemTest(
new Config(3, 0).orientation(VERTICAL).reverseLayout(true), 100,
0);
}
- public void testRemoveAnchorItemHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemHorizontal() throws Throwable {
removeAnchorItemTest(
new Config(3, 0).orientation(HORIZONTAL).reverseLayout(
false), 100, 0);
}
- public void testRemoveAnchorItemReverseHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemReverseHorizontal() throws Throwable {
removeAnchorItemTest(
new Config(3, 0).orientation(HORIZONTAL).reverseLayout(true),
100, 0);
@@ -719,7 +656,8 @@
mRecyclerView.getChildCount() <= childCount + 1 + 3);
}
- public void testSpanGroupIndex() {
+ @Test
+ public void spanGroupIndex() {
final GridLayoutManager.SpanSizeLookup ssl
= new GridLayoutManager.SpanSizeLookup() {
@Override
@@ -742,7 +680,8 @@
assertEquals(2, ssl.getSpanGroupIndex(8, 5));
}
- public void testNotifyDataSetChange() throws Throwable {
+ @Test
+ public void notifyDataSetChange() throws Throwable {
final RecyclerView recyclerView = setupBasic(new Config(3, 100));
final GridLayoutManager.SpanSizeLookup ssl = mGlm.getSpanSizeLookup();
ssl.setSpanIndexCacheEnabled(true);
@@ -770,7 +709,8 @@
checkForMainThreadException();
}
- public void testUnevenHeights() throws Throwable {
+ @Test
+ public void unevenHeights() throws Throwable {
final Map<Integer, RecyclerView.ViewHolder> viewHolderMap =
new HashMap<Integer, RecyclerView.ViewHolder>();
RecyclerView recyclerView = setupBasic(new Config(3, 3), new GridTestAdapter(3) {
@@ -795,7 +735,8 @@
}
}
- public void testUnevenWidths() throws Throwable {
+ @Test
+ public void unevenWidths() throws Throwable {
final Map<Integer, RecyclerView.ViewHolder> viewHolderMap =
new HashMap<Integer, RecyclerView.ViewHolder>();
RecyclerView recyclerView = setupBasic(new Config(3, HORIZONTAL, false),
@@ -821,15 +762,8 @@
}
}
- public void testScrollBackAndPreservePositions() throws Throwable {
- for (Config config : mBaseVariations) {
- config.mItemCount = 150;
- scrollBackAndPreservePositionsTest(config);
- removeRecyclerView();
- }
- }
-
- public void testSpanSizeChange() throws Throwable {
+ @Test
+ public void spanSizeChange() throws Throwable {
final RecyclerView rv = setupBasic(new Config(3, 100));
waitForFirstLayout(rv);
assertTrue(mGlm.supportsPredictiveItemAnimations());
@@ -841,7 +775,6 @@
assertFalse(mGlm.supportsPredictiveItemAnimations());
}
});
- checkForMainThreadException();
mGlm.waitForLayout(2);
mGlm.expectLayout(2);
mAdapter.deleteAndNotify(3, 2);
@@ -849,7 +782,8 @@
assertTrue(mGlm.supportsPredictiveItemAnimations());
}
- public void testCacheSpanIndices() throws Throwable {
+ @Test
+ public void cacheSpanIndices() throws Throwable {
final RecyclerView rv = setupBasic(new Config(3, 100));
mGlm.mSpanSizeLookup.setSpanIndexCacheEnabled(true);
waitForFirstLayout(rv);
@@ -864,277 +798,4 @@
assertEquals("item index 5 should be in span 2", 0,
getLp(mGlm.findViewByPosition(5)).getSpanIndex());
}
-
- GridLayoutManager.LayoutParams getLp(View view) {
- return (GridLayoutManager.LayoutParams) view.getLayoutParams();
- }
-
- public void scrollBackAndPreservePositionsTest(final Config config) throws Throwable {
- final RecyclerView rv = setupBasic(config);
- for (int i = 1; i < mAdapter.getItemCount(); i += config.mSpanCount + 2) {
- mAdapter.setFullSpan(i);
- }
- waitForFirstLayout(rv);
- final int[] globalPositions = new int[mAdapter.getItemCount()];
- Arrays.fill(globalPositions, Integer.MIN_VALUE);
- final int scrollStep = (mGlm.mOrientationHelper.getTotalSpace() / 20)
- * (config.mReverseLayout ? -1 : 1);
- final String logPrefix = config.toString();
- final int[] globalPos = new int[1];
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- assertSame("test sanity", mRecyclerView, rv);
- int globalScrollPosition = 0;
- int visited = 0;
- while (visited < mAdapter.getItemCount()) {
- for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
- View child = mRecyclerView.getChildAt(i);
- final int pos = mRecyclerView.getChildLayoutPosition(child);
- if (globalPositions[pos] != Integer.MIN_VALUE) {
- continue;
- }
- visited++;
- GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
- child.getLayoutParams();
- if (config.mReverseLayout) {
- globalPositions[pos] = globalScrollPosition +
- mGlm.mOrientationHelper.getDecoratedEnd(child);
- } else {
- globalPositions[pos] = globalScrollPosition +
- mGlm.mOrientationHelper.getDecoratedStart(child);
- }
- assertEquals(logPrefix + " span index should match",
- mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
- lp.getSpanIndex());
- }
- int scrolled = mGlm.scrollBy(scrollStep,
- mRecyclerView.mRecycler, mRecyclerView.mState);
- globalScrollPosition += scrolled;
- if (scrolled == 0) {
- assertEquals(
- logPrefix + " If scroll is complete, all views should be visited",
- visited, mAdapter.getItemCount());
- }
- }
- if (DEBUG) {
- Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
- }
- globalPos[0] = globalScrollPosition;
- }
- });
- checkForMainThreadException();
- // test sanity, ensure scroll happened
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- final int childCount = mGlm.getChildCount();
- final BitSet expectedPositions = new BitSet();
- for (int i = 0; i < childCount; i ++) {
- expectedPositions.set(mAdapter.getItemCount() - i - 1);
- }
- for (int i = 0; i <childCount; i ++) {
- final View view = mGlm.getChildAt(i);
- int position = mGlm.getPosition(view);
- assertTrue("child position should be in last page", expectedPositions.get(position));
- }
- }
- });
- getInstrumentation().waitForIdleSync();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- int globalScrollPosition = globalPos[0];
- // now scroll back and make sure global positions match
- BitSet shouldTest = new BitSet(mAdapter.getItemCount());
- shouldTest.set(0, mAdapter.getItemCount() - 1, true);
- String assertPrefix = config
- + " global pos must match when scrolling in reverse for position ";
- int scrollAmount = Integer.MAX_VALUE;
- while (!shouldTest.isEmpty() && scrollAmount != 0) {
- for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
- View child = mRecyclerView.getChildAt(i);
- int pos = mRecyclerView.getChildLayoutPosition(child);
- if (!shouldTest.get(pos)) {
- continue;
- }
- GridLayoutManager.LayoutParams lp = (GridLayoutManager.LayoutParams)
- child.getLayoutParams();
- shouldTest.clear(pos);
- int globalPos;
- if (config.mReverseLayout) {
- globalPos = globalScrollPosition +
- mGlm.mOrientationHelper.getDecoratedEnd(child);
- } else {
- globalPos = globalScrollPosition +
- mGlm.mOrientationHelper.getDecoratedStart(child);
- }
- assertEquals(assertPrefix + pos,
- globalPositions[pos], globalPos);
- assertEquals("span index should match",
- mGlm.getSpanSizeLookup().getSpanIndex(pos, mGlm.getSpanCount()),
- lp.getSpanIndex());
- }
- scrollAmount = mGlm.scrollBy(-scrollStep,
- mRecyclerView.mRecycler, mRecyclerView.mState);
- globalScrollPosition += scrollAmount;
- }
- assertTrue("all views should be seen", shouldTest.isEmpty());
- }
- });
- checkForMainThreadException();
- }
-
- class WrappedGridLayoutManager extends GridLayoutManager {
-
- CountDownLatch mLayoutLatch;
-
- List<Callback> mCallbacks = new ArrayList<Callback>();
-
- Boolean mFakeRTL;
-
- public WrappedGridLayoutManager(Context context, int spanCount) {
- super(context, spanCount);
- }
-
- public WrappedGridLayoutManager(Context context, int spanCount, int orientation,
- boolean reverseLayout) {
- super(context, spanCount, orientation, reverseLayout);
- }
-
- @Override
- protected boolean isLayoutRTL() {
- return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
- }
-
- public void setFakeRtl(Boolean fakeRtl) {
- mFakeRTL = fakeRtl;
- try {
- requestLayoutOnUIThread(mRecyclerView);
- } catch (Throwable throwable) {
- postExceptionToInstrumentation(throwable);
- }
- }
-
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- try {
- for (Callback callback : mCallbacks) {
- callback.onBeforeLayout(recycler, state);
- }
- super.onLayoutChildren(recycler, state);
- for (Callback callback : mCallbacks) {
- callback.onAfterLayout(recycler, state);
- }
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
- mLayoutLatch.countDown();
- }
-
- @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 void expectLayout(int layoutCount) {
- mLayoutLatch = new CountDownLatch(layoutCount);
- }
-
- public void waitForLayout(int seconds) throws InterruptedException {
- mLayoutLatch.await(seconds, SECONDS);
- }
- }
-
- class Config {
-
- int mSpanCount;
- int mOrientation = GridLayoutManager.VERTICAL;
- int mItemCount = 1000;
- int mSpanPerItem = 1;
- boolean mReverseLayout = false;
-
- Config(int spanCount, int itemCount) {
- mSpanCount = spanCount;
- mItemCount = itemCount;
- }
-
- public Config(int spanCount, int orientation, boolean reverseLayout) {
- mSpanCount = spanCount;
- mOrientation = orientation;
- mReverseLayout = reverseLayout;
- }
-
- Config orientation(int orientation) {
- mOrientation = orientation;
- return this;
- }
-
- @Override
- public String toString() {
- return "Config{" +
- "mSpanCount=" + mSpanCount +
- ", mOrientation=" + (mOrientation == GridLayoutManager.HORIZONTAL ? "h" : "v") +
- ", mItemCount=" + mItemCount +
- ", mReverseLayout=" + mReverseLayout +
- '}';
- }
-
- public Config reverseLayout(boolean reverseLayout) {
- mReverseLayout = reverseLayout;
- return this;
- }
-
-
- }
-
- class GridTestAdapter extends TestAdapter {
-
- Set<Integer> mFullSpanItems = new HashSet<Integer>();
- int mSpanPerItem = 1;
-
- GridTestAdapter(int count) {
- super(count);
- }
-
- GridTestAdapter(int count, int spanPerItem) {
- super(count);
- mSpanPerItem = spanPerItem;
- }
-
- void setFullSpan(int... items) {
- for (int i : items) {
- mFullSpanItems.add(i);
- }
- }
-
- void assignSpanSizeLookup(final GridLayoutManager glm) {
- glm.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
- @Override
- public int getSpanSize(int position) {
- return mFullSpanItems.contains(position) ? glm.getSpanCount() : mSpanPerItem;
- }
- });
- }
- }
-
- class Callback {
-
- public void onBeforeLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
- }
-
- public void onAfterLayout(RecyclerView.Recycler recycler, RecyclerView.State state) {
- }
- }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..a96b2f6
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentTest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.AspectRatioMeasureBehavior;
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.MeasureBehavior;
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.WrapContentAdapter;
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.app.Activity;
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class GridLayoutManagerWrapContentTest extends BaseWrapContentTest {
+ private boolean mHorizontal = false;
+ private int mSpanCount = 3;
+ private RecyclerView.ItemDecoration mItemDecoration;
+ public GridLayoutManagerWrapContentTest(Rect padding) {
+ super(new WrapContentConfig(false, false, padding));
+ }
+
+ @Parameterized.Parameters(name = "paddingRect={0}")
+ public static List<Rect> params() {
+ return Arrays.asList(
+ new Rect(0, 0, 0, 0),
+ new Rect(5, 0, 0, 0),
+ new Rect(0, 3, 0, 0),
+ new Rect(0, 0, 2, 0),
+ new Rect(0, 0, 0, 7),
+ new Rect(3, 5, 7, 11)
+ );
+ }
+
+ @Override
+ RecyclerView.LayoutManager createLayoutManager() {
+ GridLayoutManager lm = new GridLayoutManager(getActivity(), mSpanCount);
+ lm.setOrientation(mHorizontal ? HORIZONTAL : VERTICAL);
+ return lm;
+ }
+
+ @Override
+ protected WrappedRecyclerView createRecyclerView(Activity activity) {
+ WrappedRecyclerView recyclerView = super.createRecyclerView(activity);
+ if (mItemDecoration != null) {
+ recyclerView.addItemDecoration(mItemDecoration);
+ }
+ return recyclerView;
+ }
+
+ @Test
+ public void testUnspecifiedWithHint() throws Throwable {
+ unspecifiedWithHintTest(mHorizontal);
+ }
+
+ @Test
+ public void testVerticalWithItemDecors() throws Throwable {
+ mItemDecoration = new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.set(0, 5, 0, 10);
+ }
+ };
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, WRAP_CONTENT, MATCH_PARENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 25)
+ };
+ layoutAndCheck(lp, adapter, expected, 30, 25);
+ }
+
+ @Test
+ public void testHorizontalWithItemDecors() throws Throwable {
+ mItemDecoration = new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.set(5, 0, 10, 0);
+ }
+ };
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, MATCH_PARENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 25, 10)
+ };
+ layoutAndCheck(lp, adapter, expected, 75, 10);
+ }
+
+ @Test
+ public void testHorizontal() throws Throwable {
+ mHorizontal = true;
+ mSpanCount = 2;
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 10),
+ new Rect(0, 10, 10, 20),
+ new Rect(10, 0, 30, 10),
+ new Rect(10, 10, 30, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 30, 20);
+ }
+
+ @Test
+ public void testHandleSecondLineChangingBorders() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 10),
+ new Rect(20, 0, 30, 10),
+ new Rect(40, 0, 50, 10),
+ new Rect(0, 10, 20, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 60, 20);
+ }
+
+ @Test
+ public void testSecondLineAffectingBordersWithAspectRatio() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new AspectRatioMeasureBehavior(10, 5, MATCH_PARENT, WRAP_CONTENT)
+ .aspectRatio(HORIZONTAL, .5f),
+ new MeasureBehavior(10, 5, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 5, MATCH_PARENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 20, 10),
+ new Rect(20, 0, 30, 10),
+ new Rect(40, 0, 60, 10),
+ new Rect(0, 10, 20, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 60, 20);
+ }
+
+ @Override
+ protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+ return Gravity.TOP;
+ }
+
+ @Override
+ protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+ return Gravity.LEFT;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..d33611c
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerWrapContentWithAspectRatioTest.java
@@ -0,0 +1,354 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Color;
+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;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static android.support.v7.widget.BaseWrapContentTest.WrapContentConfig;
+import static android.support.v7.widget.GridLayoutManagerTest.Config;
+import static org.hamcrest.CoreMatchers.*;
+import static android.view.View.MeasureSpec.UNSPECIFIED;
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class GridLayoutManagerWrapContentWithAspectRatioTest
+ extends BaseWrapContentWithAspectRatioTest {
+
+ @Parameterized.Parameters(name = "{0} {1} {2}")
+ public static List<Object[]> params() {
+ List<Object[]> params = new ArrayList<>();
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (boolean unlimitedW : new boolean[]{true, false}) {
+ for (boolean unlimitedH : new boolean[]{true, false}) {
+ for (int behavior1Size : new int[]{8, 10, 12}) {
+ params.add(new Object[]{
+ new WrapContentConfig(unlimitedW, unlimitedH),
+ new Config(3, orientation, reverseLayout),
+ behavior1Size
+ });
+ }
+
+ }
+ }
+
+ }
+ }
+ return params;
+ }
+
+ private GridLayoutManagerTest.Config mConfig;
+
+ private int mBehavior1Size;
+
+ int mTestOrientation;
+
+ boolean mUnlimited;
+
+ RecyclerView.LayoutManager mLayoutManager;
+
+ BaseWrapContentTest.WrappedRecyclerView mRecyclerView;
+
+ OrientationHelper mHelper;
+
+ public GridLayoutManagerWrapContentWithAspectRatioTest(WrapContentConfig wrapContentConfig,
+ GridLayoutManagerTest.Config config, int behavior1Size) {
+ super(wrapContentConfig);
+ mConfig = config;
+ mBehavior1Size = behavior1Size;
+ }
+
+ @Before
+ public final void init() {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ if (mConfig.mOrientation == VERTICAL) {
+ mTestOrientation = HORIZONTAL;
+ mUnlimited = lp.wSpec != null;
+ } else {
+ mTestOrientation = VERTICAL;
+ mUnlimited = lp.hSpec != null;
+ }
+ mLayoutManager = createFromConfig();
+
+ mRecyclerView = new BaseWrapContentTest.WrappedRecyclerView(getActivity());
+ mHelper = OrientationHelper.createOrientationHelper(
+ mLayoutManager, 1 - mConfig.mOrientation);
+
+ mRecyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.setLayoutParams(lp);
+ }
+
+ @Test
+ public void testChildWithMultipleSpans() throws Throwable {
+ MeasureBehavior behavior1;
+ behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size,
+ mTestOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+ mTestOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT);
+
+ MeasureBehavior behavior2 = new MeasureBehavior(
+ mTestOrientation == HORIZONTAL ? 30 : 10,
+ mTestOrientation == VERTICAL ? 30 : 10, WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+ ((GridLayoutManager) mLayoutManager).setSpanSizeLookup(
+ new GridLayoutManager.SpanSizeLookup() {
+ @Override
+ public int getSpanSize(int position) {
+ return position == 1 ? 2 : 1;
+ }
+ });
+ mRecyclerView.setAdapter(adapter);
+ setRecyclerView(mRecyclerView);
+ mRecyclerView.waitUntilLayout();
+
+ final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+
+ if (mUnlimited) {
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ } else {
+ int[] borders = GridLayoutManager.calculateItemBorders(null,
+ mConfig.mSpanCount, parentSize);
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[3] - borders[1], AT_MOST));
+ }
+ // child0 should be measured again because it measured its size as 10
+ assertThat(behavior1.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(15, EXACTLY));
+ assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ switch (mBehavior1Size) {
+ case 10:
+ assertThat(behavior1.measureSpecs.size(), is(2));
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ break;
+ case 8:
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ assertThat(behavior1.measureSpecs.size(), is(3));
+ assertThat(behavior1.getSpec(2, mTestOrientation),
+ MeasureSpecMatcher.is(15, EXACTLY));
+ assertThat(behavior1.getSpec(2, mConfig.mOrientation),
+ MeasureSpecMatcher.is(10, EXACTLY));
+ break;
+ case 12:
+ assertThat(behavior1.measureSpecs.size(), is(2));
+ assertThat(behavior2.measureSpecs.size(), is(2));
+ assertThat(behavior2.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(30, AT_MOST));
+ assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.is(12, EXACTLY));
+ break;
+ }
+
+ View child0 = mRecyclerView.getChildAt(0);
+ assertThat(getSize(child0, mTestOrientation), is(15));
+
+ View child1 = mRecyclerView.getChildAt(1);
+ assertThat(getSize(child1, mTestOrientation), is(30));
+
+ assertThat(mHelper.getDecoratedStart(child0), is(0));
+ assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+ assertThat(mHelper.getDecoratedEnd(child0), is(15));
+ assertThat(mHelper.getDecoratedEnd(child1), is(45));
+
+ assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+ is(Math.max(10, mBehavior1Size)));
+ assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+ is(Math.max(10, mBehavior1Size)));
+
+ assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+ assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+ }
+
+ @Test
+ public void testChildWithMatchParentInOtherDirection() throws Throwable {
+ MeasureBehavior behavior1;
+ behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size,
+ mTestOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+ mTestOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT);
+
+ MeasureBehavior behavior2 = new MeasureBehavior(
+ mTestOrientation == HORIZONTAL ? 15 : 10,
+ mTestOrientation == VERTICAL ? 15 : 10, WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+
+ mRecyclerView.setAdapter(adapter);
+ setRecyclerView(mRecyclerView);
+ mRecyclerView.waitUntilLayout();
+ final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+ if (mUnlimited) {
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ } else {
+ int[] borders = GridLayoutManager.calculateItemBorders(null, mConfig.mSpanCount,
+ parentSize);
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[2] - borders[1], AT_MOST));
+ }
+ // child0 should be measured again because it measured its size as 10
+ assertThat(behavior1.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(15, EXACTLY));
+ assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ switch (mBehavior1Size) {
+ case 10:
+ assertThat(behavior1.measureSpecs.size(), is(2));
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ break;
+ case 8:
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ assertThat(behavior1.measureSpecs.size(), is(3));
+ assertThat(behavior1.getSpec(2, mTestOrientation),
+ MeasureSpecMatcher.is(15, EXACTLY));
+ assertThat(behavior1.getSpec(2, mConfig.mOrientation),
+ MeasureSpecMatcher.is(10, EXACTLY));
+ break;
+ case 12:
+ assertThat(behavior1.measureSpecs.size(), is(2));
+ assertThat(behavior2.measureSpecs.size(), is(2));
+ assertThat(behavior2.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(15, AT_MOST));
+ assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.is(12, EXACTLY));
+ break;
+ }
+
+ View child0 = mRecyclerView.getChildAt(0);
+ assertThat(getSize(child0, mTestOrientation), is(15));
+
+ View child1 = mRecyclerView.getChildAt(1);
+ assertThat(getSize(child1, mTestOrientation), is(15));
+
+ assertThat(mHelper.getDecoratedStart(child0), is(0));
+ assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+ assertThat(mHelper.getDecoratedEnd(child0), is(15));
+ assertThat(mHelper.getDecoratedEnd(child1), is(30));
+
+ assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+ is(Math.max(10, mBehavior1Size)));
+ assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+ is(Math.max(10, mBehavior1Size)));
+
+ assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+ assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+ }
+
+ @Test
+ public void testAllChildrenWrapContentInOtherDirection() throws Throwable {
+ MeasureBehavior behavior1;
+ behavior1 = new MeasureBehavior(mBehavior1Size, mBehavior1Size, WRAP_CONTENT, WRAP_CONTENT);
+
+ MeasureBehavior behavior2 = new MeasureBehavior(
+ mTestOrientation == HORIZONTAL ? 15 : 10,
+ mTestOrientation == VERTICAL ? 15 : 10, WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(behavior1, behavior2);
+
+ mRecyclerView.setAdapter(adapter);
+ setRecyclerView(mRecyclerView);
+ mRecyclerView.waitUntilLayout();
+ final int parentSize = getSize((View) mRecyclerView.getParent(), mTestOrientation);
+ if (mUnlimited) {
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.mode(UNSPECIFIED));
+ } else {
+ int[] borders = GridLayoutManager.calculateItemBorders(null,
+ mConfig.mSpanCount, parentSize);
+ assertThat(behavior1.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[1] - borders[0], AT_MOST));
+ assertThat(behavior2.getSpec(0, mTestOrientation),
+ MeasureSpecMatcher.is(borders[2] - borders[1], AT_MOST));
+ }
+
+ switch (mBehavior1Size) {
+ case 10:
+ assertThat(behavior1.measureSpecs.size(), is(1));
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ break;
+ case 8:
+ assertThat(behavior2.measureSpecs.size(), is(1));
+ assertThat(behavior1.measureSpecs.size(), is(2));
+ assertThat(behavior1.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(15, AT_MOST));
+ assertThat(behavior1.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.is(10, EXACTLY));
+ break;
+ case 12:
+ assertThat(behavior1.measureSpecs.size(), is(1));
+ assertThat(behavior2.measureSpecs.size(), is(2));
+ assertThat(behavior2.getSpec(1, mTestOrientation),
+ MeasureSpecMatcher.is(15, AT_MOST));
+ assertThat(behavior2.getSpec(1, mConfig.mOrientation),
+ MeasureSpecMatcher.is(12, EXACTLY));
+ break;
+ }
+
+ View child0 = mRecyclerView.getChildAt(0);
+ assertThat(getSize(child0, mTestOrientation), is(mBehavior1Size));
+
+ View child1 = mRecyclerView.getChildAt(1);
+ assertThat(getSize(child1, mTestOrientation), is(15));
+
+ assertThat(mHelper.getDecoratedStart(child0), is(0));
+ assertThat(mHelper.getDecoratedStart(child1), is(15));
+
+ assertThat(mHelper.getDecoratedEnd(child0), is(mBehavior1Size));
+ assertThat(mHelper.getDecoratedEnd(child1), is(30));
+
+ assertThat(mHelper.getDecoratedMeasurementInOther(child0),
+ is(Math.max(10, mBehavior1Size)));
+ assertThat(mHelper.getDecoratedMeasurementInOther(child1),
+ is(Math.max(10, mBehavior1Size)));
+
+ assertThat(getSize(mRecyclerView, mTestOrientation), is(45));
+ assertThat(getSize(mRecyclerView, 1 - mTestOrientation), is(Math.max(10, mBehavior1Size)));
+ }
+
+ private RecyclerView.LayoutManager createFromConfig() {
+ return new GridLayoutManager(getActivity(), mConfig.mSpanCount,
+ mConfig.mOrientation, mConfig.mReverseLayout);
+ }
+}
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 5733e720..15bbf8c 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ItemAnimatorV2ApiTest.java
@@ -15,8 +15,29 @@
*/
package android.support.v7.widget;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_CHANGED;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_MOVED;
+import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_REMOVED;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashMap;
@@ -24,27 +45,26 @@
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
-import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_CHANGED;
-import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_MOVED;
-import static android.support.v7.widget.RecyclerView.ItemAnimator.FLAG_REMOVED;
-
/**
* Includes tests for the new RecyclerView animations API (v2).
*/
+@MediumTest
+@RunWith(AndroidJUnit4.class)
public class ItemAnimatorV2ApiTest extends BaseRecyclerViewAnimationsTest {
@Override
protected RecyclerView.ItemAnimator createItemAnimator() {
return mAnimator;
}
- public void testChangeMovedOutside() throws Throwable {
+ @Test
+ public void changeMovedOutside() throws Throwable {
setupBasic(10);
final RecyclerView.ViewHolder target = mRecyclerView.findViewHolderForAdapterPosition(9);
mLayoutManager.expectLayouts(2);
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
mTestAdapter.changeAndNotify(9, 1);
mLayoutManager.waitForLayout(2);
- // changed item shold not be laid out and should just receive disappear
+ // changed item should not be laid out and should just receive disappear
LoggingInfo pre = mAnimator.preLayoutInfoMap.get(target);
assertNotNull("test sanity", pre);
assertNull("test sanity", mAnimator.postLayoutInfoMap.get(target));
@@ -57,7 +77,116 @@
// I don't think we can do much better since other option is to bind a fresh view
}
- public void testSimpleAdd() throws Throwable {
+ @Test
+ public void changeMovedOutsideWithPredictiveAndTwoViewHolders() throws Throwable {
+ final RecyclerView.ViewHolder[] targets = new RecyclerView.ViewHolder[2];
+
+ setupBasic(10, 0, 10, new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (position == 0) {
+ if (targets[0] == null) {
+ targets[0] = holder;
+ } else {
+ assertThat(targets[1], CoreMatchers.nullValue());
+ targets[1] = holder;
+ }
+ }
+ }
+ });
+ final RecyclerView.ViewHolder singleItemTarget =
+ mRecyclerView.findViewHolderForAdapterPosition(1);
+ mAnimator.canReUseCallback = new CanReUseCallback() {
+ @Override
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
+ return viewHolder == singleItemTarget;
+ }
+ };
+ mLayoutManager.expectLayouts(2);
+ mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+ @Override
+ void onLayoutChildren(RecyclerView.Recycler recycler,
+ AnimationLayoutManager lm, RecyclerView.State state) {
+ super.onLayoutChildren(recycler, lm, state);
+ if (!state.isPreLayout()) {
+ mLayoutManager.addDisappearingView(recycler.getViewForPosition(0));
+ mLayoutManager.addDisappearingView(recycler.getScrapList().get(0).itemView);
+ }
+ }
+ };
+ mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 8;
+ mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 2;
+ mTestAdapter.changeAndNotify(0, 2);
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ final RecyclerView.ViewHolder oldTarget = targets[0];
+ final RecyclerView.ViewHolder newTarget = targets[1];
+ assertNotNull("test sanity", targets[0]);
+ assertNotNull("test sanity", targets[1]);
+ // changed item should not be laid out and should just receive disappear
+ LoggingInfo pre = mAnimator.preLayoutInfoMap.get(oldTarget);
+ assertNotNull("test sanity", pre);
+ assertNull("test sanity", mAnimator.postLayoutInfoMap.get(oldTarget));
+
+ assertNull("test sanity", mAnimator.preLayoutInfoMap.get(newTarget));
+ LoggingInfo post = mAnimator.postLayoutInfoMap.get(newTarget);
+ assertNotNull("test sanity", post);
+ assertEquals(1, mAnimator.animateChangeList.size());
+ assertEquals(1, mAnimator.animateDisappearanceList.size());
+
+ assertEquals(new AnimateChange(oldTarget, newTarget, pre, post),
+ mAnimator.animateChangeList.get(0));
+
+ LoggingInfo singleItemPre = mAnimator.preLayoutInfoMap.get(singleItemTarget);
+ assertNotNull("test sanity", singleItemPre);
+ LoggingInfo singleItemPost = mAnimator.postLayoutInfoMap.get(singleItemTarget);
+ assertNotNull("test sanity", singleItemPost);
+
+ assertEquals(new AnimateDisappearance(singleItemTarget, singleItemPre, singleItemPost),
+ mAnimator.animateDisappearanceList.get(0));
+ }
+ @Test
+ public void changeMovedOutsideWithPredictive() throws Throwable {
+ setupBasic(10);
+ final RecyclerView.ViewHolder target = mRecyclerView.findViewHolderForAdapterPosition(0);
+ mLayoutManager.expectLayouts(2);
+ mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+ @Override
+ void onLayoutChildren(RecyclerView.Recycler recycler,
+ AnimationLayoutManager lm, RecyclerView.State state) {
+ super.onLayoutChildren(recycler, lm, state);
+ List<RecyclerView.ViewHolder> scrapList = recycler.getScrapList();
+ assertThat(scrapList.size(), CoreMatchers.is(2));
+ mLayoutManager.addDisappearingView(scrapList.get(0).itemView);
+ mLayoutManager.addDisappearingView(scrapList.get(0).itemView);
+ }
+ };
+ mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 8;
+ mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 2;
+ mTestAdapter.changeAndNotify(0, 2);
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ // changed item should not be laid out and should just receive disappear
+ LoggingInfo pre = mAnimator.preLayoutInfoMap.get(target);
+ assertNotNull("test sanity", pre);
+ LoggingInfo postInfo = mAnimator.postLayoutInfoMap.get(target);
+ assertNotNull("test sanity", postInfo);
+ assertTrue(mAnimator.animateChangeList.isEmpty());
+ assertEquals(2, mAnimator.animateDisappearanceList.size());
+ try {
+ assertEquals(new AnimateDisappearance(target, pre, postInfo),
+ mAnimator.animateDisappearanceList.get(0));
+ } catch (Throwable t) {
+ assertEquals(new AnimateDisappearance(target, pre, postInfo),
+ mAnimator.animateDisappearanceList.get(1));
+ }
+
+ }
+
+ @Test
+ public void simpleAdd() throws Throwable {
setupBasic(10);
mLayoutManager.expectLayouts(2);
mTestAdapter.addAndNotify(2, 1);
@@ -80,7 +209,8 @@
checkForMainThreadException();
}
- public void testSimpleRemove() throws Throwable {
+ @Test
+ public void simpleRemove() throws Throwable {
setupBasic(10);
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
mLayoutManager.expectLayouts(2);
@@ -104,7 +234,8 @@
checkForMainThreadException();
}
- public void testSimpleUpdate() throws Throwable {
+ @Test
+ public void simpleUpdate() throws Throwable {
setupBasic(10);
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
mLayoutManager.expectLayouts(2);
@@ -129,12 +260,13 @@
checkForMainThreadException();
}
- public void testUpdateWithDuplicateViewHolder() throws Throwable {
+ @Test
+ public void updateWithDuplicateViewHolder() throws Throwable {
setupBasic(10);
final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
mAnimator.canReUseCallback = new CanReUseCallback() {
@Override
- public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
assertSame(viewHolder, vh);
return false;
}
@@ -165,13 +297,14 @@
checkForMainThreadException();
}
- public void testUpdateWithOneDuplicateAndOneInPlace() throws Throwable {
+ @Test
+ public void updateWithOneDuplicateAndOneInPlace() throws Throwable {
setupBasic(10);
final RecyclerView.ViewHolder replaced = mRecyclerView.findViewHolderForAdapterPosition(2);
final RecyclerView.ViewHolder reused = mRecyclerView.findViewHolderForAdapterPosition(3);
mAnimator.canReUseCallback = new CanReUseCallback() {
@Override
- public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
if (viewHolder == replaced) {
return false;
} else if (viewHolder == reused) {
@@ -228,7 +361,8 @@
checkForMainThreadException();
}
- public void testChangeToDisappear() throws Throwable {
+ @Test
+ public void changeToDisappear() throws Throwable {
setupBasic(10);
RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(9);
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
@@ -246,11 +380,41 @@
checkForMainThreadException();
}
- public void testUpdatePayload() throws Throwable {
+ @Test
+ public void changeToDisappearFromHead() throws Throwable {
setupBasic(10);
- RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+ RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(0);
+ mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 9;
+ mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
mLayoutManager.expectLayouts(2);
- Object payload = new Object();
+ mTestAdapter.changeAndNotify(0, 1);
+ mLayoutManager.waitForLayout(2);
+ assertEquals(1, mAnimator.animateDisappearanceList.size());
+ AnimateDisappearance log = mAnimator.animateDisappearanceList.get(0);
+ assertSame(vh, log.viewHolder);
+ assertFalse(mAnimator.postLayoutInfoMap.containsKey(vh));
+ assertEquals(FLAG_CHANGED, log.preInfo.changeFlags);
+ assertEquals(0, mAnimator.animateChangeList.size());
+ assertEquals(0, mAnimator.animateAppearanceList.size());
+ assertEquals(9, mAnimator.animatePersistenceList.size());
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void updatePayload() throws Throwable {
+ setupBasic(10);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(2);
+ final Object payload = new Object();
+ mAnimator.canReUseCallback = new CanReUseCallback() {
+ @Override
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
+ assertSame(vh, viewHolder);
+ assertEquals(1, payloads.size());
+ assertSame(payload, payloads.get(0));
+ return true;
+ }
+ };
+ mLayoutManager.expectLayouts(2);
mTestAdapter.changeAndNotifyWithPayload(2, 1, payload);
mLayoutManager.waitForLayout(2);
assertEquals(1, mAnimator.animateChangeList.size());
@@ -274,7 +438,8 @@
checkForMainThreadException();
}
- public void testNotifyDataSetChanged() throws Throwable {
+ @Test
+ public void notifyDataSetChanged() throws Throwable {
TestAdapter adapter = new TestAdapter(10);
adapter.setHasStableIds(true);
setupBasic(10, 0, 10, adapter);
@@ -292,7 +457,8 @@
assertEquals(0, mAnimator.animateDisappearanceList.size());
}
- public void testNotifyDataSetChangedWithoutStableIds() throws Throwable {
+ @Test
+ public void notifyDataSetChangedWithoutStableIds() throws Throwable {
TestAdapter adapter = new TestAdapter(10);
adapter.setHasStableIds(false);
setupBasic(10, 0, 10, adapter);
@@ -305,11 +471,13 @@
assertEquals(0, mAnimator.animateDisappearanceList.size());
}
- public void testNotifyDataSetChangedWithAppearing() throws Throwable {
+ @Test
+ public void notifyDataSetChangedWithAppearing() throws Throwable {
notifyDataSetChangedWithAppearing(false);
}
- public void testNotifyDataSetChangedWithAppearingNotifyBoth() throws Throwable {
+ @Test
+ public void notifyDataSetChangedWithAppearingNotifyBoth() throws Throwable {
notifyDataSetChangedWithAppearing(true);
}
@@ -342,11 +510,13 @@
assertEquals(0, mAnimator.animateDisappearanceList.size());
}
- public void testNotifyDataSetChangedWithDispappearing() throws Throwable {
+ @Test
+ public void notifyDataSetChangedWithDispappearing() throws Throwable {
notifyDataSetChangedWithDispappearing(false);
}
- public void testNotifyDataSetChangedWithDispappearingNotifyBoth() throws Throwable {
+ @Test
+ public void notifyDataSetChangedWithDispappearingNotifyBoth() throws Throwable {
notifyDataSetChangedWithDispappearing(true);
}
@@ -378,7 +548,8 @@
assertEquals(2, mAnimator.animateDisappearanceList.size());
}
- public void testNotifyUpdateWithChangedAdapterType() throws Throwable {
+ @Test
+ public void notifyUpdateWithChangedAdapterType() throws Throwable {
final AtomicInteger itemType = new AtomicInteger(1);
final TestAdapter adapter = new TestAdapter(10) {
@Override
@@ -392,7 +563,7 @@
mAnimator.canReUseCallback = new CanReUseCallback() {
@Override
- public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
return viewHolder != vh;
}
};
@@ -425,7 +596,7 @@
CanReUseCallback canReUseCallback = new CanReUseCallback() {
@Override
- public boolean canReUse(RecyclerView.ViewHolder viewHolder) {
+ public boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads) {
return true;
}
};
@@ -438,8 +609,9 @@
List<AnimateChange> animateChangeList = new ArrayList<>();
@Override
- public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder) {
- return canReUseCallback.canReUse(viewHolder);
+ public boolean canReuseUpdatedViewHolder(RecyclerView.ViewHolder viewHolder,
+ List<Object> payloads) {
+ return canReUseCallback.canReUse(viewHolder, payloads);
}
@NonNull
@@ -536,6 +708,6 @@
interface CanReUseCallback {
- boolean canReUse(RecyclerView.ViewHolder viewHolder);
+ boolean canReUse(RecyclerView.ViewHolder viewHolder, List<Object> payloads);
}
}
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..9ee7958
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -0,0 +1,334 @@
+/*
+ * 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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.ViewParent;
+
+import java.util.ArrayList;
+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;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static android.view.ViewGroup.LayoutParams.FILL_PARENT;
+
+/**
+ * Tests that rely on the basic configuration and does not do any additions / removals
+ */
+@RunWith(Parameterized.class)
+@MediumTest
+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 {
+ List<Config> result = new ArrayList<>();
+ for (Config config : createBaseVariations()) {
+ result.add(config);
+ }
+ return result;
+ }
+
+ @Test
+ public void scrollToPositionWithOffsetTest() throws Throwable {
+ Config config = ((Config) mConfig.clone()).itemCount(300);
+ 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;
+ }
+ }
+
+ @Test
+ 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);
+ }
+
+ @Test
+ public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(1000);
+ setupByConfig(config, true);
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(500);
+ mLayoutManager.waitForLayout(2);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
+ OrientationHelper helper = mLayoutManager.mOrientationHelper;
+ int gap = helper.getDecoratedStart(vh.itemView);
+ scrollBy(gap);
+ gap = helper.getDecoratedStart(vh.itemView);
+ assertThat("test sanity", gap, is(0));
+
+ final int size = helper.getDecoratedMeasurement(vh.itemView);
+ AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ ViewCompat.setTranslationX(vh.itemView, size * 2);
+ } else {
+ ViewCompat.setTranslationY(vh.itemView, size * 2);
+ }
+ }
+ });
+ scrollBy(size * 2);
+ assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+ assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+ assertThat(vh.getAdapterPosition(), is(500));
+ scrollBy(size * 2);
+ assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+ }
+
+ @Test
+ public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(1000);
+ setupByConfig(config, true);
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(500);
+ mLayoutManager.waitForLayout(2);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(500);
+ OrientationHelper helper = mLayoutManager.mOrientationHelper;
+ int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+ scrollBy(-gap);
+ gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+ assertThat("test sanity", gap, is(0));
+
+ final int size = helper.getDecoratedMeasurement(vh.itemView);
+ AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ ViewCompat.setTranslationX(vh.itemView, -size * 2);
+ } else {
+ ViewCompat.setTranslationY(vh.itemView, -size * 2);
+ }
+ }
+ });
+ scrollBy(-size * 2);
+ assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+ assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+ assertThat(vh.getAdapterPosition(), is(500));
+ scrollBy(-size * 2);
+ assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+ }
+
+ 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..27c93fc
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerPrepareForDropTest.java
@@ -0,0 +1,172 @@
+/*
+ * 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;
+import static org.junit.Assert.*;
+
+@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;
+ }
+
+ @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 = mLayoutManager.getWidth() / 5;
+ final int itemRand = holder.mBoundItem.mText.hashCode() % base;
+ holder.itemView.setMinimumWidth(base + itemRand);
+ } else {
+ final int base = mLayoutManager.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..8ed6f59
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerResizeTest.java
@@ -0,0 +1,100 @@
+/*
+ * 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;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+public class LinearLayoutManagerResizeTest extends BaseLinearLayoutManagerTest {
+
+ final Config mConfig;
+
+ public LinearLayoutManagerResizeTest(Config config) {
+ mConfig = config;
+ }
+
+ @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..5fcd33c
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerSavedStateTest.java
@@ -0,0 +1,418 @@
+/*
+ * 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.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.LargeTest;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import static org.junit.Assert.*;
+
+@RunWith(Parameterized.class)
+@LargeTest
+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;
+ }
+
+ @Test
+ 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);
+
+ final int itemCount = mTestAdapter.getItemCount();
+ List<Item> testItems = new ArrayList<>();
+ if (mLoadDataAfterRestore) {
+ // we cannot delete and re-add since new items may have different sizes. We need the
+ // exact same adapter.
+ testItems.addAll(mTestAdapter.mItems);
+ 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) {
+ // add the same items back
+ mTestAdapter.resetItemsTo(testItems);
+ }
+
+ 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..2d6bee7 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerTest.java
@@ -16,6 +16,8 @@
package android.support.v7.widget;
+import org.junit.Test;
+
import android.content.Context;
import android.graphics.Rect;
import android.os.Parcel;
@@ -23,6 +25,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;
@@ -42,6 +45,7 @@
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.Assert.*;
/**
* Includes tests for {@link LinearLayoutManager}.
@@ -50,105 +54,60 @@
* 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 {
+@MediumTest
+public class LinearLayoutManagerTest extends BaseLinearLayoutManagerTest {
- 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();
- }
- }
-
- public void testRemoveAnchorItem() throws Throwable {
+ @Test
+ public void removeAnchorItem() throws Throwable {
removeAnchorItemTest(
new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout(
false), 100, 0);
}
- public void testRemoveAnchorItemReverse() throws Throwable {
+ @Test
+ public void removeAnchorItemReverse() throws Throwable {
removeAnchorItemTest(
new Config().orientation(VERTICAL).stackFromBottom(false).reverseLayout(true), 100,
0);
}
- public void testRemoveAnchorItemStackFromEnd() throws Throwable {
+ @Test
+ public void removeAnchorItemStackFromEnd() throws Throwable {
removeAnchorItemTest(
new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(false), 100,
99);
}
- public void testRemoveAnchorItemStackFromEndAndReverse() throws Throwable {
+ @Test
+ public void removeAnchorItemStackFromEndAndReverse() throws Throwable {
removeAnchorItemTest(
new Config().orientation(VERTICAL).stackFromBottom(true).reverseLayout(true), 100,
99);
}
- public void testRemoveAnchorItemHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemHorizontal() throws Throwable {
removeAnchorItemTest(
new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout(
false), 100, 0);
}
- public void testRemoveAnchorItemReverseHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemReverseHorizontal() throws Throwable {
removeAnchorItemTest(
new Config().orientation(HORIZONTAL).stackFromBottom(false).reverseLayout(true),
100, 0);
}
- public void testRemoveAnchorItemStackFromEndHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemStackFromEndHorizontal() throws Throwable {
removeAnchorItemTest(
new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(false),
100, 99);
}
- public void testRemoveAnchorItemStackFromEndAndReverseHorizontal() throws Throwable {
+ @Test
+ public void removeAnchorItemStackFromEndAndReverseHorizontal() throws Throwable {
removeAnchorItemTest(
new Config().orientation(HORIZONTAL).stackFromBottom(true).reverseLayout(true), 100,
99);
@@ -245,18 +204,14 @@
mRecyclerView.getChildCount() <= childCount + 3 /*1 for removed view, 2 for its size*/);
}
- public void testKeepFocusOnRelayout() throws Throwable {
+ @Test
+ public void keepFocusOnRelayout() throws Throwable {
setupByConfig(new Config(VERTICAL, false, false).itemCount(500), true);
int center = (mLayoutManager.findLastVisibleItemPosition()
- mLayoutManager.findFirstVisibleItemPosition()) / 2;
final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForLayoutPosition(center);
final int top = mLayoutManager.mOrientationHelper.getDecoratedStart(vh.itemView);
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- vh.itemView.requestFocus();
- }
- });
+ requestFocus(vh.itemView, true);
assertTrue("view should have the focus", vh.itemView.hasFocus());
// add a bunch of items right before that view, make sure it keeps its position
mLayoutManager.expectLayouts(2);
@@ -272,27 +227,33 @@
mLayoutManager.mOrientationHelper.getDecoratedStart(postVH.itemView));
}
- public void testKeepFullFocusOnResize() throws Throwable {
+ @Test
+ public void keepFullFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), true);
}
- public void testKeepPartialFocusOnResize() throws Throwable {
+ @Test
+ public void keepPartialFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, false, false).itemCount(500), false);
}
- public void testKeepReverseFullFocusOnResize() throws Throwable {
+ @Test
+ public void keepReverseFullFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), true);
}
- public void testKeepReversePartialFocusOnResize() throws Throwable {
+ @Test
+ public void keepReversePartialFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, true, false).itemCount(500), false);
}
- public void testKeepStackFromEndFullFocusOnResize() throws Throwable {
+ @Test
+ public void keepStackFromEndFullFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), true);
}
- public void testKeepStackFromEndPartialFocusOnResize() throws Throwable {
+ @Test
+ public void keepStackFromEndPartialFocusOnResize() throws Throwable {
keepFocusOnResizeTest(new Config(VERTICAL, false, true).itemCount(500), false);
}
@@ -314,16 +275,8 @@
int endMargin = helper.getEndAfterPadding() -
helper.getDecoratedEnd(vh.itemView);
Log.d(TAG, "initial start margin " + startMargin + " , end margin:" + endMargin);
- requestFocus(vh.itemView);
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- assertTrue("view should gain the focus", vh.itemView.hasFocus());
- }
- });
- do {
- Thread.sleep(100);
- } while (mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE);
+ requestFocus(vh.itemView, true);
+ assertTrue("view should gain the focus", vh.itemView.hasFocus());
// scroll enough to offset the child
startMargin = helper.getDecoratedStart(vh.itemView) -
helper.getStartAfterPadding();
@@ -401,177 +354,8 @@
}
}
- 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 {
+ @Test
+ public void scrollToPositionWithPredictive() throws Throwable {
scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(3, 20);
@@ -582,64 +366,8 @@
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 {
+ @Test
+ public void recycleDuringAnimations() throws Throwable {
final AtomicInteger childCount = new AtomicInteger(0);
final TestAdapter adapter = new TestAdapter(300) {
@Override
@@ -705,13 +433,8 @@
}
- public void testGetFirstLastChildrenTest() throws Throwable {
- for (Config config : mBaseVariations) {
- getFirstLastChildrenTest(config);
- }
- }
-
- public void testDontRecycleChildrenOnDetach() throws Throwable {
+ @Test
+ public void dontRecycleChildrenOnDetach() throws Throwable {
setupByConfig(new Config().recycleChildrenOnDetach(false), true);
runTestOnUiThread(new Runnable() {
@Override
@@ -724,7 +447,8 @@
});
}
- public void testRecycleChildrenOnDetach() throws Throwable {
+ @Test
+ public void recycleChildrenOnDetach() throws Throwable {
setupByConfig(new Config().recycleChildrenOnDetach(true), true);
final int childCount = mLayoutManager.getChildCount();
runTestOnUiThread(new Runnable() {
@@ -740,341 +464,8 @@
});
}
- 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 {
+ @Test
+ public void scrollAndClear() throws Throwable {
setupByConfig(new Config(), true);
assertTrue("Children not laid out", mLayoutManager.collectChildCoordinates().size() > 0);
@@ -1093,70 +484,8 @@
}
- 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 {
+ @Test
+ public void accessibilityPositions() throws Throwable {
setupByConfig(new Config(VERTICAL, false, false), true);
final AccessibilityDelegateCompat delegateCompat = mRecyclerView
.getCompatAccessibilityDelegate();
@@ -1176,453 +505,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/LinearLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..b4f1e72
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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 static android.support.v7.widget.BaseLinearLayoutManagerTest.Config;
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+
+import android.graphics.Rect;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.Gravity;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class LinearLayoutManagerWrapContentTest extends BaseWrapContentTest {
+
+ Config mConfig;
+
+ public LinearLayoutManagerWrapContentTest(Config config,
+ WrapContentConfig wrapContentConfig) {
+ super(wrapContentConfig);
+ mConfig = config;
+ }
+
+ @Test
+ public void testUnspecifiedWithHint() throws Throwable {
+ unspecifiedWithHintTest(mConfig.mOrientation == StaggeredGridLayoutManager.HORIZONTAL);
+ }
+
+ @Test
+ public void deletion() throws Throwable {
+ testScenerio(new Scenario(
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.deleteAndNotify(3, 3);
+ }
+ },
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.deleteAndNotify(3, 3);
+ }
+ },
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.deleteAndNotify(1, 2);
+ }
+ }) {
+ });
+ }
+
+ @Test
+ public void addition() throws Throwable {
+ testScenerio(new Scenario(
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.addAndNotify(1, 2);
+ }
+ }
+ ,
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.addAndNotify(0, 2);
+ }
+ },
+ new Step() {
+ @Override
+ void onRun() throws Throwable {
+ mTestAdapter.addAndNotify(6, 3);
+ }
+ }
+ ) {
+ @Override
+ public int getSeedAdapterSize() {
+ return 2;
+ }
+ });
+ }
+
+ @Parameterized.Parameters(name = "{0} {1}")
+ public static Iterable<Object[]> data() {
+ List<Object[]> params = new ArrayList<>();
+ List<Rect> paddings = Arrays.asList(
+ new Rect(0, 0, 0, 0),
+ new Rect(5, 0, 0, 0),
+ new Rect(0, 6, 0, 0),
+ new Rect(0, 0, 7, 0),
+ new Rect(0, 0, 0, 8),
+ new Rect(3, 5, 7, 11)
+ );
+ for (Rect padding : paddings) {
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (boolean stackFromBottom : new boolean[]{false, true}) {
+ params.add(
+ new Object[]{
+ new Config(orientation, reverseLayout, stackFromBottom),
+ new WrapContentConfig(false, false, new Rect(padding))
+ }
+ );
+ params.add(
+ new Object[]{
+ new Config(orientation, reverseLayout, stackFromBottom),
+ new WrapContentConfig(HORIZONTAL == orientation,
+ VERTICAL == orientation, new Rect(padding))
+ }
+ );
+ }
+ }
+ }
+ }
+ return params;
+ }
+
+ @Override
+ RecyclerView.LayoutManager createLayoutManager() {
+ return createFromConfig();
+ }
+
+ private LinearLayoutManager createFromConfig() {
+ LinearLayoutManager llm = new LinearLayoutManager(getActivity(), mConfig.mOrientation,
+ mConfig.mReverseLayout);
+ llm.setStackFromEnd(mConfig.mStackFromEnd);
+ return llm;
+ }
+
+ @Override
+ protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ return Gravity.TOP;
+ }
+ if (mConfig.mReverseLayout ^ mConfig.mStackFromEnd) {
+ return Gravity.BOTTOM;
+ }
+ return Gravity.TOP;
+ }
+
+ @Override
+ protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+ boolean rtl = layoutManager.getLayoutDirection() == ViewCompat.LAYOUT_DIRECTION_RTL;
+ if (mConfig.mOrientation == VERTICAL) {
+ if (rtl) {
+ return Gravity.RIGHT;
+ }
+ return Gravity.LEFT;
+ }
+ boolean end = mConfig.mReverseLayout ^ mConfig.mStackFromEnd;
+ if (rtl ^ end) {
+ return Gravity.RIGHT;
+ }
+ return Gravity.LEFT;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java
new file mode 100644
index 0000000..2bb0750
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerWrapContentWithAspectRatioTest.java
@@ -0,0 +1,216 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.hamcrest.CoreMatchers;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Color;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+import android.view.ViewGroup;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import static android.support.v7.widget.LinearLayoutManager.HORIZONTAL;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class LinearLayoutManagerWrapContentWithAspectRatioTest
+ extends BaseWrapContentWithAspectRatioTest {
+
+ final LinearLayoutManagerTest.Config mConfig;
+ final float mRatio;
+
+ public LinearLayoutManagerWrapContentWithAspectRatioTest(
+ BaseLinearLayoutManagerTest.Config config,
+ BaseWrapContentTest.WrapContentConfig wrapContentConfig,
+ float ratio) {
+ super(wrapContentConfig);
+ mConfig = config;
+ mRatio = ratio;
+ }
+
+ @Parameterized.Parameters(name = "{0} {1} ratio:{2}")
+ public static Iterable<Object[]> data() {
+ List<Object[]> params = new ArrayList<>();
+ for (float ratio : new float[]{.5f, 1f, 2f}) {
+ for (int orientation : new int[]{VERTICAL, HORIZONTAL}) {
+ for (boolean reverseLayout : new boolean[]{false, true}) {
+ for (boolean stackFromBottom : new boolean[]{false, true}) {
+ params.add(
+ new Object[]{
+ new BaseLinearLayoutManagerTest.Config(orientation,
+ reverseLayout, stackFromBottom),
+ new BaseWrapContentTest.WrapContentConfig(
+ false, false),
+ ratio
+ }
+ );
+ params.add(
+ new Object[]{
+ new BaseLinearLayoutManagerTest.Config(orientation,
+ reverseLayout, stackFromBottom),
+ new BaseWrapContentTest.WrapContentConfig(
+ HORIZONTAL == orientation,
+ VERTICAL == orientation),
+ ratio
+ }
+ );
+ params.add(
+ new Object[]{
+ new BaseLinearLayoutManagerTest.Config(orientation,
+ reverseLayout, stackFromBottom),
+ new BaseWrapContentTest.WrapContentConfig(
+ VERTICAL == orientation,
+ HORIZONTAL == orientation),
+ ratio
+ }
+ );
+ params.add(
+ new Object[]{
+ new BaseLinearLayoutManagerTest.Config(orientation,
+ reverseLayout, stackFromBottom),
+ new BaseWrapContentTest.WrapContentConfig(
+ true, true),
+ ratio
+ }
+ );
+ }
+ }
+ }
+ }
+ return params;
+ }
+
+ @Test
+ public void wrapContentAffectsOtherOrientation() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams
+ wrapContent = new TestedFrameLayout.FullControlLayoutParams(
+ ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ int testOrientation = mConfig.mOrientation == VERTICAL ? HORIZONTAL : VERTICAL;
+ boolean unlimitedSize = false;
+ if (mWrapContentConfig.isUnlimitedHeight()) {
+ wrapContent.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ unlimitedSize = testOrientation == VERTICAL;
+ }
+ if (mWrapContentConfig.isUnlimitedWidth()) {
+ wrapContent.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ unlimitedSize |= testOrientation == HORIZONTAL;
+ }
+
+ RecyclerView.LayoutManager layoutManager = createFromConfig();
+ BaseWrapContentTest.WrappedRecyclerView
+ recyclerView = new BaseWrapContentTest.WrappedRecyclerView(getActivity());
+ recyclerView.setBackgroundColor(Color.rgb(0, 0, 255));
+ recyclerView.setLayoutManager(layoutManager);
+ recyclerView.setLayoutParams(wrapContent);
+
+ AspectRatioMeasureBehavior behavior1 = new AspectRatioMeasureBehavior(10, 10,
+ WRAP_CONTENT, WRAP_CONTENT).aspectRatio(testOrientation, mRatio);
+ AspectRatioMeasureBehavior behavior2 = new AspectRatioMeasureBehavior(15, 15,
+ testOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+ testOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT)
+ .aspectRatio(testOrientation, mRatio);
+ AspectRatioMeasureBehavior behavior3 = new AspectRatioMeasureBehavior(8, 8,
+ testOrientation == HORIZONTAL ? MATCH_PARENT : WRAP_CONTENT,
+ testOrientation == VERTICAL ? MATCH_PARENT : WRAP_CONTENT)
+ .aspectRatio(testOrientation, mRatio);
+
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ behavior1, behavior2, behavior3
+ );
+
+ recyclerView.setAdapter(adapter);
+ setRecyclerView(recyclerView);
+ recyclerView.waitUntilLayout();
+
+ int parentDim = getSize((View) recyclerView.getParent(), testOrientation);
+
+ View itemView1 = recyclerView.findViewHolderForAdapterPosition(0).itemView;
+ assertThat("first child test size", getSize(itemView1, testOrientation),
+ CoreMatchers.is(10));
+ assertThat("first child dependant size", getSize(itemView1, mConfig.mOrientation),
+ CoreMatchers.is(behavior1.getSecondary(10)));
+
+ View itemView2 = recyclerView.findViewHolderForAdapterPosition(1).itemView;
+ assertThat("second child test size", getSize(itemView2, testOrientation),
+ CoreMatchers.is(15));
+ assertThat("second child dependant size", getSize(itemView2, mConfig.mOrientation),
+ CoreMatchers.is(behavior2.getSecondary(15)));
+
+ View itemView3 = recyclerView.findViewHolderForAdapterPosition(2).itemView;
+ assertThat("third child test size", getSize(itemView3, testOrientation),
+ CoreMatchers.is(15));
+ assertThat("third child dependant size", getSize(itemView3, mConfig.mOrientation),
+ CoreMatchers.is(behavior3.getSecondary(15)));
+
+ assertThat("it should be measured only once", behavior1.measureSpecs.size(),
+ CoreMatchers.is(1));
+ if (unlimitedSize) {
+ assertThat(behavior1.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+ } else {
+ assertThat(behavior1.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+ }
+
+ assertThat("it should be measured once", behavior2.measureSpecs.size(), CoreMatchers.is(1));
+ if (unlimitedSize) {
+ assertThat(behavior2.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+ } else {
+ assertThat(behavior2.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+ }
+
+ assertThat("it should be measured twice", behavior3.measureSpecs.size(),
+ CoreMatchers.is(2));
+ if (unlimitedSize) {
+ assertThat(behavior3.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(0, View.MeasureSpec.UNSPECIFIED));
+ } else {
+ assertThat(behavior3.getSpec(0, testOrientation),
+ MeasureSpecMatcher.is(parentDim, View.MeasureSpec.AT_MOST));
+ }
+
+ assertThat(behavior3.getSpec(1, testOrientation),
+ MeasureSpecMatcher.is(15, View.MeasureSpec.EXACTLY));
+ final int totalScrollSize = getSize(itemView1, mConfig.mOrientation)
+ + getSize(itemView2, mConfig.mOrientation)
+ + getSize(itemView3, mConfig.mOrientation);
+ assertThat("RecyclerView should wrap its content in the scroll direction",
+ getSize(mRecyclerView, mConfig.mOrientation), CoreMatchers.is(totalScrollSize));
+ assertThat("RecyclerView should wrap its content in the scroll direction",
+ getSize(mRecyclerView, testOrientation), CoreMatchers.is(15));
+ }
+
+ private LinearLayoutManager createFromConfig() {
+ LinearLayoutManager llm = new LinearLayoutManager(getActivity(), mConfig.mOrientation,
+ mConfig.mReverseLayout);
+ llm.setStackFromEnd(mConfig.mStackFromEnd);
+ return llm;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
index 9bbdd4b..65471aa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LoggingItemAnimator.java
@@ -49,15 +49,42 @@
if (log.viewHolder == viewHolder) {
return true;
}
+ if (log instanceof AnimateChange) {
+ if (((AnimateChange) log).newHolder == viewHolder) {
+ return true;
+ }
+ }
}
return false;
}
+ @NonNull
+ @Override
+ public ItemHolderInfo recordPreLayoutInformation(@NonNull RecyclerView.State state,
+ @NonNull RecyclerView.ViewHolder viewHolder,
+ @AdapterChanges int changeFlags, @NonNull List<Object> payloads) {
+ BaseRecyclerViewAnimationsTest.LoggingInfo
+ loggingInfo = new BaseRecyclerViewAnimationsTest.LoggingInfo(viewHolder, changeFlags, payloads);
+ return loggingInfo;
+ }
+
+ @NonNull
+ @Override
+ public ItemHolderInfo recordPostLayoutInformation(@NonNull RecyclerView.State state,
+ @NonNull RecyclerView.ViewHolder viewHolder) {
+ BaseRecyclerViewAnimationsTest.LoggingInfo
+ loggingInfo = new BaseRecyclerViewAnimationsTest.LoggingInfo(viewHolder, 0, null);
+ return loggingInfo;
+ }
+
+
@Override
public boolean animateDisappearance(@NonNull RecyclerView.ViewHolder viewHolder,
@NonNull ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo) {
mAnimateDisappearanceList
- .add(new AnimateDisappearance(viewHolder, null, null));
+ .add(new AnimateDisappearance(viewHolder,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) preLayoutInfo,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) postLayoutInfo));
return super.animateDisappearance(viewHolder, preLayoutInfo, postLayoutInfo);
}
@@ -66,7 +93,9 @@
ItemHolderInfo preLayoutInfo,
@NonNull ItemHolderInfo postLayoutInfo) {
mAnimateAppearanceList
- .add(new AnimateAppearance(viewHolder, null, null));
+ .add(new AnimateAppearance(viewHolder,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) preLayoutInfo,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) postLayoutInfo));
return super.animateAppearance(viewHolder, preLayoutInfo, postLayoutInfo);
}
@@ -75,7 +104,9 @@
@NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo) {
mAnimatePersistenceList
- .add(new AnimatePersistence(viewHolder, null, null));
+ .add(new AnimatePersistence(viewHolder,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) preInfo,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) postInfo));
return super.animatePersistence(viewHolder, preInfo, postInfo);
}
@@ -84,7 +115,9 @@
@NonNull RecyclerView.ViewHolder newHolder, @NonNull ItemHolderInfo preInfo,
@NonNull ItemHolderInfo postInfo) {
mAnimateChangeList
- .add(new AnimateChange(oldHolder, newHolder, null, null));
+ .add(new AnimateChange(oldHolder, newHolder,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) preInfo,
+ (BaseRecyclerViewAnimationsTest.LoggingInfo) postInfo));
return super.animateChange(oldHolder, newHolder, preInfo, postInfo);
}
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..be0fc10 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
@@ -16,38 +16,61 @@
package android.support.v7.widget;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
import android.support.v4.view.AccessibilityDelegateCompat;
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.ArrayList;
+import java.util.Arrays;
+import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+@MediumTest
+@RunWith(Parameterized.class)
public class RecyclerViewAccessibilityTest extends BaseRecyclerViewInstrumentationTest {
- public RecyclerViewAccessibilityTest() {
- super(false);
+ final boolean verticalScrollBefore, horizontalScrollBefore, verticalScrollAfter,
+ horizontalScrollAfter;
+
+ public RecyclerViewAccessibilityTest(boolean verticalScrollBefore,
+ boolean horizontalScrollBefore, boolean verticalScrollAfter,
+ boolean horizontalScrollAfter) {
+ this.verticalScrollBefore = verticalScrollBefore;
+ this.horizontalScrollBefore = horizontalScrollBefore;
+ this.verticalScrollAfter = verticalScrollAfter;
+ this.horizontalScrollAfter = horizontalScrollAfter;
}
- public void testOnInitializeAccessibilityNodeInfo() throws Throwable {
+ @Parameterized.Parameters(name = "vBefore={0} vAfter={1} hBefore={2} hAfter={3}")
+ public static List<Object[]> getParams() {
+ List<Object[]> params = new ArrayList<>();
for (boolean vBefore : new boolean[]{true, false}) {
for (boolean vAfter : new boolean[]{true, false}) {
for (boolean hBefore : new boolean[]{true, false}) {
for (boolean hAfter : new boolean[]{true, false}) {
- onInitializeAccessibilityNodeInfoTest(vBefore, hBefore,
- vAfter, hAfter);
- removeRecyclerView();
+ params.add(new Object[]{vBefore, hBefore, vAfter, hAfter});
}
}
}
}
+ return params;
}
- public void onInitializeAccessibilityNodeInfoTest(final boolean verticalScrollBefore,
- final boolean horizontalScrollBefore, final boolean verticalScrollAfter,
- final boolean horizontalScrollAfter) throws Throwable {
+ @Test
+ public void onInitializeAccessibilityNodeInfoTest() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity()) {
//@Override
public boolean canScrollHorizontally(int direction) {
@@ -147,11 +170,11 @@
final AccessibilityRecordCompat record = AccessibilityEventCompat
.asRecord(event);
assertEquals(record.isScrollable(), verticalScrollAfter || horizontalScrollAfter ||
- verticalScrollBefore || horizontalScrollBefore);
+ verticalScrollBefore || horizontalScrollBefore);
assertEquals(record.getItemCount(), adapter.getItemCount());
getInstrumentation().waitForIdleSync();
- for (int i = 0; i < mRecyclerView.getChildCount(); i ++) {
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
final View view = mRecyclerView.getChildAt(i);
final AccessibilityNodeInfoCompat childInfo = AccessibilityNodeInfoCompat.obtain();
runTestOnUiThread(new Runnable() {
@@ -206,7 +229,8 @@
assertEquals(verticalScrollAfter, vScrolledFwd.get());
}
- public void testIgnoreAccessibilityIfAdapterHasChanged() throws Throwable {
+ @Test
+ public void ignoreAccessibilityIfAdapterHasChanged() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity()) {
//@Override
public boolean canScrollHorizontally(int direction) {
@@ -237,18 +261,24 @@
});
assertTrue("test sanity", info.isScrollable());
final AccessibilityNodeInfoCompat info2 = AccessibilityNodeInfoCompat.obtain();
- layoutManager.blockLayout();
- layoutManager.expectLayouts(1);
- adapter.deleteAndNotify(1, 1);
- // we can run this here since we blocked layout.
- delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
- layoutManager.unblockLayout();
- assertFalse("info should not be filled if data is out of date", info2.isScrollable());
- layoutManager.waitForLayout(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ adapter.deleteAndNotify(1, 1);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ delegateCompat.onInitializeAccessibilityNodeInfo(recyclerView, info2);
+ assertFalse("info should not be filled if data is out of date",
+ info2.isScrollable());
+ }
+ });
+ checkForMainThreadException();
}
boolean performAccessibilityAction(final AccessibilityDelegateCompat delegate,
- final RecyclerView recyclerView, final int action) throws Throwable {
+ final RecyclerView recyclerView, final int action) throws Throwable {
final boolean[] result = new boolean[1];
runTestOnUiThread(new Runnable() {
@Override
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 0b9ef15..2ddbabf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -16,9 +16,16 @@
package android.support.v7.widget;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.graphics.Rect;
-import android.os.Debug;
+import android.support.annotation.NonNull;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
@@ -29,16 +36,175 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
+import static org.junit.Assert.*;
/**
* Tests for {@link SimpleItemAnimator} API.
*/
+@MediumTest
+@RunWith(AndroidJUnit4.class)
public class RecyclerViewAnimationsTest extends BaseRecyclerViewAnimationsTest {
final List<TestViewHolder> recycledVHs = new ArrayList<>();
- public void testDontLayoutReusedViewWithoutPredictive() throws Throwable {
+ @Test
+ public void keepFocusAfterChangeAnimation() throws Throwable {
+ setupBasic(10, 0, 5, new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setFocusableInTouchMode(true);
+ }
+ });
+ ((SimpleItemAnimator)(mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+
+ final RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+ assertNotNull("test sanity", oldVh);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ oldVh.itemView.requestFocus();
+ }
+ });
+ assertTrue("test sanity", oldVh.itemView.hasFocus());
+ mLayoutManager.expectLayouts(2);
+ mTestAdapter.changeAndNotify(3, 1);
+ mLayoutManager.waitForLayout(2);
+
+ RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+ assertNotNull("test sanity", newVh);
+ assertNotSame(oldVh, newVh);
+ assertFalse(oldVh.itemView.hasFocus());
+ assertTrue(newVh.itemView.hasFocus());
+ }
+
+ @Test
+ public void changeAndDisappearDontReUseViewHolder() throws Throwable {
+ changeAndDisappearTest(false, false);
+ }
+
+ @Test
+ public void changeAndDisappearReUseViewHolder() throws Throwable {
+ changeAndDisappearTest(true, false);
+ }
+
+ @Test
+ public void changeAndDisappearReUseWithScrapViewHolder() throws Throwable {
+ changeAndDisappearTest(true, true);
+ }
+
+ public void changeAndDisappearTest(final boolean reUse, final boolean useScrap)
+ throws Throwable {
+ final List<RecyclerView.ViewHolder> mRecycled = new ArrayList<>();
+ final TestAdapter adapter = new TestAdapter(1) {
+ @Override
+ public void onViewRecycled(TestViewHolder holder) {
+ super.onViewRecycled(holder);
+ mRecycled.add(holder);
+ }
+ };
+ setupBasic(1, 0, 1, adapter);
+ RecyclerView.ViewHolder vh = mRecyclerView.getChildViewHolder(mRecyclerView.getChildAt(0));
+ LoggingItemAnimator animator = new LoggingItemAnimator() {
+ @Override
+ public boolean canReuseUpdatedViewHolder(@NonNull RecyclerView.ViewHolder viewHolder,
+ @NonNull List<Object> payloads) {
+ return reUse;
+ }
+ };
+ mRecyclerView.setItemAnimator(animator);
+ mLayoutManager.expectLayouts(2);
+ final RecyclerView.ViewHolder[] updatedVH = new RecyclerView.ViewHolder[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ adapter.notifyItemChanged(0);
+ mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
+ @Override
+ void doLayout(RecyclerView.Recycler recycler, AnimationLayoutManager lm,
+ RecyclerView.State state) {
+ if (state.isPreLayout()) {
+ super.doLayout(recycler, lm, state);
+ } else {
+ lm.detachAndScrapAttachedViews(recycler);
+ final View view;
+ if (reUse && useScrap) {
+ view = recycler.getScrapViewAt(0);
+ } else {
+ view = recycler.getViewForPosition(0);
+ }
+ updatedVH[0] = RecyclerView.getChildViewHolderInt(view);
+ lm.addDisappearingView(view);
+ }
+ }
+ };
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+
+ MatcherAssert.assertThat(animator.contains(vh, animator.mAnimateDisappearanceList),
+ CoreMatchers.is(reUse));
+ MatcherAssert.assertThat(animator.contains(vh, animator.mAnimateChangeList),
+ CoreMatchers.is(!reUse));
+ MatcherAssert.assertThat(animator.contains(updatedVH[0], animator.mAnimateChangeList),
+ CoreMatchers.is(!reUse));
+ MatcherAssert.assertThat(animator.contains(updatedVH[0],
+ animator.mAnimateDisappearanceList), CoreMatchers.is(reUse));
+ waitForAnimations(10);
+ MatcherAssert.assertThat(mRecyclerView.getChildCount(), CoreMatchers.is(0));
+ if (useScrap || !reUse) {
+ MatcherAssert.assertThat(mRecycled.contains(vh), CoreMatchers.is(true));
+ } else {
+ MatcherAssert.assertThat(mRecyclerView.mRecycler.mCachedViews.contains(vh),
+ CoreMatchers.is(true));
+ }
+
+ if (!reUse) {
+ MatcherAssert.assertThat(mRecycled.contains(updatedVH[0]), CoreMatchers.is(false));
+ MatcherAssert.assertThat(mRecyclerView.mRecycler.mCachedViews.contains(updatedVH[0]),
+ CoreMatchers.is(true));
+ }
+ }
+
+ @Test
+ public void detectStableIdError() throws Throwable {
+ setIgnoreMainThreadException(true);
+ final AtomicBoolean useBadIds = new AtomicBoolean(false);
+ TestAdapter adapter = new TestAdapter(10) {
+ @Override
+ public long getItemId(int position) {
+ if (useBadIds.get() && position == 5) {
+ return super.getItemId(position) - 1;
+ }
+ return super.getItemId(position);
+ }
+
+ @Override
+ public void onDetachedFromRecyclerView(RecyclerView recyclerView) {
+ // ignore validation
+ }
+ };
+ adapter.setHasStableIds(true);
+ setupBasic(10, 0, 10, adapter);
+ mLayoutManager.expectLayouts(2);
+ useBadIds.set(true);
+ adapter.changeAndNotify(4, 2);
+ mLayoutManager.waitForLayout(2);
+ assertTrue(getMainThreadException() instanceof IllegalStateException);
+ assertTrue(getMainThreadException().getMessage()
+ .contains("Two different ViewHolders have the same stable ID."));
+ // TODO don't use this after moving this class to Junit 4
+ try {
+ removeRecyclerView();
+ } catch (Throwable t){}
+ }
+
+
+ @Test
+ public void dontLayoutReusedViewWithoutPredictive() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -90,7 +256,8 @@
});
}
- public void testDontLayoutReusedViewWithPredictive() throws Throwable {
+ @Test
+ public void dontLayoutReusedViewWithPredictive() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -139,7 +306,8 @@
});
}
- public void testReuseHiddenViewWithoutPredictive() throws Throwable {
+ @Test
+ public void reuseHiddenViewWithoutPredictive() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -162,7 +330,8 @@
});
}
- public void testReuseHiddenViewWithoutAnimations() throws Throwable {
+ @Test
+ public void reuseHiddenViewWithoutAnimations() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -184,7 +353,8 @@
});
}
- public void testReuseHiddenViewWithPredictive() throws Throwable {
+ @Test
+ public void reuseHiddenViewWithPredictive() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -209,7 +379,8 @@
});
}
- public void testReuseHiddenViewWithProperPredictive() throws Throwable {
+ @Test
+ public void reuseHiddenViewWithProperPredictive() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -251,7 +422,8 @@
});
}
- public void testDontReuseHiddenViewOnInvalidate() throws Throwable {
+ @Test
+ public void dontReuseHiddenViewOnInvalidate() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -274,7 +446,8 @@
});
}
- public void testDontReuseOnTypeChange() throws Throwable {
+ @Test
+ public void dontReuseOnTypeChange() throws Throwable {
reuseHiddenViewTest(new ReuseTestCallback() {
@Override
public void postSetup(List<TestViewHolder> recycledList,
@@ -340,7 +513,8 @@
}
- public void testDetachBeforeAnimations() throws Throwable {
+ @Test
+ public void detachBeforeAnimations() throws Throwable {
setupBasic(10, 0, 5);
final RecyclerView rv = mRecyclerView;
waitForAnimations(2);
@@ -360,7 +534,8 @@
assertFalse("there should not be any animations running", animator.isRunning());
}
- public void testMoveDeleted() throws Throwable {
+ @Test
+ public void moveDeleted() throws Throwable {
setupBasic(4, 0, 3);
waitForAnimations(2);
final View[] targetChild = new View[1];
@@ -488,31 +663,36 @@
});
}
- public void testImportantForAccessibilityWhileDetelingAuto() throws Throwable {
+ @Test
+ public void importantForAccessibilityWhileDetelingAuto() throws Throwable {
runTestImportantForAccessibilityWhileDeteling(
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- public void testImportantForAccessibilityWhileDetelingNo() throws Throwable {
+ @Test
+ public void importantForAccessibilityWhileDetelingNo() throws Throwable {
runTestImportantForAccessibilityWhileDeteling(
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO);
}
- public void testImportantForAccessibilityWhileDetelingNoHideDescandants() throws Throwable {
+ @Test
+ public void importantForAccessibilityWhileDetelingNoHideDescandants() throws Throwable {
runTestImportantForAccessibilityWhileDeteling(
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
}
- public void testImportantForAccessibilityWhileDetelingYes() throws Throwable {
+ @Test
+ public void importantForAccessibilityWhileDetelingYes() throws Throwable {
runTestImportantForAccessibilityWhileDeteling(
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
- public void testPreLayoutPositionCleanup() throws Throwable {
+ @Test
+ public void preLayoutPositionCleanup() throws Throwable {
setupBasic(4, 0, 4);
mLayoutManager.expectLayouts(2);
mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
@@ -537,7 +717,8 @@
}
- public void testAddRemoveSamePass() throws Throwable {
+ @Test
+ public void addRemoveSamePass() throws Throwable {
final List<RecyclerView.ViewHolder> mRecycledViews
= new ArrayList<RecyclerView.ViewHolder>();
TestAdapter adapter = new TestAdapter(50) {
@@ -626,11 +807,13 @@
assertTrue("added-removed view should be recycled", found);
}
- public void testTmpRemoveMe() throws Throwable {
+ @Test
+ public void tmpRemoveMe() throws Throwable {
changeAnimTest(false, false, true, false);
}
- public void testChangeAnimations() throws Throwable {
+ @Test
+ public void changeAnimations() throws Throwable {
final boolean[] booleans = {true, false};
for (boolean supportsChange : booleans) {
for (boolean changeType : booleans) {
@@ -735,20 +918,8 @@
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,
- Object[][] notifyPayloads, Object[][] expectedPayloadsInOnBind)
+ final boolean canReUse, Object[][] notifyPayloads, Object[][] expectedPayloadsInOnBind)
throws Throwable {
final List<Object> expectedPayloads = new ArrayList<Object>();
final int changedIndex = 3;
@@ -775,11 +946,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);
@@ -804,9 +982,30 @@
}
}
- public void testCrossFadingChangeAnimationWithPayload() throws Throwable {
+ @Test
+ public void crossFadingChangeAnimationWithPayload() 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"}
+ });
+ }
+
+ @Test
+ public void crossFadingChangeAnimationWithPayloadWithoutReuse() 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"},
@@ -823,10 +1022,11 @@
});
}
- public void testNoChangeAnimationWithPayload() throws Throwable {
+ @Test
+ public void noChangeAnimationWithPayload() 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"},
@@ -843,7 +1043,8 @@
});
}
- public void testRecycleDuringAnimations() throws Throwable {
+ @Test
+ public void recycleDuringAnimations() throws Throwable {
final AtomicInteger childCount = new AtomicInteger(0);
final TestAdapter adapter = new TestAdapter(1000) {
@Override
@@ -885,7 +1086,8 @@
mRecyclerView.getChildCount() + mRecyclerView.mRecycler.mCachedViews.size());
}
- public void testNotifyDataSetChanged() throws Throwable {
+ @Test
+ public void notifyDataSetChanged() throws Throwable {
setupBasic(10, 3, 4);
int layoutCount = mLayoutManager.mTotalLayoutCount;
mLayoutManager.expectLayouts(1);
@@ -911,7 +1113,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testStableIdNotifyDataSetChanged() throws Throwable {
+ @Test
+ public void stableIdNotifyDataSetChanged() throws Throwable {
final int itemCount = 20;
List<Item> initialSet = new ArrayList<Item>();
final TestAdapter adapter = new TestAdapter(itemCount) {
@@ -942,7 +1145,8 @@
}
- public void testGetItemForDeletedView() throws Throwable {
+ @Test
+ public void getItemForDeletedView() throws Throwable {
getItemForDeletedViewTest(false);
getItemForDeletedViewTest(true);
}
@@ -1001,7 +1205,8 @@
}
}
- public void testDeleteInvisibleMultiStep() throws Throwable {
+ @Test
+ public void deleteInvisibleMultiStep() throws Throwable {
setupBasic(1000, 1, 7);
mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
@@ -1020,7 +1225,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testAddManyMultiStep() throws Throwable {
+ @Test
+ public void addManyMultiStep() throws Throwable {
setupBasic(10, 1, 7);
mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
@@ -1043,7 +1249,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testBasicDelete() throws Throwable {
+ @Test
+ public void basicDelete() throws Throwable {
setupBasic(10);
final OnLayoutCallbacks callbacks = new OnLayoutCallbacks() {
@Override
@@ -1074,7 +1281,8 @@
}
- public void testAdapterChangeDuringScrolling() throws Throwable {
+ @Test
+ public void adapterChangeDuringScrolling() throws Throwable {
setupBasic(10);
final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
final AtomicInteger onScrollItemCount = new AtomicInteger(0);
@@ -1108,7 +1316,8 @@
});
}
- public void testNotifyDataSetChangedDuringScroll() throws Throwable {
+ @Test
+ public void notifyDataSetChangedDuringScroll() throws Throwable {
setupBasic(10);
final AtomicInteger onLayoutItemCount = new AtomicInteger(0);
final AtomicInteger onScrollItemCount = new AtomicInteger(0);
@@ -1142,7 +1351,8 @@
});
}
- public void testAddInvisibleAndVisible() throws Throwable {
+ @Test
+ public void addInvisibleAndVisible() throws Throwable {
setupBasic(10, 1, 7);
mLayoutManager.expectLayouts(2);
mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(10, 12);
@@ -1150,7 +1360,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testAddInvisible() throws Throwable {
+ @Test
+ public void addInvisible() throws Throwable {
setupBasic(10, 1, 7);
mLayoutManager.expectLayouts(1);
mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(10, 12);
@@ -1158,7 +1369,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testBasicAdd() throws Throwable {
+ @Test
+ public void basicAdd() throws Throwable {
setupBasic(10);
mLayoutManager.expectLayouts(2);
setExpectedItemCounts(10, 13);
@@ -1166,7 +1378,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testAppCancelAnimationInDetach() throws Throwable {
+ @Test
+ public void appCancelAnimationInDetach() throws Throwable {
final View[] addedView = new View[2];
TestAdapter adapter = new TestAdapter(1) {
@Override
@@ -1185,7 +1398,7 @@
// add 2 items
setExpectedItemCounts(1, 3);
mTestAdapter.addAndNotify(0, 2);
- mLayoutManager.waitForLayout(2, false);
+ mLayoutManager.waitForLayout(2);
checkForMainThreadException();
// wait till "add animation" starts
int limit = 200;
@@ -1224,7 +1437,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testAdapterChangeFrozen() throws Throwable {
+ @Test
+ public void adapterChangeFrozen() throws Throwable {
setupBasic(10, 1, 7);
assertTrue(mRecyclerView.getChildCount() == 7);
@@ -1243,7 +1457,8 @@
8, mRecyclerView.getChildCount());
}
- public void testRemoveScrapInvalidate() throws Throwable {
+ @Test
+ public void removeScrapInvalidate() throws Throwable {
setupBasic(10);
TestRecyclerView testRecyclerView = getTestRecyclerView();
mLayoutManager.expectLayouts(1);
@@ -1259,7 +1474,8 @@
testRecyclerView.waitForDraw(2);
}
- public void testDeleteVisibleAndInvisible() throws Throwable {
+ @Test
+ public void deleteVisibleAndInvisible() throws Throwable {
setupBasic(11, 3, 5); //layout items 3 4 5 6 7
mLayoutManager.expectLayouts(2);
setLayoutRange(3, 5); //layout previously invisible child 10 from end of the list
@@ -1268,7 +1484,8 @@
mLayoutManager.waitForLayout(2);
}
- public void testFindPositionOffset() throws Throwable {
+ @Test
+ public void findPositionOffset() throws Throwable {
setupBasic(10);
mLayoutManager.mOnLayoutCallbacks = new OnLayoutCallbacks() {
@Override
@@ -1306,7 +1523,8 @@
mLayoutManager.mOnLayoutCallbacks.setExpectedItemCounts(preLayout, postLayout);
}
- public void testDeleteInvisible() throws Throwable {
+ @Test
+ public void deleteInvisible() throws Throwable {
setupBasic(10, 1, 7);
mLayoutManager.mOnLayoutCallbacks.mLayoutMin = 1;
mLayoutManager.mOnLayoutCallbacks.mLayoutItemCount = 7;
@@ -1342,7 +1560,8 @@
return positionToAdapterMapping;
}
- public void testAddDelete2() throws Throwable {
+ @Test
+ public void addDelete2() throws Throwable {
positionStatesTest(5, 0, 5, new AdapterOps() {
// 0 1 2 3 4
// 0 1 2 a b 3 4
@@ -1358,7 +1577,8 @@
);
}
- public void testAddDelete1() throws Throwable {
+ @Test
+ public void addDelete1() throws Throwable {
positionStatesTest(5, 0, 5, new AdapterOps() {
// 0 1 2 3 4
// 0 1 2 a b 3 4
@@ -1379,7 +1599,8 @@
);
}
- public void testAddSameIndexTwice() throws Throwable {
+ @Test
+ public void addSameIndexTwice() throws Throwable {
positionStatesTest(12, 2, 7, new AdapterOps() {
@Override
void onRun(TestAdapter adapter) throws Throwable {
@@ -1393,7 +1614,8 @@
);
}
- public void testDeleteTwice() throws Throwable {
+ @Test
+ public void deleteTwice() throws Throwable {
positionStatesTest(12, 2, 7, new AdapterOps() {
@Override
void onRun(TestAdapter adapter) throws Throwable {
@@ -1478,7 +1700,8 @@
}
}
- public void testAddThenRecycleRemovedView() throws Throwable {
+ @Test
+ public void addThenRecycleRemovedView() throws Throwable {
setupBasic(10);
final AtomicInteger step = new AtomicInteger(0);
final List<RecyclerView.ViewHolder> animateRemoveList
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 06dcbb0..b232271 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java
@@ -16,11 +16,19 @@
package android.support.v7.widget;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import android.app.Instrumentation;
import android.content.Context;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.test.AndroidTestCase;
+import android.test.suitebuilder.annotation.SmallTest;
import android.util.AttributeSet;
import android.util.SparseArray;
import android.view.MotionEvent;
@@ -28,19 +36,29 @@
import android.view.ViewGroup;
import android.widget.TextView;
+import java.util.ArrayList;
+import java.util.List;
import java.util.UUID;
+import static org.junit.Assert.*;
-public class RecyclerViewBasicTest extends AndroidTestCase {
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecyclerViewBasicTest {
RecyclerView mRecyclerView;
- @Override
- protected void setUp() throws Exception {
- super.setUp();
- mRecyclerView = new RecyclerView(mContext);
+
+ @Before
+ public void setUp() throws Exception {
+ mRecyclerView = new RecyclerView(getContext());
}
- public void testMeasureWithoutLayoutManager() {
+ private Context getContext() {
+ return InstrumentationRegistry.getContext();
+ }
+
+ @Test
+ public void measureWithoutLayoutManager() {
measure();
}
@@ -56,7 +74,8 @@
mRecyclerView.focusSearch(1);
}
- public void testLayoutWithoutAdapter() throws InterruptedException {
+ @Test
+ public void layoutWithoutAdapter() throws InterruptedException {
MockLayoutManager layoutManager = new MockLayoutManager();
mRecyclerView.setLayoutManager(layoutManager);
layout();
@@ -69,48 +88,55 @@
+ "handle it properly", true, mRecyclerView.isScrollContainer());
}
- public void testLayoutWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void layoutWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
}
- public void testFocusWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void focusWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
focusSearch();
}
- public void testScrollWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void scrollWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
mRecyclerView.scrollBy(10, 10);
}
- public void testSmoothScrollWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void smoothScrollWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
mRecyclerView.smoothScrollBy(10, 10);
}
- public void testScrollToPositionWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void scrollToPositionWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
mRecyclerView.scrollToPosition(5);
}
- public void testSmoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
+ @Test
+ public void smoothScrollToPositionWithoutLayoutManager() throws InterruptedException {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
mRecyclerView.smoothScrollToPosition(5);
}
- public void testInterceptTouchWithoutLayoutManager() {
+ @Test
+ public void interceptTouchWithoutLayoutManager() {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
@@ -119,7 +145,8 @@
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
}
- public void testOnTouchWithoutLayoutManager() {
+ @Test
+ public void onTouchWithoutLayoutManager() {
mRecyclerView.setAdapter(new MockAdapter(20));
measure();
layout();
@@ -127,7 +154,8 @@
SystemClock.uptimeMillis(), MotionEvent.ACTION_DOWN, 10, 10, 0)));
}
- public void testLayout() throws InterruptedException {
+ @Test
+ public void layoutSimple() throws InterruptedException {
MockLayoutManager layoutManager = new MockLayoutManager();
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(new MockAdapter(3));
@@ -136,7 +164,8 @@
+ " layout manager's layout method", 1, layoutManager.mLayoutCount);
}
- public void testObservingAdapters() {
+ @Test
+ public void observingAdapters() {
MockAdapter adapterOld = new MockAdapter(1);
mRecyclerView.setAdapter(adapterOld);
assertTrue("attached adapter should have observables", adapterOld.hasObservers());
@@ -152,7 +181,8 @@
adapterNew.hasObservers());
}
- public void testAdapterChangeCallbacks() {
+ @Test
+ public void adapterChangeCallbacks() {
MockLayoutManager layoutManager = new MockLayoutManager();
mRecyclerView.setLayoutManager(layoutManager);
MockAdapter adapterOld = new MockAdapter(1);
@@ -170,7 +200,53 @@
adapterNew, null);
}
- public void testSavedStateWithStatelessLayoutManager() throws InterruptedException {
+ @Test
+ public void recyclerOffsetsOnMove() {
+ MockLayoutManager layoutManager = new MockLayoutManager();
+ final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
+ mRecyclerView.setLayoutManager(layoutManager);
+ MockAdapter adapter = new MockAdapter(100) {
+ @Override
+ public void onViewRecycled(RecyclerView.ViewHolder holder) {
+ super.onViewRecycled(holder);
+ recycledVhs.add(holder);
+ }
+ };
+ MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
+ mRecyclerView.setAdapter(adapter);
+ adapter.bindViewHolder(mvh, 20);
+ mRecyclerView.mRecycler.mCachedViews.add(mvh);
+ mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
+
+ mRecyclerView.offsetPositionRecordsForRemove(11, 1, false);
+ assertEquals(1, recycledVhs.size());
+ assertSame(mvh, recycledVhs.get(0));
+ }
+
+ @Test
+ public void recyclerOffsetsOnAdd() {
+ MockLayoutManager layoutManager = new MockLayoutManager();
+ final List<RecyclerView.ViewHolder> recycledVhs = new ArrayList<>();
+ mRecyclerView.setLayoutManager(layoutManager);
+ MockAdapter adapter = new MockAdapter(100) {
+ @Override
+ public void onViewRecycled(RecyclerView.ViewHolder holder) {
+ super.onViewRecycled(holder);
+ recycledVhs.add(holder);
+ }
+ };
+ MockViewHolder mvh = new MockViewHolder(new TextView(getContext()));
+ mRecyclerView.setAdapter(adapter);
+ adapter.bindViewHolder(mvh, 20);
+ mRecyclerView.mRecycler.mCachedViews.add(mvh);
+ mRecyclerView.offsetPositionRecordsForRemove(10, 9, false);
+
+ mRecyclerView.offsetPositionRecordsForInsert(15, 10);
+ assertEquals(11, mvh.mPosition);
+ }
+
+ @Test
+ public void savedStateWithStatelessLayoutManager() throws InterruptedException {
mRecyclerView.setLayoutManager(new MockLayoutManager() {
@Override
public Parcelable onSaveInstanceState() {
@@ -186,7 +262,7 @@
// reset position for reading
parcel.setDataPosition(0);
- RecyclerView restored = new RecyclerView(mContext);
+ RecyclerView restored = new RecyclerView(getContext());
restored.setLayoutManager(new MockLayoutManager());
mRecyclerView.setAdapter(new MockAdapter(3));
// restore
@@ -199,7 +275,8 @@
}
- public void testSavedState() throws InterruptedException {
+ @Test
+ public void savedState() throws InterruptedException {
MockLayoutManager mlm = new MockLayoutManager();
mRecyclerView.setLayoutManager(mlm);
mRecyclerView.setAdapter(new MockAdapter(3));
@@ -216,7 +293,7 @@
// re-create
savedState = RecyclerView.SavedState.CREATOR.createFromParcel(parcel);
- RecyclerView restored = new RecyclerView(mContext);
+ RecyclerView restored = new RecyclerView(getContext());
MockLayoutManager mlmRestored = new MockLayoutManager();
restored.setLayoutManager(mlmRestored);
restored.setAdapter(new MockAdapter(3));
@@ -233,7 +310,8 @@
}
- public void testDontSaveChildrenState() throws InterruptedException {
+ @Test
+ public void dontSaveChildrenState() throws InterruptedException {
MockLayoutManager mlm = new MockLayoutManager() {
@Override
public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -249,6 +327,7 @@
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
final LoggingView itemView = new LoggingView(parent.getContext());
+ //noinspection ResourceType
itemView.setId(3);
return new MockViewHolder(itemView);
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
new file mode 100644
index 0000000..ea1573d3
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewFocusRecoveryTest.java
@@ -0,0 +1,553 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.support.v7.widget;
+
+import static org.hamcrest.CoreMatchers.instanceOf;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.v7.recyclerview.test.R;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.atomic.AtomicLong;
+
+/**
+ * This class only tests the RV's focus recovery logic as focus moves between two views that
+ * represent the same item in the adapter. Keeping a focused view visible is up-to-the
+ * LayoutManager and all FW LayoutManagers already have tests for it.
+ */
+@RunWith(Parameterized.class)
+public class RecyclerViewFocusRecoveryTest extends BaseRecyclerViewInstrumentationTest {
+ TestLayoutManager mLayoutManager;
+ TestAdapter mAdapter;
+
+ private final boolean mFocusOnChild;
+ private final boolean mDisableRecovery;
+
+ @Parameterized.Parameters(name = "focusSubChild:{0}, disable:{1}")
+ public static List<Object[]> getParams() {
+ return Arrays.asList(
+ new Object[]{false, false},
+ new Object[]{true, false},
+ new Object[]{false, true},
+ new Object[]{true, true}
+ );
+ }
+
+ public RecyclerViewFocusRecoveryTest(boolean focusOnChild, boolean disableRecovery) {
+ super(false);
+ mFocusOnChild = focusOnChild;
+ mDisableRecovery = disableRecovery;
+ }
+
+ void setupBasic() throws Throwable {
+ setupBasic(false);
+ }
+
+ void setupBasic(boolean hasStableIds) throws Throwable {
+ TestAdapter adapter = new FocusTestAdapter(10);
+ adapter.setHasStableIds(hasStableIds);
+ setupBasic(adapter, null);
+ }
+
+ void setupBasic(TestLayoutManager layoutManager) throws Throwable {
+ setupBasic(null, layoutManager);
+ }
+
+ void setupBasic(TestAdapter adapter) throws Throwable {
+ setupBasic(adapter, null);
+ }
+
+ void setupBasic(@Nullable TestAdapter adapter, @Nullable TestLayoutManager layoutManager)
+ throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ if (layoutManager == null) {
+ layoutManager = new FocusLayoutManager();
+ }
+
+ if (adapter == null) {
+ adapter = new FocusTestAdapter(10);
+ }
+ mLayoutManager = layoutManager;
+ mAdapter = adapter;
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(mLayoutManager);
+ recyclerView.setPreserveFocusAfterLayout(!mDisableRecovery);
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ mLayoutManager.waitForLayout(1);
+ }
+
+ @Test
+ public void testFocusRecoveryInChange() throws Throwable {
+ setupBasic();
+ ((SimpleItemAnimator) (mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+ mLayoutManager.setSupportsPredictive(true);
+ final RecyclerView.ViewHolder oldVh = focusVh(3);
+
+ mLayoutManager.expectLayouts(2);
+ mAdapter.changeAndNotify(3, 1);
+ mLayoutManager.waitForLayout(2);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+ assertFocusTransition(oldVh, newVh);
+
+ }
+ });
+ mLayoutManager.expectLayouts(1);
+ }
+
+ private void assertFocusTransition(RecyclerView.ViewHolder oldVh,
+ RecyclerView.ViewHolder newVh) {
+ if (mDisableRecovery) {
+ assertFocus(newVh, false);
+ return;
+ }
+ assertThat("test sanity", newVh, notNullValue());
+ assertThat(oldVh, not(sameInstance(newVh)));
+ assertFocus(oldVh, false);
+ assertFocus(newVh, true);
+ }
+
+ @Test
+ public void testFocusRecoveryInTypeChangeWithPredictive() throws Throwable {
+ testFocusRecoveryInTypeChange(true);
+ }
+
+ @Test
+ public void testFocusRecoveryInTypeChangeWithoutPredictive() throws Throwable {
+ testFocusRecoveryInTypeChange(false);
+ }
+
+ private void testFocusRecoveryInTypeChange(boolean withAnimation) throws Throwable {
+ setupBasic();
+ ((SimpleItemAnimator) (mRecyclerView.getItemAnimator())).setSupportsChangeAnimations(true);
+ mLayoutManager.setSupportsPredictive(withAnimation);
+ final RecyclerView.ViewHolder oldVh = focusVh(3);
+ mLayoutManager.expectLayouts(withAnimation ? 2 : 1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Item item = mAdapter.mItems.get(3);
+ item.mType += 2;
+ mAdapter.notifyItemChanged(3);
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+
+ RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForAdapterPosition(3);
+ assertFocusTransition(oldVh, newVh);
+ assertThat("test sanity", oldVh.getItemViewType(), not(newVh.getItemViewType()));
+ }
+
+ @Test
+ public void testRecoverAdapterChangeViaStableIdOnDataSetChanged() throws Throwable {
+ recoverAdapterChangeViaStableId(false, false);
+ }
+
+ @Test
+ public void testRecoverAdapterChangeViaStableIdOnSwap() throws Throwable {
+ recoverAdapterChangeViaStableId(true, false);
+ }
+
+ @Test
+ public void testRecoverAdapterChangeViaStableIdOnDataSetChangedWithTypeChange()
+ throws Throwable {
+ recoverAdapterChangeViaStableId(false, true);
+ }
+
+ @Test
+ public void testRecoverAdapterChangeViaStableIdOnSwapWithTypeChange() throws Throwable {
+ recoverAdapterChangeViaStableId(true, true);
+ }
+
+ private void recoverAdapterChangeViaStableId(final boolean swap, final boolean changeType)
+ throws Throwable {
+ setupBasic(true);
+ RecyclerView.ViewHolder oldVh = focusVh(4);
+ long itemId = oldVh.getItemId();
+
+ mLayoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ Item item = mAdapter.mItems.get(4);
+ if (changeType) {
+ item.mType += 2;
+ }
+ if (swap) {
+ mAdapter = new FocusTestAdapter(8);
+ mAdapter.setHasStableIds(true);
+ mAdapter.mItems.add(2, item);
+ mRecyclerView.swapAdapter(mAdapter, false);
+ } else {
+ mAdapter.mItems.remove(0);
+ mAdapter.mItems.remove(0);
+ mAdapter.notifyDataSetChanged();
+ }
+ }
+ });
+ mLayoutManager.waitForLayout(1);
+
+ RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForItemId(itemId);
+ if (changeType) {
+ assertFocusTransition(oldVh, newVh);
+ } else {
+ // in this case we should use the same VH because we have stable ids
+ assertThat(oldVh, sameInstance(newVh));
+ assertFocus(newVh, true);
+ }
+ }
+
+ @Test
+ public void testDoNotRecoverViaPositionOnSetAdapter() throws Throwable {
+ testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ mRecyclerView.setAdapter(new FocusTestAdapter(10));
+ }
+ });
+ }
+
+ @Test
+ public void testDoNotRecoverViaPositionOnSwapAdapterWithRecycle() throws Throwable {
+ testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ mRecyclerView.swapAdapter(new FocusTestAdapter(10), true);
+ }
+ });
+ }
+
+ @Test
+ public void testDoNotRecoverViaPositionOnSwapAdapterWithoutRecycle() throws Throwable {
+ testDoNotRecoverViaPositionOnNewDataSet(new RecyclerViewLayoutTest.AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ mRecyclerView.swapAdapter(new FocusTestAdapter(10), false);
+ }
+ });
+ }
+
+ public void testDoNotRecoverViaPositionOnNewDataSet(
+ final RecyclerViewLayoutTest.AdapterRunnable runnable) throws Throwable {
+ setupBasic(false);
+ assertThat("test sanity", mAdapter.hasStableIds(), is(false));
+ focusVh(4);
+ mLayoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ runnable.run(mAdapter);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ });
+
+ mLayoutManager.waitForLayout(1);
+ RecyclerView.ViewHolder otherVh = mRecyclerView.findViewHolderForAdapterPosition(4);
+ checkForMainThreadException();
+ // even if the VH is re-used, it will be removed-reAdded so focus will go away from it.
+ assertFocus("should not recover focus if data set is badly invalid", otherVh, false);
+
+ }
+
+ @Test
+ public void testDoNotRecoverIfReplacementIsNotFocusable() throws Throwable {
+ final int TYPE_NO_FOCUS = 1001;
+ TestAdapter adapter = new FocusTestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (holder.getItemViewType() == TYPE_NO_FOCUS) {
+ cast(holder).setFocusable(false);
+ }
+ }
+ };
+ adapter.setHasStableIds(true);
+ setupBasic(adapter);
+ RecyclerView.ViewHolder oldVh = focusVh(3);
+ final long itemId = oldVh.getItemId();
+ mLayoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.mItems.get(3).mType = TYPE_NO_FOCUS;
+ mAdapter.notifyDataSetChanged();
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ RecyclerView.ViewHolder newVh = mRecyclerView.findViewHolderForItemId(itemId);
+ assertFocus(newVh, false);
+ }
+
+ @NonNull
+ private RecyclerView.ViewHolder focusVh(int pos) throws Throwable {
+ final RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+ assertThat("test sanity", oldVh, notNullValue());
+ requestFocus(oldVh);
+ assertFocus("test sanity", oldVh, true);
+ getInstrumentation().waitForIdleSync();
+ return oldVh;
+ }
+
+ @Test
+ public void testDoNotOverrideAdapterRequestedFocus() throws Throwable {
+ final AtomicLong toFocusId = new AtomicLong(-1);
+
+ FocusTestAdapter adapter = new FocusTestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (holder.getItemId() == toFocusId.get()) {
+ try {
+ requestFocus(holder);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ }
+ };
+ adapter.setHasStableIds(true);
+ toFocusId.set(adapter.mItems.get(3).mId);
+ long firstFocusId = toFocusId.get();
+ setupBasic(adapter);
+ RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+ assertFocus(oldVh, true);
+ toFocusId.set(mAdapter.mItems.get(5).mId);
+ mLayoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mAdapter.mItems.get(3).mType += 2;
+ mAdapter.mItems.get(5).mType += 2;
+ mAdapter.notifyDataSetChanged();
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ RecyclerView.ViewHolder requested = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+ assertFocus(oldVh, false);
+ assertFocus(requested, true);
+ RecyclerView.ViewHolder oldReplacement = mRecyclerView
+ .findViewHolderForItemId(firstFocusId);
+ assertFocus(oldReplacement, false);
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void testDoNotOverrideLayoutManagerRequestedFocus() throws Throwable {
+ final AtomicLong toFocusId = new AtomicLong(-1);
+ FocusTestAdapter adapter = new FocusTestAdapter(10);
+ adapter.setHasStableIds(true);
+
+ FocusLayoutManager lm = new FocusLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, state.getItemCount());
+ RecyclerView.ViewHolder toFocus = mRecyclerView
+ .findViewHolderForItemId(toFocusId.get());
+ if (toFocus != null) {
+ try {
+ requestFocus(toFocus);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ layoutLatch.countDown();
+ }
+ };
+
+ toFocusId.set(adapter.mItems.get(3).mId);
+ long firstFocusId = toFocusId.get();
+ setupBasic(adapter, lm);
+
+ RecyclerView.ViewHolder oldVh = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+ assertFocus(oldVh, true);
+ toFocusId.set(mAdapter.mItems.get(5).mId);
+ mLayoutManager.expectLayouts(1);
+ requestLayoutOnUIThread(mRecyclerView);
+ mLayoutManager.waitForLayout(2);
+ RecyclerView.ViewHolder requested = mRecyclerView.findViewHolderForItemId(toFocusId.get());
+ assertFocus(oldVh, false);
+ assertFocus(requested, true);
+ RecyclerView.ViewHolder oldReplacement = mRecyclerView
+ .findViewHolderForItemId(firstFocusId);
+ assertFocus(oldReplacement, false);
+ checkForMainThreadException();
+ }
+
+ private void requestFocus(RecyclerView.ViewHolder viewHolder) throws Throwable {
+ FocusViewHolder fvh = cast(viewHolder);
+ requestFocus(fvh.getViewToFocus(), false);
+ }
+
+ private void assertFocus(RecyclerView.ViewHolder viewHolder, boolean hasFocus) {
+ assertFocus("", viewHolder, hasFocus);
+ }
+
+ private void assertFocus(String msg, RecyclerView.ViewHolder vh, boolean hasFocus) {
+ FocusViewHolder fvh = cast(vh);
+ assertThat(msg, fvh.getViewToFocus().hasFocus(), is(hasFocus));
+ }
+
+ private <T extends FocusViewHolder> T cast(RecyclerView.ViewHolder vh) {
+ assertThat(vh, instanceOf(FocusViewHolder.class));
+ //noinspection unchecked
+ return (T) vh;
+ }
+
+ private class FocusTestAdapter extends TestAdapter {
+
+ public FocusTestAdapter(int count) {
+ super(count);
+ }
+
+ @Override
+ public FocusViewHolder onCreateViewHolder(ViewGroup parent,
+ int viewType) {
+ final FocusViewHolder fvh;
+ if (mFocusOnChild) {
+ fvh = new FocusViewHolderWithChildren(
+ LayoutInflater.from(parent.getContext())
+ .inflate(R.layout.focus_test_item_view, parent, false));
+ } else {
+ fvh = new SimpleFocusViewHolder(new TextView(parent.getContext()));
+ }
+ fvh.setFocusable(true);
+ return fvh;
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ cast(holder).bindTo(mItems.get(position));
+ }
+ }
+
+ private class FocusLayoutManager extends TestLayoutManager {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, state.getItemCount());
+ layoutLatch.countDown();
+ }
+ }
+
+ private class FocusViewHolderWithChildren extends FocusViewHolder {
+ public final ViewGroup root;
+ public final ViewGroup parent1;
+ public final ViewGroup parent2;
+ public final TextView textView;
+
+ public FocusViewHolderWithChildren(View view) {
+ super(view);
+ root = (ViewGroup) view;
+ parent1 = (ViewGroup) root.findViewById(R.id.parent1);
+ parent2 = (ViewGroup) root.findViewById(R.id.parent2);
+ textView = (TextView) root.findViewById(R.id.text_view);
+
+ }
+
+ @Override
+ void setFocusable(boolean focusable) {
+ parent1.setFocusableInTouchMode(focusable);
+ parent2.setFocusableInTouchMode(focusable);
+ textView.setFocusableInTouchMode(focusable);
+ root.setFocusableInTouchMode(focusable);
+
+ parent1.setFocusable(focusable);
+ parent2.setFocusable(focusable);
+ textView.setFocusable(focusable);
+ root.setFocusable(focusable);
+ }
+
+ @Override
+ void onBind(Item item) {
+ textView.setText(getText(item));
+ }
+
+ @Override
+ View getViewToFocus() {
+ return textView;
+ }
+ }
+
+ private class SimpleFocusViewHolder extends FocusViewHolder {
+
+ public SimpleFocusViewHolder(View itemView) {
+ super(itemView);
+ }
+
+ @Override
+ void setFocusable(boolean focusable) {
+ itemView.setFocusableInTouchMode(focusable);
+ itemView.setFocusable(focusable);
+ }
+
+ @Override
+ View getViewToFocus() {
+ return itemView;
+ }
+
+ @Override
+ void onBind(Item item) {
+ ((TextView) (itemView)).setText(getText(item));
+ }
+ }
+
+ private abstract class FocusViewHolder extends TestViewHolder {
+
+ public FocusViewHolder(View itemView) {
+ super(itemView);
+ }
+
+ protected String getText(Item item) {
+ return item.mText + "(" + item.mId + ")";
+ }
+
+ abstract void setFocusable(boolean focusable);
+
+ abstract View getViewToFocus();
+
+ abstract void onBind(Item item);
+
+ final void bindTo(Item item) {
+ mBoundItem = item;
+ onBind(item);
+ }
+ }
+}
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 6fb9592..4f71056 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -17,26 +17,35 @@
package android.support.v7.widget;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.Mockito;
+import android.content.Context;
+import android.support.annotation.Nullable;
import android.support.test.InstrumentationRegistry;
import android.graphics.Color;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.SystemClock;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.ViewCompat;
-import android.test.TouchUtils;
+import android.support.v7.util.TouchUtils;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.Gravity;
-import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewTreeObserver;
+import android.widget.LinearLayout;
import android.widget.TextView;
import java.util.ArrayList;
@@ -49,13 +58,18 @@
import java.util.concurrent.atomic.AtomicInteger;
import static android.support.v7.widget.RecyclerView.NO_POSITION;
-import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_DRAGGING;
+import static android.support.v7.widget.RecyclerView.SCROLL_STATE_IDLE;
import static android.support.v7.widget.RecyclerView.SCROLL_STATE_SETTLING;
import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
-import android.support.test.runner.AndroidJUnit4;
+
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
+import static org.hamcrest.CoreMatchers.is;
@RunWith(AndroidJUnit4.class)
+@MediumTest
public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest {
private static final int FLAG_HORIZONTAL = 1;
private static final int FLAG_VERTICAL = 1 << 1;
@@ -69,29 +83,403 @@
super(DEBUG);
}
- @Before
- @Override
- public void setUp() throws Exception {
- super.setUp();
- injectInstrumentation(InstrumentationRegistry.getInstrumentation());
- }
-
- @After
- @Override
- public void tearDown() throws Exception {
- super.tearDown();
+ @Test
+ public void detachAttachGetReadyWithoutChanges() throws Throwable {
+ detachAttachGetReady(false, false, false);
}
@Test
- public void testFlingFrozen() throws Throwable {
+ public void detachAttachGetReadyRequireLayout() throws Throwable {
+ detachAttachGetReady(true, false, false);
+ }
+
+ @Test
+ public void detachAttachGetReadyRemoveAdapter() throws Throwable {
+ detachAttachGetReady(false, true, false);
+ }
+
+ @Test
+ public void detachAttachGetReadyRemoveLayoutManager() throws Throwable {
+ detachAttachGetReady(false, false, true);
+ }
+
+ private void detachAttachGetReady(final boolean requestLayoutOnDetach,
+ final boolean removeAdapter, final boolean removeLayoutManager) throws Throwable {
+ final LinearLayout ll1 = new LinearLayout(getActivity());
+ final LinearLayout ll2 = new LinearLayout(getActivity());
+ final LinearLayout ll3 = new LinearLayout(getActivity());
+
+ final RecyclerView rv = new RecyclerView(getActivity());
+ ll1.addView(ll2);
+ ll2.addView(ll3);
+ ll3.addView(rv);
+ TestLayoutManager layoutManager = new TestLayoutManager() {
+ @Override
+ public void onLayoutCompleted(RecyclerView.State state) {
+ super.onLayoutCompleted(state);
+ layoutLatch.countDown();
+ }
+
+ @Override
+ public void onDetachedFromWindow(RecyclerView view, RecyclerView.Recycler recycler) {
+ super.onDetachedFromWindow(view, recycler);
+ if (requestLayoutOnDetach) {
+ view.requestLayout();
+ }
+ }
+ };
+ rv.setLayoutManager(layoutManager);
+ rv.setAdapter(new TestAdapter(10));
+ layoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getContainer().addView(ll1);
+ }
+ });
+ layoutManager.waitForLayout(2);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ll1.removeView(ll2);
+ }
+ });
+ getInstrumentation().waitForIdleSync();
+ if (removeLayoutManager) {
+ rv.setLayoutManager(null);
+ rv.setLayoutManager(layoutManager);
+ }
+ if (removeAdapter) {
+ rv.setAdapter(null);
+ rv.setAdapter(new TestAdapter(10));
+ }
+ final boolean requireLayout = requestLayoutOnDetach || removeAdapter || removeLayoutManager;
+ layoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ ll1.addView(ll2);
+ if (requireLayout) {
+ assertTrue(rv.hasPendingAdapterUpdates());
+ assertFalse(rv.mFirstLayoutComplete);
+ } else {
+ assertFalse(rv.hasPendingAdapterUpdates());
+ assertTrue(rv.mFirstLayoutComplete);
+ }
+ }
+ });
+ if (requireLayout) {
+ layoutManager.waitForLayout(2);
+ } else {
+ layoutManager.assertNoLayout("nothing is invalid, layout should not happen", 2);
+ }
+ }
+
+ @Test
+ public void focusSearchWithOtherFocusables() throws Throwable {
+ final LinearLayout container = new LinearLayout(getActivity());
+ container.setOrientation(LinearLayout.VERTICAL);
+ RecyclerView rv = new RecyclerView(getActivity());
+ mRecyclerView = rv;
+ rv.setAdapter(new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setFocusableInTouchMode(true);
+ holder.itemView.setLayoutParams(
+ new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT));
+ }
+ });
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, 1);
+ layoutLatch.countDown();
+ }
+
+ @Nullable
+ @Override
+ public View onFocusSearchFailed(View focused, int direction,
+ RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ assertEquals(View.FOCUS_FORWARD, direction);
+ assertEquals(1, getChildCount());
+ View child0 = getChildAt(0);
+ View view = recycler.getViewForPosition(1);
+ addView(view);
+ measureChild(view, 0, 0);
+ layoutDecorated(view, 0, child0.getBottom(), getDecoratedMeasuredWidth(view),
+ child0.getBottom() + getDecoratedMeasuredHeight(view));
+ return view;
+ }
+ };
+ tlm.setAutoMeasureEnabled(true);
+ rv.setLayoutManager(tlm);
+ TextView viewAbove = new TextView(getActivity());
+ viewAbove.setText("view above");
+ viewAbove.setFocusableInTouchMode(true);
+ container.addView(viewAbove);
+ container.addView(rv);
+ TextView viewBelow = new TextView(getActivity());
+ viewBelow.setText("view below");
+ viewBelow.setFocusableInTouchMode(true);
+ container.addView(viewBelow);
+ tlm.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getContainer().addView(container);
+ }
+ });
+
+ tlm.waitForLayout(2);
+ requestFocus(viewAbove, true);
+ assertTrue(viewAbove.hasFocus());
+ View newFocused = focusSearch(viewAbove, View.FOCUS_FORWARD);
+ assertThat(newFocused, sameInstance(rv.getChildAt(0)));
+ newFocused = focusSearch(rv.getChildAt(0), View.FOCUS_FORWARD);
+ assertThat(newFocused, sameInstance(rv.getChildAt(1)));
+ }
+
+ @Test
+ public void boundingBoxNoTranslation() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(10, 10, 30, 50)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxTranslateX() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setTranslationX(view, 10);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(20, 10, 40, 50)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxTranslateY() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setTranslationY(view, 10);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(10, 20, 30, 60)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxScaleX() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setScaleX(view, 2);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(0, 10, 40, 50)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxScaleY() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setScaleY(view, 2);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(10, -10, 30, 70)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxRotated() throws Throwable {
+ transformedBoundingBoxTest(new ViewRunnable() {
+ @Override
+ public void run(View view) throws RuntimeException {
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setRotation(view, 90);
+ assertThat(getTransformedBoundingBox(view), is(new Rect(0, 20, 40, 40)));
+ }
+ });
+ }
+
+ @Test
+ public void boundingBoxRotatedWithDecorOffsets() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestAdapter adapter = new TestAdapter(1);
+ recyclerView.setAdapter(adapter);
+ recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+ @Override
+ public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
+ RecyclerView.State state) {
+ outRect.set(1, 2, 3, 4);
+ }
+ });
+ TestLayoutManager layoutManager = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ View view = recycler.getViewForPosition(0);
+ addView(view);
+ view.measure(
+ View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
+ );
+ // trigger decor offsets calculation
+ calculateItemDecorationsForChild(view, new Rect());
+ view.layout(10, 10, 30, 50);
+ ViewCompat.setRotation(view, 90);
+ assertThat(RecyclerViewLayoutTest.this.getTransformedBoundingBox(view),
+ is(new Rect(-4, 19, 42, 43)));
+
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setLayoutManager(layoutManager);
+ layoutManager.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ layoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ }
+
+ private Rect getTransformedBoundingBox(View child) {
+ Rect rect = new Rect();
+ mRecyclerView.getLayoutManager().getTransformedBoundingBox(child, true, rect);
+ return rect;
+ }
+
+ public void transformedBoundingBoxTest(final ViewRunnable layout) throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestAdapter adapter = new TestAdapter(1);
+ recyclerView.setAdapter(adapter);
+ TestLayoutManager layoutManager = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ View view = recycler.getViewForPosition(0);
+ addView(view);
+ view.measure(
+ View.MeasureSpec.makeMeasureSpec(20, View.MeasureSpec.EXACTLY),
+ View.MeasureSpec.makeMeasureSpec(40, View.MeasureSpec.EXACTLY)
+ );
+ layout.run(view);
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setLayoutManager(layoutManager);
+ layoutManager.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ layoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void flingFrozen() throws Throwable {
testScrollFrozen(true);
}
@Test
- public void testDragFrozen() throws Throwable {
+ public void dragFrozen() throws Throwable {
testScrollFrozen(false);
}
+ @Test
+ public void requestRectOnScreenWithScrollOffset() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final LayoutAllLayoutManager tlm = spy(new LayoutAllLayoutManager());
+ final int scrollY = 50;
+ RecyclerView.Adapter adapter = new RecyclerView.Adapter() {
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ View view = new View(parent.getContext());
+ view.setScrollY(scrollY);
+ return new RecyclerView.ViewHolder(view) {
+ };
+ }
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {}
+ @Override
+ public int getItemCount() {
+ return 1;
+ }
+ };
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(tlm);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ final View child = recyclerView.getChildAt(0);
+ assertThat(child.getScrollY(), CoreMatchers.is(scrollY));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ recyclerView.requestChildRectangleOnScreen(child, new Rect(3, 4, 5, 6), true);
+ verify(tlm, times(1)).scrollVerticallyBy(eq(-46), any(RecyclerView.Recycler.class),
+ any(RecyclerView.State.class));
+ }
+ });
+ }
+
+ @Test
+ public void reattachAndScrollCrash() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestLayoutManager tlm = new TestLayoutManager() {
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, 0, Math.min(state.getItemCount(), 10));
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ // Access views in the state (that might have been deleted).
+ for (int i = 10; i < state.getItemCount(); i++) {
+ recycler.getViewForPosition(i);
+ }
+ return dy;
+ }
+ };
+
+ final TestAdapter adapter = new TestAdapter(12);
+
+ recyclerView.setAdapter(adapter);
+ recyclerView.setLayoutManager(tlm);
+
+ setRecyclerView(recyclerView);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ getActivity().getContainer().removeView(recyclerView);
+ getActivity().getContainer().addView(recyclerView);
+ try {
+ adapter.deleteAndNotify(1, adapter.getItemCount() - 1);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ recyclerView.scrollBy(0, 10);
+ }
+ });
+ }
+
private void testScrollFrozen(boolean fling) throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -150,7 +538,7 @@
if (fling) {
assertFalse("fling should be blocked", fling(horizontalVelocity, verticalVelocity));
} else { // drag
- TouchUtils.dragViewTo(this, recyclerView,
+ TouchUtils.dragViewTo(getInstrumentation(), recyclerView,
Gravity.LEFT | Gravity.TOP,
mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
}
@@ -164,7 +552,7 @@
if (fling) {
assertTrue("fling should be started", fling(horizontalVelocity, verticalVelocity));
} else { // drag
- TouchUtils.dragViewTo(this, recyclerView,
+ TouchUtils.dragViewTo(getInstrumentation(), recyclerView,
Gravity.LEFT | Gravity.TOP,
mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
}
@@ -173,6 +561,86 @@
}
@Test
+ public void testFocusSearchAfterChangedData() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, 0, 2);
+ layoutLatch.countDown();
+ }
+
+ @Nullable
+ @Override
+ public View onFocusSearchFailed(View focused, int direction,
+ RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ try {
+ View view = recycler.getViewForPosition(state.getItemCount() - 1);
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ return null;
+ }
+ };
+ recyclerView.setLayoutManager(tlm);
+ final TestAdapter adapter = new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setFocusable(false);
+ holder.itemView.setFocusableInTouchMode(false);
+ }
+ };
+ recyclerView.setAdapter(adapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ adapter.mItems.remove(9);
+ adapter.notifyItemRemoved(9);
+ recyclerView.focusSearch(recyclerView.getChildAt(1), View.FOCUS_DOWN);
+ }
+ });
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void testFocusSearchWithRemovedFocusedItem() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setItemAnimator(null);
+ TestLayoutManager tlm = new LayoutAllLayoutManager();
+ recyclerView.setLayoutManager(tlm);
+ final TestAdapter adapter = new TestAdapter(10) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ super.onBindViewHolder(holder, position);
+ holder.itemView.setFocusable(true);
+ holder.itemView.setFocusableInTouchMode(true);
+ }
+ };
+ recyclerView.setAdapter(adapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ final RecyclerView.ViewHolder toFocus = recyclerView.findViewHolderForAdapterPosition(9);
+ requestFocus(toFocus.itemView, true);
+ assertThat("test sanity", toFocus.itemView.hasFocus(), is(true));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ adapter.mItems.remove(9);
+ adapter.notifyItemRemoved(9);
+ recyclerView.focusSearch(toFocus.itemView, View.FOCUS_DOWN);
+ }
+ });
+ checkForMainThreadException();
+ }
+
+
+ @Test
public void testFocusSearchFailFrozen() throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
final CountDownLatch focusLatch = new CountDownLatch(1);
@@ -208,27 +676,33 @@
tlm.expectLayouts(1);
setRecyclerView(recyclerView);
tlm.waitForLayout(2);
-
final View c = recyclerView.getChildAt(recyclerView.getChildCount() - 1);
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- c.requestFocus();
- }
- });
- assertTrue(c.hasFocus());
+ assertTrue("test sanity", requestFocus(c, true));
+ assertTrue("test sanity", c.hasFocus());
freezeLayout(true);
- sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ focusSearch(recyclerView, c, View.FOCUS_DOWN);
assertEquals("onFocusSearchFailed should not be called when layout is frozen",
0, focusSearchCalled.get());
freezeLayout(false);
- sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ focusSearch(c, View.FOCUS_DOWN);
assertTrue(focusLatch.await(2, TimeUnit.SECONDS));
assertEquals(1, focusSearchCalled.get());
}
+ public View focusSearch(final ViewGroup parent, final View focused, final int direction)
+ throws Throwable {
+ final View[] result = new View[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ result[0] = parent.focusSearch(focused, direction);
+ }
+ });
+ return result[0];
+ }
+
@Test
- public void testFrozenAndChangeAdapter() throws Throwable {
+ public void frozenAndChangeAdapter() throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
final AtomicInteger focusSearchCalled = new AtomicInteger(0);
@@ -277,7 +751,88 @@
}
@Test
- public void testScrollToPositionCallback() throws Throwable {
+ public void noLayoutIf0ItemsAreChanged() throws Throwable {
+ unnecessaryNotifyEvents(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.notifyItemRangeChanged(3, 0);
+ }
+ });
+ }
+
+ @Test
+ public void noLayoutIf0ItemsAreChangedWithPayload() throws Throwable {
+ unnecessaryNotifyEvents(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.notifyItemRangeChanged(0, 0, new Object());
+ }
+ });
+ }
+
+ @Test
+ public void noLayoutIf0ItemsAreAdded() throws Throwable {
+ unnecessaryNotifyEvents(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.notifyItemRangeInserted(3, 0);
+ }
+ });
+ }
+
+ @Test
+ public void noLayoutIf0ItemsAreRemoved() throws Throwable {
+ unnecessaryNotifyEvents(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.notifyItemRangeRemoved(3, 0);
+ }
+ });
+ }
+
+ @Test
+ public void noLayoutIfItemMovedIntoItsOwnPlace() throws Throwable {
+ unnecessaryNotifyEvents(new AdapterRunnable() {
+ @Override
+ public void run(TestAdapter adapter) throws Throwable {
+ adapter.notifyItemMoved(3, 3);
+ }
+ });
+ }
+
+ public void unnecessaryNotifyEvents(final AdapterRunnable action) throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ final TestAdapter adapter = new TestAdapter(5);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ super.onLayoutChildren(recycler, state);
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setLayoutManager(tlm);
+ recyclerView.setAdapter(adapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ // ready
+ tlm.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ action.run(adapter);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ });
+ tlm.assertNoLayout("dummy event should not trigger a layout", 1);
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void scrollToPositionCallback() throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
TestLayoutManager tlm = new TestLayoutManager() {
int scrollPos = RecyclerView.NO_POSITION;
@@ -343,56 +898,148 @@
Thread.sleep(1000);
assertEquals("on scroll should NOT be called", 2, rvCounter.get());
assertEquals("on scroll should NOT be called", 2, viewGroupCounter.get());
-
}
@Test
- public void testScrollInBothDirectionEqual() throws Throwable {
+ public void scrollCalllbackOnVisibleRangeExpand() throws Throwable {
+ scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{3, 6});
+ }
+
+ @Test
+ public void scrollCalllbackOnVisibleRangeShrink() throws Throwable {
+ scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{3, 5});
+ }
+
+ @Test
+ public void scrollCalllbackOnVisibleRangeExpand2() throws Throwable {
+ scrollCallbackOnVisibleRangeChange(10, new int[]{3, 5}, new int[]{2, 5});
+ }
+
+ @Test
+ public void scrollCalllbackOnVisibleRangeShrink2() throws Throwable {
+ scrollCallbackOnVisibleRangeChange(10, new int[]{3, 6}, new int[]{2, 6});
+ }
+
+ private void scrollCallbackOnVisibleRangeChange(int itemCount, final int[] beforeRange,
+ final int[] afterRange) throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ final AtomicBoolean beforeState = new AtomicBoolean(true);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ int[] range = beforeState.get() ? beforeRange : afterRange;
+ layoutRange(recycler, range[0], range[1]);
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setLayoutManager(tlm);
+ final TestAdapter adapter = new TestAdapter(itemCount);
+ recyclerView.setAdapter(adapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+
+ RecyclerView.OnScrollListener mockListener = mock(RecyclerView.OnScrollListener.class);
+ recyclerView.addOnScrollListener(mockListener);
+ verify(mockListener, never()).onScrolled(any(RecyclerView.class), anyInt(), anyInt());
+
+ tlm.expectLayouts(1);
+ beforeState.set(false);
+ requestLayoutOnUIThread(recyclerView);
+ tlm.waitForLayout(2);
+ checkForMainThreadException();
+ verify(mockListener).onScrolled(recyclerView, 0, 0);
+ }
+
+ @Test
+ public void addItemOnScroll() throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ final AtomicInteger start = new AtomicInteger(0);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ layoutRange(recycler, start.get(), start.get() + 10);
+ layoutLatch.countDown();
+ }
+ };
+ recyclerView.setLayoutManager(tlm);
+ final TestAdapter adapter = new TestAdapter(100);
+ recyclerView.setAdapter(adapter);
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(1);
+ final Throwable[] error = new Throwable[1];
+ final AtomicBoolean calledOnScroll = new AtomicBoolean(false);
+ recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
+ @Override
+ public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
+ super.onScrolled(recyclerView, dx, dy);
+ calledOnScroll.set(true);
+ try {
+ adapter.addAndNotify(5, 20);
+ } catch (Throwable throwable) {
+ error[0] = throwable;
+ }
+ }
+ });
+ start.set(4);
+ MatcherAssert.assertThat("test sanity", calledOnScroll.get(), CoreMatchers.is(false));
+ tlm.expectLayouts(1);
+ requestLayoutOnUIThread(recyclerView);
+ tlm.waitForLayout(2);
+ checkForMainThreadException();
+ MatcherAssert.assertThat("test sanity", calledOnScroll.get(), CoreMatchers.is(true));
+ MatcherAssert.assertThat(error[0], CoreMatchers.nullValue());
+ }
+
+ @Test
+ public void scrollInBothDirectionEqual() throws Throwable {
scrollInBothDirection(3, 3, 1000, 1000);
}
@Test
- public void testScrollInBothDirectionMoreVertical() throws Throwable {
+ public void scrollInBothDirectionMoreVertical() throws Throwable {
scrollInBothDirection(2, 3, 1000, 1000);
}
@Test
- public void testScrollInBothDirectionMoreHorizontal() throws Throwable {
+ public void scrollInBothDirectionMoreHorizontal() throws Throwable {
scrollInBothDirection(3, 2, 1000, 1000);
}
@Test
- public void testScrollHorizontalOnly() throws Throwable {
+ public void scrollHorizontalOnly() throws Throwable {
scrollInBothDirection(3, 0, 1000, 0);
}
@Test
- public void testScrollVerticalOnly() throws Throwable {
+ public void scrollVerticalOnly() throws Throwable {
scrollInBothDirection(0, 3, 0, 1000);
}
@Test
- public void testScrollInBothDirectionEqualReverse() throws Throwable {
+ public void scrollInBothDirectionEqualReverse() throws Throwable {
scrollInBothDirection(3, 3, -1000, -1000);
}
@Test
- public void testScrollInBothDirectionMoreVerticalReverse() throws Throwable {
+ public void scrollInBothDirectionMoreVerticalReverse() throws Throwable {
scrollInBothDirection(2, 3, -1000, -1000);
}
@Test
- public void testScrollInBothDirectionMoreHorizontalReverse() throws Throwable {
+ public void scrollInBothDirectionMoreHorizontalReverse() throws Throwable {
scrollInBothDirection(3, 2, -1000, -1000);
}
@Test
- public void testScrollHorizontalOnlyReverse() throws Throwable {
+ public void scrollHorizontalOnlyReverse() throws Throwable {
scrollInBothDirection(3, 0, -1000, 0);
}
@Test
- public void testScrollVerticalOnlyReverse() throws Throwable {
+ public void scrollVerticalOnlyReverse() throws Throwable {
scrollInBothDirection(0, 3, 0, -1000);
}
@@ -453,44 +1100,44 @@
}
@Test
- public void testDragHorizontal() throws Throwable {
+ public void dragHorizontal() throws Throwable {
scrollInOtherOrientationTest(FLAG_HORIZONTAL);
}
@Test
- public void testDragVertical() throws Throwable {
+ public void dragVertical() throws Throwable {
scrollInOtherOrientationTest(FLAG_VERTICAL);
}
@Test
- public void testFlingHorizontal() throws Throwable {
+ public void flingHorizontal() throws Throwable {
scrollInOtherOrientationTest(FLAG_HORIZONTAL | FLAG_FLING);
}
@Test
- public void testFlingVertical() throws Throwable {
+ public void flingVertical() throws Throwable {
scrollInOtherOrientationTest(FLAG_VERTICAL | FLAG_FLING);
}
@Test
- public void testNestedDragVertical() throws Throwable {
- TestedFrameLayout tfl = getActivity().mContainer;
+ public void nestedDragVertical() throws Throwable {
+ TestedFrameLayout tfl = getActivity().getContainer();
tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
}
@Test
- public void testNestedDragHorizontal() throws Throwable {
- TestedFrameLayout tfl = getActivity().mContainer;
+ public void nestedDragHorizontal() throws Throwable {
+ TestedFrameLayout tfl = getActivity().getContainer();
tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
}
@Test
- public void testNestedDragHorizontalCallsStopNestedScroll() throws Throwable {
- TestedFrameLayout tfl = getActivity().mContainer;
+ public void nestedDragHorizontalCallsStopNestedScroll() throws Throwable {
+ TestedFrameLayout tfl = getActivity().getContainer();
tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
scrollInOtherOrientationTest(FLAG_HORIZONTAL, 0);
@@ -498,8 +1145,8 @@
}
@Test
- public void testNestedDragVerticalCallsStopNestedScroll() throws Throwable {
- TestedFrameLayout tfl = getActivity().mContainer;
+ public void nestedDragVerticalCallsStopNestedScroll() throws Throwable {
+ TestedFrameLayout tfl = getActivity().getContainer();
tfl.setNestedScrollMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
tfl.setNestedFlingMode(TestedFrameLayout.TEST_NESTED_SCROLL_MODE_CONSUME);
scrollInOtherOrientationTest(FLAG_VERTICAL, 0);
@@ -559,7 +1206,7 @@
assertEquals("fling started", (expectedFlags & FLAG_FLING) != 0,
fling(flingVelocity, flingVelocity));
} else { // drag
- TouchUtils.dragViewTo(this, recyclerView, Gravity.LEFT | Gravity.TOP,
+ TouchUtils.dragViewTo(getInstrumentation(), recyclerView, Gravity.LEFT | Gravity.TOP,
mRecyclerView.getWidth() / 2, mRecyclerView.getHeight() / 2);
}
assertEquals("horizontally scrolled: " + tlm.mScrollHorizontallyAmount,
@@ -580,26 +1227,30 @@
if (!didStart.get()) {
return false;
}
- // cannot set scroll listener in case it is subject to some test so instead doing a busy
- // loop until state goes idle
- while (mRecyclerView.getScrollState() != SCROLL_STATE_IDLE) {
- getInstrumentation().waitForIdleSync();
- }
+ waitForIdleScroll(mRecyclerView);
return true;
}
- private void assertPendingUpdatesAndLayout(TestLayoutManager testLayoutManager,
- final Runnable runnable) throws Throwable {
- testLayoutManager.expectLayouts(1);
+ private void assertPendingUpdatesAndLayoutTest(final AdapterRunnable runnable) throws Throwable {
+ RecyclerView recyclerView = new RecyclerView(getActivity());
+ TestLayoutManager layoutManager = new DumbLayoutManager();
+ final TestAdapter testAdapter = new TestAdapter(10);
+ setupBasic(recyclerView, layoutManager, testAdapter, false);
+ layoutManager.expectLayouts(1);
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- runnable.run();
+ try {
+ runnable.run(testAdapter);
+ } catch (Throwable throwable) {
+ fail("runnable has thrown an exception");
+ }
assertTrue(mRecyclerView.hasPendingAdapterUpdates());
}
});
- testLayoutManager.waitForLayout(1);
+ layoutManager.waitForLayout(1);
assertFalse(mRecyclerView.hasPendingAdapterUpdates());
+ checkForMainThreadException();
}
private void setupBasic(RecyclerView recyclerView, TestLayoutManager tlm,
@@ -616,7 +1267,7 @@
}
@Test
- public void testHasPendingUpdatesBeforeFirstLayout() throws Throwable {
+ public void hasPendingUpdatesBeforeFirstLayout() throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
TestLayoutManager layoutManager = new DumbLayoutManager();
TestAdapter testAdapter = new TestAdapter(10);
@@ -625,7 +1276,7 @@
}
@Test
- public void testNoPendingUpdatesAfterLayout() throws Throwable {
+ public void noPendingUpdatesAfterLayout() throws Throwable {
RecyclerView recyclerView = new RecyclerView(getActivity());
TestLayoutManager layoutManager = new DumbLayoutManager();
TestAdapter testAdapter = new TestAdapter(10);
@@ -634,58 +1285,63 @@
}
@Test
- public void testHasPendingUpdatesWhenAdapterIsChanged() throws Throwable {
- RecyclerView recyclerView = new RecyclerView(getActivity());
- TestLayoutManager layoutManager = new DumbLayoutManager();
- final TestAdapter testAdapter = new TestAdapter(10);
- setupBasic(recyclerView, layoutManager, testAdapter, false);
- assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+ public void hasPendingUpdatesAfterItemIsRemoved() throws Throwable {
+ assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
@Override
- public void run() {
- testAdapter.notifyItemRemoved(1);
+ public void run(TestAdapter testAdapter) throws Throwable {
+ testAdapter.deleteAndNotify(1, 1);
}
});
- assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+ }
+ @Test
+ public void hasPendingUpdatesAfterItemIsInserted() throws Throwable {
+ assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
@Override
- public void run() {
- testAdapter.notifyItemInserted(2);
+ public void run(TestAdapter testAdapter) throws Throwable {
+ testAdapter.addAndNotify(2, 1);
}
});
-
- assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+ }
+ @Test
+ public void hasPendingUpdatesAfterItemIsMoved() throws Throwable {
+ assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
@Override
- public void run() {
- testAdapter.notifyItemMoved(2, 3);
+ public void run(TestAdapter testAdapter) throws Throwable {
+ testAdapter.moveItem(2, 3, true);
}
});
-
- assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+ }
+ @Test
+ public void hasPendingUpdatesAfterItemIsChanged() throws Throwable {
+ assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
@Override
- public void run() {
- testAdapter.notifyItemChanged(2);
+ public void run(TestAdapter testAdapter) throws Throwable {
+ testAdapter.changeAndNotify(2, 1);
}
});
-
- assertPendingUpdatesAndLayout(layoutManager, new Runnable() {
+ }
+ @Test
+ public void hasPendingUpdatesAfterDataSetIsChanged() throws Throwable {
+ assertPendingUpdatesAndLayoutTest(new AdapterRunnable() {
@Override
- public void run() {
- testAdapter.notifyDataSetChanged();
+ public void run(TestAdapter testAdapter) {
+ mRecyclerView.getAdapter().notifyDataSetChanged();
}
});
}
@Test
- public void testTransientStateRecycleViaAdapter() throws Throwable {
+ public void transientStateRecycleViaAdapter() throws Throwable {
transientStateRecycleTest(true, false);
}
@Test
- public void testTransientStateRecycleViaTransientStateCleanup() throws Throwable {
+ public void transientStateRecycleViaTransientStateCleanup() throws Throwable {
transientStateRecycleTest(false, true);
}
@Test
- public void testTransientStateDontRecycle() throws Throwable {
+ public void transientStateDontRecycle() throws Throwable {
transientStateRecycleTest(false, false);
}
@@ -743,7 +1399,7 @@
}
@Test
- public void testAdapterPositionInvalidation() throws Throwable {
+ public void adapterPositionInvalidation() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
final TestAdapter adapter = new TestAdapter(10);
final TestLayoutManager tlm = new TestLayoutManager() {
@@ -777,12 +1433,12 @@
}
@Test
- public void testAdapterPositionsBasic() throws Throwable {
+ public void adapterPositionsBasic() throws Throwable {
adapterPositionsTest(null);
}
@Test
- public void testAdapterPositionsRemoveItems() throws Throwable {
+ public void adapterPositionsRemoveItems() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -792,7 +1448,7 @@
}
@Test
- public void testAdapterPositionsRemoveItemsBefore() throws Throwable {
+ public void adapterPositionsRemoveItemsBefore() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -802,7 +1458,7 @@
}
@Test
- public void testAdapterPositionsAddItemsBefore() throws Throwable {
+ public void adapterPositionsAddItemsBefore() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -812,7 +1468,7 @@
}
@Test
- public void testAdapterPositionsAddItemsInside() throws Throwable {
+ public void adapterPositionsAddItemsInside() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -822,7 +1478,7 @@
}
@Test
- public void testAdapterPositionsMoveItems() throws Throwable {
+ public void adapterPositionsMoveItems() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -832,7 +1488,7 @@
}
@Test
- public void testAdapterPositionsNotifyDataSetChanged() throws Throwable {
+ public void adapterPositionsNotifyDataSetChanged() throws Throwable {
adapterPositionsTest(new AdapterRunnable() {
@Override
public void run(TestAdapter adapter) throws Throwable {
@@ -846,7 +1502,7 @@
}
@Test
- public void testAvoidLeakingRecyclerViewIfViewIsNotRecycled() throws Throwable {
+ public void avoidLeakingRecyclerViewIfViewIsNotRecycled() throws Throwable {
final AtomicBoolean failedToRecycle = new AtomicBoolean(false);
RecyclerView rv = new RecyclerView(getActivity());
TestLayoutManager tlm = new TestLayoutManager() {
@@ -895,7 +1551,7 @@
}
@Test
- public void testAvoidLeakingRecyclerViewViaViewHolder() throws Throwable {
+ public void avoidLeakingRecyclerViewViaViewHolder() throws Throwable {
RecyclerView rv = new RecyclerView(getActivity());
TestLayoutManager tlm = new TestLayoutManager() {
@Override
@@ -928,6 +1584,147 @@
checkForMainThreadException();
}
+ @Test
+ public void duplicateAdapterPositionTest() throws Throwable {
+ final TestAdapter testAdapter = new TestAdapter(10);
+ final TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, state.getItemCount());
+ if (!state.isPreLayout()) {
+ while (!recycler.getScrapList().isEmpty()) {
+ RecyclerView.ViewHolder viewHolder = recycler.getScrapList().get(0);
+ addDisappearingView(viewHolder.itemView, 0);
+ }
+ }
+ layoutLatch.countDown();
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return true;
+ }
+ };
+ final DefaultItemAnimator animator = new DefaultItemAnimator();
+ animator.setSupportsChangeAnimations(true);
+ animator.setChangeDuration(10000);
+ testAdapter.setHasStableIds(true);
+ final TestRecyclerView recyclerView = new TestRecyclerView(getActivity());
+ recyclerView.setLayoutManager(tlm);
+ recyclerView.setAdapter(testAdapter);
+ recyclerView.setItemAnimator(animator);
+
+ tlm.expectLayouts(1);
+ setRecyclerView(recyclerView);
+ tlm.waitForLayout(2);
+
+ tlm.expectLayouts(2);
+ testAdapter.mItems.get(2).mType += 2;
+ final int itemId = testAdapter.mItems.get(2).mId;
+ testAdapter.changeAndNotify(2, 1);
+ tlm.waitForLayout(2);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ assertThat("test sanity", recyclerView.getChildCount(), CoreMatchers.is(11));
+ // now mangle the order and run the test
+ RecyclerView.ViewHolder hidden = null;
+ RecyclerView.ViewHolder updated = null;
+ for (int i = 0; i < recyclerView.getChildCount(); i ++) {
+ View view = recyclerView.getChildAt(i);
+ RecyclerView.ViewHolder vh = recyclerView.getChildViewHolder(view);
+ if (vh.getAdapterPosition() == 2) {
+ if (mRecyclerView.mChildHelper.isHidden(view)) {
+ assertThat(hidden, CoreMatchers.nullValue());
+ hidden = vh;
+ } else {
+ assertThat(updated, CoreMatchers.nullValue());
+ updated = vh;
+ }
+ }
+ }
+ assertThat(hidden, CoreMatchers.notNullValue());
+ assertThat(updated, CoreMatchers.notNullValue());
+
+ mRecyclerView.eatRequestLayout();
+
+ // first put the hidden child back
+ int index1 = mRecyclerView.indexOfChild(hidden.itemView);
+ int index2 = mRecyclerView.indexOfChild(updated.itemView);
+ if (index1 < index2) {
+ // swap views
+ swapViewsAtIndices(recyclerView, index1, index2);
+ }
+ assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
+
+ assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+ CoreMatchers.sameInstance(updated));
+ assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+ CoreMatchers.sameInstance(updated));
+ assertThat(recyclerView.findViewHolderForItemId(itemId),
+ CoreMatchers.sameInstance(updated));
+
+ // now swap back
+ swapViewsAtIndices(recyclerView, index1, index2);
+
+ assertThat(tlm.findViewByPosition(2), CoreMatchers.sameInstance(updated.itemView));
+ assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+ CoreMatchers.sameInstance(updated));
+ assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+ CoreMatchers.sameInstance(updated));
+ assertThat(recyclerView.findViewHolderForItemId(itemId),
+ CoreMatchers.sameInstance(updated));
+
+ // now remove updated. re-assert fallback to the hidden one
+ tlm.removeView(updated.itemView);
+
+ assertThat(tlm.findViewByPosition(2), CoreMatchers.nullValue());
+ assertThat(recyclerView.findViewHolderForAdapterPosition(2),
+ CoreMatchers.sameInstance(hidden));
+ assertThat(recyclerView.findViewHolderForLayoutPosition(2),
+ CoreMatchers.sameInstance(hidden));
+ assertThat(recyclerView.findViewHolderForItemId(itemId),
+ CoreMatchers.sameInstance(hidden));
+ }
+ });
+
+ }
+
+ private void swapViewsAtIndices(TestRecyclerView recyclerView, int index1, int index2) {
+ if (index1 == index2) {
+ return;
+ }
+ if (index2 < index1) {
+ int tmp = index1;
+ index1 = index2;
+ index2 = tmp;
+ }
+ final View v1 = recyclerView.getChildAt(index1);
+ final View v2 = recyclerView.getChildAt(index2);
+ boolean v1Hidden = recyclerView.mChildHelper.isHidden(v1);
+ boolean v2Hidden = recyclerView.mChildHelper.isHidden(v2);
+ // must unhide before swap otherwise bucket indices will become invalid.
+ if (v1Hidden) {
+ mRecyclerView.mChildHelper.unhide(v1);
+ }
+ if (v2Hidden) {
+ mRecyclerView.mChildHelper.unhide(v2);
+ }
+ recyclerView.detachViewFromParent(index2);
+ recyclerView.attachViewToParent(v2, index1, v2.getLayoutParams());
+ recyclerView.detachViewFromParent(index1 + 1);
+ recyclerView.attachViewToParent(v1, index2, v1.getLayoutParams());
+
+ if (v1Hidden) {
+ mRecyclerView.mChildHelper.hide(v1);
+ }
+ if (v2Hidden) {
+ mRecyclerView.mChildHelper.hide(v2);
+ }
+ }
+
public void adapterPositionsTest(final AdapterRunnable adapterChanges) throws Throwable {
final TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager() {
@@ -995,7 +1792,7 @@
}
@Test
- public void testScrollStateForSmoothScroll() throws Throwable {
+ public void scrollStateForSmoothScroll() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1026,7 +1823,7 @@
}
@Test
- public void testScrollStateForSmoothScrollWithStop() throws Throwable {
+ public void scrollStateForSmoothScrollWithStop() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1064,7 +1861,7 @@
}
@Test
- public void testScrollStateForFling() throws Throwable {
+ public void scrollStateForFling() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1098,7 +1895,7 @@
}
@Test
- public void testScrollStateForFlingWithStop() throws Throwable {
+ public void scrollStateForFlingWithStop() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1139,7 +1936,7 @@
}
@Test
- public void testScrollStateDrag() throws Throwable {
+ public void scrollStateDrag() throws Throwable {
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager tlm = new TestLayoutManager();
RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1201,7 +1998,7 @@
}
@Test
- public void testRecycleScrap() throws Throwable {
+ public void recycleScrap() throws Throwable {
recycleScrapTest(false);
removeRecyclerView();
recycleScrapTest(true);
@@ -1271,19 +2068,22 @@
}
@Test
- public void testAccessRecyclerOnOnMeasure() throws Throwable {
- accessRecyclerOnOnMeasureTest(false);
- removeRecyclerView();
+ public void aAccessRecyclerOnOnMeasureWithPredictive() throws Throwable {
accessRecyclerOnOnMeasureTest(true);
}
@Test
- public void testSmoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
+ public void accessRecyclerOnOnMeasureWithoutPredictive() throws Throwable {
+ accessRecyclerOnOnMeasureTest(false);
+ }
+
+ @Test
+ public void smoothScrollWithRemovedItemsAndRemoveItem() throws Throwable {
smoothScrollTest(true);
}
@Test
- public void testSmoothScrollWithRemovedItems() throws Throwable {
+ public void smoothScrollWithRemovedItems() throws Throwable {
smoothScrollTest(false);
}
@@ -1408,7 +2208,7 @@
}
@Test
- public void testConsecutiveSmoothScroll() throws Throwable {
+ public void consecutiveSmoothScroll() throws Throwable {
final AtomicInteger visibleChildCount = new AtomicInteger(10);
final AtomicInteger totalScrolled = new AtomicInteger(0);
final TestLayoutManager lm = new TestLayoutManager() {
@@ -1491,8 +2291,10 @@
assertNotNull(view);
assertEquals(i, getPosition(view));
}
- assertEquals(state.toString(),
- expectedOnMeasureStateCount.get(), state.getItemCount());
+ if (!state.isPreLayout()) {
+ assertEquals(state.toString(),
+ expectedOnMeasureStateCount.get(), state.getItemCount());
+ }
} catch (Throwable t) {
postExceptionToInstrumentation(t);
}
@@ -1522,7 +2324,7 @@
}
@Test
- public void testSetCompatibleAdapter() throws Throwable {
+ public void setCompatibleAdapter() throws Throwable {
compatibleAdapterTest(true, true);
removeRecyclerView();
compatibleAdapterTest(false, true);
@@ -1576,7 +2378,7 @@
}
@Test
- public void testSetIncompatibleAdapter() throws Throwable {
+ public void setIncompatibleAdapter() throws Throwable {
incompatibleAdapterTest(true);
incompatibleAdapterTest(false);
}
@@ -1612,7 +2414,7 @@
}
@Test
- public void testRecycleIgnored() throws Throwable {
+ public void recycleIgnored() throws Throwable {
final TestAdapter adapter = new TestAdapter(10);
final TestLayoutManager lm = new TestLayoutManager() {
@Override
@@ -1654,7 +2456,7 @@
}
@Test
- public void testFindIgnoredByPosition() throws Throwable {
+ public void findIgnoredByPosition() throws Throwable {
final TestAdapter adapter = new TestAdapter(10);
final TestLayoutManager lm = new TestLayoutManager() {
@Override
@@ -1694,7 +2496,7 @@
}
@Test
- public void testInvalidateAllDecorOffsets() throws Throwable {
+ public void invalidateAllDecorOffsets() throws Throwable {
final TestAdapter adapter = new TestAdapter(10);
final RecyclerView recyclerView = new RecyclerView(getActivity());
final AtomicBoolean invalidatedOffsets = new AtomicBoolean(true);
@@ -1836,7 +2638,7 @@
}
@Test
- public void testInvalidateDecorOffsets() throws Throwable {
+ public void invalidateDecorOffsets() throws Throwable {
final TestAdapter adapter = new TestAdapter(10);
adapter.setHasStableIds(true);
final RecyclerView recyclerView = new RecyclerView(getActivity());
@@ -1918,7 +2720,7 @@
}
@Test
- public void testMovingViaStableIds() throws Throwable {
+ public void movingViaStableIds() throws Throwable {
stableIdsMoveTest(true);
removeRecyclerView();
stableIdsMoveTest(false);
@@ -1995,7 +2797,7 @@
}
@Test
- public void testAdapterChangeDuringLayout() throws Throwable {
+ public void adapterChangeDuringLayout() throws Throwable {
adapterChangeInMainThreadTest("notifyDataSetChanged", new Runnable() {
@Override
public void run() {
@@ -2026,6 +2828,7 @@
public void adapterChangeInMainThreadTest(String msg,
final Runnable onLayoutRunnable) throws Throwable {
+ setIgnoreMainThreadException(true);
final AtomicBoolean doneFirstLayout = new AtomicBoolean(false);
TestAdapter testAdapter = new TestAdapter(10);
TestLayoutManager lm = new TestLayoutManager() {
@@ -2057,12 +2860,11 @@
lm.waitForLayout(2);
removeRecyclerView();
assertTrue("Invalid data updates should be caught:" + msg,
- mainThreadException instanceof IllegalStateException);
- mainThreadException = null;
+ getMainThreadException() instanceof IllegalStateException);
}
@Test
- public void testAdapterChangeDuringScroll() throws Throwable {
+ public void adapterChangeDuringScroll() throws Throwable {
for (int orientation : new int[]{OrientationHelper.HORIZONTAL,
OrientationHelper.VERTICAL}) {
adapterChangeDuringScrollTest("notifyDataSetChanged", orientation,
@@ -2096,6 +2898,7 @@
public void adapterChangeDuringScrollTest(String msg, final int orientation,
final Runnable onScrollRunnable) throws Throwable {
+ setIgnoreMainThreadException(true);
TestAdapter testAdapter = new TestAdapter(100);
TestLayoutManager lm = new TestLayoutManager() {
@Override
@@ -2154,12 +2957,11 @@
lm.waitForLayout(2);
removeRecyclerView();
assertTrue("Invalid data updates should be caught:" + msg,
- mainThreadException instanceof IllegalStateException);
- mainThreadException = null;
+ getMainThreadException() instanceof IllegalStateException);
}
@Test
- public void testRecycleOnDetach() throws Throwable {
+ public void recycleOnDetach() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
final TestAdapter testAdapter = new TestAdapter(10);
final AtomicBoolean didRunOnDetach = new AtomicBoolean(false);
@@ -2189,7 +2991,7 @@
}
@Test
- public void testUpdatesWhileDetached() throws Throwable {
+ public void updatesWhileDetached() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
final int initialAdapterSize = 20;
final TestAdapter adapter = new TestAdapter(initialAdapterSize);
@@ -2212,7 +3014,7 @@
}
@Test
- public void testUpdatesAfterDetach() throws Throwable {
+ public void updatesAfterDetach() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
final int initialAdapterSize = 20;
final TestAdapter adapter = new TestAdapter(initialAdapterSize);
@@ -2253,7 +3055,7 @@
}
@Test
- public void testNotifyDataSetChangedWithStableIds() throws Throwable {
+ public void notifyDataSetChangedWithStableIds() throws Throwable {
final int defaultViewType = 1;
final Map<Item, Integer> viewTypeMap = new HashMap<Item, Integer>();
final Map<Integer, Integer> oldPositionToNewPositionMapping =
@@ -2365,12 +3167,12 @@
}
@Test
- public void testCallbacksDuringAdapterSwap() throws Throwable {
+ public void callbacksDuringAdapterSwap() throws Throwable {
callbacksDuringAdapterChange(true);
}
@Test
- public void testCallbacksDuringAdapterSet() throws Throwable {
+ public void callbacksDuringAdapterSet() throws Throwable {
callbacksDuringAdapterChange(false);
}
@@ -2453,7 +3255,7 @@
}
@Test
- public void testFindViewById() throws Throwable {
+ public void findViewById() throws Throwable {
findViewByIdTest(false);
removeRecyclerView();
findViewByIdTest(true);
@@ -2535,7 +3337,7 @@
}
@Test
- public void testTypeForCache() throws Throwable {
+ public void typeForCache() throws Throwable {
final AtomicInteger viewType = new AtomicInteger(1);
final TestAdapter adapter = new TestAdapter(100) {
@Override
@@ -2593,7 +3395,7 @@
}
@Test
- public void testTypeForExistingViews() throws Throwable {
+ public void typeForExistingViews() throws Throwable {
final AtomicInteger viewType = new AtomicInteger(1);
final int invalidatedCount = 2;
final int layoutStart = 2;
@@ -2650,7 +3452,7 @@
@Test
- public void testState() throws Throwable {
+ public void state() throws Throwable {
final TestAdapter adapter = new TestAdapter(10);
final RecyclerView recyclerView = new RecyclerView(getActivity());
recyclerView.setAdapter(adapter);
@@ -2672,10 +3474,10 @@
runTestOnUiThread(new Runnable() {
@Override
public void run() {
- getActivity().mContainer.addView(recyclerView);
+ getActivity().getContainer().addView(recyclerView);
}
});
- testLayoutManager.waitForLayout(2, TimeUnit.SECONDS);
+ testLayoutManager.waitForLayout(2);
assertEquals("item count in state should be correct", adapter.getItemCount()
, itemCount.get());
@@ -2712,7 +3514,7 @@
}
@Test
- public void testDetachWithoutLayoutManager() throws Throwable {
+ public void detachWithoutLayoutManager() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
runTestOnUiThread(new Runnable() {
@Override
@@ -2729,13 +3531,12 @@
}
@Test
- public void testUpdateHiddenView() throws Throwable {
- final RecyclerView.ViewHolder[] mTargetVH = new RecyclerView.ViewHolder[1];
+ public void updateHiddenView() throws Throwable {
final RecyclerView recyclerView = new RecyclerView(getActivity());
final int[] preLayoutRange = new int[]{0, 10};
final int[] postLayoutRange = new int[]{0, 10};
final AtomicBoolean enableGetViewTest = new AtomicBoolean(false);
- final List<Integer> disappearingPositions = new ArrayList<Integer>();
+ final List<Integer> disappearingPositions = new ArrayList<>();
final TestLayoutManager tlm = new TestLayoutManager() {
@Override
public boolean supportsPredictiveItemAnimations() {
@@ -2754,6 +3555,7 @@
// test sanity.
assertNull(findViewByPosition(position));
final View view = recycler.getViewForPosition(position);
+ assertNotNull(view);
addDisappearingView(view);
measureChildWithMargins(view, 0, 0);
// position item out of bounds.
@@ -2767,18 +3569,15 @@
layoutLatch.countDown();
}
};
-
- recyclerView.getItemAnimator().setMoveDuration(2000);
- recyclerView.getItemAnimator().setRemoveDuration(2000);
+ recyclerView.getItemAnimator().setMoveDuration(4000);
+ recyclerView.getItemAnimator().setRemoveDuration(4000);
final TestAdapter adapter = new TestAdapter(100);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(tlm);
tlm.expectLayouts(1);
setRecyclerView(recyclerView);
-
tlm.waitForLayout(1);
checkForMainThreadException();
- mTargetVH[0] = recyclerView.findViewHolderForAdapterPosition(0);
// now, a child disappears
disappearingPositions.add(0);
// layout one shifted
@@ -2797,10 +3596,12 @@
@Override
public void run() {
try {
+ assertThat("test sanity, should still be animating",
+ mRecyclerView.isAnimating(), CoreMatchers.is(true));
adapter.changeAndNotify(0, 1);
adapter.deleteAndNotify(0, 1);
} catch (Throwable throwable) {
- throwable.printStackTrace();
+ fail(throwable.getMessage());
}
}
});
@@ -2809,34 +3610,34 @@
}
@Test
- public void testFocusBigViewOnTop() throws Throwable {
+ public void focusBigViewOnTop() throws Throwable {
focusTooBigViewTest(Gravity.TOP);
}
@Test
- public void testFocusBigViewOnLeft() throws Throwable {
+ public void focusBigViewOnLeft() throws Throwable {
focusTooBigViewTest(Gravity.LEFT);
}
@Test
- public void testFocusBigViewOnRight() throws Throwable {
+ public void focusBigViewOnRight() throws Throwable {
focusTooBigViewTest(Gravity.RIGHT);
}
@Test
- public void testFocusBigViewOnBottom() throws Throwable {
+ public void focusBigViewOnBottom() throws Throwable {
focusTooBigViewTest(Gravity.BOTTOM);
}
@Test
- public void testFocusBigViewOnLeftRTL() throws Throwable {
+ public void focusBigViewOnLeftRTL() throws Throwable {
focusTooBigViewTest(Gravity.LEFT, true);
assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
mRecyclerView.getLayoutManager().getLayoutDirection());
}
@Test
- public void testFocusBigViewOnRightRTL() throws Throwable {
+ public void focusBigViewOnRightRTL() throws Throwable {
focusTooBigViewTest(Gravity.RIGHT, true);
assertEquals("test sanity", ViewCompat.LAYOUT_DIRECTION_RTL,
mRecyclerView.getLayoutManager().getLayoutDirection());
@@ -2938,8 +3739,8 @@
setRecyclerView(rv);
tlm.waitForLayout(2);
View view = rv.getChildAt(0);
- requestFocus(view);
- Thread.sleep(1000);
+ assertTrue("test sanity", requestFocus(view, true));
+ assertTrue("test sanity", view.hasFocus());
assertEquals(vDesiredDist.get(), vScrollDist.get());
assertEquals(hDesiredDist.get(), hScrollDist.get());
assertEquals(mRecyclerView.getPaddingTop(), view.getTop());
@@ -2952,16 +3753,90 @@
}
@Test
- public void testFocusRectOnScreenWithDecorOffsets() throws Throwable {
+ public void firstLayoutWithAdapterChanges() throws Throwable {
+ final TestAdapter adapter = new TestAdapter(0);
+ final RecyclerView rv = new RecyclerView(getActivity());
+ setVisibility(rv, View.GONE);
+ TestLayoutManager tlm = new TestLayoutManager() {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ try {
+ super.onLayoutChildren(recycler, state);
+ layoutRange(recycler, 0, state.getItemCount());
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ } finally {
+ layoutLatch.countDown();
+ }
+ }
+
+ @Override
+ public boolean supportsPredictiveItemAnimations() {
+ return true;
+ }
+ };
+ rv.setLayoutManager(tlm);
+ rv.setAdapter(adapter);
+ rv.setHasFixedSize(true);
+ setRecyclerView(rv);
+ tlm.expectLayouts(1);
+ tlm.assertNoLayout("test sanity, layout should not run", 1);
+ getInstrumentation().waitForIdleSync();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ adapter.addAndNotify(2);
+ } catch (Throwable throwable) {
+ throwable.printStackTrace();
+ }
+ rv.setVisibility(View.VISIBLE);
+ }
+ });
+ checkForMainThreadException();
+ tlm.waitForLayout(2);
+ assertEquals(2, rv.getChildCount());
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void computeScrollOfsetWithoutLayoutManager() throws Throwable {
+ RecyclerView rv = new RecyclerView(getActivity());
+ rv.setAdapter(new TestAdapter(10));
+ setRecyclerView(rv);
+ assertEquals(0, rv.computeHorizontalScrollExtent());
+ assertEquals(0, rv.computeHorizontalScrollOffset());
+ assertEquals(0, rv.computeHorizontalScrollRange());
+
+ assertEquals(0, rv.computeVerticalScrollExtent());
+ assertEquals(0, rv.computeVerticalScrollOffset());
+ assertEquals(0, rv.computeVerticalScrollRange());
+ }
+
+ @Test
+ public void computeScrollOfsetWithoutAdapter() throws Throwable {
+ RecyclerView rv = new RecyclerView(getActivity());
+ rv.setLayoutManager(new TestLayoutManager());
+ setRecyclerView(rv);
+ assertEquals(0, rv.computeHorizontalScrollExtent());
+ assertEquals(0, rv.computeHorizontalScrollOffset());
+ assertEquals(0, rv.computeHorizontalScrollRange());
+
+ assertEquals(0, rv.computeVerticalScrollExtent());
+ assertEquals(0, rv.computeVerticalScrollOffset());
+ assertEquals(0, rv.computeVerticalScrollRange());
+ }
+
+ @Test
+ public void focusRectOnScreenWithDecorOffsets() throws Throwable {
focusRectOnScreenTest(true);
}
@Test
- public void testFocusRectOnScreenWithout() throws Throwable {
+ public void focusRectOnScreenWithout() throws Throwable {
focusRectOnScreenTest(false);
}
-
public void focusRectOnScreenTest(boolean addItemDecors) throws Throwable {
RecyclerView rv = new RecyclerView(getActivity());
final AtomicInteger scrollDist = new AtomicInteger(0);
@@ -3006,13 +3881,12 @@
tlm.waitForLayout(2);
View view = rv.getChildAt(0);
- requestFocus(view);
- Thread.sleep(1000);
+ requestFocus(view, true);
assertEquals(addItemDecors ? -30 : -20, scrollDist.get());
}
@Test
- public void testUnimplementedSmoothScroll() throws Throwable {
+ public void unimplementedSmoothScroll() throws Throwable {
final AtomicInteger receivedScrollToPosition = new AtomicInteger(-1);
final AtomicInteger receivedSmoothScrollToPosition = new AtomicInteger(-1);
final CountDownLatch cbLatch = new CountDownLatch(2);
@@ -3055,11 +3929,11 @@
setRecyclerView(rv);
tlm.waitForLayout(2);
freezeLayout(true);
- smoothScrollToPosition(35);
+ smoothScrollToPosition(35, false);
assertEquals("smoothScrollToPosition should be ignored when frozen",
-1, receivedSmoothScrollToPosition.get());
freezeLayout(false);
- smoothScrollToPosition(35);
+ smoothScrollToPosition(35, false);
assertTrue("both scrolls should be called", cbLatch.await(3, TimeUnit.SECONDS));
checkForMainThreadException();
assertEquals(35, receivedSmoothScrollToPosition.get());
@@ -3067,15 +3941,57 @@
}
@Test
- public void testJumpingJackSmoothScroller() throws Throwable {
+ public void jumpingJackSmoothScroller() throws Throwable {
jumpingJackSmoothScrollerTest(true);
}
@Test
- public void testJumpingJackSmoothScrollerGoesIdle() throws Throwable {
+ public void jumpingJackSmoothScrollerGoesIdle() throws Throwable {
jumpingJackSmoothScrollerTest(false);
}
+ @Test
+ public void testScrollByBeforeFirstLayout() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ TestAdapter adapter = new TestAdapter(10);
+ recyclerView.setLayoutManager(new TestLayoutManager() {
+ AtomicBoolean didLayout = new AtomicBoolean(false);
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ super.onLayoutChildren(recycler, state);
+ didLayout.set(true);
+ }
+
+ @Override
+ public int scrollVerticallyBy(int dy, RecyclerView.Recycler recycler,
+ RecyclerView.State state) {
+ assertThat("should run layout before scroll",
+ didLayout.get(), CoreMatchers.is(true));
+ return super.scrollVerticallyBy(dy, recycler, state);
+ }
+
+ @Override
+ public boolean canScrollVertically() {
+ return true;
+ }
+ });
+ recyclerView.setAdapter(adapter);
+
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ setRecyclerView(recyclerView);
+ recyclerView.scrollBy(10, 19);
+ } catch (Throwable throwable) {
+ postExceptionToInstrumentation(throwable);
+ }
+ }
+ });
+
+ checkForMainThreadException();
+ }
+
private void jumpingJackSmoothScrollerTest(final boolean succeed) throws Throwable {
final List<Integer> receivedScrollToPositions = new ArrayList<>();
final TestAdapter testAdapter = new TestAdapter(200);
@@ -3200,9 +4116,49 @@
}
}
- private static interface AdapterRunnable {
+ public interface AdapterRunnable {
- public void run(TestAdapter adapter) throws Throwable;
+ void run(TestAdapter adapter) throws Throwable;
}
+ public class LayoutAllLayoutManager extends TestLayoutManager {
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+ detachAndScrapAttachedViews(recycler);
+ layoutRange(recycler, 0, state.getItemCount());
+ layoutLatch.countDown();
+ }
+ }
+
+ /**
+ * Proxy class to make protected methods public
+ */
+ public static class TestRecyclerView extends RecyclerView {
+
+ public TestRecyclerView(Context context) {
+ super(context);
+ }
+
+ public TestRecyclerView(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public TestRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ }
+
+ @Override
+ public void detachViewFromParent(int index) {
+ super.detachViewFromParent(index);
+ }
+
+ @Override
+ public void attachViewToParent(View child, int index, ViewGroup.LayoutParams params) {
+ super.attachViewToParent(child, index, params);
+ }
+ }
+
+ private static interface ViewRunnable {
+ void run(View view) throws RuntimeException;
+ }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java b/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java
new file mode 100644
index 0000000..4b670fa
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/ScrollToPositionWithAutoMeasure.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.graphics.Rect;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Tests scroll to position with wrap content to make sure that LayoutManagers can keep track of
+ * the position if layout is called multiple times.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4.class)
+public class ScrollToPositionWithAutoMeasure extends BaseRecyclerViewInstrumentationTest {
+ @Test
+ public void testLinearLayoutManager() throws Throwable {
+ LinearLayoutManager llm = new LinearLayoutManager(getActivity());
+ llm.ensureLayoutState();
+ test(llm, llm.mOrientationHelper);
+ }
+
+ @Test
+ public void testGridLayoutManager() throws Throwable {
+ GridLayoutManager glm = new GridLayoutManager(getActivity(), 3);
+ glm.ensureLayoutState();
+ test(glm, glm.mOrientationHelper);
+ }
+
+ @Test
+ public void testStaggeredGridLayoutManager() throws Throwable {
+ StaggeredGridLayoutManager sglm = new StaggeredGridLayoutManager(3,
+ StaggeredGridLayoutManager.VERTICAL);
+ test(sglm, sglm.mPrimaryOrientation);
+ }
+
+ public void test(final RecyclerView.LayoutManager llm,
+ final OrientationHelper orientationHelper) throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setLayoutManager(llm);
+ recyclerView.setAdapter(new TestAdapter(1000));
+ setRecyclerView(recyclerView);
+ getInstrumentation().waitForIdleSync();
+ assertThat("Test sanity", recyclerView.getChildCount() > 0, is(true));
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ View lastChild = llm.getChildAt(llm.getChildCount() - 1);
+ int lastChildPos = recyclerView.getChildAdapterPosition(lastChild);
+ int targetPos = lastChildPos * 2;
+ llm.scrollToPosition(targetPos);
+ recyclerView.measure(
+ makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+ makeMeasureSpec(recyclerView.getHeight(), AT_MOST));
+ assertThat(recyclerView.findViewHolderForAdapterPosition(targetPos),
+ notNullValue());
+ // make sure it is still visible from top at least
+ int size = orientationHelper.getDecoratedMeasurement(
+ recyclerView.findViewHolderForAdapterPosition(targetPos).itemView);
+ recyclerView.measure(
+ makeMeasureSpec(recyclerView.getWidth(), EXACTLY),
+ makeMeasureSpec(size + 1, EXACTLY));
+ recyclerView.layout(0, 0, recyclerView.getMeasuredWidth(),
+ recyclerView.getMeasuredHeight());
+ assertThat(recyclerView.findViewHolderForAdapterPosition(targetPos),
+ notNullValue());
+ RecyclerView.ViewHolder viewHolder =
+ recyclerView.findViewHolderForAdapterPosition(targetPos);
+ assertThat(viewHolder, notNullValue());
+ Rect viewBounds = new Rect();
+ llm.getDecoratedBoundsWithMargins(viewHolder.itemView, viewBounds);
+ Rect rvBounds = new Rect(0, 0, llm.getWidth(), llm.getHeight());
+ assertThat(rvBounds + " vs " + viewBounds, rvBounds.contains(viewBounds), is(true));
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
new file mode 100644
index 0000000..f8d11d8
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -0,0 +1,847 @@
+/*
+ * 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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Rect;
+import android.os.Looper;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.v4.view.ViewCompat;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.util.Log;
+import android.view.View;
+import android.view.ViewParent;
+
+import java.util.Arrays;
+import java.util.BitSet;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static android.support.v7.widget.LayoutState.LAYOUT_START;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
+
+import static org.hamcrest.CoreMatchers.hasItem;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.sameInstance;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class StaggeredGridLayoutManagerBaseConfigSetTest
+ extends BaseStaggeredGridLayoutManagerTest {
+
+ @Parameterized.Parameters(name = "{0}")
+ public static List<Config> getParams() {
+ return createBaseVariations();
+ }
+
+ private final Config mConfig;
+
+ public StaggeredGridLayoutManagerBaseConfigSetTest(Config config)
+ throws CloneNotSupportedException {
+ mConfig = (Config) config.clone();
+ }
+
+ @Test
+ public void rTL() throws Throwable {
+ rtlTest(false, false);
+ }
+
+ @Test
+ public void rTLChangeAfter() throws Throwable {
+ rtlTest(true, false);
+ }
+
+ @Test
+ public void rTLItemWrapContent() throws Throwable {
+ rtlTest(false, true);
+ }
+
+ @Test
+ public void rTLChangeAfterItemWrapContent() throws Throwable {
+ rtlTest(true, true);
+ }
+
+ void rtlTest(boolean changeRtlAfter, final boolean wrapContent) throws Throwable {
+ if (mConfig.mSpanCount == 1) {
+ mConfig.mSpanCount = 2;
+ }
+ String logPrefix = mConfig + ", changeRtlAfterLayout:" + changeRtlAfter;
+ setupByConfig(mConfig.itemCount(5),
+ new GridTestAdapter(mConfig.mItemCount, mConfig.mOrientation) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (wrapContent) {
+ if (mOrientation == HORIZONTAL) {
+ holder.itemView.getLayoutParams().height
+ = RecyclerView.LayoutParams.WRAP_CONTENT;
+ } else {
+ holder.itemView.getLayoutParams().width
+ = RecyclerView.LayoutParams.MATCH_PARENT;
+ }
+ }
+ }
+ });
+ if (changeRtlAfter) {
+ waitFirstLayout();
+ mLayoutManager.expectLayouts(1);
+ mLayoutManager.setFakeRtl(true);
+ mLayoutManager.waitForLayout(2);
+ } else {
+ mLayoutManager.mFakeRTL = true;
+ waitFirstLayout();
+ }
+
+ assertEquals("view should become rtl", true, mLayoutManager.isLayoutRTL());
+ OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
+ View child0 = mLayoutManager.findViewByPosition(0);
+ View child1 = mLayoutManager.findViewByPosition(mConfig.mOrientation == VERTICAL ? 1
+ : mConfig.mSpanCount);
+ assertNotNull(logPrefix + " child position 0 should be laid out", child0);
+ assertNotNull(logPrefix + " child position 0 should be laid out", child1);
+ logPrefix += " child1 pos:" + mLayoutManager.getPosition(child1);
+ if (mConfig.mOrientation == VERTICAL || !mConfig.mReverseLayout) {
+ assertTrue(logPrefix + " second child should be to the left of first child",
+ helper.getDecoratedEnd(child0) > helper.getDecoratedEnd(child1));
+ assertEquals(logPrefix + " first child should be right aligned",
+ helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
+ } else {
+ assertTrue(logPrefix + " first child should be to the left of second child",
+ helper.getDecoratedStart(child1) >= helper.getDecoratedStart(child0));
+ assertEquals(logPrefix + " first child should be left aligned",
+ helper.getDecoratedStart(child0), helper.getStartAfterPadding());
+ }
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void scrollBackAndPreservePositions() throws Throwable {
+ scrollBackAndPreservePositionsTest(false);
+ }
+
+ @Test
+ public void scrollBackAndPreservePositionsWithRestore() throws Throwable {
+ scrollBackAndPreservePositionsTest(true);
+ }
+
+ public void scrollBackAndPreservePositionsTest(final boolean saveRestoreInBetween)
+ throws Throwable {
+ setupByConfig(mConfig);
+ mAdapter.mOnBindCallback = new OnBindCallback() {
+ @Override
+ public void onBoundItem(TestViewHolder vh, int position) {
+ StaggeredGridLayoutManager.LayoutParams
+ lp = (StaggeredGridLayoutManager.LayoutParams) vh.itemView
+ .getLayoutParams();
+ lp.setFullSpan((position * 7) % (mConfig.mSpanCount + 1) == 0);
+ }
+ };
+ waitFirstLayout();
+ final int[] globalPositions = new int[mAdapter.getItemCount()];
+ Arrays.fill(globalPositions, Integer.MIN_VALUE);
+ final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
+ * (mConfig.mReverseLayout ? -1 : 1);
+
+ final int[] globalPos = new int[1];
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ int globalScrollPosition = 0;
+ while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ final int pos = mRecyclerView.getChildLayoutPosition(child);
+ if (globalPositions[pos] != Integer.MIN_VALUE) {
+ continue;
+ }
+ if (mConfig.mReverseLayout) {
+ globalPositions[pos] = globalScrollPosition +
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
+ } else {
+ globalPositions[pos] = globalScrollPosition +
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
+ }
+ }
+ globalScrollPosition += mLayoutManager.scrollBy(scrollStep,
+ mRecyclerView.mRecycler, mRecyclerView.mState);
+ }
+ if (DEBUG) {
+ Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
+ }
+ globalPos[0] = globalScrollPosition;
+ }
+ });
+ checkForMainThreadException();
+
+ if (saveRestoreInBetween) {
+ saveRestore(mConfig);
+ }
+
+ checkForMainThreadException();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ int globalScrollPosition = globalPos[0];
+ // now scroll back and make sure global positions match
+ BitSet shouldTest = new BitSet(mAdapter.getItemCount());
+ shouldTest.set(0, mAdapter.getItemCount() - 1, true);
+ String assertPrefix = mConfig + ", restored in between:" + saveRestoreInBetween
+ + " global pos must match when scrolling in reverse for position ";
+ int scrollAmount = Integer.MAX_VALUE;
+ while (!shouldTest.isEmpty() && scrollAmount != 0) {
+ for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
+ View child = mRecyclerView.getChildAt(i);
+ int pos = mRecyclerView.getChildLayoutPosition(child);
+ if (!shouldTest.get(pos)) {
+ continue;
+ }
+ shouldTest.clear(pos);
+ int globalPos;
+ if (mConfig.mReverseLayout) {
+ globalPos = globalScrollPosition +
+ mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
+ } else {
+ globalPos = globalScrollPosition +
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
+ }
+ assertEquals(assertPrefix + pos,
+ globalPositions[pos], globalPos);
+ }
+ scrollAmount = mLayoutManager.scrollBy(-scrollStep,
+ mRecyclerView.mRecycler, mRecyclerView.mState);
+ globalScrollPosition += scrollAmount;
+ }
+ assertTrue("all views should be seen", shouldTest.isEmpty());
+ }
+ });
+ checkForMainThreadException();
+ }
+
+ private void saveRestore(final Config config) throws Throwable {
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ try {
+ 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);
+ RecyclerView restored = new RecyclerView(getActivity());
+ mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
+ config.mOrientation);
+ mLayoutManager.setGapStrategy(config.mGapStrategy);
+ restored.setLayoutManager(mLayoutManager);
+ // use the same adapter for Rect matching
+ restored.setAdapter(mAdapter);
+ restored.onRestoreInstanceState(savedState);
+ if (Looper.myLooper() == Looper.getMainLooper()) {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ } else {
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ mLayoutManager.waitForLayout(2);
+ }
+ } catch (Throwable t) {
+ postExceptionToInstrumentation(t);
+ }
+ }
+ });
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void getFirstLastChildrenTest() throws Throwable {
+ getFirstLastChildrenTest(false);
+ }
+
+ @Test
+ public void getFirstLastChildrenTestProvideArray() throws Throwable {
+ getFirstLastChildrenTest(true);
+ }
+
+ public void getFirstLastChildrenTest(final boolean provideArr) throws Throwable {
+ setupByConfig(mConfig);
+ waitFirstLayout();
+ Runnable viewInBoundsTest = new Runnable() {
+ @Override
+ public void run() {
+ VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
+ final String boundsLog = mLayoutManager.getBoundsLog();
+ VisibleChildren queryResult = new VisibleChildren(mLayoutManager.getSpanCount());
+ queryResult.findFirstPartialVisibleClosestToStart = mLayoutManager
+ .findFirstVisibleItemClosestToStart(false, true);
+ queryResult.findFirstPartialVisibleClosestToEnd = mLayoutManager
+ .findFirstVisibleItemClosestToEnd(false, true);
+ queryResult.firstFullyVisiblePositions = mLayoutManager
+ .findFirstCompletelyVisibleItemPositions(
+ provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+ queryResult.firstVisiblePositions = mLayoutManager
+ .findFirstVisibleItemPositions(
+ provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+ queryResult.lastFullyVisiblePositions = mLayoutManager
+ .findLastCompletelyVisibleItemPositions(
+ provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+ queryResult.lastVisiblePositions = mLayoutManager
+ .findLastVisibleItemPositions(
+ provideArr ? new int[mLayoutManager.getSpanCount()] : null);
+ assertEquals(mConfig + ":\nfirst visible child should match traversal result\n"
+ + "traversed:" + visibleChildren + "\n"
+ + "queried:" + queryResult + "\n"
+ + boundsLog, visibleChildren, queryResult
+ );
+ }
+ };
+ runTestOnUiThread(viewInBoundsTest);
+ // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+ // case
+ final int scrollPosition = mAdapter.getItemCount();
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.smoothScrollToPosition(scrollPosition);
+ }
+ });
+ while (mLayoutManager.isSmoothScrolling() ||
+ mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
+ runTestOnUiThread(viewInBoundsTest);
+ checkForMainThreadException();
+ Thread.sleep(400);
+ }
+ // delete all items
+ mLayoutManager.expectLayouts(2);
+ mAdapter.deleteAndNotify(0, mAdapter.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.mPrimaryOrientation.getTotalSpace();
+ final TestAdapter newAdapter = new TestAdapter(100) {
+ @Override
+ public void onBindViewHolder(TestViewHolder holder,
+ int position) {
+ super.onBindViewHolder(holder, position);
+ if (mConfig.mOrientation == LinearLayoutManager.HORIZONTAL) {
+ holder.itemView.setMinimumWidth(totalSpace + 100);
+ } else {
+ holder.itemView.setMinimumHeight(totalSpace + 100);
+ }
+ }
+ };
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mRecyclerView.setAdapter(newAdapter);
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ runTestOnUiThread(viewInBoundsTest);
+ checkForMainThreadException();
+
+ // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
+ // case
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ final int diff;
+ if (mConfig.mReverseLayout) {
+ diff = -1;
+ } else {
+ diff = 1;
+ }
+ final int distance = diff * 10;
+ if (mConfig.mOrientation == HORIZONTAL) {
+ mRecyclerView.scrollBy(distance, 0);
+ } else {
+ mRecyclerView.scrollBy(0, distance);
+ }
+ }
+ });
+ runTestOnUiThread(viewInBoundsTest);
+ checkForMainThreadException();
+ }
+
+ @Test
+ public void viewSnapTest() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(mConfig.mSpanCount + 1);
+ setupByConfig(config);
+ mAdapter.mOnBindCallback = new OnBindCallback() {
+ @Override
+ void onBoundItem(TestViewHolder vh, int position) {
+ StaggeredGridLayoutManager.LayoutParams
+ lp = (StaggeredGridLayoutManager.LayoutParams) vh.itemView
+ .getLayoutParams();
+ if (config.mOrientation == HORIZONTAL) {
+ lp.width = mRecyclerView.getWidth() / 3;
+ } else {
+ lp.height = mRecyclerView.getHeight() / 3;
+ }
+ }
+
+ @Override
+ boolean assignRandomSize() {
+ return false;
+ }
+ };
+ waitFirstLayout();
+ // run these tests twice. once initial layout, once after scroll
+ String logSuffix = "";
+ for (int i = 0; i < 2; i++) {
+ Map<Item, Rect> itemRectMap = mLayoutManager.collectChildCoordinates();
+ 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) {
+ recyclerViewBounds.bottom -= recyclerViewBounds.height() % config.mSpanCount;
+ } else {
+ recyclerViewBounds.right -= recyclerViewBounds.width() % config.mSpanCount;
+ }
+
+ Rect usedLayoutBounds = new Rect();
+ for (Rect rect : itemRectMap.values()) {
+ usedLayoutBounds.union(rect);
+ }
+
+ if (DEBUG) {
+ Log.d(TAG, "testing view snapping (" + logSuffix + ") for config " + config);
+ }
+ if (config.mOrientation == VERTICAL) {
+ assertEquals(config + " there should be no gap on left" + logSuffix,
+ usedLayoutBounds.left, recyclerViewBounds.left);
+ assertEquals(config + " there should be no gap on right" + logSuffix,
+ usedLayoutBounds.right, recyclerViewBounds.right);
+ if (config.mReverseLayout) {
+ assertEquals(config + " there should be no gap on bottom" + logSuffix,
+ usedLayoutBounds.bottom, recyclerViewBounds.bottom);
+ assertTrue(config + " there should be some gap on top" + logSuffix,
+ usedLayoutBounds.top > recyclerViewBounds.top);
+ } else {
+ assertEquals(config + " there should be no gap on top" + logSuffix,
+ usedLayoutBounds.top, recyclerViewBounds.top);
+ assertTrue(config + " there should be some gap at the bottom" + logSuffix,
+ usedLayoutBounds.bottom < recyclerViewBounds.bottom);
+ }
+ } else {
+ assertEquals(config + " there should be no gap on top" + logSuffix,
+ usedLayoutBounds.top, recyclerViewBounds.top);
+ assertEquals(config + " there should be no gap at the bottom" + logSuffix,
+ usedLayoutBounds.bottom, recyclerViewBounds.bottom);
+ if (config.mReverseLayout) {
+ assertEquals(config + " there should be no on right" + logSuffix,
+ usedLayoutBounds.right, recyclerViewBounds.right);
+ assertTrue(config + " there should be some gap on left" + logSuffix,
+ usedLayoutBounds.left > recyclerViewBounds.left);
+ } else {
+ assertEquals(config + " there should be no gap on left" + logSuffix,
+ usedLayoutBounds.left, recyclerViewBounds.left);
+ assertTrue(config + " there should be some gap on right" + logSuffix,
+ usedLayoutBounds.right < recyclerViewBounds.right);
+ }
+ }
+ final int scroll = config.mReverseLayout ? -500 : 500;
+ scrollBy(scroll);
+ logSuffix = " scrolled " + scroll;
+ }
+ }
+
+ @Test
+ public void scrollToPositionWithOffsetTest() throws Throwable {
+ setupByConfig(mConfig);
+ waitFirstLayout();
+ OrientationHelper orientationHelper = OrientationHelper
+ .createOrientationHelper(mLayoutManager, mConfig.mOrientation);
+ Rect layoutBounds = getDecoratedRecyclerViewBounds();
+ // try scrolling towards head, should not affect anything
+ Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+ scrollToPositionWithOffset(0, 20);
+ assertRectSetsEqual(mConfig + " 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 = mConfig.mReverseLayout ?
+ orientationHelper.getEndAfterPadding() - orientationHelper
+ .getDecoratedEnd(child)
+ : orientationHelper.getDecoratedStart(child) - orientationHelper
+ .getStartAfterPadding();
+ final int scrollOffset = startOffset / 2;
+ mLayoutManager.expectLayouts(1);
+ scrollToPositionWithOffset(position, scrollOffset);
+ mLayoutManager.waitForLayout(2);
+ final int finalOffset = mConfig.mReverseLayout ?
+ orientationHelper.getEndAfterPadding() - orientationHelper
+ .getDecoratedEnd(child)
+ : orientationHelper.getDecoratedStart(child) - orientationHelper
+ .getStartAfterPadding();
+ assertEquals(mConfig + " scroll with offset on a visible child should work fine",
+ 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(mConfig);
+ 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(mConfig + " scrolling to a mPosition with offset " + offset
+ + " should layout it", child);
+ final Rect bounds = mLayoutManager.getViewBounds(child);
+ if (DEBUG) {
+ Log.d(TAG, mConfig + " post scroll to invisible mPosition " + bounds + " in "
+ + layoutBounds + " with offset " + offset);
+ }
+
+ if (mConfig.mReverseLayout) {
+ assertEquals(mConfig + " 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(mConfig + " 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
+ public void scrollToPositionTest() throws Throwable {
+ setupByConfig(mConfig);
+ waitFirstLayout();
+ OrientationHelper orientationHelper = OrientationHelper
+ .createOrientationHelper(mLayoutManager, mConfig.mOrientation);
+ Rect layoutBounds = getDecoratedRecyclerViewBounds();
+ for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+ View view = mLayoutManager.getChildAt(i);
+ Rect bounds = mLayoutManager.getViewBounds(view);
+ if (layoutBounds.contains(bounds)) {
+ Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
+ final int position = mRecyclerView.getChildLayoutPosition(view);
+ StaggeredGridLayoutManager.LayoutParams layoutParams
+ = (StaggeredGridLayoutManager.LayoutParams) (view.getLayoutParams());
+ TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
+ assertEquals("recycler view mPosition should match adapter mPosition", position,
+ vh.mBoundItem.mAdapterIndex);
+ if (DEBUG) {
+ Log.d(TAG, "testing scroll to visible mPosition at " + position
+ + " " + bounds + " inside " + layoutBounds);
+ }
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(position);
+ mLayoutManager.waitForLayout(2);
+ if (DEBUG) {
+ view = mLayoutManager.findViewByPosition(position);
+ Rect newBounds = mLayoutManager.getViewBounds(view);
+ Log.d(TAG, "after scrolling to visible mPosition " +
+ bounds + " equals " + newBounds);
+ }
+
+ assertRectSetsEqual(
+ mConfig + "scroll to mPosition on fully visible child should be no-op",
+ initialBounds, mLayoutManager.collectChildCoordinates());
+ } else {
+ final int position = mRecyclerView.getChildLayoutPosition(view);
+ if (DEBUG) {
+ Log.d(TAG,
+ "child(" + position + ") not fully visible " + bounds + " not inside "
+ + layoutBounds
+ + mRecyclerView.getChildLayoutPosition(view)
+ );
+ }
+ mLayoutManager.expectLayouts(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ mLayoutManager.scrollToPosition(position);
+ }
+ });
+ mLayoutManager.waitForLayout(2);
+ view = mLayoutManager.findViewByPosition(position);
+ bounds = mLayoutManager.getViewBounds(view);
+ if (DEBUG) {
+ Log.d(TAG, "after scroll to partially visible child " + bounds + " in "
+ + layoutBounds);
+ }
+ assertTrue(mConfig
+ + " after scrolling to a partially visible child, it should become fully "
+ + " visible. " + bounds + " not inside " + layoutBounds,
+ layoutBounds.contains(bounds)
+ );
+ assertTrue(
+ mConfig + " when scrolling to a partially visible item, one of its edges "
+ + "should be on the boundaries",
+ orientationHelper.getStartAfterPadding() ==
+ orientationHelper.getDecoratedStart(view)
+ || orientationHelper.getEndAfterPadding() ==
+ orientationHelper.getDecoratedEnd(view));
+ }
+ }
+
+ // try scrolling to invisible children
+ int testCount = 10;
+ while (testCount-- > 0) {
+ final TargetTuple target = findInvisibleTarget(mConfig);
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(target.mPosition);
+ mLayoutManager.waitForLayout(2);
+ final View child = mLayoutManager.findViewByPosition(target.mPosition);
+ assertNotNull(mConfig + " scrolling to a mPosition should lay it out", child);
+ final Rect bounds = mLayoutManager.getViewBounds(child);
+ if (DEBUG) {
+ Log.d(TAG, mConfig + " post scroll to invisible mPosition " + bounds + " in "
+ + layoutBounds);
+ }
+ assertTrue(mConfig + " scrolling to a mPosition should make it fully visible",
+ layoutBounds.contains(bounds));
+ if (target.mLayoutDirection == LAYOUT_START) {
+ assertEquals(
+ mConfig + " when scrolling to an invisible child above, its start should"
+ + " align with recycler view's start",
+ orientationHelper.getStartAfterPadding(),
+ orientationHelper.getDecoratedStart(child)
+ );
+ } else {
+ assertEquals(mConfig + " when scrolling to an invisible child below, its end "
+ + "should align with recycler view's end",
+ orientationHelper.getEndAfterPadding(),
+ orientationHelper.getDecoratedEnd(child)
+ );
+ }
+ }
+ }
+
+ @Test
+ public void scollByTest() throws Throwable {
+ setupByConfig(mConfig);
+ waitFirstLayout();
+ // try invalid scroll. should not happen
+ final View first = mLayoutManager.getChildAt(0);
+ OrientationHelper primaryOrientation = OrientationHelper
+ .createOrientationHelper(mLayoutManager, mConfig.mOrientation);
+ int scrollDist;
+ if (mConfig.mReverseLayout) {
+ scrollDist = primaryOrientation.getDecoratedMeasurement(first) / 2;
+ } else {
+ scrollDist = -primaryOrientation.getDecoratedMeasurement(first) / 2;
+ }
+ Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+ scrollBy(scrollDist);
+ Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
+ assertRectSetsEqual(
+ mConfig + " if there are no more items, scroll should not happen (dt:" + scrollDist
+ + ")",
+ before, after
+ );
+
+ scrollDist = -scrollDist * 3;
+ before = mLayoutManager.collectChildCoordinates();
+ scrollBy(scrollDist);
+ after = mLayoutManager.collectChildCoordinates();
+ int layoutStart = primaryOrientation.getStartAfterPadding();
+ int layoutEnd = primaryOrientation.getEndAfterPadding();
+ for (Map.Entry<Item, Rect> entry : before.entrySet()) {
+ Rect afterRect = after.get(entry.getKey());
+ // offset rect
+ if (mConfig.mOrientation == VERTICAL) {
+ entry.getValue().offset(0, -scrollDist);
+ } else {
+ entry.getValue().offset(-scrollDist, 0);
+ }
+ if (afterRect == null || afterRect.isEmpty()) {
+ // assert item is out of bounds
+ int start, end;
+ if (mConfig.mOrientation == VERTICAL) {
+ start = entry.getValue().top;
+ end = entry.getValue().bottom;
+ } else {
+ start = entry.getValue().left;
+ end = entry.getValue().right;
+ }
+ assertTrue(
+ mConfig + " if item is missing after relayout, it should be out of bounds."
+ + "item start: " + start + ", end:" + end + " layout start:"
+ + layoutStart +
+ ", layout end:" + layoutEnd,
+ start <= layoutStart && end <= layoutEnd ||
+ start >= layoutEnd && end >= layoutEnd
+ );
+ } else {
+ assertEquals(mConfig + " Item should be laid out at the scroll offset coordinates",
+ entry.getValue(),
+ afterRect);
+ }
+ }
+ assertViewPositions(mConfig);
+ }
+
+ @Test
+ public void layoutOrderTest() throws Throwable {
+ setupByConfig(mConfig);
+ assertViewPositions(mConfig);
+ }
+
+ @Test
+ public void consistentRelayout() throws Throwable {
+ consistentRelayoutTest(mConfig, false);
+ }
+
+ @Test
+ public void consistentRelayoutWithFullSpanFirstChild() throws Throwable {
+ consistentRelayoutTest(mConfig, true);
+ }
+
+ @Test
+ public void dontRecycleViewsTranslatedOutOfBoundsFromStart() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(1000);
+ setupByConfig(config);
+ waitFirstLayout();
+ // pick position from child count so that it is not too far away
+ int pos = mRecyclerView.getChildCount() * 2;
+ smoothScrollToPosition(pos, true);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+ OrientationHelper helper = mLayoutManager.mPrimaryOrientation;
+ int gap = helper.getDecoratedStart(vh.itemView);
+ scrollBy(gap);
+ gap = helper.getDecoratedStart(vh.itemView);
+ assertThat("test sanity", gap, is(0));
+
+ final int size = helper.getDecoratedMeasurement(vh.itemView);
+ AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ ViewCompat.setTranslationX(vh.itemView, size * 2);
+ } else {
+ ViewCompat.setTranslationY(vh.itemView, size * 2);
+ }
+ }
+ });
+ scrollBy(size * 2);
+ assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+ assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+ assertThat(vh.getAdapterPosition(), is(pos));
+ scrollBy(size * 2);
+ assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+ }
+
+ @Test
+ public void dontRecycleViewsTranslatedOutOfBoundsFromEnd() throws Throwable {
+ final Config config = ((Config) mConfig.clone()).itemCount(1000);
+ setupByConfig(config);
+ waitFirstLayout();
+ // pick position from child count so that it is not too far away
+ int pos = mRecyclerView.getChildCount() * 2;
+ mLayoutManager.expectLayouts(1);
+ scrollToPosition(pos);
+ mLayoutManager.waitForLayout(2);
+ final RecyclerView.ViewHolder vh = mRecyclerView.findViewHolderForAdapterPosition(pos);
+ OrientationHelper helper = mLayoutManager.mPrimaryOrientation;
+ int gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+ scrollBy(-gap);
+ gap = helper.getEnd() - helper.getDecoratedEnd(vh.itemView);
+ assertThat("test sanity", gap, is(0));
+
+ final int size = helper.getDecoratedMeasurement(vh.itemView);
+ AttachDetachCollector collector = new AttachDetachCollector(mRecyclerView);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ if (mConfig.mOrientation == HORIZONTAL) {
+ ViewCompat.setTranslationX(vh.itemView, -size * 2);
+ } else {
+ ViewCompat.setTranslationY(vh.itemView, -size * 2);
+ }
+ }
+ });
+ scrollBy(-size * 2);
+ assertThat(collector.getDetached(), not(hasItem(sameInstance(vh.itemView))));
+ assertThat(vh.itemView.getParent(), is((ViewParent) mRecyclerView));
+ assertThat(vh.getAdapterPosition(), is(pos));
+ scrollBy(-size * 2);
+ assertThat(collector.getDetached(), hasItem(sameInstance(vh.itemView)));
+ }
+
+ public void consistentRelayoutTest(Config config, boolean firstChildMultiSpan)
+ throws Throwable {
+ setupByConfig(config);
+ if (firstChildMultiSpan) {
+ mAdapter.mFullSpanItems.add(0);
+ }
+ waitFirstLayout();
+ // record all child positions
+ Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
+ requestLayoutOnUIThread(mRecyclerView);
+ Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
+ assertRectSetsEqual(
+ config + " simple re-layout, firstChildMultiSpan:" + firstChildMultiSpan, before,
+ after);
+ // scroll some to create inconsistency
+ View firstChild = mLayoutManager.getChildAt(0);
+ final int firstChildStartBeforeScroll = mLayoutManager.mPrimaryOrientation
+ .getDecoratedStart(firstChild);
+ int distance = mLayoutManager.mPrimaryOrientation.getDecoratedMeasurement(firstChild) / 2;
+ if (config.mReverseLayout) {
+ distance *= -1;
+ }
+ scrollBy(distance);
+ waitForMainThread(2);
+ assertTrue("scroll by should move children", firstChildStartBeforeScroll !=
+ mLayoutManager.mPrimaryOrientation.getDecoratedStart(firstChild));
+ before = mLayoutManager.collectChildCoordinates();
+ mLayoutManager.expectLayouts(1);
+ requestLayoutOnUIThread(mRecyclerView);
+ mLayoutManager.waitForLayout(2);
+ after = mLayoutManager.collectChildCoordinates();
+ assertRectSetsEqual(config + " simple re-layout after scroll", before, after);
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerGapTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerGapTest.java
new file mode 100644
index 0000000..1a4d225e
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerGapTest.java
@@ -0,0 +1,108 @@
+/*
+ * 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.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import android.graphics.Rect;
+import android.test.suitebuilder.annotation.MediumTest;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+
+import static android.support.v7.widget.StaggeredGridLayoutManager.GAP_HANDLING_NONE;
+import static org.junit.Assert.assertNull;
+
+@RunWith(Parameterized.class)
+@MediumTest
+public class StaggeredGridLayoutManagerGapTest extends BaseStaggeredGridLayoutManagerTest {
+ private final Config mConfig;
+ private final int mDeletePosition;
+ private final int mDeleteCount;
+
+ public StaggeredGridLayoutManagerGapTest(Config config, int deletePosition, int deleteCount) {
+ mConfig = config;
+ mDeletePosition = deletePosition;
+ mDeleteCount = deleteCount;
+ }
+
+ @Parameterized.Parameters(name = "config={0} deletePos={1} deleteCount={2}")
+ public static List<Object[]> getParams() throws CloneNotSupportedException {
+ List<Config> variations = createBaseVariations();
+ List<Object[]> params = new ArrayList<>();
+ for (Config config : variations) {
+ for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
+ for (int deletePosition = config.mSpanCount - 1;
+ deletePosition < config.mSpanCount + 2; deletePosition++) {
+ params.add(new Object[]{config.clone(), deletePosition, deleteCount});
+ }
+ }
+ }
+ return params;
+ }
+
+ @Test
+ public void gapAtTheBeginningOfTheListTest() throws Throwable {
+ if (mConfig.mSpanCount < 2 || mConfig.mGapStrategy == GAP_HANDLING_NONE) {
+ return;
+ }
+ if (mConfig.mItemCount < 100) {
+ mConfig.itemCount(100);
+ }
+ setupByConfig(mConfig);
+ final RecyclerView.Adapter adapter = mAdapter;
+ waitFirstLayout();
+ // scroll far away
+ smoothScrollToPosition(mConfig.mItemCount / 2);
+ checkForMainThreadException();
+ // assert to be deleted child is not visible
+ assertNull(" test sanity, to be deleted child should be invisible",
+ mRecyclerView.findViewHolderForLayoutPosition(mDeletePosition));
+ // delete the child and notify
+ mAdapter.deleteAndNotify(mDeletePosition, mDeleteCount);
+ getInstrumentation().waitForIdleSync();
+ mLayoutManager.expectLayouts(1);
+ smoothScrollToPosition(0);
+ mLayoutManager.waitForLayout(2);
+ checkForMainThreadException();
+ // due to data changes, first item may become visible before others which will cause
+ // smooth scrolling to stop. Triggering it twice more is a naive hack.
+ // Until we have time to consider it as a bug, this is the only workaround.
+ smoothScrollToPosition(0);
+ Thread.sleep(500);
+ checkForMainThreadException();
+ smoothScrollToPosition(0);
+ Thread.sleep(500);
+ checkForMainThreadException();
+ // some animations should happen and we should recover layout
+ final Map<Item, Rect> actualCoords = mLayoutManager.collectChildCoordinates();
+
+ // now layout another RV with same adapter
+ removeRecyclerView();
+ setupByConfig(mConfig);
+ mRecyclerView.setAdapter(adapter);// use same adapter so that items can be matched
+ waitFirstLayout();
+ final Map<Item, Rect> desiredCoords = mLayoutManager.collectChildCoordinates();
+ assertRectSetsEqual(" when an item from the start of the list is deleted, "
+ + "layout should recover the state once scrolling is stopped",
+ desiredCoords, actualCoords);
+ checkForMainThreadException();
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerSavedStateTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerSavedStateTest.java
new file mode 100644
index 0000000..3c9ab739
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerSavedStateTest.java
@@ -0,0 +1,256 @@
+/*
+ * 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.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.test.suitebuilder.annotation.LargeTest;
+import android.util.Log;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+
+@RunWith(Parameterized.class)
+@LargeTest
+public class StaggeredGridLayoutManagerSavedStateTest extends BaseStaggeredGridLayoutManagerTest {
+ private final Config mConfig;
+ private final boolean mWaitForLayout;
+ private final boolean mLoadDataAfterRestore;
+ private final PostLayoutRunnable mPostLayoutOperations;
+
+ public StaggeredGridLayoutManagerSavedStateTest(
+ Config config, boolean waitForLayout, boolean loadDataAfterRestore,
+ PostLayoutRunnable postLayoutOperations) throws CloneNotSupportedException {
+ this.mConfig = (Config) config.clone();
+ this.mWaitForLayout = waitForLayout;
+ this.mLoadDataAfterRestore = loadDataAfterRestore;
+ this.mPostLayoutOperations = postLayoutOperations;
+ if (postLayoutOperations != null) {
+ postLayoutOperations.mTest = this;
+ }
+ }
+
+ @Parameterized.Parameters(name = "config={0} waitForLayout={1} loadDataAfterRestore={2}"
+ + " postLayoutRunnable={3}")
+ public static List<Object[]> getParams() throws CloneNotSupportedException {
+ List<Config> variations = createBaseVariations();
+
+ 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(adapter().getItemCount() * 3 / 4);
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position item count * 3 / 4";
+ }
+ },
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ layoutManager().expectLayouts(1);
+ scrollToPositionWithOffset(adapter().getItemCount() / 3,
+ 50);
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position item count / 3 with positive offset";
+ }
+ },
+ new PostLayoutRunnable() {
+ @Override
+ public void run() throws Throwable {
+ layoutManager().expectLayouts(1);
+ scrollToPositionWithOffset(adapter().getItemCount() * 2 / 3,
+ -50);
+ layoutManager().waitForLayout(2);
+ }
+
+ @Override
+ public String describe() {
+ return "scroll to position with negative offset";
+ }
+ }
+ };
+ boolean[] waitForLayoutOptions = new boolean[]{false, true};
+ boolean[] loadDataAfterRestoreOptions = new boolean[]{false, true};
+ List<Config> testVariations = new ArrayList<Config>();
+ testVariations.addAll(variations);
+ for (Config config : variations) {
+ if (config.mSpanCount < 2) {
+ continue;
+ }
+ final Config clone = (Config) config.clone();
+ clone.mItemCount = clone.mSpanCount - 1;
+ testVariations.add(clone);
+ }
+
+ List<Object[]> params = new ArrayList<>();
+ for (Config config : testVariations) {
+ for (PostLayoutRunnable runnable : postLayoutOptions) {
+ for (boolean waitForLayout : waitForLayoutOptions) {
+ for (boolean loadDataAfterRestore : loadDataAfterRestoreOptions) {
+ params.add(new Object[]{config, waitForLayout, loadDataAfterRestore,
+ runnable});
+ }
+ }
+ }
+ }
+ return params;
+ }
+
+ @Test
+ public void savedState() throws Throwable {
+ if (DEBUG) {
+ Log.d(TAG, "testing saved state with wait for layout = " + mWaitForLayout + " config "
+ + mConfig + " post layout action " + mPostLayoutOperations.describe());
+ }
+ setupByConfig(mConfig);
+ if (mLoadDataAfterRestore) {
+ // We are going to re-create items, force non-random item size.
+ mAdapter.mOnBindCallback = new OnBindCallback() {
+ @Override
+ void onBoundItem(TestViewHolder vh, int position) {
+ }
+
+ boolean assignRandomSize() {
+ return false;
+ }
+ };
+ }
+ waitFirstLayout();
+ if (mWaitForLayout) {
+ mPostLayoutOperations.run();
+ }
+ getInstrumentation().waitForIdleSync();
+ final int firstCompletelyVisiblePosition = mLayoutManager.findFirstVisibleItemPositionInt();
+ 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 = mAdapter.getItemCount();
+ List<Item> mItems = new ArrayList<>();
+ if (mLoadDataAfterRestore) {
+ mItems.addAll(mAdapter.mItems);
+ mAdapter.deleteAndNotify(0, itemCount);
+ }
+
+ RecyclerView restored = new RecyclerView(getActivity());
+ mLayoutManager = new WrappedLayoutManager(mConfig.mSpanCount, mConfig.mOrientation);
+ mLayoutManager.setGapStrategy(mConfig.mGapStrategy);
+ restored.setLayoutManager(mLayoutManager);
+ // use the same adapter for Rect matching
+ restored.setAdapter(mAdapter);
+ restored.onRestoreInstanceState(savedState);
+
+ if (mLoadDataAfterRestore) {
+ mAdapter.resetItemsTo(mItems);
+ }
+
+ assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
+ parcel.readString());
+ mLayoutManager.expectLayouts(1);
+ setRecyclerView(restored);
+ mLayoutManager.waitForLayout(2);
+ assertEquals(mConfig + " on saved state, reverse layout should be preserved",
+ mConfig.mReverseLayout, mLayoutManager.getReverseLayout());
+ assertEquals(mConfig + " on saved state, orientation should be preserved",
+ mConfig.mOrientation, mLayoutManager.getOrientation());
+ assertEquals(mConfig + " on saved state, span count should be preserved",
+ mConfig.mSpanCount, mLayoutManager.getSpanCount());
+ assertEquals(mConfig + " on saved state, gap strategy should be preserved",
+ mConfig.mGapStrategy, mLayoutManager.getGapStrategy());
+ assertEquals(mConfig + " on saved state, first completely visible child position should"
+ + " be preserved", firstCompletelyVisiblePosition,
+ mLayoutManager.findFirstVisibleItemPositionInt());
+ if (mWaitForLayout) {
+ final boolean strictItemEquality = !mLoadDataAfterRestore;
+ assertRectSetsEqual(mConfig + "\npost layout op:" + mPostLayoutOperations.describe()
+ + ": on restore, previous view positions should be preserved",
+ before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
+ }
+ // TODO add tests for changing values after restore before layout
+ }
+
+ static abstract class PostLayoutRunnable {
+ StaggeredGridLayoutManagerSavedStateTest mTest;
+ public void setup(StaggeredGridLayoutManagerSavedStateTest test) {
+ mTest = test;
+ }
+
+ public GridTestAdapter adapter() {
+ return mTest.mAdapter;
+ }
+
+ public WrappedLayoutManager layoutManager() {
+ return mTest.mLayoutManager;
+ }
+
+ public void scrollToPositionWithOffset(int position, int offset) throws Throwable {
+ mTest.scrollToPositionWithOffset(position, offset);
+ }
+
+ public void scrollToPosition(int position) throws Throwable {
+ mTest.scrollToPosition(position);
+ }
+
+ abstract void run() throws Throwable;
+
+ abstract String describe();
+
+ @Override
+ public String toString() {
+ return describe();
+ }
+ }
+}
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..3edcce5 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerTest.java
@@ -18,93 +18,66 @@
package android.support.v7.widget;
+import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
+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;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertSame;
+import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Color;
import android.graphics.Rect;
-import android.os.Looper;
+import android.graphics.drawable.ColorDrawable;
+import android.graphics.drawable.StateListDrawable;
import android.os.Parcel;
import android.os.Parcelable;
-import android.support.annotation.Nullable;
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.text.TextUtils;
import android.util.Log;
+import android.util.StateSet;
import android.view.View;
import android.view.ViewGroup;
import android.view.accessibility.AccessibilityEvent;
+import android.widget.EditText;
+import android.widget.FrameLayout;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.BitSet;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
+import org.junit.Test;
+
import java.util.HashMap;
-import java.util.HashSet;
-import java.util.LinkedHashMap;
-import java.util.List;
import java.util.Map;
import java.util.UUID;
-import java.util.concurrent.CountDownLatch;
-import java.util.concurrent.TimeUnit;
-import java.util.concurrent.atomic.AtomicInteger;
-import static android.support.v7.widget.LayoutState.*;
-import static android.support.v7.widget.LinearLayoutManager.VERTICAL;
-import static android.support.v7.widget.StaggeredGridLayoutManager.*;
-public class StaggeredGridLayoutManagerTest extends BaseRecyclerViewInstrumentationTest {
-
- private static final boolean DEBUG = false;
-
- private static final int AVG_ITEM_PER_VIEW = 3;
-
- private static final String TAG = "StaggeredGridLayoutManagerTest";
-
- volatile WrappedLayoutManager mLayoutManager;
-
- GridTestAdapter mAdapter;
-
- 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 (int spanCount : new int[]{1, 3}) {
- for (int gapStrategy : new int[]{GAP_HANDLING_NONE,
- GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS}) {
- mBaseVariations.add(new Config(orientation, reverseLayout, spanCount,
- gapStrategy));
- }
- }
- }
- }
- }
-
- void setupByConfig(Config config) throws Throwable {
- mAdapter = new GridTestAdapter(config.mItemCount, config.mOrientation);
- mRecyclerView = new RecyclerView(getActivity());
- mRecyclerView.setAdapter(mAdapter);
- mRecyclerView.setHasFixedSize(true);
- mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
- config.mOrientation);
- mLayoutManager.setGapStrategy(config.mGapStrategy);
- mLayoutManager.setReverseLayout(config.mReverseLayout);
- mRecyclerView.setLayoutManager(mLayoutManager);
- mRecyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
+@MediumTest
+public class StaggeredGridLayoutManagerTest extends BaseStaggeredGridLayoutManagerTest {
+ @Test
+ public void forceLayoutOnDetach() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+ waitFirstLayout();
+ assertFalse("test sanity", mRecyclerView.isLayoutRequested());
+ runTestOnUiThread(new Runnable() {
@Override
- public void getItemOffsets(Rect outRect, View view, RecyclerView parent,
- RecyclerView.State state) {
- try {
- LayoutParams lp = (LayoutParams) view.getLayoutParams();
- assertNotNull("view should have layout params assigned", lp);
- assertNotNull("when item offsets are requested, view should have a valid span",
- lp.mSpan);
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
+ public void run() {
+ mLayoutManager.onDetachedFromWindow(mRecyclerView, mRecyclerView.mRecycler);
}
});
+ assertTrue(mRecyclerView.isLayoutRequested());
}
-
- public void testAreAllStartsTheSame() throws Throwable {
+ @Test
+ public void areAllStartsTheSame() throws Throwable {
setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE).itemCount(300));
waitFirstLayout();
smoothScrollToPosition(100);
@@ -115,7 +88,8 @@
assertFalse("all starts should not be the same", mLayoutManager.areAllStartsEqual());
}
- public void testAreAllEndsTheSame() throws Throwable {
+ @Test
+ public void areAllEndsTheSame() throws Throwable {
setupByConfig(new Config(VERTICAL, true, 3, GAP_HANDLING_NONE).itemCount(300));
waitFirstLayout();
smoothScrollToPosition(100);
@@ -126,7 +100,17 @@
assertFalse("all ends should not be the same", mLayoutManager.areAllEndsEqual());
}
- public void testFindLastInUnevenDistribution() throws Throwable {
+ @Test
+ public void getPositionsBeforeInitialization() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
+ int[] positions = mLayoutManager.findFirstCompletelyVisibleItemPositions(null);
+ MatcherAssert.assertThat(positions,
+ CoreMatchers.is(new int[]{RecyclerView.NO_POSITION, RecyclerView.NO_POSITION,
+ RecyclerView.NO_POSITION}));
+ }
+
+ @Test
+ public void findLastInUnevenDistribution() throws Throwable {
setupByConfig(new Config(VERTICAL, false, 2, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS)
.itemCount(5));
mAdapter.mOnBindCallback = new OnBindCallback() {
@@ -165,12 +149,14 @@
}
- public void testCustomWidthInHorizontal() throws Throwable {
+ @Test
+ public void customWidthInHorizontal() throws Throwable {
customSizeInScrollDirectionTest(
new Config(HORIZONTAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
}
- public void testCustomHeightInVertical() throws Throwable {
+ @Test
+ public void customHeightInVertical() throws Throwable {
customSizeInScrollDirectionTest(
new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS));
}
@@ -211,62 +197,18 @@
checkForMainThreadException();
}
- public void testRTL() throws Throwable {
- for (boolean changeRtlAfter : new boolean[]{false, true}) {
- for (Config config : mBaseVariations) {
- rtlTest(config, changeRtlAfter);
- removeRecyclerView();
- }
- }
- }
-
- void rtlTest(Config config, boolean changeRtlAfter) throws Throwable {
- if (config.mSpanCount == 1) {
- config.mSpanCount = 2;
- }
- String logPrefix = config + ", changeRtlAfterLayout:" + changeRtlAfter;
- setupByConfig(config.itemCount(5));
- if (changeRtlAfter) {
- waitFirstLayout();
- mLayoutManager.expectLayouts(1);
- mLayoutManager.setFakeRtl(true);
- mLayoutManager.waitForLayout(2);
- } else {
- mLayoutManager.mFakeRTL = true;
- waitFirstLayout();
- }
-
- assertEquals("view should become rtl", true, mLayoutManager.isLayoutRTL());
- OrientationHelper helper = OrientationHelper.createHorizontalHelper(mLayoutManager);
- View child0 = mLayoutManager.findViewByPosition(0);
- View child1 = mLayoutManager.findViewByPosition(config.mOrientation == VERTICAL ? 1
- : config.mSpanCount);
- assertNotNull(logPrefix + " child position 0 should be laid out", child0);
- assertNotNull(logPrefix + " child position 0 should be laid out", child1);
- logPrefix += " child1 pos:" + mLayoutManager.getPosition(child1);
- if (config.mOrientation == VERTICAL || !config.mReverseLayout) {
- assertTrue(logPrefix + " second child should be to the left of first child",
- helper.getDecoratedEnd(child0) > helper.getDecoratedEnd(child1));
- assertEquals(logPrefix + " first child should be right aligned",
- helper.getDecoratedEnd(child0), helper.getEndAfterPadding());
- } else {
- assertTrue(logPrefix + " first child should be to the left of second child",
- helper.getDecoratedStart(child1) >= helper.getDecoratedStart(child0));
- assertEquals(logPrefix + " first child should be left aligned",
- helper.getDecoratedStart(child0), helper.getStartAfterPadding());
- }
- checkForMainThreadException();
- }
-
- public void testGapHandlingWhenItemMovesToTop() throws Throwable {
+ @Test
+ public void gapHandlingWhenItemMovesToTop() throws Throwable {
gapHandlingWhenItemMovesToTopTest();
}
- public void testGapHandlingWhenItemMovesToTopWithFullSpan() throws Throwable {
+ @Test
+ public void gapHandlingWhenItemMovesToTopWithFullSpan() throws Throwable {
gapHandlingWhenItemMovesToTopTest(0);
}
- public void testGapHandlingWhenItemMovesToTopWithFullSpan2() throws Throwable {
+ @Test
+ public void gapHandlingWhenItemMovesToTopWithFullSpan2() throws Throwable {
gapHandlingWhenItemMovesToTopTest(1);
}
@@ -308,109 +250,152 @@
}
-
- public void testScrollBackAndPreservePositions() throws Throwable {
- for (boolean saveRestore : new boolean[]{false, true}) {
- for (Config config : mBaseVariations) {
- scrollBackAndPreservePositionsTest(config, saveRestore);
- removeRecyclerView();
- }
- }
+ @Test
+ public void focusSearchFailureUp() throws Throwable {
+ focusSearchFailure(false);
}
- public void scrollBackAndPreservePositionsTest(final Config config,
- final boolean saveRestoreInBetween)
- throws Throwable {
- setupByConfig(config);
- mAdapter.mOnBindCallback = new OnBindCallback() {
- @Override
- public void onBoundItem(TestViewHolder vh, int position) {
- LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
- lp.setFullSpan((position * 7) % (config.mSpanCount + 1) == 0);
- }
- };
+ @Test
+ public void focusSearchFailureDown() throws Throwable {
+ focusSearchFailure(true);
+ }
+
+ @Test
+ public void focusSearchFailureFromSubChild() throws Throwable {
+ setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS),
+ new GridTestAdapter(1000, VERTICAL) {
+
+ @Override
+ public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ FrameLayout fl = new FrameLayout(parent.getContext());
+ EditText editText = new EditText(parent.getContext());
+ fl.addView(editText);
+ editText.setEllipsize(TextUtils.TruncateAt.END);
+ return new TestViewHolder(fl);
+ }
+
+ @Override
+ public void onBindViewHolder(TestViewHolder holder, int position) {
+ Item item = mItems.get(position);
+ holder.mBoundItem = item;
+ ((EditText) ((FrameLayout) holder.itemView).getChildAt(0)).setText(
+ item.mText + " (" + item.mId + ")");
+ }
+ });
waitFirstLayout();
- final int[] globalPositions = new int[mAdapter.getItemCount()];
- Arrays.fill(globalPositions, Integer.MIN_VALUE);
- final int scrollStep = (mLayoutManager.mPrimaryOrientation.getTotalSpace() / 10)
- * (config.mReverseLayout ? -1 : 1);
-
- final int[] globalPos = new int[1];
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- int globalScrollPosition = 0;
- while (globalPositions[mAdapter.getItemCount() - 1] == Integer.MIN_VALUE) {
- for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
- View child = mRecyclerView.getChildAt(i);
- final int pos = mRecyclerView.getChildLayoutPosition(child);
- if (globalPositions[pos] != Integer.MIN_VALUE) {
- continue;
- }
- if (config.mReverseLayout) {
- globalPositions[pos] = globalScrollPosition +
- mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
- } else {
- globalPositions[pos] = globalScrollPosition +
- mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
- }
- }
- globalScrollPosition += mLayoutManager.scrollBy(scrollStep,
- mRecyclerView.mRecycler, mRecyclerView.mState);
- }
- if (DEBUG) {
- Log.d(TAG, "done recording positions " + Arrays.toString(globalPositions));
- }
- globalPos[0] = globalScrollPosition;
- }
- });
+ ViewGroup lastChild = (ViewGroup) mRecyclerView.getChildAt(
+ mRecyclerView.getChildCount() - 1);
+ RecyclerView.ViewHolder lastViewHolder = mRecyclerView.getChildViewHolder(lastChild);
+ View subChildToFocus = lastChild.getChildAt(0);
+ requestFocus(subChildToFocus, true);
+ assertThat("test sanity", subChildToFocus.isFocused(), CoreMatchers.is(true));
+ focusSearch(subChildToFocus, View.FOCUS_FORWARD);
+ waitForIdleScroll(mRecyclerView);
checkForMainThreadException();
-
- if (saveRestoreInBetween) {
- saveRestore(config);
+ View focusedChild = mRecyclerView.getFocusedChild();
+ if (focusedChild == subChildToFocus.getParent()) {
+ focusSearch(focusedChild, View.FOCUS_FORWARD);
+ waitForIdleScroll(mRecyclerView);
+ focusedChild = mRecyclerView.getFocusedChild();
}
-
- checkForMainThreadException();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- int globalScrollPosition = globalPos[0];
- // now scroll back and make sure global positions match
- BitSet shouldTest = new BitSet(mAdapter.getItemCount());
- shouldTest.set(0, mAdapter.getItemCount() - 1, true);
- String assertPrefix = config + ", restored in between:" + saveRestoreInBetween
- + " global pos must match when scrolling in reverse for position ";
- int scrollAmount = Integer.MAX_VALUE;
- while (!shouldTest.isEmpty() && scrollAmount != 0) {
- for (int i = 0; i < mRecyclerView.getChildCount(); i++) {
- View child = mRecyclerView.getChildAt(i);
- int pos = mRecyclerView.getChildLayoutPosition(child);
- if (!shouldTest.get(pos)) {
- continue;
- }
- shouldTest.clear(pos);
- int globalPos;
- if (config.mReverseLayout) {
- globalPos = globalScrollPosition +
- mLayoutManager.mPrimaryOrientation.getDecoratedEnd(child);
- } else {
- globalPos = globalScrollPosition +
- mLayoutManager.mPrimaryOrientation.getDecoratedStart(child);
- }
- assertEquals(assertPrefix + pos,
- globalPositions[pos], globalPos);
- }
- scrollAmount = mLayoutManager.scrollBy(-scrollStep,
- mRecyclerView.mRecycler, mRecyclerView.mState);
- globalScrollPosition += scrollAmount;
- }
- assertTrue("all views should be seen", shouldTest.isEmpty());
- }
- });
- checkForMainThreadException();
+ RecyclerView.ViewHolder containingViewHolder = mRecyclerView.findContainingViewHolder(
+ focusedChild);
+ assertTrue("new focused view should have a larger position "
+ + lastViewHolder.getAdapterPosition() + " vs "
+ + containingViewHolder.getAdapterPosition(),
+ lastViewHolder.getAdapterPosition() < containingViewHolder.getAdapterPosition());
}
- public void testScrollToPositionWithPredictive() throws Throwable {
+ 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, true));
+ 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);
+ waitForIdleScroll(mRecyclerView);
+ 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);
+ waitForIdleScroll(mRecyclerView);
+ }
+
+
+ @Test
+ public void scrollToPositionWithPredictive() throws Throwable {
scrollToPositionWithPredictive(0, LinearLayoutManager.INVALID_OFFSET);
removeRecyclerView();
scrollToPositionWithPredictive(Config.DEFAULT_ITEM_COUNT / 2,
@@ -474,123 +459,8 @@
checkForMainThreadException();
}
- LayoutParams getLp(View view) {
- return (LayoutParams) view.getLayoutParams();
- }
-
- public void testGetFirstLastChildrenTest() throws Throwable {
- for (boolean provideArr : new boolean[]{true, false}) {
- for (Config config : mBaseVariations) {
- getFirstLastChildrenTest(config, provideArr);
- removeRecyclerView();
- }
- }
- }
-
- public void getFirstLastChildrenTest(final Config config, final boolean provideArr)
- throws Throwable {
- setupByConfig(config);
- waitFirstLayout();
- Runnable viewInBoundsTest = new Runnable() {
- @Override
- public void run() {
- VisibleChildren visibleChildren = mLayoutManager.traverseAndFindVisibleChildren();
- final String boundsLog = mLayoutManager.getBoundsLog();
- VisibleChildren queryResult = new VisibleChildren(mLayoutManager.getSpanCount());
- queryResult.findFirstPartialVisibleClosestToStart = mLayoutManager
- .findFirstVisibleItemClosestToStart(false, true);
- queryResult.findFirstPartialVisibleClosestToEnd = mLayoutManager
- .findFirstVisibleItemClosestToEnd(false, true);
- queryResult.firstFullyVisiblePositions = mLayoutManager
- .findFirstCompletelyVisibleItemPositions(
- provideArr ? new int[mLayoutManager.getSpanCount()] : null);
- queryResult.firstVisiblePositions = mLayoutManager
- .findFirstVisibleItemPositions(
- provideArr ? new int[mLayoutManager.getSpanCount()] : null);
- queryResult.lastFullyVisiblePositions = mLayoutManager
- .findLastCompletelyVisibleItemPositions(
- provideArr ? new int[mLayoutManager.getSpanCount()] : null);
- queryResult.lastVisiblePositions = mLayoutManager
- .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
- );
- }
- };
- runTestOnUiThread(viewInBoundsTest);
- // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
- // case
- final int scrollPosition = mAdapter.getItemCount();
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.smoothScrollToPosition(scrollPosition);
- }
- });
- while (mLayoutManager.isSmoothScrolling() ||
- mRecyclerView.getScrollState() != RecyclerView.SCROLL_STATE_IDLE) {
- runTestOnUiThread(viewInBoundsTest);
- checkForMainThreadException();
- Thread.sleep(400);
- }
- // delete all items
- mLayoutManager.expectLayouts(2);
- mAdapter.deleteAndNotify(0, mAdapter.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.mPrimaryOrientation.getTotalSpace();
- final TestAdapter newAdapter = new TestAdapter(100) {
- @Override
- public void onBindViewHolder(TestViewHolder holder,
- int position) {
- super.onBindViewHolder(holder, position);
- if (config.mOrientation == LinearLayoutManager.HORIZONTAL) {
- holder.itemView.setMinimumWidth(totalSpace + 100);
- } else {
- holder.itemView.setMinimumHeight(totalSpace + 100);
- }
- }
- };
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mRecyclerView.setAdapter(newAdapter);
- }
- });
- mLayoutManager.waitForLayout(2);
- runTestOnUiThread(viewInBoundsTest);
- checkForMainThreadException();
-
- // smooth scroll to end of the list and keep testing meanwhile. This will test pre-caching
- // case
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- final int diff;
- if (config.mReverseLayout) {
- diff = -1;
- } else {
- diff = 1;
- }
- final int distance = diff * 10;
- if (config.mOrientation == HORIZONTAL) {
- mRecyclerView.scrollBy(distance, 0);
- } else {
- mRecyclerView.scrollBy(0, distance);
- }
- }
- });
- runTestOnUiThread(viewInBoundsTest);
- checkForMainThreadException();
- }
-
- public void testMoveGapHandling() throws Throwable {
+ @Test
+ public void moveGapHandling() throws Throwable {
Config config = new Config().spanCount(2).itemCount(40);
setupByConfig(config);
waitFirstLayout();
@@ -600,15 +470,18 @@
assertNull("moving item to upper should not cause gaps", mLayoutManager.hasGapsToFix());
}
- public void testUpdateAfterFullSpan() throws Throwable {
+ @Test
+ public void updateAfterFullSpan() throws Throwable {
updateAfterFullSpanGapHandlingTest(0);
}
- public void testUpdateAfterFullSpan2() throws Throwable {
+ @Test
+ public void updateAfterFullSpan2() throws Throwable {
updateAfterFullSpanGapHandlingTest(20);
}
- public void testTemporaryGapHandling() throws Throwable {
+ @Test
+ public void temporaryGapHandling() throws Throwable {
int fullSpanIndex = 200;
setupByConfig(new Config().spanCount(2).itemCount(500));
mAdapter.mFullSpanItems.add(fullSpanIndex);
@@ -672,7 +545,8 @@
mLayoutManager.mPrimaryOrientation.getDecoratedStart(view2));
}
- public void testInnerGapHandling() throws Throwable {
+ @Test
+ public void innerGapHandling() throws Throwable {
innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_NONE);
innerGapHandlingTest(StaggeredGridLayoutManager.GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS);
}
@@ -735,7 +609,8 @@
}
- public void testFullSizeSpans() throws Throwable {
+ @Test
+ public void fullSizeSpans() throws Throwable {
Config config = new Config().spanCount(5).itemCount(30);
setupByConfig(config);
mAdapter.mFullSpanItems.add(3);
@@ -759,69 +634,8 @@
getLp(view).mSpan.mIndex);
}
- public void testGapAtTheBeginning() throws Throwable {
- for (Config config : mBaseVariations) {
- for (int deleteCount = 1; deleteCount < config.mSpanCount * 2; deleteCount++) {
- for (int deletePosition = config.mSpanCount - 1;
- deletePosition < config.mSpanCount + 2; deletePosition++) {
- gapAtTheBeginningOfTheListTest(config, deletePosition, deleteCount);
- removeRecyclerView();
- }
- }
- }
- }
-
- public void gapAtTheBeginningOfTheListTest(final Config config, int deletePosition,
- int deleteCount) throws Throwable {
- if (config.mSpanCount < 2 || config.mGapStrategy == GAP_HANDLING_NONE) {
- return;
- }
- if (config.mItemCount < 100) {
- config.itemCount(100);
- }
- final String logPrefix = config + ", deletePos:" + deletePosition + ", deleteCount:"
- + deleteCount;
- setupByConfig(config);
- final RecyclerView.Adapter adapter = mAdapter;
- waitFirstLayout();
- // scroll far away
- smoothScrollToPosition(config.mItemCount / 2);
- checkForMainThreadException();
- // assert to be deleted child is not visible
- assertNull(logPrefix + " test sanity, to be deleted child should be invisible",
- mRecyclerView.findViewHolderForLayoutPosition(deletePosition));
- // delete the child and notify
- mAdapter.deleteAndNotify(deletePosition, deleteCount);
- getInstrumentation().waitForIdleSync();
- mLayoutManager.expectLayouts(1);
- smoothScrollToPosition(0);
- mLayoutManager.waitForLayout(2);
- checkForMainThreadException();
- // due to data changes, first item may become visible before others which will cause
- // smooth scrolling to stop. Triggering it twice more is a naive hack.
- // Until we have time to consider it as a bug, this is the only workaround.
- smoothScrollToPosition(0);
- checkForMainThreadException();
- Thread.sleep(300);
- smoothScrollToPosition(0);
- checkForMainThreadException();
- Thread.sleep(500);
- // some animations should happen and we should recover layout
- final Map<Item, Rect> actualCoords = mLayoutManager.collectChildCoordinates();
-
- // now layout another RV with same adapter
- removeRecyclerView();
- setupByConfig(config);
- mRecyclerView.setAdapter(adapter);// use same adapter so that items can be matched
- waitFirstLayout();
- final Map<Item, Rect> desiredCoords = mLayoutManager.collectChildCoordinates();
- assertRectSetsEqual(logPrefix + " when an item from the start of the list is deleted, "
- + "layout should recover the state once scrolling is stopped",
- desiredCoords, actualCoords);
- checkForMainThreadException();
- }
-
- public void testPartialSpanInvalidation() throws Throwable {
+ @Test
+ public void partialSpanInvalidation() throws Throwable {
Config config = new Config().spanCount(5).itemCount(100);
setupByConfig(config);
for (int i = 20; i < mAdapter.getItemCount(); i += 20) {
@@ -859,7 +673,8 @@
return copy;
}
- public void testSpanReassignmentsOnItemChange() throws Throwable {
+ @Test
+ public void spanReassignmentsOnItemChange() throws Throwable {
Config config = new Config().spanCount(5);
setupByConfig(config);
waitFirstLayout();
@@ -894,12 +709,6 @@
+ "layout", before, mLayoutManager.collectChildCoordinates());
}
- void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start, int end) {
- for (int i = start; i < end; i++) {
- assertEquals(msg + " ind:" + i, set1[i], set2[i]);
- }
- }
-
void assertSpanAssignmentEquality(String msg, int[] set1, int[] set2, int start1, int start2,
int length) {
for (int i = 0; i < length; i++) {
@@ -908,93 +717,8 @@
}
}
- public void testViewSnapping() throws Throwable {
- for (Config config : mBaseVariations) {
- viewSnapTest(config.itemCount(config.mSpanCount + 1));
- removeRecyclerView();
- }
- }
-
- public void viewSnapTest(final Config config) throws Throwable {
- setupByConfig(config);
- mAdapter.mOnBindCallback = new OnBindCallback() {
- @Override
- void onBoundItem(TestViewHolder vh, int position) {
- LayoutParams lp = (LayoutParams) vh.itemView.getLayoutParams();
- if (config.mOrientation == HORIZONTAL) {
- lp.width = mRecyclerView.getWidth() / 3;
- } else {
- lp.height = mRecyclerView.getHeight() / 3;
- }
- }
- @Override
- boolean assignRandomSize() {
- return false;
- }
- };
- waitFirstLayout();
- // run these tests twice. once initial layout, once after scroll
- String logSuffix = "";
- for (int i = 0; i < 2; i++) {
- Map<Item, Rect> itemRectMap = mLayoutManager.collectChildCoordinates();
- 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) {
- recyclerViewBounds.bottom -= recyclerViewBounds.height() % config.mSpanCount;
- } else {
- recyclerViewBounds.right -= recyclerViewBounds.width() % config.mSpanCount;
- }
-
- Rect usedLayoutBounds = new Rect();
- for (Rect rect : itemRectMap.values()) {
- usedLayoutBounds.union(rect);
- }
-
- if (DEBUG) {
- Log.d(TAG, "testing view snapping (" + logSuffix + ") for config " + config);
- }
- if (config.mOrientation == VERTICAL) {
- assertEquals(config + " there should be no gap on left" + logSuffix,
- usedLayoutBounds.left, recyclerViewBounds.left);
- assertEquals(config + " there should be no gap on right" + logSuffix,
- usedLayoutBounds.right, recyclerViewBounds.right);
- if (config.mReverseLayout) {
- assertEquals(config + " there should be no gap on bottom" + logSuffix,
- usedLayoutBounds.bottom, recyclerViewBounds.bottom);
- assertTrue(config + " there should be some gap on top" + logSuffix,
- usedLayoutBounds.top > recyclerViewBounds.top);
- } else {
- assertEquals(config + " there should be no gap on top" + logSuffix,
- usedLayoutBounds.top, recyclerViewBounds.top);
- assertTrue(config + " there should be some gap at the bottom" + logSuffix,
- usedLayoutBounds.bottom < recyclerViewBounds.bottom);
- }
- } else {
- assertEquals(config + " there should be no gap on top" + logSuffix,
- usedLayoutBounds.top, recyclerViewBounds.top);
- assertEquals(config + " there should be no gap at the bottom" + logSuffix,
- usedLayoutBounds.bottom, recyclerViewBounds.bottom);
- if (config.mReverseLayout) {
- assertEquals(config + " there should be no on right" + logSuffix,
- usedLayoutBounds.right, recyclerViewBounds.right);
- assertTrue(config + " there should be some gap on left" + logSuffix,
- usedLayoutBounds.left > recyclerViewBounds.left);
- } else {
- assertEquals(config + " there should be no gap on left" + logSuffix,
- usedLayoutBounds.left, recyclerViewBounds.left);
- assertTrue(config + " there should be some gap on right" + logSuffix,
- usedLayoutBounds.right < recyclerViewBounds.right);
- }
- }
- final int scroll = config.mReverseLayout ? -500 : 500;
- scrollBy(scroll);
- logSuffix = " scrolled " + scroll;
- }
-
- }
-
- public void testSpanCountChangeOnRestoreSavedState() throws Throwable {
+ @Test
+ public void spanCountChangeOnRestoreSavedState() throws Throwable {
Config config = new Config(HORIZONTAL, true, 5, GAP_HANDLING_NONE);
setupByConfig(config);
waitFirstLayout();
@@ -1039,213 +763,8 @@
smoothScrollToPosition(mAdapter.getItemCount() - 1);
}
- 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(mAdapter.getItemCount() * 3 / 4);
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position " + (mAdapter == null ? "" :
- mAdapter.getItemCount() * 3 / 4);
- }
- },
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- mLayoutManager.expectLayouts(1);
- scrollToPositionWithOffset(mAdapter.getItemCount() / 3,
- 50);
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position " + (mAdapter == null ? "" :
- mAdapter.getItemCount() / 3) + "with positive offset";
- }
- },
- new PostLayoutRunnable() {
- @Override
- public void run() throws Throwable {
- mLayoutManager.expectLayouts(1);
- scrollToPositionWithOffset(mAdapter.getItemCount() * 2 / 3,
- -50);
- mLayoutManager.waitForLayout(2);
- }
-
- @Override
- public String describe() {
- return "scroll to position with negative offset";
- }
- }
- };
- boolean[] waitForLayoutOptions = new boolean[]{false, true};
- boolean[] loadDataAfterRestoreOptions = new boolean[]{false, true};
- List<Config> testVariations = new ArrayList<Config>();
- testVariations.addAll(mBaseVariations);
- for (Config config : mBaseVariations) {
- if (config.mSpanCount < 2) {
- continue;
- }
- final Config clone = (Config) config.clone();
- clone.mItemCount = clone.mSpanCount - 1;
- testVariations.add(clone);
- }
- for (Config config : testVariations) {
- for (PostLayoutRunnable runnable : postLayoutOptions) {
- for (boolean waitForLayout : waitForLayoutOptions) {
- for (boolean loadDataAfterRestore : loadDataAfterRestoreOptions) {
- savedStateTest(config, waitForLayout, loadDataAfterRestore, runnable);
- removeRecyclerView();
- checkForMainThreadException();
- }
- }
- }
- }
- }
-
- private void saveRestore(final Config config) throws Throwable {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- try {
- 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);
- RecyclerView restored = new RecyclerView(getActivity());
- mLayoutManager = new WrappedLayoutManager(config.mSpanCount,
- config.mOrientation);
- mLayoutManager.setGapStrategy(config.mGapStrategy);
- restored.setLayoutManager(mLayoutManager);
- // use the same adapter for Rect matching
- restored.setAdapter(mAdapter);
- restored.onRestoreInstanceState(savedState);
- if (Looper.myLooper() == Looper.getMainLooper()) {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- } else {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- mLayoutManager.waitForLayout(2);
- }
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
- }
- });
- checkForMainThreadException();
- }
-
- public void savedStateTest(Config config, boolean waitForLayout, boolean loadDataAfterRestore,
- PostLayoutRunnable postLayoutOperations)
- throws Throwable {
- if (DEBUG) {
- Log.d(TAG, "testing saved state with wait for layout = " + waitForLayout + " config "
- + config + " post layout action " + postLayoutOperations.describe());
- }
- setupByConfig(config);
- if (loadDataAfterRestore) {
- // We are going to re-create items, force non-random item size.
- mAdapter.mOnBindCallback = new OnBindCallback() {
- @Override
- void onBoundItem(TestViewHolder vh, int position) {
- }
-
- boolean assignRandomSize() {
- return false;
- }
- };
- }
- waitFirstLayout();
- if (waitForLayout) {
- postLayoutOperations.run();
- // ugly thread sleep but since post op is anything, we need to give it time to settle.
- Thread.sleep(500);
- }
- getInstrumentation().waitForIdleSync();
- final int firstCompletelyVisiblePosition = mLayoutManager.findFirstVisibleItemPositionInt();
- 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 = mAdapter.getItemCount();
- if (loadDataAfterRestore) {
- mAdapter.deleteAndNotify(0, itemCount);
- }
-
- RecyclerView restored = new RecyclerView(getActivity());
- mLayoutManager = new WrappedLayoutManager(config.mSpanCount, config.mOrientation);
- mLayoutManager.setGapStrategy(config.mGapStrategy);
- restored.setLayoutManager(mLayoutManager);
- // use the same adapter for Rect matching
- restored.setAdapter(mAdapter);
- restored.onRestoreInstanceState(savedState);
-
- if (loadDataAfterRestore) {
- mAdapter.addAndNotify(itemCount);
- }
-
- assertEquals("Parcel reading should not go out of bounds", parcelSuffix,
- parcel.readString());
- mLayoutManager.expectLayouts(1);
- setRecyclerView(restored);
- mLayoutManager.waitForLayout(2);
- assertEquals(config + " on saved state, reverse layout should be preserved",
- config.mReverseLayout, mLayoutManager.getReverseLayout());
- assertEquals(config + " on saved state, orientation should be preserved",
- config.mOrientation, mLayoutManager.getOrientation());
- assertEquals(config + " on saved state, span count should be preserved",
- config.mSpanCount, mLayoutManager.getSpanCount());
- assertEquals(config + " on saved state, gap strategy should be preserved",
- config.mGapStrategy, mLayoutManager.getGapStrategy());
- assertEquals(config + " on saved state, first completely visible child position should"
- + " be preserved", firstCompletelyVisiblePosition,
- mLayoutManager.findFirstVisibleItemPositionInt());
- if (waitForLayout) {
- final boolean strictItemEquality = !loadDataAfterRestore;
- assertRectSetsEqual(config + "\npost layout op:" + postLayoutOperations.describe()
- + ": on restore, previous view positions should be preserved",
- before, mLayoutManager.collectChildCoordinates(), strictItemEquality);
- }
- // TODO add tests for changing values after restore before layout
- }
-
- public void testScrollAndClear() throws Throwable {
+ @Test
+ public void scrollAndClear() throws Throwable {
setupByConfig(new Config());
waitFirstLayout();
@@ -1264,345 +783,8 @@
assertEquals("Remaining children", 0, mLayoutManager.collectChildCoordinates().size());
}
- public void testScrollToPositionWithOffset() throws Throwable {
- for (Config config : mBaseVariations) {
- scrollToPositionWithOffsetTest(config);
- removeRecyclerView();
- }
- }
-
- public void scrollToPositionWithOffsetTest(Config config) throws Throwable {
- setupByConfig(config);
- waitFirstLayout();
- OrientationHelper orientationHelper = OrientationHelper
- .createOrientationHelper(mLayoutManager, config.mOrientation);
- Rect layoutBounds = getDecoratedRecyclerViewBounds();
- // try scrolling towards head, should not affect anything
- Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
- 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 = 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",
- 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);
- 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(config + " scrolling to a mPosition with offset " + offset
- + " should layout it", child);
- final Rect bounds = mLayoutManager.getViewBounds(child);
- if (DEBUG) {
- Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
- + layoutBounds + " with offset " + offset);
- }
-
- if (config.mReverseLayout) {
- assertEquals(config + " 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(config + " 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;
- }
- }
-
- public void testScrollToPosition() throws Throwable {
- for (Config config : mBaseVariations) {
- scrollToPositionTest(config);
- removeRecyclerView();
- }
- }
-
- 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 + (mAdapter.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 scrollToPositionTest(Config config) throws Throwable {
- setupByConfig(config);
- waitFirstLayout();
- OrientationHelper orientationHelper = OrientationHelper
- .createOrientationHelper(mLayoutManager, config.mOrientation);
- Rect layoutBounds = getDecoratedRecyclerViewBounds();
- for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
- View view = mLayoutManager.getChildAt(i);
- Rect bounds = mLayoutManager.getViewBounds(view);
- if (layoutBounds.contains(bounds)) {
- Map<Item, Rect> initialBounds = mLayoutManager.collectChildCoordinates();
- final int position = mRecyclerView.getChildLayoutPosition(view);
- LayoutParams layoutParams
- = (LayoutParams) (view.getLayoutParams());
- TestViewHolder vh = (TestViewHolder) layoutParams.mViewHolder;
- assertEquals("recycler view mPosition should match adapter mPosition", position,
- vh.mBoundItem.mAdapterIndex);
- if (DEBUG) {
- Log.d(TAG, "testing scroll to visible mPosition at " + position
- + " " + bounds + " inside " + layoutBounds);
- }
- mLayoutManager.expectLayouts(1);
- scrollToPosition(position);
- mLayoutManager.waitForLayout(2);
- if (DEBUG) {
- view = mLayoutManager.findViewByPosition(position);
- Rect newBounds = mLayoutManager.getViewBounds(view);
- Log.d(TAG, "after scrolling to visible mPosition " +
- bounds + " equals " + newBounds);
- }
-
- assertRectSetsEqual(
- config + "scroll to mPosition on fully visible child should be no-op",
- initialBounds, mLayoutManager.collectChildCoordinates());
- } else {
- final int position = mRecyclerView.getChildLayoutPosition(view);
- if (DEBUG) {
- Log.d(TAG,
- "child(" + position + ") not fully visible " + bounds + " not inside "
- + layoutBounds
- + mRecyclerView.getChildLayoutPosition(view)
- );
- }
- mLayoutManager.expectLayouts(1);
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLayoutManager.scrollToPosition(position);
- }
- });
- mLayoutManager.waitForLayout(2);
- view = mLayoutManager.findViewByPosition(position);
- bounds = mLayoutManager.getViewBounds(view);
- if (DEBUG) {
- Log.d(TAG, "after scroll to partially visible child " + bounds + " in "
- + layoutBounds);
- }
- assertTrue(config
- + " after scrolling to a partially visible child, it should become fully "
- + " visible. " + bounds + " not inside " + layoutBounds,
- layoutBounds.contains(bounds)
- );
- assertTrue(config + " when scrolling to a partially visible item, one of its edges "
- + "should be on the boundaries", orientationHelper.getStartAfterPadding() ==
- orientationHelper.getDecoratedStart(view)
- || orientationHelper.getEndAfterPadding() ==
- orientationHelper.getDecoratedEnd(view));
- }
- }
-
- // try scrolling to invisible children
- int testCount = 10;
- while (testCount-- > 0) {
- final TargetTuple target = findInvisibleTarget(config);
- mLayoutManager.expectLayouts(1);
- scrollToPosition(target.mPosition);
- mLayoutManager.waitForLayout(2);
- final View child = mLayoutManager.findViewByPosition(target.mPosition);
- assertNotNull(config + " scrolling to a mPosition should lay it out", child);
- final Rect bounds = mLayoutManager.getViewBounds(child);
- if (DEBUG) {
- Log.d(TAG, config + " post scroll to invisible mPosition " + bounds + " in "
- + layoutBounds);
- }
- assertTrue(config + " scrolling to a mPosition should make it fully visible",
- layoutBounds.contains(bounds));
- if (target.mLayoutDirection == LAYOUT_START) {
- assertEquals(
- config + " when scrolling to an invisible child above, its start should"
- + " align with recycler view's start",
- orientationHelper.getStartAfterPadding(),
- orientationHelper.getDecoratedStart(child)
- );
- } else {
- assertEquals(config + " when scrolling to an invisible child below, its end "
- + "should align with recycler view's end",
- orientationHelper.getEndAfterPadding(),
- orientationHelper.getDecoratedEnd(child)
- );
- }
- }
- }
-
- private void scrollToPositionWithOffset(final int position, final int offset) throws Throwable {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- mLayoutManager.scrollToPositionWithOffset(position, offset);
- }
- });
- }
-
- public void testLayoutOrder() throws Throwable {
- for (Config config : mBaseVariations) {
- layoutOrderTest(config);
- removeRecyclerView();
- }
- }
-
- public void layoutOrderTest(Config config) throws Throwable {
- setupByConfig(config);
- assertViewPositions(config);
- }
-
- void assertViewPositions(Config config) {
- ArrayList<ArrayList<View>> viewsBySpan = mLayoutManager.collectChildrenBySpan();
- OrientationHelper orientationHelper = OrientationHelper
- .createOrientationHelper(mLayoutManager, config.mOrientation);
- for (ArrayList<View> span : viewsBySpan) {
- // validate all children's order. first child should have min start mPosition
- final int count = span.size();
- for (int i = 0, j = 1; j < count; i++, j++) {
- View prev = span.get(i);
- View next = span.get(j);
- assertTrue(config + " prev item should be above next item",
- orientationHelper.getDecoratedEnd(prev) <= orientationHelper
- .getDecoratedStart(next)
- );
-
- }
- }
- }
-
- public void testScrollBy() throws Throwable {
- for (Config config : mBaseVariations) {
- scollByTest(config);
- removeRecyclerView();
- }
- }
-
- void waitFirstLayout() throws Throwable {
- mLayoutManager.expectLayouts(1);
- setRecyclerView(mRecyclerView);
- mLayoutManager.waitForLayout(2);
- getInstrumentation().waitForIdleSync();
- }
-
- public void scollByTest(Config config) throws Throwable {
- setupByConfig(config);
- waitFirstLayout();
- // try invalid scroll. should not happen
- final View first = mLayoutManager.getChildAt(0);
- OrientationHelper primaryOrientation = OrientationHelper
- .createOrientationHelper(mLayoutManager, config.mOrientation);
- int scrollDist;
- if (config.mReverseLayout) {
- scrollDist = primaryOrientation.getDecoratedMeasurement(first) / 2;
- } else {
- scrollDist = -primaryOrientation.getDecoratedMeasurement(first) / 2;
- }
- Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
- scrollBy(scrollDist);
- Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
- assertRectSetsEqual(
- config + " if there are no more items, scroll should not happen (dt:" + scrollDist
- + ")",
- before, after
- );
-
- scrollDist = -scrollDist * 3;
- before = mLayoutManager.collectChildCoordinates();
- scrollBy(scrollDist);
- after = mLayoutManager.collectChildCoordinates();
- int layoutStart = primaryOrientation.getStartAfterPadding();
- int layoutEnd = primaryOrientation.getEndAfterPadding();
- for (Map.Entry<Item, Rect> entry : before.entrySet()) {
- Rect afterRect = after.get(entry.getKey());
- // offset rect
- if (config.mOrientation == VERTICAL) {
- entry.getValue().offset(0, -scrollDist);
- } else {
- entry.getValue().offset(-scrollDist, 0);
- }
- if (afterRect == null || afterRect.isEmpty()) {
- // assert item is out of bounds
- int start, end;
- if (config.mOrientation == VERTICAL) {
- start = entry.getValue().top;
- end = entry.getValue().bottom;
- } else {
- start = entry.getValue().left;
- end = entry.getValue().right;
- }
- assertTrue(
- config + " if item is missing after relayout, it should be out of bounds."
- + "item start: " + start + ", end:" + end + " layout start:"
- + layoutStart +
- ", layout end:" + layoutEnd,
- start <= layoutStart && end <= layoutEnd ||
- start >= layoutEnd && end >= layoutEnd
- );
- } else {
- assertEquals(config + " Item should be laid out at the scroll offset coordinates",
- entry.getValue(),
- afterRect);
- }
- }
- assertViewPositions(config);
- }
-
- public void testAccessibilityPositions() throws Throwable {
+ @Test
+ public void accessibilityPositions() throws Throwable {
setupByConfig(new Config(VERTICAL, false, 3, GAP_HANDLING_NONE));
waitFirstLayout();
final AccessibilityDelegateCompat delegateCompat = mRecyclerView
@@ -1628,704 +810,4 @@
Math.max(start, end), record.getToIndex());
}
-
- public void testConsistentRelayout() throws Throwable {
- for (Config config : mBaseVariations) {
- for (boolean firstChildMultiSpan : new boolean[]{false, true}) {
- consistentRelayoutTest(config, firstChildMultiSpan);
- }
- removeRecyclerView();
- }
- }
-
- public void consistentRelayoutTest(Config config, boolean firstChildMultiSpan)
- throws Throwable {
- setupByConfig(config);
- if (firstChildMultiSpan) {
- mAdapter.mFullSpanItems.add(0);
- }
- waitFirstLayout();
- // record all child positions
- Map<Item, Rect> before = mLayoutManager.collectChildCoordinates();
- requestLayoutOnUIThread(mRecyclerView);
- Map<Item, Rect> after = mLayoutManager.collectChildCoordinates();
- assertRectSetsEqual(
- config + " simple re-layout, firstChildMultiSpan:" + firstChildMultiSpan, before,
- after);
- // scroll some to create inconsistency
- View firstChild = mLayoutManager.getChildAt(0);
- final int firstChildStartBeforeScroll = mLayoutManager.mPrimaryOrientation
- .getDecoratedStart(firstChild);
- int distance = mLayoutManager.mPrimaryOrientation.getDecoratedMeasurement(firstChild) / 2;
- if (config.mReverseLayout) {
- distance *= -1;
- }
- scrollBy(distance);
- waitForMainThread(2);
- assertTrue("scroll by should move children", firstChildStartBeforeScroll !=
- mLayoutManager.mPrimaryOrientation.getDecoratedStart(firstChild));
- before = mLayoutManager.collectChildCoordinates();
- mLayoutManager.expectLayouts(1);
- requestLayoutOnUIThread(mRecyclerView);
- mLayoutManager.waitForLayout(2);
- after = mLayoutManager.collectChildCoordinates();
- assertRectSetsEqual(config + " simple re-layout after scroll", before, after);
- }
-
- /**
- * enqueues an empty runnable to main thread so that we can be assured it did run
- *
- * @param count Number of times to run
- */
- private void waitForMainThread(int count) throws Throwable {
- final AtomicInteger i = new AtomicInteger(count);
- while (i.get() > 0) {
- runTestOnUiThread(new Runnable() {
- @Override
- public void run() {
- i.decrementAndGet();
- }
- });
- }
- }
-
- public void assertRectSetsNotEqual(String message, Map<Item, Rect> before,
- Map<Item, Rect> after) {
- Throwable throwable = null;
- try {
- assertRectSetsEqual("NOT " + message, before, after);
- } catch (Throwable t) {
- throwable = t;
- }
- assertNotNull(message + " two 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 log = new StringBuilder();
- if (DEBUG) {
- log.append("checking rectangle equality.\n");
- log.append("total space:" + mLayoutManager.mPrimaryOrientation.getTotalSpace());
- log.append("before:");
- for (Map.Entry<Item, Rect> entry : before.entrySet()) {
- log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
- .append(entry.getValue());
- }
- log.append("\nafter:");
- for (Map.Entry<Item, Rect> entry : after.entrySet()) {
- log.append("\n").append(entry.getKey().mAdapterIndex).append(":")
- .append(entry.getValue());
- }
- message += "\n\n" + log.toString();
- }
- assertEquals(message + ": item 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 + ": Same 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 + ": Item with same adapter index should be visible " +
- "after simple re-layout",
- afterRect);
- }
- assertEquals(message + ": Item should be laid out at the same coordinates",
- entry.getValue(),
- afterRect);
- }
- }
-
- // test layout params assignment
-
- static class OnLayoutListener {
-
- void before(RecyclerView.Recycler recycler, RecyclerView.State state) {
- }
-
- void after(RecyclerView.Recycler recycler, RecyclerView.State state) {
- }
- }
-
- class WrappedLayoutManager extends StaggeredGridLayoutManager {
-
- CountDownLatch layoutLatch;
- OnLayoutListener mOnLayoutListener;
- // gradle does not yet let us customize manifest for tests which is necessary to test RTL.
- // until bug is fixed, we'll fake it.
- // public issue id: 57819
- Boolean mFakeRTL;
-
- @Override
- boolean isLayoutRTL() {
- return mFakeRTL == null ? super.isLayoutRTL() : mFakeRTL;
- }
-
- public void expectLayouts(int count) {
- layoutLatch = new CountDownLatch(count);
- }
-
- public void waitForLayout(long timeout) throws InterruptedException {
- waitForLayout(timeout * (DEBUG ? 1000 : 1), TimeUnit.SECONDS);
- }
-
- public void waitForLayout(long timeout, TimeUnit timeUnit) throws InterruptedException {
- layoutLatch.await(timeout, timeUnit);
- assertEquals("all expected layouts should be executed at the expected time",
- 0, layoutLatch.getCount());
- }
-
- public void assertNoLayout(String msg, long timeout) throws Throwable {
- layoutLatch.await(timeout, TimeUnit.SECONDS);
- assertFalse(msg, layoutLatch.getCount() == 0);
- }
-
- @Override
- public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
- String before;
- if (DEBUG) {
- before = layoutToString("before");
- } else {
- before = "enable DEBUG";
- }
- try {
- if (mOnLayoutListener != null) {
- mOnLayoutListener.before(recycler, state);
- }
- super.onLayoutChildren(recycler, state);
- if (mOnLayoutListener != null) {
- mOnLayoutListener.after(recycler, state);
- }
- validateChildren(before);
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
-
- layoutLatch.countDown();
- }
-
- @Override
- int scrollBy(int dt, RecyclerView.Recycler recycler, RecyclerView.State state) {
- try {
- int result = super.scrollBy(dt, recycler, state);
- validateChildren();
- return result;
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
-
- return 0;
- }
-
- public WrappedLayoutManager(int spanCount, int orientation) {
- super(spanCount, orientation);
- }
-
- ArrayList<ArrayList<View>> collectChildrenBySpan() {
- ArrayList<ArrayList<View>> viewsBySpan = new ArrayList<ArrayList<View>>();
- for (int i = 0; i < getSpanCount(); i++) {
- viewsBySpan.add(new ArrayList<View>());
- }
- for (int i = 0; i < getChildCount(); i++) {
- View view = getChildAt(i);
- LayoutParams lp
- = (LayoutParams) view
- .getLayoutParams();
- viewsBySpan.get(lp.mSpan.mIndex).add(view);
- }
- return viewsBySpan;
- }
-
- @Nullable
- @Override
- public View onFocusSearchFailed(View focused, int direction, RecyclerView.Recycler recycler,
- RecyclerView.State state) {
- View result = null;
- try {
- result = super.onFocusSearchFailed(focused, direction, recycler, state);
- validateChildren();
- } catch (Throwable t) {
- postExceptionToInstrumentation(t);
- }
- return result;
- }
-
- Rect getViewBounds(View view) {
- if (getOrientation() == HORIZONTAL) {
- return new Rect(
- mPrimaryOrientation.getDecoratedStart(view),
- mSecondaryOrientation.getDecoratedStart(view),
- mPrimaryOrientation.getDecoratedEnd(view),
- mSecondaryOrientation.getDecoratedEnd(view));
- } else {
- return new Rect(
- mSecondaryOrientation.getDecoratedStart(view),
- mPrimaryOrientation.getDecoratedStart(view),
- mSecondaryOrientation.getDecoratedEnd(view),
- mPrimaryOrientation.getDecoratedEnd(view));
- }
- }
-
- public String getBoundsLog() {
- StringBuilder sb = new StringBuilder();
- sb.append("view bounds:[start:").append(mPrimaryOrientation.getStartAfterPadding())
- .append(",").append(" end").append(mPrimaryOrientation.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(
- mPrimaryOrientation.getDecoratedStart(child)).append(", end:")
- .append(mPrimaryOrientation.getDecoratedEnd(child)).append("]\n");
- }
- return sb.toString();
- }
-
- public VisibleChildren traverseAndFindVisibleChildren() {
- int childCount = getChildCount();
- final VisibleChildren visibleChildren = new VisibleChildren(getSpanCount());
- final int start = mPrimaryOrientation.getStartAfterPadding();
- final int end = mPrimaryOrientation.getEndAfterPadding();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- final int childStart = mPrimaryOrientation.getDecoratedStart(child);
- final int childEnd = mPrimaryOrientation.getDecoratedEnd(child);
- final boolean fullyVisible = childStart >= start && childEnd <= end;
- final boolean hidden = childEnd <= start || childStart >= end;
- if (hidden) {
- continue;
- }
- final int position = getPosition(child);
- final int span = getLp(child).getSpanIndex();
- if (fullyVisible) {
- if (position < visibleChildren.firstFullyVisiblePositions[span] ||
- visibleChildren.firstFullyVisiblePositions[span]
- == RecyclerView.NO_POSITION) {
- visibleChildren.firstFullyVisiblePositions[span] = position;
- }
-
- if (position > visibleChildren.lastFullyVisiblePositions[span]) {
- visibleChildren.lastFullyVisiblePositions[span] = position;
- }
- }
-
- if (position < visibleChildren.firstVisiblePositions[span] ||
- visibleChildren.firstVisiblePositions[span] == RecyclerView.NO_POSITION) {
- visibleChildren.firstVisiblePositions[span] = position;
- }
-
- if (position > visibleChildren.lastVisiblePositions[span]) {
- visibleChildren.lastVisiblePositions[span] = position;
- }
- if (visibleChildren.findFirstPartialVisibleClosestToStart == null) {
- visibleChildren.findFirstPartialVisibleClosestToStart = child;
- }
- visibleChildren.findFirstPartialVisibleClosestToEnd = child;
- }
- return visibleChildren;
- }
-
- 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();
- for (int i = 0; i < childCount; i++) {
- View child = getChildAt(i);
- // do it if and only if child is visible
- if (child.getRight() < 0 || child.getBottom() < 0 ||
- child.getLeft() >= getWidth() || child.getTop() >= getHeight()) {
- // invisible children may be drawn in cases like scrolling so we should
- // ignore them
- continue;
- }
- LayoutParams lp = (LayoutParams) child
- .getLayoutParams();
- TestViewHolder vh = (TestViewHolder) lp.mViewHolder;
- items.put(vh.mBoundItem, getViewBounds(child));
- }
- }
- });
- return items;
- }
-
-
- public void setFakeRtl(Boolean fakeRtl) {
- mFakeRTL = fakeRtl;
- try {
- requestLayoutOnUIThread(mRecyclerView);
- } catch (Throwable throwable) {
- postExceptionToInstrumentation(throwable);
- }
- }
-
- private String layoutToString(String hint) {
- StringBuilder sb = new StringBuilder();
- sb.append("LAYOUT POSITIONS AND INDICES ").append(hint).append("\n");
- for (int i = 0; i < getChildCount(); i++) {
- final View view = getChildAt(i);
- final LayoutParams layoutParams = (LayoutParams) view.getLayoutParams();
- sb.append(String.format("index: %d pos: %d top: %d bottom: %d span: %d isFull:%s",
- i, getPosition(view),
- mPrimaryOrientation.getDecoratedStart(view),
- mPrimaryOrientation.getDecoratedEnd(view),
- layoutParams.getSpanIndex(), layoutParams.isFullSpan())).append("\n");
- }
- return sb.toString();
- }
-
- private void validateChildren() {
- validateChildren(null);
- }
-
- private void validateChildren(String msg) {
- if (getChildCount() == 0 || mRecyclerView.mState.isPreLayout()) {
- return;
- }
- final int dir = mShouldReverseLayout ? -1 : 1;
- int i = 0;
- int pos = -1;
- while (i < getChildCount()) {
- LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- if (lp.isItemRemoved()) {
- i++;
- continue;
- }
- pos = getPosition(getChildAt(i));
- break;
- }
- if (pos == -1) {
- return;
- }
- while (++i < getChildCount()) {
- LayoutParams lp = (LayoutParams) getChildAt(i).getLayoutParams();
- if (lp.isItemRemoved()) {
- continue;
- }
- pos += dir;
- if (getPosition(getChildAt(i)) != pos) {
- throw new RuntimeException("INVALID POSITION FOR CHILD " + i + "\n" +
- layoutToString("ERROR") + "\n msg:" + msg);
- }
- }
- }
- }
-
- static class VisibleChildren {
-
- int[] firstVisiblePositions;
-
- int[] firstFullyVisiblePositions;
-
- int[] lastVisiblePositions;
-
- int[] lastFullyVisiblePositions;
-
- View findFirstPartialVisibleClosestToStart;
- View findFirstPartialVisibleClosestToEnd;
-
- VisibleChildren(int spanCount) {
- firstFullyVisiblePositions = new int[spanCount];
- firstVisiblePositions = new int[spanCount];
- lastVisiblePositions = new int[spanCount];
- lastFullyVisiblePositions = new int[spanCount];
- for (int i = 0; i < spanCount; i++) {
- firstFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
- firstVisiblePositions[i] = RecyclerView.NO_POSITION;
- lastVisiblePositions[i] = RecyclerView.NO_POSITION;
- lastFullyVisiblePositions[i] = RecyclerView.NO_POSITION;
- }
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) {
- return true;
- }
- if (o == null || getClass() != o.getClass()) {
- return false;
- }
-
- VisibleChildren that = (VisibleChildren) o;
-
- if (!Arrays.equals(firstFullyVisiblePositions, that.firstFullyVisiblePositions)) {
- return false;
- }
- if (findFirstPartialVisibleClosestToStart
- != null ? !findFirstPartialVisibleClosestToStart
- .equals(that.findFirstPartialVisibleClosestToStart)
- : that.findFirstPartialVisibleClosestToStart != null) {
- return false;
- }
- if (!Arrays.equals(firstVisiblePositions, that.firstVisiblePositions)) {
- return false;
- }
- if (!Arrays.equals(lastFullyVisiblePositions, that.lastFullyVisiblePositions)) {
- return false;
- }
- if (findFirstPartialVisibleClosestToEnd != null ? !findFirstPartialVisibleClosestToEnd
- .equals(that.findFirstPartialVisibleClosestToEnd)
- : that.findFirstPartialVisibleClosestToEnd
- != null) {
- return false;
- }
- if (!Arrays.equals(lastVisiblePositions, that.lastVisiblePositions)) {
- return false;
- }
-
- return true;
- }
-
- @Override
- public int hashCode() {
- int result = Arrays.hashCode(firstVisiblePositions);
- result = 31 * result + Arrays.hashCode(firstFullyVisiblePositions);
- result = 31 * result + Arrays.hashCode(lastVisiblePositions);
- result = 31 * result + Arrays.hashCode(lastFullyVisiblePositions);
- result = 31 * result + (findFirstPartialVisibleClosestToStart != null
- ? findFirstPartialVisibleClosestToStart
- .hashCode() : 0);
- result = 31 * result + (findFirstPartialVisibleClosestToEnd != null
- ? findFirstPartialVisibleClosestToEnd
- .hashCode()
- : 0);
- return result;
- }
-
- @Override
- public String toString() {
- return "VisibleChildren{" +
- "firstVisiblePositions=" + Arrays.toString(firstVisiblePositions) +
- ", firstFullyVisiblePositions=" + Arrays.toString(firstFullyVisiblePositions) +
- ", lastVisiblePositions=" + Arrays.toString(lastVisiblePositions) +
- ", lastFullyVisiblePositions=" + Arrays.toString(lastFullyVisiblePositions) +
- ", findFirstPartialVisibleClosestToStart=" +
- viewToString(findFirstPartialVisibleClosestToStart) +
- ", findFirstPartialVisibleClosestToEnd=" +
- viewToString(findFirstPartialVisibleClosestToEnd) +
- '}';
- }
-
- private String viewToString(View view) {
- if (view == null) {
- return null;
- }
- ViewGroup.LayoutParams lp = view.getLayoutParams();
- if (lp instanceof RecyclerView.LayoutParams == false) {
- return System.identityHashCode(view) + "(?)";
- }
- RecyclerView.LayoutParams rvlp = (RecyclerView.LayoutParams) lp;
- return System.identityHashCode(view) + "(" + rvlp.getViewAdapterPosition() + ")";
- }
- }
-
- class GridTestAdapter extends TestAdapter {
-
- int mOrientation;
- int mRecyclerViewWidth;
- int mRecyclerViewHeight;
- Integer mSizeReference = null;
-
- // original ids of items that should be full span
- HashSet<Integer> mFullSpanItems = new HashSet<Integer>();
-
- private boolean mViewsHaveEqualSize = false; // size in the scrollable direction
-
- private OnBindCallback mOnBindCallback;
-
- GridTestAdapter(int count, int orientation) {
- super(count);
- mOrientation = orientation;
- }
-
- @Override
- public TestViewHolder onCreateViewHolder(ViewGroup parent,
- int viewType) {
- mRecyclerViewWidth = parent.getWidth();
- mRecyclerViewHeight = parent.getHeight();
- TestViewHolder vh = super.onCreateViewHolder(parent, viewType);
- if (mOnBindCallback != null) {
- mOnBindCallback.onCreatedViewHolder(vh);
- }
- return vh;
- }
-
- @Override
- public void offsetOriginalIndices(int start, int offset) {
- if (mFullSpanItems.size() > 0) {
- HashSet<Integer> old = mFullSpanItems;
- mFullSpanItems = new HashSet<Integer>();
- for (Integer i : old) {
- if (i < start) {
- mFullSpanItems.add(i);
- } else if (offset > 0 || (start + Math.abs(offset)) <= i) {
- mFullSpanItems.add(i + offset);
- } else if (DEBUG) {
- Log.d(TAG, "removed full span item " + i);
- }
- }
- }
- super.offsetOriginalIndices(start, offset);
- }
-
- @Override
- protected void moveInUIThread(int from, int to) {
- boolean setAsFullSpanAgain = mFullSpanItems.contains(from);
- super.moveInUIThread(from, to);
- if (setAsFullSpanAgain) {
- mFullSpanItems.add(to);
- }
- }
-
- @Override
- public void onBindViewHolder(TestViewHolder holder,
- int position) {
- if (mSizeReference == null) {
- mSizeReference = mOrientation == OrientationHelper.HORIZONTAL ? mRecyclerViewWidth
- / AVG_ITEM_PER_VIEW : mRecyclerViewHeight / AVG_ITEM_PER_VIEW;
- }
- super.onBindViewHolder(holder, position);
- Item item = mItems.get(position);
- RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams) holder.itemView
- .getLayoutParams();
- if (lp instanceof LayoutParams) {
- ((LayoutParams) lp).setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
- } else {
- LayoutParams slp = new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
- ViewGroup.LayoutParams.WRAP_CONTENT);
- holder.itemView.setLayoutParams(slp);
- slp.setFullSpan(mFullSpanItems.contains(item.mAdapterIndex));
- lp = slp;
- }
-
- if (mOnBindCallback == null || mOnBindCallback.assignRandomSize()) {
- final int minSize = mViewsHaveEqualSize ? mSizeReference :
- mSizeReference + 20 * (item.mId % 10);
- if (mOrientation == OrientationHelper.HORIZONTAL) {
- holder.itemView.setMinimumWidth(minSize);
- } else {
- holder.itemView.setMinimumHeight(minSize);
- }
- lp.topMargin = 3;
- lp.leftMargin = 5;
- lp.rightMargin = 7;
- lp.bottomMargin = 9;
- }
-
- if (mOnBindCallback != null) {
- mOnBindCallback.onBoundItem(holder, position);
- }
- }
- }
-
- abstract static class OnBindCallback {
-
- abstract void onBoundItem(TestViewHolder vh, int position);
-
- boolean assignRandomSize() {
- return true;
- }
-
- void onCreatedViewHolder(TestViewHolder vh) {
- }
- }
-
- static class Config implements Cloneable {
-
- private static final int DEFAULT_ITEM_COUNT = 300;
-
- int mOrientation = OrientationHelper.VERTICAL;
-
- boolean mReverseLayout = false;
-
- int mSpanCount = 3;
-
- int mGapStrategy = GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS;
-
- int mItemCount = DEFAULT_ITEM_COUNT;
-
- Config(int orientation, boolean reverseLayout, int spanCount, int gapStrategy) {
- mOrientation = orientation;
- mReverseLayout = reverseLayout;
- mSpanCount = spanCount;
- mGapStrategy = gapStrategy;
- }
-
- public Config() {
-
- }
-
- Config orientation(int orientation) {
- mOrientation = orientation;
- return this;
- }
-
- Config reverseLayout(boolean reverseLayout) {
- mReverseLayout = reverseLayout;
- return this;
- }
-
- Config spanCount(int spanCount) {
- mSpanCount = spanCount;
- return this;
- }
-
- Config gapStrategy(int gapStrategy) {
- mGapStrategy = gapStrategy;
- return this;
- }
-
- public Config itemCount(int itemCount) {
- mItemCount = itemCount;
- return this;
- }
-
- @Override
- public String toString() {
- return "[CONFIG:" +
- " span:" + mSpanCount + "," +
- " orientation:" + (mOrientation == HORIZONTAL ? "horz," : "vert,") +
- " reverse:" + (mReverseLayout ? "T" : "F") +
- " itemCount:" + mItemCount +
- " gap strategy: " + gapStrategyName(mGapStrategy);
- }
-
- private static String gapStrategyName(int gapStrategy) {
- switch (gapStrategy) {
- case GAP_HANDLING_NONE:
- return "none";
- case GAP_HANDLING_MOVE_ITEMS_BETWEEN_SPANS:
- return "move spans";
- }
- return "gap strategy: unknown";
- }
-
- @Override
- public Object clone() throws CloneNotSupportedException {
- return super.clone();
- }
- }
-
- private interface PostLayoutRunnable {
-
- void run() throws Throwable;
-
- String describe();
- }
-
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java
new file mode 100644
index 0000000..afd19ab
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerWrapContentTest.java
@@ -0,0 +1,153 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.MeasureBehavior;
+import static android.support.v7.widget.BaseWrapContentWithAspectRatioTest.WrapContentAdapter;
+import static android.support.v7.widget.StaggeredGridLayoutManager.HORIZONTAL;
+import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
+import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
+
+import android.graphics.Rect;
+import android.view.Gravity;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.Arrays;
+import java.util.List;
+
+@RunWith(Parameterized.class)
+public class StaggeredGridLayoutManagerWrapContentTest extends BaseWrapContentTest {
+ int mOrientation = StaggeredGridLayoutManager.VERTICAL;
+ public StaggeredGridLayoutManagerWrapContentTest(Rect padding) {
+ super(new WrapContentConfig(false, false, padding));
+ }
+
+ @Parameterized.Parameters(name = "paddingRect={0}")
+ public static List<Rect> params() {
+ return Arrays.asList(
+ new Rect(0, 0, 0, 0),
+ new Rect(5, 0, 0, 0),
+ new Rect(0, 3, 0, 0),
+ new Rect(0, 0, 2, 0),
+ new Rect(0, 0, 0, 7),
+ new Rect(3, 5, 7, 11)
+ );
+ }
+
+ @Test
+ public void testUnspecifiedWithHint() throws Throwable {
+ unspecifiedWithHintTest(mOrientation == StaggeredGridLayoutManager.HORIZONTAL);
+ }
+
+ @Test
+ public void testSimple() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 15, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 20, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 10),
+ new Rect(20, 0, 30, 15),
+ new Rect(40, 0, 50, 20),
+ new Rect(0, 10, 20, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 60, 20);
+ }
+
+ @Test
+ public void testSimpleHorizontal() throws Throwable {
+ mOrientation = HORIZONTAL;
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(15, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 20, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 10),
+ new Rect(0, 20, 15, 30),
+ new Rect(0, 40, 20, 50),
+ new Rect(10, 0, 20, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 20, 60);
+ }
+
+ @Test
+ public void testUnspecifiedWidth() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ lp.wSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(2000, 10, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(500, 15, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(400, 20, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(50, 10, MATCH_PARENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 2000, 10),
+ new Rect(2000, 0, 2500, 15),
+ new Rect(4000, 0, 4400, 20),
+ new Rect(0, 10, 2000, 20)
+ };
+ layoutAndCheck(lp, adapter, expected, 6000, 20);
+ }
+
+ @Test
+ public void testUnspecifiedHeight() throws Throwable {
+ TestedFrameLayout.FullControlLayoutParams lp =
+ mWrapContentConfig.toLayoutParams(WRAP_CONTENT, WRAP_CONTENT);
+ lp.hSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+ WrapContentAdapter adapter = new WrapContentAdapter(
+ new MeasureBehavior(10, 4000, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 5500, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(10, 3000, WRAP_CONTENT, WRAP_CONTENT),
+ new MeasureBehavior(20, 100, WRAP_CONTENT, WRAP_CONTENT)
+ );
+ Rect[] expected = new Rect[] {
+ new Rect(0, 0, 10, 4000),
+ new Rect(20, 0, 30, 5500),
+ new Rect(40, 0, 50, 3000),
+ new Rect(40, 3000, 60, 3100)
+ };
+ layoutAndCheck(lp, adapter, expected, 60, 5500);
+ }
+
+ @Override
+ RecyclerView.LayoutManager createLayoutManager() {
+ return new StaggeredGridLayoutManager(3, mOrientation);
+ }
+
+ @Override
+ protected int getVerticalGravity(RecyclerView.LayoutManager layoutManager) {
+ return Gravity.TOP;
+ }
+
+ @Override
+ protected int getHorizontalGravity(RecyclerView.LayoutManager layoutManager) {
+ return Gravity.LEFT;
+ }
+}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
index 2e3c881..3e37d77 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestActivity.java
@@ -18,19 +18,91 @@
import android.app.Activity;
import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
+import android.support.test.runner.MonitoringInstrumentation;
import android.view.WindowManager;
-public class TestActivity extends Activity {
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
- TestedFrameLayout mContainer;
+public class TestActivity extends Activity {
+ // This is not great but the only way to do this until test runner adds support to not kill
+ // activities after tests.
+ private static final String TEST_RUNNER =
+ MonitoringInstrumentation.class.getCanonicalName() + "$" + MonitoringInstrumentation
+ .ActivityFinisher.class.getSimpleName();
+ private volatile TestedFrameLayout mContainer;
+ boolean mVisible;
+ boolean mAllowFinish;
+ private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ overridePendingTransition(0, 0);
+
mContainer = new TestedFrameLayout(this);
+ mHandler = new Handler(Looper.getMainLooper());
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
+ getWindow().addFlags(WindowManager.LayoutParams.FLAG_DISMISS_KEYGUARD);
setContentView(mContainer);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
+
+ public TestedFrameLayout getContainer() {
+ return mContainer;
+ }
+
+ public void resetContent() throws InterruptedException {
+ final CountDownLatch done = new CountDownLatch(1);
+ mHandler.post(new Runnable() {
+ @Override
+ public void run() {
+ mContainer = new TestedFrameLayout(TestActivity.this);
+ setContentView(mContainer);
+ done.countDown();
+ }
+ });
+ if (!done.await(5, TimeUnit.SECONDS)) {
+ throw new AssertionError("could not cleanup activity contents in 5 seconds");
+ }
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mVisible = false;
+ }
+
+ @Override
+ public void onResume() {
+ super.onResume();
+ mVisible = true;
+ }
+
+ @Override
+ public void finish() {
+ if (!mAllowFinish) {
+ StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
+ // this is terrible but easy workaround for selective finishing
+ for (StackTraceElement element : stackTrace) {
+
+ if (TEST_RUNNER.equals(element.getClassName())) {
+ // don't allow activity finisher to finish this.
+ return;
+ }
+ }
+ }
+ super.finish();
+ }
+
+ public void setAllowFinish(boolean allowFinish) {
+ mAllowFinish = allowFinish;
+ }
+
+ public boolean canBeReUsed() {
+ return getWindow() != null && mVisible && !mAllowFinish;
+ }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestResizingRelayoutWithAutoMeasure.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestResizingRelayoutWithAutoMeasure.java
new file mode 100644
index 0000000..1f0f3ef
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestResizingRelayoutWithAutoMeasure.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import static android.view.View.MeasureSpec.AT_MOST;
+import static android.view.View.MeasureSpec.EXACTLY;
+import static android.view.View.MeasureSpec.makeMeasureSpec;
+
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.notNullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+
+import android.graphics.Rect;
+import android.test.suitebuilder.annotation.MediumTest;
+import android.view.View;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Tests whether the layout manager can keep its children positions properly after it is re-laid
+ * out with larger/smaller intermediate size but the same final size.
+ */
+@MediumTest
+@RunWith(Parameterized.class)
+public class TestResizingRelayoutWithAutoMeasure extends BaseRecyclerViewInstrumentationTest {
+ private final RecyclerView.LayoutManager mLayoutManager;
+ private final float mWidthMultiplier;
+ private final float mHeightMultiplier;
+
+ public TestResizingRelayoutWithAutoMeasure(@SuppressWarnings("UnusedParameters") String name,
+ RecyclerView.LayoutManager layoutManager, float widthMultiplier,
+ float heightMultiplier) {
+ mLayoutManager = layoutManager;
+ mWidthMultiplier = widthMultiplier;
+ mHeightMultiplier = heightMultiplier;
+ }
+
+ @Parameterized.Parameters(name = "{0} w:{2} h:{3}")
+ public static List<Object[]> getParams() {
+ List<Object[]> params = new ArrayList<>();
+ for (float w : new float[]{.5f, 1f, 2f}) {
+ for (float h : new float[]{.5f, 1f, 2f}) {
+ params.add(
+ new Object[]{"linear layout", new LinearLayoutManager(null), w, h}
+ );
+ params.add(
+ new Object[]{"grid layout", new GridLayoutManager(null, 3), w, h}
+ );
+ params.add(
+ new Object[]{"staggered", new StaggeredGridLayoutManager(3,
+ StaggeredGridLayoutManager.VERTICAL), w, h}
+ );
+ }
+ }
+ return params;
+ }
+
+ @Test
+ public void testResizeDuringMeasurements() throws Throwable {
+ final RecyclerView recyclerView = new RecyclerView(getActivity());
+ recyclerView.setLayoutManager(mLayoutManager);
+ recyclerView.setAdapter(new TestAdapter(500));
+ setRecyclerView(recyclerView);
+ getInstrumentation().waitForIdleSync();
+ assertThat("Test sanity", recyclerView.getChildCount() > 0, is(true));
+ final int lastPosition = recyclerView.getAdapter().getItemCount() - 1;
+ smoothScrollToPosition(lastPosition);
+ assertThat("test sanity", recyclerView.findViewHolderForAdapterPosition(lastPosition),
+ notNullValue());
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ int startHeight = recyclerView.getMeasuredHeight();
+ int startWidth = recyclerView.getMeasuredWidth();
+ Map<Integer, Rect> startPositions = capturePositions(recyclerView);
+ recyclerView.measure(
+ makeMeasureSpec((int) (startWidth * mWidthMultiplier),
+ mWidthMultiplier == 1f ? EXACTLY : AT_MOST),
+ makeMeasureSpec((int) (startHeight * mHeightMultiplier),
+ mHeightMultiplier == 1f ? EXACTLY : AT_MOST));
+
+ recyclerView.measure(
+ makeMeasureSpec(startWidth, EXACTLY),
+ makeMeasureSpec(startHeight, EXACTLY));
+ recyclerView.dispatchLayout();
+ Map<Integer, Rect> endPositions = capturePositions(recyclerView);
+ assertStartItemPositions(startPositions, endPositions);
+ }
+ });
+ }
+
+ private void assertStartItemPositions(Map<Integer, Rect> startPositions,
+ Map<Integer, Rect> endPositions) {
+ for (Map.Entry<Integer, Rect> entry : startPositions.entrySet()) {
+ Rect rect = endPositions.get(entry.getKey());
+ assertThat("view for position " + entry.getKey() + " at" + entry.getValue(), rect,
+ notNullValue());
+ assertThat("rect for position " + entry.getKey(), entry.getValue(), is(rect));
+ }
+ }
+
+ private Map<Integer, Rect> capturePositions(RecyclerView recyclerView) {
+ Map<Integer, Rect> positions = new HashMap<>();
+ for (int i = 0; i < mLayoutManager.getChildCount(); i++) {
+ View view = mLayoutManager.getChildAt(i);
+ int childAdapterPosition = recyclerView.getChildAdapterPosition(view);
+ Rect outRect = new Rect();
+ mLayoutManager.getDecoratedBoundsWithMargins(view, outRect);
+ // only record if outRect is visible
+ if (outRect.left >= mRecyclerView.getWidth() ||
+ outRect.top >= mRecyclerView.getHeight() ||
+ outRect.right < 0 ||
+ outRect.bottom < 0) {
+ continue;
+ }
+ positions.put(childAdapterPosition, outRect);
+ }
+ return positions;
+ }
+}
\ No newline at end of file
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
index 18aeba4..040d001 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/TestedFrameLayout.java
@@ -19,8 +19,10 @@
import android.content.Context;
import android.support.v4.view.NestedScrollingParent;
import android.support.v4.view.ViewCompat;
+import android.util.AttributeSet;
import android.util.Log;
import android.view.View;
+import android.view.ViewGroup;
import android.widget.FrameLayout;
public class TestedFrameLayout extends FrameLayout implements NestedScrollingParent {
@@ -37,6 +39,103 @@
}
@Override
+ protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
+ RecyclerView recyclerView = getRvChild();
+ if (recyclerView == null) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ FullControlLayoutParams lp = (FullControlLayoutParams) recyclerView.getLayoutParams();
+ if (lp.wSpec == null && lp.hSpec == null) {
+ super.onMeasure(widthMeasureSpec, heightMeasureSpec);
+ return;
+ }
+ final int childWidthMeasureSpec;
+ if (lp.wSpec != null) {
+ childWidthMeasureSpec = lp.wSpec;
+ } else if (lp.width == LayoutParams.MATCH_PARENT) {
+ final int width = Math.max(0, getMeasuredWidth()
+ - lp.leftMargin - lp.rightMargin);
+ childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY);
+ } else {
+ childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec,
+ lp.leftMargin + lp.rightMargin, lp.width);
+ }
+
+ final int childHeightMeasureSpec;
+ if (lp.hSpec != null) {
+ childHeightMeasureSpec = lp.hSpec;
+ } else if (lp.height == LayoutParams.MATCH_PARENT) {
+ final int height = Math.max(0, getMeasuredHeight()
+ - lp.topMargin - lp.bottomMargin);
+ childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY);
+ } else {
+ childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec,
+ lp.topMargin + lp.bottomMargin, lp.height);
+ }
+ recyclerView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
+ if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
+ MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY) {
+ setMeasuredDimension(
+ MeasureSpec.getSize(widthMeasureSpec),
+ MeasureSpec.getSize(heightMeasureSpec)
+ );
+ } else {
+ setMeasuredDimension(
+ chooseSize(widthMeasureSpec,
+ recyclerView.getWidth() + getPaddingLeft() + getPaddingRight(),
+ getMinimumWidth()),
+ chooseSize(heightMeasureSpec,
+ recyclerView.getHeight() + getPaddingTop() + getPaddingBottom(),
+ getMinimumHeight()));
+ }
+ }
+
+ public static int chooseSize(int spec, int desired, int min) {
+ final int mode = View.MeasureSpec.getMode(spec);
+ final int size = View.MeasureSpec.getSize(spec);
+ switch (mode) {
+ case View.MeasureSpec.EXACTLY:
+ return size;
+ case View.MeasureSpec.AT_MOST:
+ return Math.min(size, desired);
+ case View.MeasureSpec.UNSPECIFIED:
+ default:
+ return Math.max(desired, min);
+ }
+ }
+
+
+ private RecyclerView getRvChild() {
+ for (int i = 0; i < getChildCount(); i++) {
+ if (getChildAt(i) instanceof RecyclerView) {
+ return (RecyclerView) getChildAt(i);
+ }
+ }
+ return null;
+ }
+
+ @Override
+ protected boolean checkLayoutParams(ViewGroup.LayoutParams p) {
+ return p instanceof FullControlLayoutParams;
+ }
+
+ @Override
+ protected ViewGroup.LayoutParams generateLayoutParams(ViewGroup.LayoutParams p) {
+ return new FullControlLayoutParams(p);
+ }
+
+ @Override
+ public LayoutParams generateLayoutParams(AttributeSet attrs) {
+ return new FullControlLayoutParams(getContext(), attrs);
+ }
+
+ @Override
+ protected LayoutParams generateDefaultLayoutParams() {
+ return new FullControlLayoutParams(getWidth(), getHeight());
+ }
+
+ @Override
public boolean onStartNestedScroll(View child, View target, int nestedScrollAxes) {
// Always start nested scroll
return mNestedFlingMode == TEST_NESTED_SCROLL_MODE_CONSUME
@@ -98,4 +197,30 @@
public void setNestedFlingMode(int mode) {
mNestedFlingMode = mode;
}
+
+ public static class FullControlLayoutParams extends FrameLayout.LayoutParams {
+
+ Integer wSpec;
+ Integer hSpec;
+
+ public FullControlLayoutParams(Context c, AttributeSet attrs) {
+ super(c, attrs);
+ }
+
+ public FullControlLayoutParams(int width, int height) {
+ super(width, height);
+ }
+
+ public FullControlLayoutParams(ViewGroup.LayoutParams source) {
+ super(source);
+ }
+
+ public FullControlLayoutParams(FrameLayout.LayoutParams source) {
+ super(source);
+ }
+
+ public FullControlLayoutParams(MarginLayoutParams source) {
+ super(source);
+ }
+ }
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
deleted file mode 100644
index 4c78e3d..0000000
--- a/v7/recyclerview/tests/src/android/support/v7/widget/ViewInfoStoreTest.java
+++ /dev/null
@@ -1,20 +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;
-
-public class ViewInfoStoreTest {
-
-}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.java
new file mode 100644
index 0000000..b485fa6
--- /dev/null
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/WrapContentBasicTest.java
@@ -0,0 +1,222 @@
+/*
+ * 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 android.content.Context;
+import android.support.annotation.Nullable;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.runner.AndroidJUnit4;
+import android.test.AndroidTestCase;
+import android.view.Display;
+import android.view.View;
+import android.view.ViewGroup;
+
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(AndroidJUnit4.class)
+public class WrapContentBasicTest extends AndroidTestCase {
+
+ private WrapContentLayoutManager mLayoutManager;
+ private RecyclerView mRecyclerView;
+ private WrapAdapter mAdapter;
+ private static int WRAP = View.MeasureSpec.makeMeasureSpec(10, View.MeasureSpec.AT_MOST);
+ private static int EXACT = View.MeasureSpec.makeMeasureSpec(10, View.MeasureSpec.EXACTLY);
+ private static int UNSPECIFIED = View.MeasureSpec
+ .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED);
+
+ @Before
+ @Override
+ public void setUp() throws Exception {
+ super.setUp();
+ setContext(InstrumentationRegistry.getContext());
+ RecyclerView rv = new RecyclerView(getContext());
+ mRecyclerView = spy(rv);
+ mLayoutManager = spy(new WrapContentLayoutManager());
+ // working around a mockito issue
+ rv.mLayout = mLayoutManager;
+ mAdapter = spy(new WrapAdapter());
+ mRecyclerView.setLayoutManager(mLayoutManager);
+ mRecyclerView.setAdapter(mAdapter);
+ }
+
+ @After
+ @Override
+ public void tearDown() throws Exception {
+ super.tearDown();
+ }
+
+ @Test
+ public void testLayoutInOnMeasureWithoutPredictive() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ when(mLayoutManager.supportsPredictiveItemAnimations()).thenReturn(false);
+ mRecyclerView.onMeasure(WRAP, WRAP);
+ mRecyclerView.onMeasure(WRAP, WRAP);
+ mRecyclerView.onLayout(true, 0, 10, 10, 10);
+ verify(mLayoutManager, times(3))
+ .onLayoutChildren(mRecyclerView.mRecycler, mRecyclerView.mState);
+ }
+
+ @Test
+ public void dataChangeAfterMeasure() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ mRecyclerView.onMeasure(WRAP, WRAP);
+ mRecyclerView.onMeasure(WRAP, WRAP);
+ mAdapter.notifyItemChanged(1);
+ mRecyclerView.onLayout(true, 0, 10, 10, 10);
+ verify(mLayoutManager, times(3))
+ .onLayoutChildren(mRecyclerView.mRecycler, mRecyclerView.mState);
+ }
+
+ @Test
+ public void setDimensionsFromChildren() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ mLayoutManager.setMeasuredDimensionFromChildren(WRAP, WRAP);
+ verify(mLayoutManager).setMeasuredDimension(children[0].getWidth(),
+ children[0].getHeight());
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec1() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ int hSpec = View.MeasureSpec.makeMeasureSpec(111, View.MeasureSpec.EXACTLY);
+ mLayoutManager.setMeasuredDimensionFromChildren(WRAP, hSpec);
+ verify(mLayoutManager).setMeasuredDimension(children[0].getWidth(), 111);
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec2() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ int wSpec = View.MeasureSpec.makeMeasureSpec(111, View.MeasureSpec.EXACTLY);
+ mLayoutManager.setMeasuredDimensionFromChildren(wSpec, WRAP);
+ verify(mLayoutManager).setMeasuredDimension(111, children[0].getHeight());
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec3() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ children[0].layout(0, 0, 100, 100);
+ children[1].layout(-5, 0, 100, 100);
+ children[2].layout(-5, -10, 100, 100);
+ mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+ verify(mLayoutManager).setMeasuredDimension(105, 110);
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec4() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ children[0].layout(0, 0, 100, 100);
+ children[1].layout(-5, 0, 100, 100);
+ children[2].layout(-5, -10, 100, 100);
+ int atMost = View.MeasureSpec.makeMeasureSpec(95, View.MeasureSpec.AT_MOST);
+ mLayoutManager.setMeasuredDimensionFromChildren(atMost, atMost);
+ verify(mLayoutManager).setMeasuredDimension(95, 95);
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec5() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ children[0].layout(0, 0, 100, 100);
+ children[1].layout(-5, 0, 100, 100);
+ children[2].layout(-5, -10, 100, 100);
+ when(mRecyclerView.getMinimumWidth()).thenReturn(250);
+ mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+ verify(mLayoutManager).setMeasuredDimension(250, 110);
+
+ when(mRecyclerView.getMinimumWidth()).thenReturn(5);
+ mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+ verify(mLayoutManager).setMeasuredDimension(105, 110);
+ }
+
+ @Test
+ public void setDimensionsFromChildrenAnsSpec6() {
+ mLayoutManager.setAutoMeasureEnabled(true);
+ View[] children = createMockChildren(3);
+ children[0].layout(0, 0, 100, 100);
+ children[1].layout(-5, 0, 100, 100);
+ children[2].layout(-5, -10, 100, 100);
+ when(mRecyclerView.getMinimumHeight()).thenReturn(250);
+ mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+ verify(mLayoutManager).setMeasuredDimension(105, 250);
+
+ when(mRecyclerView.getMinimumHeight()).thenReturn(50);
+ mLayoutManager.setMeasuredDimensionFromChildren(UNSPECIFIED, UNSPECIFIED);
+ verify(mLayoutManager).setMeasuredDimension(105, 110);
+ }
+
+ private View[] createMockChildren(int count) {
+ View[] views = new View[count];
+ for (int i = 0; i < count; i++) {
+ View v = new View(getContext());
+ v.setLayoutParams(new RecyclerView.LayoutParams(1, 1));
+ views[i] = v;
+ when(mLayoutManager.getChildAt(i)).thenReturn(v);
+ }
+ when(mLayoutManager.getChildCount()).thenReturn(3);
+ return views;
+ }
+
+ public class WrapContentLayoutManager extends RecyclerView.LayoutManager {
+
+ @Override
+ public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
+
+ }
+
+ @Override
+ public RecyclerView.LayoutParams generateDefaultLayoutParams() {
+ return new RecyclerView.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
+ ViewGroup.LayoutParams.WRAP_CONTENT);
+ }
+ }
+
+ public class WrapAdapter extends RecyclerView.Adapter {
+
+ @Override
+ public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+ return null;
+ }
+
+ @Override
+ public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+
+ }
+
+ @Override
+ public int getItemCount() {
+ return 10;
+ }
+ }
+}
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..cdc9662 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
@@ -16,13 +16,19 @@
package android.support.v7.widget.helper;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.app.Instrumentation;
import android.os.SystemClock;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v4.view.ViewCompat;
+import android.support.v7.util.TouchUtils;
import android.support.v7.widget.BaseRecyclerViewInstrumentationTest;
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;
@@ -33,7 +39,10 @@
import java.util.List;
import static android.support.v7.widget.helper.ItemTouchHelper.*;
+import static org.junit.Assert.*;
+@MediumTest
+@RunWith(AndroidJUnit4.class)
public class ItemTouchHelperTest extends BaseRecyclerViewInstrumentationTest {
TestAdapter mAdapter;
@@ -88,28 +97,34 @@
return mWrappedRecyclerView;
}
- public void testSwipeLeft() throws Throwable {
+ @Test
+ public void swipeLeft() throws Throwable {
basicSwipeTest(LEFT, LEFT | RIGHT, -getActivity().getWindow().getDecorView().getWidth());
}
- public void testSwipeRight() throws Throwable {
+ @Test
+ public void swipeRight() throws Throwable {
basicSwipeTest(RIGHT, LEFT | RIGHT, getActivity().getWindow().getDecorView().getWidth());
}
- public void testSwipeStart() throws Throwable {
+ @Test
+ public void swipeStart() throws Throwable {
basicSwipeTest(START, START | END, -getActivity().getWindow().getDecorView().getWidth());
}
- public void testSwipeEnd() throws Throwable {
+ @Test
+ public void swipeEnd() throws Throwable {
basicSwipeTest(END, START | END, getActivity().getWindow().getDecorView().getWidth());
}
- public void testSwipeStartInRTL() throws Throwable {
+ @Test
+ public void swipeStartInRTL() throws Throwable {
mSetupRTL = true;
basicSwipeTest(START, START | END, getActivity().getWindow().getDecorView().getWidth());
}
- public void testSwipeEndInRTL() throws Throwable {
+ @Test
+ public void swipeEndInRTL() throws Throwable {
mSetupRTL = true;
basicSwipeTest(END, START | END, -getActivity().getWindow().getDecorView().getWidth());
}
@@ -131,7 +146,7 @@
final RecyclerView.ViewHolder target = mRecyclerView
.findViewHolderForAdapterPosition(1);
- TouchUtils.dragViewToX(this, target.itemView, Gravity.CENTER, targetX);
+ TouchUtils.dragViewToX(getInstrumentation(), target.itemView, Gravity.CENTER, targetX);
Thread.sleep(100); //wait for animation end
final SwipeRecord swipe = mCalback.getSwipe(target);
assertNotNull(swipe);
@@ -240,376 +255,4 @@
toPos = to.getAdapterPosition();
}
}
-
-
- /**
- * RecyclerView specific TouchUtils.
- */
- static class TouchUtils {
-
- /**
- * Simulate touching the center of a view and releasing quickly (before the tap timeout).
- *
- * @param test The test case that is being run
- * @param v The view that should be clicked
- */
- public static void tapView(InstrumentationTestCase test, RecyclerView recyclerView,
- View v) {
- int[] xy = new int[2];
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- final float x = xy[0] + (viewWidth / 2.0f);
- float y = xy[1] + (viewHeight / 2.0f);
-
- long downTime = SystemClock.uptimeMillis();
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, x, y, 0);
- Instrumentation inst = test.getInstrumentation();
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
- x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
- }
-
- /**
- * Simulate touching the center of a view and cancelling (so no onClick should
- * fire, etc).
- *
- * @param test The test case that is being run
- * @param v The view that should be clicked
- */
- public static void touchAndCancelView(InstrumentationTestCase test, View v) {
- int[] xy = new int[2];
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- final float x = xy[0] + (viewWidth / 2.0f);
- float y = xy[1] + (viewHeight / 2.0f);
-
- Instrumentation inst = test.getInstrumentation();
-
- long downTime = SystemClock.uptimeMillis();
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_CANCEL,
- x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- }
-
- /**
- * Simulate touching the center of a view and releasing.
- *
- * @param test The test case that is being run
- * @param v The view that should be clicked
- */
- public static void clickView(InstrumentationTestCase test, View v) {
- int[] xy = new int[2];
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- final float x = xy[0] + (viewWidth / 2.0f);
- float y = xy[1] + (viewHeight / 2.0f);
-
- Instrumentation inst = test.getInstrumentation();
-
- long downTime = SystemClock.uptimeMillis();
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
- x + (touchSlop / 2.0f), y + (touchSlop / 2.0f), 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- try {
- Thread.sleep(1000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
-
- /**
- * Simulate touching the center of a view, holding until it is a long press, and then
- * releasing.
- *
- * @param test The test case that is being run
- * @param v The view that should be clicked
- */
- public static void longClickView(InstrumentationTestCase test, View v) {
- int[] xy = new int[2];
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- final float x = xy[0] + (viewWidth / 2.0f);
- float y = xy[1] + (viewHeight / 2.0f);
-
- Instrumentation inst = test.getInstrumentation();
-
- long downTime = SystemClock.uptimeMillis();
- long eventTime = SystemClock.uptimeMillis();
-
- MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- eventTime = SystemClock.uptimeMillis();
- final int touchSlop = ViewConfiguration.get(v.getContext()).getScaledTouchSlop();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE,
- x + touchSlop / 2, y + touchSlop / 2, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
-
- try {
- Thread.sleep((long) (ViewConfiguration.getLongPressTimeout() * 1.5f));
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
-
- eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
- }
-
- /**
- * Simulate touching the center of a view and dragging to the top of the screen.
- *
- * @param test The test case that is being run
- * @param v The view that should be dragged
- */
- public static void dragViewToTop(InstrumentationTestCase test, View v) {
- dragViewToTop(test, v, 4);
- }
-
- /**
- * Simulate touching the center of a view and dragging to the top of the screen.
- *
- * @param test The test case that is being run
- * @param v The view that should be dragged
- * @param stepCount How many move steps to include in the drag
- */
- public static void dragViewToTop(InstrumentationTestCase test, View v, int stepCount) {
- int[] xy = new int[2];
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- final float x = xy[0] + (viewWidth / 2.0f);
- float fromY = xy[1] + (viewHeight / 2.0f);
- float toY = 0;
-
- drag(test, x, x, fromY, toY, stepCount);
- }
-
- /**
- * Get the location of a view. Use the gravity param to specify which part of the view to
- * return.
- *
- * @param v View to find
- * @param gravity A combination of (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT,
- * CENTER_HORIZONTAL,
- * RIGHT)
- * @param xy Result
- */
- private static void getStartLocation(View v, int gravity, int[] xy) {
- v.getLocationOnScreen(xy);
-
- final int viewWidth = v.getWidth();
- final int viewHeight = v.getHeight();
-
- switch (gravity & Gravity.VERTICAL_GRAVITY_MASK) {
- case Gravity.TOP:
- break;
- case Gravity.CENTER_VERTICAL:
- xy[1] += viewHeight / 2;
- break;
- case Gravity.BOTTOM:
- xy[1] += viewHeight - 1;
- break;
- default:
- // Same as top -- do nothing
- }
-
- switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
- case Gravity.LEFT:
- break;
- case Gravity.CENTER_HORIZONTAL:
- xy[0] += viewWidth / 2;
- break;
- case Gravity.RIGHT:
- xy[0] += viewWidth - 1;
- break;
- default:
- // Same as left -- do nothing
- }
- }
-
- /**
- * Simulate touching a view and dragging it to a specified location.
- *
- * @param test The test case that is being run
- * @param v The view that should be dragged
- * @param gravity Which part of the view to use for the initial down event. A combination
- * of
- * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
- * @param toX Final location of the view after dragging
- * @param toY Final location of the view after dragging
- * @return distance in pixels covered by the drag
- */
- public static int dragViewTo(InstrumentationTestCase test, View v, int gravity, int toX,
- int toY) {
- int[] xy = new int[2];
-
- getStartLocation(v, gravity, xy);
-
- final int fromX = xy[0];
- final int fromY = xy[1];
-
- int deltaX = fromX - toX;
- int deltaY = fromY - toY;
-
- int distance = (int) Math.sqrt(deltaX * deltaX + deltaY * deltaY);
- drag(test, fromX, toX, fromY, toY, distance);
-
- return distance;
- }
-
- /**
- * Simulate touching a view and dragging it to a specified location. Only moves
- * horizontally.
- *
- * @param test The test case that is being run
- * @param v The view that should be dragged
- * @param gravity Which part of the view to use for the initial down event. A combination
- * of
- * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
- * @param toX Final location of the view after dragging
- * @return distance in pixels covered by the drag
- */
- public static int dragViewToX(InstrumentationTestCase test, View v, int gravity, int toX) {
- int[] xy = new int[2];
-
- getStartLocation(v, gravity, xy);
-
- final int fromX = xy[0];
- final int fromY = xy[1];
-
- int deltaX = fromX - toX;
-
- drag(test, fromX, toX, fromY, fromY, Math.max(10, Math.abs(deltaX) / 10));
-
- return deltaX;
- }
-
- /**
- * Simulate touching a view and dragging it to a specified location. Only moves vertically.
- *
- * @param test The test case that is being run
- * @param v The view that should be dragged
- * @param gravity Which part of the view to use for the initial down event. A combination
- * of
- * (TOP, CENTER_VERTICAL, BOTTOM) and (LEFT, CENTER_HORIZONTAL, RIGHT)
- * @param toY Final location of the view after dragging
- * @return distance in pixels covered by the drag
- */
- public static int dragViewToY(InstrumentationTestCase test, View v, int gravity, int toY) {
- int[] xy = new int[2];
-
- getStartLocation(v, gravity, xy);
-
- final int fromX = xy[0];
- final int fromY = xy[1];
-
- int deltaY = fromY - toY;
-
- drag(test, fromX, fromX, fromY, toY, deltaY);
-
- return deltaY;
- }
-
-
- /**
- * Simulate touching a specific location and dragging to a new location.
- *
- * @param test The test case that is being run
- * @param fromX X coordinate of the initial touch, in screen coordinates
- * @param toX Xcoordinate of the drag destination, in screen coordinates
- * @param fromY X coordinate of the initial touch, in screen coordinates
- * @param toY Y coordinate of the drag destination, in screen coordinates
- * @param stepCount How many move steps to include in the drag
- */
- public static void drag(InstrumentationTestCase test, float fromX, float toX, float fromY,
- float toY, int stepCount) {
- Instrumentation inst = test.getInstrumentation();
-
- long downTime = SystemClock.uptimeMillis();
- long eventTime = SystemClock.uptimeMillis();
-
- float y = fromY;
- float x = fromX;
-
- float yStep = (toY - fromY) / stepCount;
- float xStep = (toX - fromX) / stepCount;
-
- MotionEvent event = MotionEvent.obtain(downTime, eventTime,
- MotionEvent.ACTION_DOWN, x, y, 0);
- inst.sendPointerSync(event);
- for (int i = 0; i < stepCount; ++i) {
- y += yStep;
- x += xStep;
- eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_MOVE, x, y, 0);
- inst.sendPointerSync(event);
- }
-
- eventTime = SystemClock.uptimeMillis();
- event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_UP, x, y, 0);
- inst.sendPointerSync(event);
- inst.waitForIdleSync();
- }
- }
-
}
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..934936b 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
@@ -16,25 +16,42 @@
package android.support.v7.widget.test;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.Build;
+import android.support.test.InstrumentationRegistry;
+import android.support.test.rule.ActivityTestRule;
+import android.support.test.runner.AndroidJUnit4;
import android.support.v7.recyclerview.test.CustomLayoutManager;
import android.support.v7.recyclerview.test.R;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
-import android.test.ActivityInstrumentationTestCase2;
+import android.test.suitebuilder.annotation.SmallTest;
+import android.view.ViewGroup;
import android.widget.LinearLayout;
-public class RecyclerViewTest extends ActivityInstrumentationTestCase2<RecyclerViewTestActivity> {
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
- public RecyclerViewTest() {
- super("android.support.v7.widget.test", RecyclerViewTestActivity.class);
- }
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class RecyclerViewTest {
+
+ @Rule
+ public ActivityTestRule<RecyclerViewTestActivity> mActivityRule
+ = new ActivityTestRule<>(RecyclerViewTestActivity.class);
private void setContentView(final int layoutId) throws Throwable {
- final Activity activity = getActivity();
- runTestOnUiThread(new Runnable() {
+ final Activity activity = mActivityRule.getActivity();
+ InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
activity.setContentView(layoutId);
@@ -42,7 +59,8 @@
});
}
- public void testSavedStateAccess() throws ClassNotFoundException {
+ @Test
+ public void savedStateAccess() throws ClassNotFoundException {
// this class should be accessible outside RecyclerView package
assertNotNull(RecyclerView.SavedState.class);
assertNotNull(LinearLayoutManager.SavedState.class);
@@ -50,10 +68,19 @@
assertNotNull(StaggeredGridLayoutManager.SavedState.class);
}
- public void testInflation() throws Throwable {
+ @Test
+ public void inflation() throws Throwable {
setContentView(R.layout.inflation_test);
getInstrumentation().waitForIdleSync();
- RecyclerView view = (RecyclerView) getActivity().findViewById(R.id.recyclerView);
+ RecyclerView view;
+ view = (RecyclerView) getActivity().findViewById(R.id.clipToPaddingUndefined);
+ assertTrue(view.getLayoutManager().getClipToPadding());
+ view = (RecyclerView) getActivity().findViewById(R.id.clipToPaddingYes);
+ assertTrue(view.getLayoutManager().getClipToPadding());
+ view = (RecyclerView) getActivity().findViewById(R.id.clipToPaddingNo);
+ assertFalse(view.getLayoutManager().getClipToPadding());
+
+ view = (RecyclerView) getActivity().findViewById(R.id.recyclerView);
RecyclerView.LayoutManager layoutManager = view.getLayoutManager();
assertNotNull("LayoutManager not created.", layoutManager);
assertEquals("Incorrect LayoutManager created",
@@ -90,5 +117,33 @@
"android.support.v7.recyclerview.test.PrivateLayoutManager",
layoutManager.getClass().getName());
+ view = (RecyclerView) getActivity().findViewById(R.id.recyclerView5);
+ assertTrue("Incorrect default nested scrolling value", view.isNestedScrollingEnabled());
+
+ if (Build.VERSION.SDK_INT >= 21) {
+ view = (RecyclerView) getActivity().findViewById(R.id.recyclerView6);
+ assertFalse("Incorrect explicit nested scrolling value",
+ view.isNestedScrollingEnabled());
+ }
+
+ view = (RecyclerView) getActivity().findViewById(R.id.focusability_undefined);
+ assertEquals(ViewGroup.FOCUS_AFTER_DESCENDANTS, view.getDescendantFocusability());
+
+ view = (RecyclerView) getActivity().findViewById(R.id.focusability_after);
+ assertEquals(ViewGroup.FOCUS_AFTER_DESCENDANTS, view.getDescendantFocusability());
+
+ view = (RecyclerView) getActivity().findViewById(R.id.focusability_before);
+ assertEquals(ViewGroup.FOCUS_BEFORE_DESCENDANTS, view.getDescendantFocusability());
+
+ view = (RecyclerView) getActivity().findViewById(R.id.focusability_block);
+ assertEquals(ViewGroup.FOCUS_BLOCK_DESCENDANTS, view.getDescendantFocusability());
+ }
+
+ private Activity getActivity() {
+ return mActivityRule.getActivity();
+ }
+
+ private Instrumentation getInstrumentation() {
+ return InstrumentationRegistry.getInstrumentation();
}
}
diff --git a/v8/renderscript/Android.mk b/v8/renderscript/Android.mk
index 17e06c0..84f151a 100644
--- a/v8/renderscript/Android.mk
+++ b/v8/renderscript/Android.mk
@@ -26,7 +26,7 @@
LOCAL_MODULE := android-support-v8-renderscript
LOCAL_SDK_VERSION := 23
LOCAL_SRC_FILES := $(call all-java-files-under, java/src)
-LOCAL_JAVA_LIBRARIES := android-support-annotations
+LOCAL_SHARED_ANDROID_LIBRARIES := android-support-annotations
LOCAL_JAVA_LANGUAGE_VERSION := 1.7
include $(BUILD_STATIC_JAVA_LIBRARY)
@@ -36,7 +36,7 @@
support_module := $(LOCAL_MODULE)
support_module_api_dir := $(LOCAL_PATH)/api
support_module_src_files := $(LOCAL_SRC_FILES)
-support_module_java_libraries := $(LOCAL_MODULE)
+support_module_java_libraries := $(LOCAL_JAVA_LIBRARIES)
support_module_java_packages := android.support.v8.renderscript
include $(SUPPORT_API_CHECK)
diff --git a/v8/renderscript/api/23.1.1.txt b/v8/renderscript/api/23.1.1.txt
new file mode 100644
index 0000000..929bd5f
--- /dev/null
+++ b/v8/renderscript/api/23.1.1.txt
@@ -0,0 +1,1011 @@
+package android.support.v8.renderscript {
+
+ public class Allocation extends android.support.v8.renderscript.BaseObj {
+ method public void copy1DRangeFrom(int, int, java.lang.Object);
+ method public void copy1DRangeFrom(int, int, int[]);
+ method public void copy1DRangeFrom(int, int, short[]);
+ method public void copy1DRangeFrom(int, int, byte[]);
+ method public void copy1DRangeFrom(int, int, float[]);
+ method public void copy1DRangeFrom(int, int, android.support.v8.renderscript.Allocation, int);
+ method public void copy1DRangeFromUnchecked(int, int, java.lang.Object);
+ method public void copy1DRangeFromUnchecked(int, int, int[]);
+ method public void copy1DRangeFromUnchecked(int, int, short[]);
+ method public void copy1DRangeFromUnchecked(int, int, byte[]);
+ method public void copy1DRangeFromUnchecked(int, int, float[]);
+ method public void copy1DRangeTo(int, int, java.lang.Object);
+ method public void copy1DRangeTo(int, int, int[]);
+ method public void copy1DRangeTo(int, int, short[]);
+ method public void copy1DRangeTo(int, int, byte[]);
+ method public void copy1DRangeTo(int, int, float[]);
+ method public void copy1DRangeToUnchecked(int, int, java.lang.Object);
+ method public void copy1DRangeToUnchecked(int, int, int[]);
+ method public void copy1DRangeToUnchecked(int, int, short[]);
+ method public void copy1DRangeToUnchecked(int, int, byte[]);
+ method public void copy1DRangeToUnchecked(int, int, float[]);
+ method public void copy2DRangeFrom(int, int, int, int, java.lang.Object);
+ method public void copy2DRangeFrom(int, int, int, int, byte[]);
+ method public void copy2DRangeFrom(int, int, int, int, short[]);
+ method public void copy2DRangeFrom(int, int, int, int, int[]);
+ method public void copy2DRangeFrom(int, int, int, int, float[]);
+ method public void copy2DRangeFrom(int, int, int, int, android.support.v8.renderscript.Allocation, int, int);
+ method public void copy2DRangeFrom(int, int, android.graphics.Bitmap);
+ method public void copy2DRangeTo(int, int, int, int, java.lang.Object);
+ method public void copy2DRangeTo(int, int, int, int, byte[]);
+ method public void copy2DRangeTo(int, int, int, int, short[]);
+ method public void copy2DRangeTo(int, int, int, int, int[]);
+ method public void copy2DRangeTo(int, int, int, int, float[]);
+ method public void copy3DRangeFrom(int, int, int, int, int, int, java.lang.Object);
+ method public void copy3DRangeFrom(int, int, int, int, int, int, android.support.v8.renderscript.Allocation, int, int, int);
+ method public void copyFrom(android.support.v8.renderscript.BaseObj[]);
+ method public void copyFrom(java.lang.Object);
+ method public void copyFrom(int[]);
+ method public void copyFrom(short[]);
+ method public void copyFrom(byte[]);
+ method public void copyFrom(float[]);
+ method public void copyFrom(android.graphics.Bitmap);
+ method public void copyFrom(android.support.v8.renderscript.Allocation);
+ method public void copyFromUnchecked(java.lang.Object);
+ method public void copyFromUnchecked(int[]);
+ method public void copyFromUnchecked(short[]);
+ method public void copyFromUnchecked(byte[]);
+ method public void copyFromUnchecked(float[]);
+ method public void copyTo(android.graphics.Bitmap);
+ method public void copyTo(java.lang.Object);
+ method public void copyTo(byte[]);
+ method public void copyTo(short[]);
+ method public void copyTo(int[]);
+ method public void copyTo(float[]);
+ method public static android.support.v8.renderscript.Allocation createCubemapFromBitmap(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap, android.support.v8.renderscript.Allocation.MipmapControl, int);
+ method public static android.support.v8.renderscript.Allocation createCubemapFromBitmap(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap);
+ method public static android.support.v8.renderscript.Allocation createCubemapFromCubeFaces(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.support.v8.renderscript.Allocation.MipmapControl, int);
+ method public static android.support.v8.renderscript.Allocation createCubemapFromCubeFaces(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap, android.graphics.Bitmap);
+ method public static android.support.v8.renderscript.Allocation createFromBitmap(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap, android.support.v8.renderscript.Allocation.MipmapControl, int);
+ method public static android.support.v8.renderscript.Allocation createFromBitmap(android.support.v8.renderscript.RenderScript, android.graphics.Bitmap);
+ method public static android.support.v8.renderscript.Allocation createFromBitmapResource(android.support.v8.renderscript.RenderScript, android.content.res.Resources, int, android.support.v8.renderscript.Allocation.MipmapControl, int);
+ method public static android.support.v8.renderscript.Allocation createFromBitmapResource(android.support.v8.renderscript.RenderScript, android.content.res.Resources, int);
+ method public static android.support.v8.renderscript.Allocation createFromString(android.support.v8.renderscript.RenderScript, java.lang.String, int);
+ method public static android.support.v8.renderscript.Allocation createSized(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element, int, int);
+ method public static android.support.v8.renderscript.Allocation createSized(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element, int);
+ method public static android.support.v8.renderscript.Allocation createTyped(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Type, android.support.v8.renderscript.Allocation.MipmapControl, int);
+ method public static android.support.v8.renderscript.Allocation createTyped(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Type, int);
+ method public static android.support.v8.renderscript.Allocation createTyped(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Type);
+ method public void generateMipmaps();
+ method public int getBytesSize();
+ method public android.support.v8.renderscript.Element getElement();
+ method public long getIncAllocID();
+ method public android.support.v8.renderscript.Type getType();
+ method public int getUsage();
+ method public void ioReceive();
+ method public void ioSend();
+ method public void ioSendOutput();
+ method public void setAutoPadding(boolean);
+ method public void setFromFieldPacker(int, android.support.v8.renderscript.FieldPacker);
+ method public void setFromFieldPacker(int, int, android.support.v8.renderscript.FieldPacker);
+ method public void setIncAllocID(long);
+ method public void setSurface(android.view.Surface);
+ method public void syncAll(int);
+ field public static final int USAGE_GRAPHICS_TEXTURE = 2; // 0x2
+ field public static final int USAGE_IO_INPUT = 32; // 0x20
+ field public static final int USAGE_IO_OUTPUT = 64; // 0x40
+ field public static final int USAGE_SCRIPT = 1; // 0x1
+ field public static final int USAGE_SHARED = 128; // 0x80
+ }
+
+ public static final class Allocation.MipmapControl extends java.lang.Enum {
+ method public static android.support.v8.renderscript.Allocation.MipmapControl valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.Allocation.MipmapControl[] values();
+ enum_constant public static final android.support.v8.renderscript.Allocation.MipmapControl MIPMAP_FULL;
+ enum_constant public static final android.support.v8.renderscript.Allocation.MipmapControl MIPMAP_NONE;
+ enum_constant public static final android.support.v8.renderscript.Allocation.MipmapControl MIPMAP_ON_SYNC_TO_TEXTURE;
+ }
+
+ public class BaseObj {
+ method public void destroy();
+ }
+
+ public class Byte2 {
+ ctor public Byte2();
+ ctor public Byte2(byte, byte);
+ field public byte x;
+ field public byte y;
+ }
+
+ public class Byte3 {
+ ctor public Byte3();
+ ctor public Byte3(byte, byte, byte);
+ field public byte x;
+ field public byte y;
+ field public byte z;
+ }
+
+ public class Byte4 {
+ ctor public Byte4();
+ ctor public Byte4(byte, byte, byte, byte);
+ field public byte w;
+ field public byte x;
+ field public byte y;
+ field public byte z;
+ }
+
+ public class Double2 {
+ ctor public Double2();
+ ctor public Double2(double, double);
+ field public double x;
+ field public double y;
+ }
+
+ public class Double3 {
+ ctor public Double3();
+ ctor public Double3(double, double, double);
+ field public double x;
+ field public double y;
+ field public double z;
+ }
+
+ public class Double4 {
+ ctor public Double4();
+ ctor public Double4(double, double, double, double);
+ field public double w;
+ field public double x;
+ field public double y;
+ field public double z;
+ }
+
+ public class Element extends android.support.v8.renderscript.BaseObj {
+ method public static android.support.v8.renderscript.Element ALLOCATION(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element A_8(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element BOOLEAN(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element ELEMENT(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F32(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F32_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F32_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F32_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F64(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F64_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F64_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element F64_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I16(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I16_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I16_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I16_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I32(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I32_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I32_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I32_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I64(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I64_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I64_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I64_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I8(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I8_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I8_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element I8_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element MATRIX_2X2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element MATRIX_3X3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element MATRIX_4X4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element RGBA_4444(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element RGBA_5551(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element RGBA_8888(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element RGB_565(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element RGB_888(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element SAMPLER(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element SCRIPT(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element TYPE(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U16(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U16_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U16_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U16_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U32(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U32_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U32_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U32_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U64(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U64_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U64_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U64_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U8(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U8_2(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U8_3(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element U8_4(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Element createPixel(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element.DataType, android.support.v8.renderscript.Element.DataKind);
+ method public static android.support.v8.renderscript.Element createVector(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element.DataType, int);
+ method public int getBytesSize();
+ method public android.support.v8.renderscript.Element.DataKind getDataKind();
+ method public android.support.v8.renderscript.Element.DataType getDataType();
+ method public long getDummyElement(android.support.v8.renderscript.RenderScript);
+ method public android.support.v8.renderscript.Element getSubElement(int);
+ method public int getSubElementArraySize(int);
+ method public int getSubElementCount();
+ method public java.lang.String getSubElementName(int);
+ method public int getSubElementOffsetBytes(int);
+ method public int getVectorSize();
+ method public boolean isCompatible(android.support.v8.renderscript.Element);
+ method public boolean isComplex();
+ }
+
+ public static class Element.Builder {
+ ctor public Element.Builder(android.support.v8.renderscript.RenderScript);
+ method public android.support.v8.renderscript.Element.Builder add(android.support.v8.renderscript.Element, java.lang.String, int);
+ method public android.support.v8.renderscript.Element.Builder add(android.support.v8.renderscript.Element, java.lang.String);
+ method public android.support.v8.renderscript.Element create();
+ }
+
+ public static final class Element.DataKind extends java.lang.Enum {
+ method public static android.support.v8.renderscript.Element.DataKind valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.Element.DataKind[] values();
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_A;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_DEPTH;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_L;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_LA;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_RGB;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_RGBA;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind PIXEL_YUV;
+ enum_constant public static final android.support.v8.renderscript.Element.DataKind USER;
+ }
+
+ public static final class Element.DataType extends java.lang.Enum {
+ method public static android.support.v8.renderscript.Element.DataType valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.Element.DataType[] values();
+ enum_constant public static final android.support.v8.renderscript.Element.DataType BOOLEAN;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType FLOAT_32;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType FLOAT_64;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType MATRIX_2X2;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType MATRIX_3X3;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType MATRIX_4X4;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType NONE;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType RS_ALLOCATION;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType RS_ELEMENT;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType RS_SAMPLER;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType RS_SCRIPT;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType RS_TYPE;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType SIGNED_16;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType SIGNED_32;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType SIGNED_64;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType SIGNED_8;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_16;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_32;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_4_4_4_4;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_5_5_5_1;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_5_6_5;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_64;
+ enum_constant public static final android.support.v8.renderscript.Element.DataType UNSIGNED_8;
+ }
+
+ public class FieldPacker {
+ ctor public FieldPacker(int);
+ ctor public FieldPacker(byte[]);
+ method public void addBoolean(boolean);
+ method public void addF32(float);
+ method public void addF32(android.support.v8.renderscript.Float2);
+ method public void addF32(android.support.v8.renderscript.Float3);
+ method public void addF32(android.support.v8.renderscript.Float4);
+ method public void addF64(double);
+ method public void addF64(android.support.v8.renderscript.Double2);
+ method public void addF64(android.support.v8.renderscript.Double3);
+ method public void addF64(android.support.v8.renderscript.Double4);
+ method public void addI16(short);
+ method public void addI16(android.support.v8.renderscript.Short2);
+ method public void addI16(android.support.v8.renderscript.Short3);
+ method public void addI16(android.support.v8.renderscript.Short4);
+ method public void addI32(int);
+ method public void addI32(android.support.v8.renderscript.Int2);
+ method public void addI32(android.support.v8.renderscript.Int3);
+ method public void addI32(android.support.v8.renderscript.Int4);
+ method public void addI64(long);
+ method public void addI64(android.support.v8.renderscript.Long2);
+ method public void addI64(android.support.v8.renderscript.Long3);
+ method public void addI64(android.support.v8.renderscript.Long4);
+ method public void addI8(byte);
+ method public void addI8(android.support.v8.renderscript.Byte2);
+ method public void addI8(android.support.v8.renderscript.Byte3);
+ method public void addI8(android.support.v8.renderscript.Byte4);
+ method public void addMatrix(android.support.v8.renderscript.Matrix4f);
+ method public void addMatrix(android.support.v8.renderscript.Matrix3f);
+ method public void addMatrix(android.support.v8.renderscript.Matrix2f);
+ method public void addObj(android.support.v8.renderscript.BaseObj);
+ method public void addU16(int);
+ method public void addU16(android.support.v8.renderscript.Int2);
+ method public void addU16(android.support.v8.renderscript.Int3);
+ method public void addU16(android.support.v8.renderscript.Int4);
+ method public void addU32(long);
+ method public void addU32(android.support.v8.renderscript.Long2);
+ method public void addU32(android.support.v8.renderscript.Long3);
+ method public void addU32(android.support.v8.renderscript.Long4);
+ method public void addU64(long);
+ method public void addU64(android.support.v8.renderscript.Long2);
+ method public void addU64(android.support.v8.renderscript.Long3);
+ method public void addU64(android.support.v8.renderscript.Long4);
+ method public void addU8(short);
+ method public void addU8(android.support.v8.renderscript.Short2);
+ method public void addU8(android.support.v8.renderscript.Short3);
+ method public void addU8(android.support.v8.renderscript.Short4);
+ method public void align(int);
+ method public final byte[] getData();
+ method public void reset();
+ method public void reset(int);
+ method public void skip(int);
+ method public boolean subBoolean();
+ method public android.support.v8.renderscript.Byte2 subByte2();
+ method public android.support.v8.renderscript.Byte3 subByte3();
+ method public android.support.v8.renderscript.Byte4 subByte4();
+ method public android.support.v8.renderscript.Double2 subDouble2();
+ method public android.support.v8.renderscript.Double3 subDouble3();
+ method public android.support.v8.renderscript.Double4 subDouble4();
+ method public float subF32();
+ method public double subF64();
+ method public android.support.v8.renderscript.Float2 subFloat2();
+ method public android.support.v8.renderscript.Float3 subFloat3();
+ method public android.support.v8.renderscript.Float4 subFloat4();
+ method public short subI16();
+ method public int subI32();
+ method public long subI64();
+ method public byte subI8();
+ method public android.support.v8.renderscript.Int2 subInt2();
+ method public android.support.v8.renderscript.Int3 subInt3();
+ method public android.support.v8.renderscript.Int4 subInt4();
+ method public android.support.v8.renderscript.Long2 subLong2();
+ method public android.support.v8.renderscript.Long3 subLong3();
+ method public android.support.v8.renderscript.Long4 subLong4();
+ method public android.support.v8.renderscript.Matrix2f subMatrix2f();
+ method public android.support.v8.renderscript.Matrix3f subMatrix3f();
+ method public android.support.v8.renderscript.Matrix4f subMatrix4f();
+ method public android.support.v8.renderscript.Short2 subShort2();
+ method public android.support.v8.renderscript.Short3 subShort3();
+ method public android.support.v8.renderscript.Short4 subShort4();
+ method public void subalign(int);
+ }
+
+ public class Float2 {
+ ctor public Float2();
+ ctor public Float2(float, float);
+ field public float x;
+ field public float y;
+ }
+
+ public class Float3 {
+ ctor public Float3();
+ ctor public Float3(float, float, float);
+ field public float x;
+ field public float y;
+ field public float z;
+ }
+
+ public class Float4 {
+ ctor public Float4();
+ ctor public Float4(float, float, float, float);
+ field public float w;
+ field public float x;
+ field public float y;
+ field public float z;
+ }
+
+ public class Int2 {
+ ctor public Int2();
+ ctor public Int2(int, int);
+ field public int x;
+ field public int y;
+ }
+
+ public class Int3 {
+ ctor public Int3();
+ ctor public Int3(int, int, int);
+ field public int x;
+ field public int y;
+ field public int z;
+ }
+
+ public class Int4 {
+ ctor public Int4();
+ ctor public Int4(int, int, int, int);
+ field public int w;
+ field public int x;
+ field public int y;
+ field public int z;
+ }
+
+ public class Long2 {
+ ctor public Long2();
+ ctor public Long2(long, long);
+ field public long x;
+ field public long y;
+ }
+
+ public class Long3 {
+ ctor public Long3();
+ ctor public Long3(long, long, long);
+ field public long x;
+ field public long y;
+ field public long z;
+ }
+
+ public class Long4 {
+ ctor public Long4();
+ ctor public Long4(long, long, long, long);
+ field public long w;
+ field public long x;
+ field public long y;
+ field public long z;
+ }
+
+ public class Matrix2f {
+ ctor public Matrix2f();
+ ctor public Matrix2f(float[]);
+ method public float get(int, int);
+ method public float[] getArray();
+ method public void load(android.support.v8.renderscript.Matrix2f);
+ method public void loadIdentity();
+ method public void loadMultiply(android.support.v8.renderscript.Matrix2f, android.support.v8.renderscript.Matrix2f);
+ method public void loadRotate(float);
+ method public void loadScale(float, float);
+ method public void multiply(android.support.v8.renderscript.Matrix2f);
+ method public void rotate(float);
+ method public void scale(float, float);
+ method public void set(int, int, float);
+ method public void transpose();
+ }
+
+ public class Matrix3f {
+ ctor public Matrix3f();
+ ctor public Matrix3f(float[]);
+ method public float get(int, int);
+ method public float[] getArray();
+ method public void load(android.support.v8.renderscript.Matrix3f);
+ method public void loadIdentity();
+ method public void loadMultiply(android.support.v8.renderscript.Matrix3f, android.support.v8.renderscript.Matrix3f);
+ method public void loadRotate(float, float, float, float);
+ method public void loadRotate(float);
+ method public void loadScale(float, float);
+ method public void loadScale(float, float, float);
+ method public void loadTranslate(float, float);
+ method public void multiply(android.support.v8.renderscript.Matrix3f);
+ method public void rotate(float, float, float, float);
+ method public void rotate(float);
+ method public void scale(float, float);
+ method public void scale(float, float, float);
+ method public void set(int, int, float);
+ method public void translate(float, float);
+ method public void transpose();
+ }
+
+ public class Matrix4f {
+ ctor public Matrix4f();
+ ctor public Matrix4f(float[]);
+ method public float get(int, int);
+ method public float[] getArray();
+ method public boolean inverse();
+ method public boolean inverseTranspose();
+ method public void load(android.support.v8.renderscript.Matrix4f);
+ method public void loadFrustum(float, float, float, float, float, float);
+ method public void loadIdentity();
+ method public void loadMultiply(android.support.v8.renderscript.Matrix4f, android.support.v8.renderscript.Matrix4f);
+ method public void loadOrtho(float, float, float, float, float, float);
+ method public void loadOrthoWindow(int, int);
+ method public void loadPerspective(float, float, float, float);
+ method public void loadProjectionNormalized(int, int);
+ method public void loadRotate(float, float, float, float);
+ method public void loadScale(float, float, float);
+ method public void loadTranslate(float, float, float);
+ method public void multiply(android.support.v8.renderscript.Matrix4f);
+ method public void rotate(float, float, float, float);
+ method public void scale(float, float, float);
+ method public void set(int, int, float);
+ method public void translate(float, float, float);
+ method public void transpose();
+ }
+
+ public class RSDriverException extends android.support.v8.renderscript.RSRuntimeException {
+ ctor public RSDriverException(java.lang.String);
+ }
+
+ public class RSIllegalArgumentException extends android.support.v8.renderscript.RSRuntimeException {
+ ctor public RSIllegalArgumentException(java.lang.String);
+ }
+
+ public class RSInvalidStateException extends android.support.v8.renderscript.RSRuntimeException {
+ ctor public RSInvalidStateException(java.lang.String);
+ }
+
+ public class RSRuntimeException extends java.lang.RuntimeException {
+ ctor public RSRuntimeException(java.lang.String);
+ }
+
+ public class RenderScript {
+ method public void contextDump();
+ method public static android.support.v8.renderscript.RenderScript create(android.content.Context);
+ method public static android.support.v8.renderscript.RenderScript create(android.content.Context, android.support.v8.renderscript.RenderScript.ContextType);
+ method public static android.support.v8.renderscript.RenderScript create(android.content.Context, android.support.v8.renderscript.RenderScript.ContextType, int);
+ method public static android.support.v8.renderscript.RenderScript create(android.content.Context, int, android.support.v8.renderscript.RenderScript.ContextType, int);
+ method public static android.support.v8.renderscript.RenderScript createMultiContext(android.content.Context, android.support.v8.renderscript.RenderScript.ContextType, int, int);
+ method public void destroy();
+ method public void finish();
+ method public static void forceCompat();
+ method public final android.content.Context getApplicationContext();
+ method public android.support.v8.renderscript.RenderScript.RSErrorHandler getErrorHandler();
+ method public android.support.v8.renderscript.RenderScript.RSMessageHandler getMessageHandler();
+ method public static void releaseAllContexts();
+ method public void sendMessage(int, int[]);
+ method public static void setBlackList(java.lang.String);
+ method public void setErrorHandler(android.support.v8.renderscript.RenderScript.RSErrorHandler);
+ method public void setMessageHandler(android.support.v8.renderscript.RenderScript.RSMessageHandler);
+ method public void setPriority(android.support.v8.renderscript.RenderScript.Priority);
+ field public static final int CREATE_FLAG_NONE = 0; // 0x0
+ }
+
+ public static final class RenderScript.ContextType extends java.lang.Enum {
+ method public static android.support.v8.renderscript.RenderScript.ContextType valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.RenderScript.ContextType[] values();
+ enum_constant public static final android.support.v8.renderscript.RenderScript.ContextType DEBUG;
+ enum_constant public static final android.support.v8.renderscript.RenderScript.ContextType NORMAL;
+ enum_constant public static final android.support.v8.renderscript.RenderScript.ContextType PROFILE;
+ }
+
+ public static final class RenderScript.Priority extends java.lang.Enum {
+ method public static android.support.v8.renderscript.RenderScript.Priority valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.RenderScript.Priority[] values();
+ enum_constant public static final android.support.v8.renderscript.RenderScript.Priority LOW;
+ enum_constant public static final android.support.v8.renderscript.RenderScript.Priority NORMAL;
+ }
+
+ public static class RenderScript.RSErrorHandler implements java.lang.Runnable {
+ ctor public RenderScript.RSErrorHandler();
+ method public void run();
+ field protected java.lang.String mErrorMessage;
+ field protected int mErrorNum;
+ }
+
+ public static class RenderScript.RSMessageHandler implements java.lang.Runnable {
+ ctor public RenderScript.RSMessageHandler();
+ method public void run();
+ field protected int[] mData;
+ field protected int mID;
+ field protected int mLength;
+ }
+
+ public class Sampler extends android.support.v8.renderscript.BaseObj {
+ method public static android.support.v8.renderscript.Sampler CLAMP_LINEAR(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler CLAMP_LINEAR_MIP_LINEAR(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler CLAMP_NEAREST(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler MIRRORED_REPEAT_LINEAR(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler MIRRORED_REPEAT_NEAREST(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler WRAP_LINEAR(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler WRAP_LINEAR_MIP_LINEAR(android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.Sampler WRAP_NEAREST(android.support.v8.renderscript.RenderScript);
+ method public float getAnisotropy();
+ method public android.support.v8.renderscript.Sampler.Value getMagnification();
+ method public android.support.v8.renderscript.Sampler.Value getMinification();
+ method public android.support.v8.renderscript.Sampler.Value getWrapS();
+ method public android.support.v8.renderscript.Sampler.Value getWrapT();
+ }
+
+ public static class Sampler.Builder {
+ ctor public Sampler.Builder(android.support.v8.renderscript.RenderScript);
+ method public android.support.v8.renderscript.Sampler create();
+ method public void setAnisotropy(float);
+ method public void setMagnification(android.support.v8.renderscript.Sampler.Value);
+ method public void setMinification(android.support.v8.renderscript.Sampler.Value);
+ method public void setWrapS(android.support.v8.renderscript.Sampler.Value);
+ method public void setWrapT(android.support.v8.renderscript.Sampler.Value);
+ }
+
+ public static final class Sampler.Value extends java.lang.Enum {
+ method public static android.support.v8.renderscript.Sampler.Value valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.Sampler.Value[] values();
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value CLAMP;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value LINEAR;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value LINEAR_MIP_LINEAR;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value LINEAR_MIP_NEAREST;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value MIRRORED_REPEAT;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value NEAREST;
+ enum_constant public static final android.support.v8.renderscript.Sampler.Value WRAP;
+ }
+
+ public class Script extends android.support.v8.renderscript.BaseObj {
+ method public void bindAllocation(android.support.v8.renderscript.Allocation, int);
+ method protected android.support.v8.renderscript.Script.FieldID createFieldID(int, android.support.v8.renderscript.Element);
+ method protected android.support.v8.renderscript.Script.InvokeID createInvokeID(int);
+ method protected android.support.v8.renderscript.Script.KernelID createKernelID(int, int, android.support.v8.renderscript.Element, android.support.v8.renderscript.Element);
+ method protected void forEach(int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.FieldPacker);
+ method protected void forEach(int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.FieldPacker, android.support.v8.renderscript.Script.LaunchOptions);
+ method protected void invoke(int);
+ method protected void invoke(int, android.support.v8.renderscript.FieldPacker);
+ method protected boolean isIncSupp();
+ method protected void setIncSupp(boolean);
+ method public void setTimeZone(java.lang.String);
+ method public void setVar(int, float);
+ method public void setVar(int, double);
+ method public void setVar(int, int);
+ method public void setVar(int, long);
+ method public void setVar(int, boolean);
+ method public void setVar(int, android.support.v8.renderscript.BaseObj);
+ method public void setVar(int, android.support.v8.renderscript.FieldPacker);
+ method public void setVar(int, android.support.v8.renderscript.FieldPacker, android.support.v8.renderscript.Element, int[]);
+ }
+
+ public static class Script.Builder {
+ }
+
+ public static class Script.FieldBase {
+ ctor protected Script.FieldBase();
+ method public android.support.v8.renderscript.Allocation getAllocation();
+ method public android.support.v8.renderscript.Element getElement();
+ method public android.support.v8.renderscript.Type getType();
+ method protected void init(android.support.v8.renderscript.RenderScript, int);
+ method protected void init(android.support.v8.renderscript.RenderScript, int, int);
+ method public void updateAllocation();
+ field protected android.support.v8.renderscript.Allocation mAllocation;
+ field protected android.support.v8.renderscript.Element mElement;
+ }
+
+ public static final class Script.FieldID extends android.support.v8.renderscript.BaseObj {
+ }
+
+ public static final class Script.InvokeID extends android.support.v8.renderscript.BaseObj {
+ }
+
+ public static final class Script.KernelID extends android.support.v8.renderscript.BaseObj {
+ }
+
+ public static final class Script.LaunchOptions {
+ ctor public Script.LaunchOptions();
+ method public int getXEnd();
+ method public int getXStart();
+ method public int getYEnd();
+ method public int getYStart();
+ method public int getZEnd();
+ method public int getZStart();
+ method public android.support.v8.renderscript.Script.LaunchOptions setX(int, int);
+ method public android.support.v8.renderscript.Script.LaunchOptions setY(int, int);
+ method public android.support.v8.renderscript.Script.LaunchOptions setZ(int, int);
+ }
+
+ public class ScriptC extends android.support.v8.renderscript.Script {
+ ctor protected ScriptC(long, android.support.v8.renderscript.RenderScript);
+ ctor protected ScriptC(android.support.v8.renderscript.RenderScript, android.content.res.Resources, int);
+ ctor protected ScriptC(android.support.v8.renderscript.RenderScript, java.lang.String, byte[], byte[]);
+ }
+
+ public final class ScriptGroup extends android.support.v8.renderscript.BaseObj {
+ method public java.lang.Object[] execute(java.lang.Object...);
+ method public deprecated void execute();
+ method public deprecated void setInput(android.support.v8.renderscript.Script.KernelID, android.support.v8.renderscript.Allocation);
+ method public deprecated void setOutput(android.support.v8.renderscript.Script.KernelID, android.support.v8.renderscript.Allocation);
+ }
+
+ public static final class ScriptGroup.Binding {
+ ctor public ScriptGroup.Binding(android.support.v8.renderscript.Script.FieldID, java.lang.Object);
+ method public android.support.v8.renderscript.Script.FieldID getField();
+ method public java.lang.Object getValue();
+ }
+
+ public static final deprecated class ScriptGroup.Builder {
+ ctor public ScriptGroup.Builder(android.support.v8.renderscript.RenderScript);
+ method public android.support.v8.renderscript.ScriptGroup.Builder addConnection(android.support.v8.renderscript.Type, android.support.v8.renderscript.Script.KernelID, android.support.v8.renderscript.Script.FieldID);
+ method public android.support.v8.renderscript.ScriptGroup.Builder addConnection(android.support.v8.renderscript.Type, android.support.v8.renderscript.Script.KernelID, android.support.v8.renderscript.Script.KernelID);
+ method public android.support.v8.renderscript.ScriptGroup.Builder addKernel(android.support.v8.renderscript.Script.KernelID);
+ method public android.support.v8.renderscript.ScriptGroup create();
+ }
+
+ public static final class ScriptGroup.Builder2 {
+ ctor public ScriptGroup.Builder2(android.support.v8.renderscript.RenderScript);
+ method public android.support.v8.renderscript.ScriptGroup.Input addInput();
+ method public android.support.v8.renderscript.ScriptGroup.Closure addInvoke(android.support.v8.renderscript.Script.InvokeID, java.lang.Object...);
+ method public android.support.v8.renderscript.ScriptGroup.Closure addKernel(android.support.v8.renderscript.Script.KernelID, android.support.v8.renderscript.Type, java.lang.Object...);
+ method public android.support.v8.renderscript.ScriptGroup create(java.lang.String, android.support.v8.renderscript.ScriptGroup.Future...);
+ }
+
+ public static final class ScriptGroup.Closure extends android.support.v8.renderscript.BaseObj {
+ method public android.support.v8.renderscript.ScriptGroup.Future getGlobal(android.support.v8.renderscript.Script.FieldID);
+ method public android.support.v8.renderscript.ScriptGroup.Future getReturn();
+ }
+
+ public static final class ScriptGroup.Future {
+ }
+
+ public static final class ScriptGroup.Input {
+ }
+
+ public abstract class ScriptIntrinsic extends android.support.v8.renderscript.Script {
+ }
+
+ public class ScriptIntrinsic3DLUT extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsic3DLUT(long, android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public static android.support.v8.renderscript.ScriptIntrinsic3DLUT create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setLUT(android.support.v8.renderscript.Allocation);
+ }
+
+ public final class ScriptIntrinsicBLAS extends android.support.v8.renderscript.ScriptIntrinsic {
+ method public void BNNM(android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, int);
+ method public void CGBMV(int, int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int);
+ method public void CGEMM(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation);
+ method public void CGEMV(int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int);
+ method public void CGERC(android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CGERU(android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CHBMV(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int);
+ method public void CHEMM(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation);
+ method public void CHEMV(int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int);
+ method public void CHER(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CHER2(int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CHER2K(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void CHERK(int, int, float, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void CHPMV(int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int);
+ method public void CHPR(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CHPR2(int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void CSYMM(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation);
+ method public void CSYR2K(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation);
+ method public void CSYRK(int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation);
+ method public void CTBMV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void CTBSV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void CTPMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void CTPSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void CTRMM(int, int, int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void CTRMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void CTRSM(int, int, int, int, android.support.v8.renderscript.Float2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void CTRSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DGBMV(int, int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, double, android.support.v8.renderscript.Allocation, int);
+ method public void DGEMM(int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void DGEMV(int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, double, android.support.v8.renderscript.Allocation, int);
+ method public void DGER(double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void DSBMV(int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, double, android.support.v8.renderscript.Allocation, int);
+ method public void DSPMV(int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, double, android.support.v8.renderscript.Allocation, int);
+ method public void DSPR(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void DSPR2(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void DSYMM(int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void DSYMV(int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, double, android.support.v8.renderscript.Allocation, int);
+ method public void DSYR(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void DSYR2(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void DSYR2K(int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void DSYRK(int, int, double, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void DTBMV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DTBSV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DTPMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DTPSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DTRMM(int, int, int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void DTRMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void DTRSM(int, int, int, int, double, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void DTRSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void SGBMV(int, int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, float, android.support.v8.renderscript.Allocation, int);
+ method public void SGEMM(int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void SGEMV(int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, float, android.support.v8.renderscript.Allocation, int);
+ method public void SGER(float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void SSBMV(int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, float, android.support.v8.renderscript.Allocation, int);
+ method public void SSPMV(int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, float, android.support.v8.renderscript.Allocation, int);
+ method public void SSPR(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void SSPR2(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void SSYMM(int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void SSYMV(int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, float, android.support.v8.renderscript.Allocation, int);
+ method public void SSYR(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void SSYR2(int, float, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void SSYR2K(int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void SSYRK(int, int, float, android.support.v8.renderscript.Allocation, float, android.support.v8.renderscript.Allocation);
+ method public void STBMV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void STBSV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void STPMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void STPSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void STRMM(int, int, int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void STRMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void STRSM(int, int, int, int, float, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void STRSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZGBMV(int, int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int);
+ method public void ZGEMM(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation);
+ method public void ZGEMV(int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int);
+ method public void ZGERC(android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZGERU(android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZHBMV(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int);
+ method public void ZHEMM(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation);
+ method public void ZHEMV(int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int);
+ method public void ZHER(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZHER2(int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZHER2K(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void ZHERK(int, int, double, android.support.v8.renderscript.Allocation, double, android.support.v8.renderscript.Allocation);
+ method public void ZHPMV(int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int);
+ method public void ZHPR(int, double, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZHPR2(int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation, int, android.support.v8.renderscript.Allocation);
+ method public void ZSYMM(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation);
+ method public void ZSYR2K(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation);
+ method public void ZSYRK(int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation);
+ method public void ZTBMV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZTBSV(int, int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZTPMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZTPSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZTRMM(int, int, int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void ZTRMV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public void ZTRSM(int, int, int, int, android.support.v8.renderscript.Double2, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void ZTRSV(int, int, int, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, int);
+ method public static android.support.v8.renderscript.ScriptIntrinsicBLAS create(android.support.v8.renderscript.RenderScript);
+ field public static final int CONJ_TRANSPOSE = 113; // 0x71
+ field public static final int LEFT = 141; // 0x8d
+ field public static final int LOWER = 122; // 0x7a
+ field public static final int NON_UNIT = 131; // 0x83
+ field public static final int NO_TRANSPOSE = 111; // 0x6f
+ field public static final int RIGHT = 142; // 0x8e
+ field public static final int TRANSPOSE = 112; // 0x70
+ field public static final int UNIT = 132; // 0x84
+ field public static final int UPPER = 121; // 0x79
+ }
+
+ public class ScriptIntrinsicBlend extends android.support.v8.renderscript.ScriptIntrinsic {
+ method public static android.support.v8.renderscript.ScriptIntrinsicBlend create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEachAdd(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachClear(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachDst(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachDstAtop(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachDstIn(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachDstOut(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachDstOver(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachMultiply(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSrc(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSrcAtop(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSrcIn(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSrcOut(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSrcOver(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachSubtract(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEachXor(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDAdd();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDClear();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDDst();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDDstAtop();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDDstIn();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDDstOut();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDDstOver();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDMultiply();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSrc();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSrcAtop();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSrcIn();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSrcOut();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSrcOver();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDSubtract();
+ method public android.support.v8.renderscript.Script.KernelID getKernelIDXor();
+ }
+
+ public class ScriptIntrinsicBlur extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsicBlur(long, android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.ScriptIntrinsicBlur create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setInput(android.support.v8.renderscript.Allocation);
+ method public void setRadius(float);
+ }
+
+ public class ScriptIntrinsicColorMatrix extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsicColorMatrix(long, android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.ScriptIntrinsicColorMatrix create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setAdd(android.support.v8.renderscript.Float4);
+ method public void setAdd(float, float, float, float);
+ method public void setColorMatrix(android.support.v8.renderscript.Matrix4f);
+ method public void setColorMatrix(android.support.v8.renderscript.Matrix3f);
+ method public void setGreyscale();
+ method public void setRGBtoYUV();
+ method public void setYUVtoRGB();
+ }
+
+ public class ScriptIntrinsicConvolve3x3 extends android.support.v8.renderscript.ScriptIntrinsic {
+ method public static android.support.v8.renderscript.ScriptIntrinsicConvolve3x3 create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setCoefficients(float[]);
+ method public void setInput(android.support.v8.renderscript.Allocation);
+ }
+
+ public class ScriptIntrinsicConvolve5x5 extends android.support.v8.renderscript.ScriptIntrinsic {
+ method public static android.support.v8.renderscript.ScriptIntrinsicConvolve5x5 create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setCoefficients(float[]);
+ method public void setInput(android.support.v8.renderscript.Allocation);
+ }
+
+ public class ScriptIntrinsicHistogram extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsicHistogram(long, android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.ScriptIntrinsicHistogram create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public void forEach_Dot(android.support.v8.renderscript.Allocation);
+ method public void forEach_Dot(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID_Separate();
+ method public void setDotCoefficients(float, float, float, float);
+ method public void setOutput(android.support.v8.renderscript.Allocation);
+ }
+
+ public class ScriptIntrinsicLUT extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsicLUT(long, android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.ScriptIntrinsicLUT create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Allocation);
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setAlpha(int, int);
+ method public void setBlue(int, int);
+ method public void setGreen(int, int);
+ method public void setRed(int, int);
+ }
+
+ public class ScriptIntrinsicResize extends android.support.v8.renderscript.ScriptIntrinsic {
+ ctor protected ScriptIntrinsicResize(long, android.support.v8.renderscript.RenderScript);
+ method public static android.support.v8.renderscript.ScriptIntrinsicResize create(android.support.v8.renderscript.RenderScript);
+ method public void forEach_bicubic(android.support.v8.renderscript.Allocation);
+ method public void forEach_bicubic(android.support.v8.renderscript.Allocation, android.support.v8.renderscript.Script.LaunchOptions);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID_bicubic();
+ method public void setInput(android.support.v8.renderscript.Allocation);
+ }
+
+ public class ScriptIntrinsicYuvToRGB extends android.support.v8.renderscript.ScriptIntrinsic {
+ method public static android.support.v8.renderscript.ScriptIntrinsicYuvToRGB create(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public void forEach(android.support.v8.renderscript.Allocation);
+ method public android.support.v8.renderscript.Script.FieldID getFieldID_Input();
+ method public android.support.v8.renderscript.Script.KernelID getKernelID();
+ method public void setInput(android.support.v8.renderscript.Allocation);
+ }
+
+ public class Short2 {
+ ctor public Short2();
+ ctor public Short2(short, short);
+ field public short x;
+ field public short y;
+ }
+
+ public class Short3 {
+ ctor public Short3();
+ ctor public Short3(short, short, short);
+ field public short x;
+ field public short y;
+ field public short z;
+ }
+
+ public class Short4 {
+ ctor public Short4();
+ ctor public Short4(short, short, short, short);
+ field public short w;
+ field public short x;
+ field public short y;
+ field public short z;
+ }
+
+ public class Type extends android.support.v8.renderscript.BaseObj {
+ method public static android.support.v8.renderscript.Type createX(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element, int);
+ method public static android.support.v8.renderscript.Type createXY(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element, int, int);
+ method public static android.support.v8.renderscript.Type createXYZ(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element, int, int, int);
+ method public int getCount();
+ method public long getDummyType(android.support.v8.renderscript.RenderScript, long);
+ method public android.support.v8.renderscript.Element getElement();
+ method public int getX();
+ method public int getY();
+ method public int getYuv();
+ method public int getZ();
+ method public boolean hasFaces();
+ method public boolean hasMipmaps();
+ }
+
+ public static class Type.Builder {
+ ctor public Type.Builder(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Element);
+ method public android.support.v8.renderscript.Type create();
+ method public android.support.v8.renderscript.Type.Builder setFaces(boolean);
+ method public android.support.v8.renderscript.Type.Builder setMipmaps(boolean);
+ method public android.support.v8.renderscript.Type.Builder setX(int);
+ method public android.support.v8.renderscript.Type.Builder setY(int);
+ method public android.support.v8.renderscript.Type.Builder setYuvFormat(int);
+ method public android.support.v8.renderscript.Type.Builder setZ(int);
+ }
+
+ public static final class Type.CubemapFace extends java.lang.Enum {
+ method public static android.support.v8.renderscript.Type.CubemapFace valueOf(java.lang.String);
+ method public static final android.support.v8.renderscript.Type.CubemapFace[] values();
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace NEGATIVE_X;
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace NEGATIVE_Y;
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace NEGATIVE_Z;
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace POSITIVE_X;
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace POSITIVE_Y;
+ enum_constant public static final android.support.v8.renderscript.Type.CubemapFace POSITIVE_Z;
+ }
+
+}
+
diff --git a/v8/renderscript/api/current.txt b/v8/renderscript/api/current.txt
index 929bd5f..3d135b1 100644
--- a/v8/renderscript/api/current.txt
+++ b/v8/renderscript/api/current.txt
@@ -70,9 +70,11 @@
method public static android.support.v8.renderscript.Allocation createTyped(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Type, int);
method public static android.support.v8.renderscript.Allocation createTyped(android.support.v8.renderscript.RenderScript, android.support.v8.renderscript.Type);
method public void generateMipmaps();
+ method public java.nio.ByteBuffer getByteBuffer();
method public int getBytesSize();
method public android.support.v8.renderscript.Element getElement();
method public long getIncAllocID();
+ method public long getStride();
method public android.support.v8.renderscript.Type getType();
method public int getUsage();
method public void ioReceive();
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
index a2a648f..2384518 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/Allocation.java
@@ -510,7 +510,6 @@
ioSend();
}
/**
- * @hide
* Gets or creates a ByteBuffer that contains the raw data of the current Allocation.
* <p> If the Allocation is created with USAGE_IO_INPUT, the returned ByteBuffer
* would contain the up-to-date data as READ ONLY.
@@ -554,7 +553,6 @@
}
/**
- * @hide
* Gets the stride of the Allocation.
* For a 2D or 3D Allocation, the raw data maybe padded so that each row of
* the Allocation has certain alignment. The size of each row including such
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
index 065712a..139fde2 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/ScriptGroup.java
@@ -476,6 +476,7 @@
* connected.
* @param a The allocation to connect.
*/
+ @Deprecated
public void setInput(Script.KernelID s, Allocation a) {
for (int ct=0; ct < mInputs.length; ct++) {
if (mInputs[ct].mKID == s) {
@@ -500,6 +501,7 @@
* connected.
* @param a The allocation to connect.
*/
+ @Deprecated
public void setOutput(Script.KernelID s, Allocation a) {
for (int ct=0; ct < mOutputs.length; ct++) {
if (mOutputs[ct].mKID == s) {
@@ -524,6 +526,7 @@
*
* @deprecated Use {@link #execute} instead.
*/
+ @Deprecated
public void execute() {
if (!mUseIncSupp) {
mRS.nScriptGroupExecute(getID(mRS));
@@ -611,6 +614,7 @@
* @deprecated Use {@link Builder2} instead.
*
*/
+ @Deprecated
public static final class Builder {
private RenderScript mRS;
private ArrayList<Node> mNodes = new ArrayList<Node>();