Merge "Add ability to jump-scroll to a particular preference" into nyc-dev
diff --git a/Android.mk b/Android.mk
index 0d8e2f9..2ef2319 100644
--- a/Android.mk
+++ b/Android.mk
@@ -21,11 +21,57 @@
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
# Run the check-support-api task on a SDK build
-sdk: check-support-api
+sdk: check-support-api $(support-aidl)
# Build all support libraries
include $(call all-makefiles-under,$(LOCAL_PATH))
diff --git a/CleanSpec.mk b/CleanSpec.mk
index 317bb03..7b2174e 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 df16cdb..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 := $(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/build.gradle b/build.gradle
index 9aa0557..8bfa9c4 100644
--- a/build.gradle
+++ b/build.gradle
@@ -9,7 +9,7 @@
maven { url "../../prebuilts/maven_repo/android" }
}
dependencies {
- classpath 'com.android.tools.build:gradle:1.5.0'
+ classpath 'com.android.tools.build:gradle:2.1.0-alpha4'
}
}
@@ -114,6 +114,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"
diff --git a/design/Android.mk b/design/Android.mk
index c4004f5..c9ba6ec 100644
--- a/design/Android.mk
+++ b/design/Android.mk
@@ -14,22 +14,23 @@
LOCAL_PATH := $(call my-dir)
+# 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 := $(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 \
- --no-version-vectors
+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)
@@ -42,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
@@ -57,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
@@ -72,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
@@ -87,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
@@ -102,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
@@ -117,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
@@ -127,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 := $(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/current.txt b/design/api/current.txt
index b68507a..2fc889b 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);
@@ -261,6 +262,7 @@
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 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);
}
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/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/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 f3abec1..4e54db4 100644
--- a/design/res/values/attrs.xml
+++ b/design/res/values/attrs.xml
@@ -210,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
@@ -217,6 +218,15 @@
<attr name="expanded" format="boolean" />
</declare-styleable>
+ <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_LayoutParams">
<attr name="layout_scrollFlags">
<!-- The view will be scroll in direct relation to scroll events. This flag needs to be
diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml
index 18fdf7f..d49764f 100644
--- a/design/res/values/styles.xml
+++ b/design/res/values/styles.xml
@@ -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>
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/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index 2419de9..03a125b 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -18,6 +18,7 @@
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;
@@ -124,15 +125,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,22 +148,29 @@
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
@@ -178,6 +189,9 @@
* @see #removeOnOffsetChangedListener(OnOffsetChangedListener)
*/
public void addOnOffsetChangedListener(OnOffsetChangedListener listener) {
+ if (mListeners == null) {
+ mListeners = new ArrayList<>();
+ }
if (listener != null && !mListeners.contains(listener)) {
mListeners.add(listener);
}
@@ -189,7 +203,7 @@
* @param listener the listener to remove.
*/
public void removeOnOffsetChangedListener(OnOffsetChangedListener listener) {
- if (listener != null) {
+ if (mListeners != null && listener != null) {
mListeners.remove(listener);
}
}
@@ -216,6 +230,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;
+ }
+ }
+ setCollapsible(haveCollapsibleChild);
}
private void invalidateScrollRanges() {
@@ -422,6 +449,19 @@
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 = getTopInset();
final int minHeight = ViewCompat.getMinimumHeight(this);
@@ -432,33 +472,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 setCollapsible(boolean collapsible) {
+ if (mCollapsible != collapsible) {
+ mCollapsible = collapsible;
+ refreshDrawableState();
+ }
+ }
+
+ private void setCollapsed(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() {
@@ -550,6 +623,8 @@
*/
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;
@@ -643,6 +718,14 @@
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;
+ }
}
/**
@@ -876,6 +959,27 @@
}
@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) {
boolean handled = super.onLayoutChild(parent, abl, layoutDirection);
@@ -917,8 +1021,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;
}
@@ -974,7 +1078,7 @@
? interpolateOffset(appBarLayout, newOffset)
: newOffset;
- boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
+ final boolean offsetChanged = setTopAndBottomOffset(interpolatedOffset);
// Update how much dy we have consumed
consumed = curOffset - newOffset;
@@ -990,7 +1094,11 @@
}
// 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
@@ -1000,19 +1108,6 @@
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);
@@ -1060,6 +1155,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.setCollapsed(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;
@@ -1182,7 +1315,7 @@
// any vertical gap, and overlap
final Behavior ablBehavior = (Behavior) behavior;
final int offset = ablBehavior.getTopBottomOffsetForScrollingSibling();
- child.offsetTopAndBottom((dependency.getBottom() - child.getTop())
+ ViewCompat.offsetTopAndBottom(child, (dependency.getBottom() - child.getTop())
+ ablBehavior.mOffsetDelta
+ getVerticalLayoutGap()
- getOverlapPixelsForOffset(dependency));
diff --git a/design/src/android/support/design/widget/CollapsingTextHelper.java b/design/src/android/support/design/widget/CollapsingTextHelper.java
index 6f8d51c..4a95a13 100644
--- a/design/src/android/support/design/widget/CollapsingTextHelper.java
+++ b/design/src/android/support/design/widget/CollapsingTextHelper.java
@@ -194,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) {
@@ -217,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) {
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index 17fada5..04755ff 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -179,7 +179,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)) {
@@ -1117,7 +1117,6 @@
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);
@@ -1151,15 +1150,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 9f9c483..dfe44d6 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -148,6 +148,8 @@
private final int[] mTempIntPair = new int[2];
private Paint mScrimPaint;
+ private boolean mDisallowInterceptReset;
+
private boolean mIsAttachedToWindow;
private int[] mKeylines;
@@ -366,6 +368,7 @@
final LayoutParams lp = (LayoutParams) child.getLayoutParams();
lp.resetTouchBehaviorTracking();
}
+ mDisallowInterceptReset = false;
}
/**
@@ -530,8 +533,9 @@
@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
super.requestDisallowInterceptTouchEvent(disallowIntercept);
- if (disallowIntercept) {
+ if (disallowIntercept && !mDisallowInterceptReset) {
resetTouchBehaviors();
+ mDisallowInterceptReset = true;
}
}
diff --git a/design/src/android/support/design/widget/FloatingActionButton.java b/design/src/android/support/design/widget/FloatingActionButton.java
index 0e31446..945b9b5 100644
--- a/design/src/android/support/design/widget/FloatingActionButton.java
+++ b/design/src/android/support/design/widget/FloatingActionButton.java
@@ -561,7 +561,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
@@ -570,6 +570,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 =
@@ -604,7 +612,7 @@
}
private void updateFabTranslationForSnackbar(CoordinatorLayout parent,
- final FloatingActionButton fab, View snackbar) {
+ 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...
@@ -618,7 +626,7 @@
mFabTranslationYAnimator.cancel();
}
- if (fab.isShown()
+ 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
@@ -676,6 +684,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;
}
diff --git a/design/src/android/support/design/widget/NavigationView.java b/design/src/android/support/design/widget/NavigationView.java
index 696a7e4..05fdedb 100644
--- a/design/src/android/support/design/widget/NavigationView.java
+++ b/design/src/android/support/design/widget/NavigationView.java
@@ -413,7 +413,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;
diff --git a/design/src/android/support/design/widget/SwipeDismissBehavior.java b/design/src/android/support/design/widget/SwipeDismissBehavior.java
index 5e4837f..bfc98ea 100644
--- a/design/src/android/support/design/widget/SwipeDismissBehavior.java
+++ b/design/src/android/support/design/widget/SwipeDismissBehavior.java
@@ -211,15 +211,20 @@
}
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
@@ -239,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/TabLayout.java b/design/src/android/support/design/widget/TabLayout.java
index 1059b9a..9922c94 100755
--- a/design/src/android/support/design/widget/TabLayout.java
+++ b/design/src/android/support/design/widget/TabLayout.java
@@ -299,10 +299,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();
}
@@ -1537,10 +1539,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;
}
}
diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java
index ffd7a83..a38439f 100644
--- a/design/src/android/support/design/widget/TextInputLayout.java
+++ b/design/src/android/support/design/widget/TextInputLayout.java
@@ -494,7 +494,7 @@
// Probably caused by our theme not extending from Theme.Design*. Instead
// we manually set something appropriate
mErrorView.setTextAppearance(getContext(),
- R.style.TextAppearance_AppCompat_Caption);
+ android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
mErrorView.setTextColor(ContextCompat.getColor(
getContext(), R.color.design_textinput_error_color_light));
}
@@ -615,7 +615,7 @@
// Probably caused by our theme not extending from Theme.Design*. Instead
// we manually set something appropriate
mCounterView.setTextAppearance(getContext(),
- R.style.TextAppearance_AppCompat_Caption);
+ android.support.v7.appcompat.R.style.TextAppearance_AppCompat_Caption);
mCounterView.setTextColor(ContextCompat.getColor(
getContext(), R.color.design_textinput_error_color_light));
}
@@ -725,7 +725,7 @@
private void ensureBackgroundDrawableStateWorkaround() {
final int sdk = Build.VERSION.SDK_INT;
- if (sdk != 21 || sdk != 22) {
+ if (sdk != 21 && sdk != 22) {
// The workaround is only required on API 21-22
return;
}
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/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/src/android/support/design/widget/AppBarLayoutBaseTest.java b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
index 793f6d9..0e73c41 100644
--- a/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarLayoutBaseTest.java
@@ -16,6 +16,7 @@
package android.support.design.widget;
+import android.os.Build;
import android.support.annotation.CallSuper;
import android.support.annotation.IdRes;
import android.support.annotation.LayoutRes;
@@ -27,6 +28,7 @@
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;
@@ -38,6 +40,8 @@
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;
@@ -48,6 +52,8 @@
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(
@@ -113,5 +119,14 @@
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/AppBarWithCollapsingToolbarTest.java b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
index 737668b..249d891 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithCollapsingToolbarTest.java
@@ -51,6 +51,8 @@
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,
@@ -74,6 +76,7 @@
// 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
@@ -89,6 +92,7 @@
// 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(
@@ -102,6 +106,7 @@
// 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(
@@ -115,6 +120,7 @@
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
}
@Test
@@ -141,6 +147,8 @@
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,
@@ -153,6 +161,7 @@
// edge aligned with the system status bar.
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
// Perform another swipe-up gesture
performVerticalSwipeUpGesture(
@@ -165,6 +174,7 @@
// 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(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
@@ -180,6 +190,7 @@
// in scrolling mode and we've swiped down, but not fully. 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(
@@ -193,6 +204,7 @@
// 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(
@@ -206,6 +218,7 @@
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(0f);
}
@Test
diff --git a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
index d3529cc..0c715de 100644
--- a/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
+++ b/design/tests/src/android/support/design/widget/AppBarWithToolbarAndTabsTest.java
@@ -76,6 +76,7 @@
// 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(
@@ -88,6 +89,7 @@
// 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
@@ -104,6 +106,7 @@
// 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(
@@ -117,6 +120,7 @@
// 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(
@@ -130,6 +134,7 @@
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
}
@Test
@@ -163,6 +168,7 @@
// 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(
@@ -175,6 +181,7 @@
// 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
@@ -191,6 +198,7 @@
// 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(
@@ -204,6 +212,7 @@
// 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(
@@ -217,6 +226,7 @@
// Allow for off-by-a-pixel margin of error.
assertEquals(originalAppbarTop, appbarOnScreenXY[1], 1);
assertEquals(originalAppbarBottom, appbarOnScreenXY[1] + appbarHeight, 1);
+ assertAppBarElevation(mDefaultElevationValue);
}
@LargeTest
@@ -264,6 +274,7 @@
// 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
@@ -281,6 +292,7 @@
// 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
@@ -298,6 +310,7 @@
// 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.
@@ -314,6 +327,7 @@
// 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.
@@ -331,6 +345,7 @@
// 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
@@ -348,6 +363,7 @@
// 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
@@ -364,6 +380,7 @@
// 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).
@@ -381,5 +398,6 @@
// 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/CoordinatorSnackbarWithFabTest.java b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
index 3b06ae2..ca56ecc 100644
--- a/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
+++ b/design/tests/src/android/support/design/widget/CoordinatorSnackbarWithFabTest.java
@@ -30,6 +30,7 @@
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;
@@ -115,6 +116,26 @@
}
@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
diff --git a/design/tests/src/android/support/design/widget/DesignViewActions.java b/design/tests/src/android/support/design/widget/DesignViewActions.java
index ad9e294..4ae608a 100644
--- a/design/tests/src/android/support/design/widget/DesignViewActions.java
+++ b/design/tests/src/android/support/design/widget/DesignViewActions.java
@@ -20,6 +20,7 @@
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 {
@@ -50,4 +51,25 @@
};
}
+ 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/documents-archive/Android.mk b/documents-archive/Android.mk
index 44c9f66..32ec7d6 100644
--- a/documents-archive/Android.mk
+++ b/documents-archive/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-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_AIDL_INCLUDES := $(LOCAL_PATH)/src
LOCAL_SRC_FILES := $(call all-java-files-under, src)
-LOCAL_JAVA_LIBRARIES := android-support-annotations \
+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)
diff --git a/documents-archive/src/android/support/provider/DocumentArchive.java b/documents-archive/src/android/support/provider/DocumentArchive.java
index dc9a7b3..5db5b38 100644
--- a/documents-archive/src/android/support/provider/DocumentArchive.java
+++ b/documents-archive/src/android/support/provider/DocumentArchive.java
@@ -21,10 +21,13 @@
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;
@@ -439,12 +442,42 @@
Preconditions.checkArgument(getDocumentType(documentId).startsWith("image/"),
"Thumbnails only supported for image/* MIME type.");
- // TODO: Extract thumbnails from EXIF.
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);
}
diff --git a/percent/Android.mk b/percent/Android.mk
index 6e04560..59556c0 100644
--- a/percent/Android.mk
+++ b/percent/Android.mk
@@ -14,30 +14,24 @@
LOCAL_PATH := $(call my-dir)
-# 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_MODULE := android-support-percent-res
-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
-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/src/android/support/percent/PercentLayoutHelper.java b/percent/src/android/support/percent/PercentLayoutHelper.java
index 2416f88..63afaec 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;
@@ -77,7 +78,10 @@
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;
}
@@ -105,8 +109,11 @@
+ 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();
@@ -341,22 +348,31 @@
* 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 PercentMarginLayoutParams mPreservedParams;
@@ -374,7 +390,9 @@
}
/**
- * 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) {
@@ -431,8 +449,9 @@
}
/**
- * Fills {@code ViewGroup.MarginLayoutParams} dimensions and margins based on percentage
- * values.
+ * 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) {
@@ -493,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);
@@ -510,9 +529,9 @@
}
/**
- * 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) {
if (!mPreservedParams.mIsWidthComputedFromAspectRatio) {
diff --git a/percent/tests/AndroidManifest.xml b/percent/tests/AndroidManifest.xml
index c4cc597..993e69b 100644
--- a/percent/tests/AndroidManifest.xml
+++ b/percent/tests/AndroidManifest.xml
@@ -29,6 +29,7 @@
<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
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
index 67d2ae5..8b2fe8b 100644
--- a/percent/tests/java/android/support/percent/PercentFrameTest.java
+++ b/percent/tests/java/android/support/percent/PercentFrameTest.java
@@ -50,7 +50,6 @@
}
@Test
- @UiThreadTest
public void testWidthHeight() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_height);
@@ -64,7 +63,6 @@
}
@Test
- @UiThreadTest
public void testWidthAspectRatio() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_width_ratio);
@@ -78,7 +76,6 @@
}
@Test
- @UiThreadTest
public void testHeightAspectRatio() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_height_ratio);
@@ -92,7 +89,6 @@
}
@Test
- @UiThreadTest
public void testMarginsSingle() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_single);
@@ -112,7 +108,6 @@
}
@Test
- @UiThreadTest
public void testMarginsMultiple() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_multiple);
@@ -132,7 +127,6 @@
}
@Test
- @UiThreadTest
public void testMarginsTopLeft() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_top_left);
@@ -152,7 +146,6 @@
}
@Test
- @UiThreadTest
public void testMarginsBottomRight() {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margins_bottom_right);
@@ -172,7 +165,6 @@
}
@Test
- @UiThreadTest
public void testMarginStart() throws Throwable {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
@@ -183,7 +175,6 @@
}
@Test
- @UiThreadTest
public void testMarginStartRtl() throws Throwable {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_start);
@@ -216,7 +207,6 @@
}
@Test
- @UiThreadTest
public void testMarginEnd() throws Throwable {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
@@ -227,7 +217,6 @@
}
@Test
- @UiThreadTest
public void testMarginEndRtl() throws Throwable {
View childToTest = mPercentFrameLayout.findViewById(R.id.child_margin_end);
diff --git a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
index 634637c..2cb679d 100644
--- a/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
+++ b/percent/tests/java/android/support/percent/PercentRelativeRtlTest.java
@@ -120,7 +120,6 @@
}
@Test
- @UiThreadTest
public void testTopChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
@@ -150,7 +149,6 @@
}
@Test
- @UiThreadTest
public void testStartChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_start);
@@ -181,7 +179,6 @@
}
@Test
- @UiThreadTest
public void testBottomChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
@@ -213,7 +210,6 @@
}
@Test
- @UiThreadTest
public void testEndChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_end);
@@ -244,7 +240,6 @@
}
@Test
- @UiThreadTest
public void testCenterChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
diff --git a/percent/tests/java/android/support/percent/PercentRelativeTest.java b/percent/tests/java/android/support/percent/PercentRelativeTest.java
index 31d406e..eaa38f6 100644
--- a/percent/tests/java/android/support/percent/PercentRelativeTest.java
+++ b/percent/tests/java/android/support/percent/PercentRelativeTest.java
@@ -73,7 +73,6 @@
}
@Test
- @UiThreadTest
public void testTopChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_top);
@@ -95,7 +94,6 @@
}
@Test
- @UiThreadTest
public void testLeftChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_left);
@@ -117,7 +115,6 @@
}
@Test
- @UiThreadTest
public void testBottomChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_bottom);
@@ -139,7 +136,6 @@
}
@Test
- @UiThreadTest
public void testRightChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_right);
@@ -161,7 +157,6 @@
}
@Test
- @UiThreadTest
public void testCenterChild() {
final View childToTest = mPercentRelativeLayout.findViewById(R.id.child_center);
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_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_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_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/v13/api/current.txt b/v13/api/current.txt
index 601b4d2..b965a4e 100644
--- a/v13/api/current.txt
+++ b/v13/api/current.txt
@@ -42,21 +42,17 @@
package android.support.v13.view {
- public abstract class DragStartHelper implements android.view.View.OnLongClickListener android.view.View.OnTouchListener {
- ctor public DragStartHelper(android.view.View);
+ public class DragStartHelper {
+ ctor public DragStartHelper(android.view.View, android.support.v13.view.DragStartHelper.OnDragStartListener);
method public void attach();
- method public android.view.View.DragShadowBuilder getShadowBuilder(android.view.View);
- method public void getTouchPosition(android.view.View, android.graphics.Point);
- method public boolean handleLongClick(android.view.View);
- method public boolean handleTouch(android.view.View, android.view.MotionEvent);
- method protected abstract boolean onDragStart(android.view.View);
+ 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);
- method public void remove();
}
- public class DragStartHelper.ShadowBuilder extends android.view.View.DragShadowBuilder {
- ctor public DragStartHelper.ShadowBuilder(android.view.View);
+ public static abstract interface DragStartHelper.OnDragStartListener {
+ method public abstract boolean onDragStart(android.view.View, android.support.v13.view.DragStartHelper);
}
public final class DropPermissionsCompat {
diff --git a/v13/java/android/support/v13/view/DragStartHelper.java b/v13/java/android/support/v13/view/DragStartHelper.java
index 71c4e13..d3b51cc 100644
--- a/v13/java/android/support/v13/view/DragStartHelper.java
+++ b/v13/java/android/support/v13/view/DragStartHelper.java
@@ -30,15 +30,22 @@
* 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 drag shadow.
+ * the hot spot position for a drag shadow.
* <p>
- * Implement {@link #onDragStart(View)} method to start the drag operation:
+ * Implement {@link DragStartHelper.OnDragStartListener} to start the drag operation:
* <pre>
- * mDragStartHelper = new DragStartHelper(mDraggableView) {
- * protected void onDragStart(View v) {
- * v.startDrag(mClipData, getShadowBuilder(view), mLocalState, mDragFlags);
+ * 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):
@@ -48,25 +55,48 @@
* It may also be used in combination with existing listeners:
* <pre>
* public boolean onTouch(View view, MotionEvent event) {
- * return mDragStartHelper.handleTouch(view, event) || handleTouchEvent(view, event);
+ * if (mDragStartHelper.onTouch(view, event)) {
+ * return true;
+ * }
+ * return handleTouchEvent(view, event);
* }
* public boolean onLongClick(View view) {
- * return mDragStartHelper.handleLongClick(view) || handleLongClickEvent(view);
+ * if (mDragStartHelper.onLongClick(view)) {
+ * return true;
+ * }
+ * return handleLongClickEvent(view);
* }
* </pre>
*/
-public abstract class DragStartHelper implements View.OnLongClickListener, View.OnTouchListener {
+public class DragStartHelper {
final private View mView;
- private float mLastTouchRawX, mLastTouchRawY;
+ 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 initally attached to the view, {@link #attach} must be
+ * 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) {
+ public DragStartHelper(View view, OnDragStartListener listener) {
mView = view;
+ mListener = listener;
}
/**
@@ -75,8 +105,8 @@
* This will replace previously existing touch and long click listeners.
*/
public void attach() {
- mView.setOnLongClickListener(this);
- mView.setOnTouchListener(this);
+ mView.setOnLongClickListener(mLongClickListener);
+ mView.setOnTouchListener(mTouchListener);
}
/**
@@ -84,21 +114,11 @@
* <p>
* This will reset touch and long click listeners to {@code null}.
*/
- public void remove() {
+ public void detach() {
mView.setOnLongClickListener(null);
mView.setOnTouchListener(null);
}
- @Override
- public boolean onTouch(View v, MotionEvent event) {
- return handleTouch(v, event);
- }
-
- @Override
- public boolean onLongClick(View v) {
- return handleLongClick(v);
- }
-
/**
* Handle a touch event.
* @param v The view the touch event has been dispatched to.
@@ -106,16 +126,16 @@
* the event.
* @return True if the listener has consumed the event, false otherwise.
*/
- public boolean handleTouch(View v, MotionEvent event) {
+ public boolean onTouch(View v, MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN ||
event.getAction() == MotionEvent.ACTION_MOVE) {
- mLastTouchRawX = event.getRawX();
- mLastTouchRawY = event.getRawY();
+ 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 onDragStart(v);
+ return mListener.onDragStart(v, this);
}
return false;
}
@@ -125,62 +145,30 @@
* @param v The view that was clicked and held.
* @return true if the callback consumed the long click, false otherwise.
*/
- public boolean handleLongClick(View v) {
- return onDragStart(v);
+ public boolean onLongClick(View v) {
+ return mListener.onDragStart(v, this);
}
/**
* Compute the position of the touch event that started the drag operation.
- * @param v The view relative to which the position should be computed.
* @param point The position of the touch event that started the drag operation.
*/
- public void getTouchPosition(View v, Point point) {
- int [] viewLocation = new int[2];
- v.getLocationOnScreen(viewLocation);
- point.set((int) mLastTouchRawX - viewLocation[0], (int) mLastTouchRawY - viewLocation[1]);
+ public void getTouchPosition(Point point) {
+ point.set(mLastTouchX, mLastTouchY);
}
- /**
- * Create a {@link View.DragShadowBuilder} which will build a drag shadow with the same
- * appearance and dimensions as the specified view and with the hot spot at the location of
- * the touch event that started the drag operation.
- * @param view A view
- * @return {@link View.DragShadowBuilder}
- */
- public View.DragShadowBuilder getShadowBuilder(View view) {
- return new ShadowBuilder(view);
- }
-
- /**
- * A utility class that builds a drag shadow with the same appearance and dimensions as the
- * specified view and with the hot spot at the location of the touch event that started the
- * drag operation.
- * <p>
- * At the start of the drag operation a drag shadow built this way will be positioned directly
- * over the specified view.
- */
- public class ShadowBuilder extends View.DragShadowBuilder {
- /**
- * Constructs a shadow image builder based on a View. By default, the resulting drag
- * shadow will have the same appearance and dimensions as the View, with the touch point
- * at the location of the touch event that started the drag operation.
- * @param view A view.
- */
- public ShadowBuilder(View view) {
- super(view);
- }
-
+ private final View.OnLongClickListener mLongClickListener = new View.OnLongClickListener() {
@Override
- public void onProvideShadowMetrics(Point shadowSize, Point shadowTouchPoint) {
- super.onProvideShadowMetrics(shadowSize, shadowTouchPoint);
- getTouchPosition(getView(), shadowTouchPoint);
+ public boolean onLongClick(View v) {
+ return DragStartHelper.this.onLongClick(v);
}
- }
+ };
- /**
- * Called when the drag start gesture has been detected.
- * @param v A view on which the drag start gesture has been detected.
- */
- protected abstract boolean onDragStart(View 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/v14/preference/Android.mk b/v14/preference/Android.mk
index f01c53f..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 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_MODULE := android-support-v14-preference-res
-LOCAL_SRC_FILES := $(call all-java-files-under, ../../v7/preference/constants)
-LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-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
diff --git a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
index 8316b0d..1695e92 100644
--- a/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
+++ b/v14/preference/src/android/support/v14/preference/MultiSelectListPreference.java
@@ -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,8 @@
}
public MultiSelectListPreference(Context context, AttributeSet attrs) {
- this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.dialogPreferenceStyle,
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ android.support.v7.preference.R.attr.dialogPreferenceStyle,
android.R.attr.dialogPreferenceStyle));
}
diff --git a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
index 43e487c..f2d9549 100644
--- a/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
+++ b/v14/preference/src/android/support/v14/preference/PreferenceFragment.java
@@ -136,7 +136,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();
@@ -210,7 +210,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");
@@ -247,7 +248,8 @@
TypedArray a = mStyledContext.obtainStyledAttributes(null,
R.styleable.PreferenceFragment,
- TypedArrayUtils.getAttr(mStyledContext, R.attr.preferenceFragmentStyle,
+ TypedArrayUtils.getAttr(mStyledContext,
+ android.support.v7.preference.R.attr.preferenceFragmentStyle,
AndroidResources.ANDROID_R_PREFERENCE_FRAGMENT_STYLE),
0);
@@ -261,7 +263,8 @@
// 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);
@@ -575,7 +578,8 @@
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());
diff --git a/v14/preference/src/android/support/v14/preference/SwitchPreference.java b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
index ac2a9c6..6e6b7f8 100644
--- a/v14/preference/src/android/support/v14/preference/SwitchPreference.java
+++ b/v14/preference/src/android/support/v14/preference/SwitchPreference.java
@@ -122,7 +122,8 @@
* @param attrs Style attributes that differ from the default
*/
public SwitchPreference(Context context, AttributeSet attrs) {
- this(context, attrs, TypedArrayUtils.getAttr(context, R.attr.switchPreferenceStyle,
+ this(context, attrs, TypedArrayUtils.getAttr(context,
+ android.support.v7.preference.R.attr.switchPreferenceStyle,
android.R.attr.switchPreferenceStyle));
}
@@ -216,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 d55ae61..8e6a224 100644
--- a/v17/leanback/Android.mk
+++ b/v17/leanback/Android.mk
@@ -19,12 +19,11 @@
# 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 := $(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)
@@ -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)
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index f9c13de..dc5da07 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -101,6 +101,11 @@
method public abstract T createFragment(java.lang.Object);
}
+ public static abstract interface BrowseFragment.FragmentHost {
+ 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);
@@ -109,6 +114,7 @@
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();
@@ -190,6 +196,11 @@
method public abstract T createFragment(java.lang.Object);
}
+ public static abstract interface BrowseSupportFragment.FragmentHost {
+ 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);
@@ -198,6 +209,7 @@
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();
@@ -898,6 +910,34 @@
method public android.widget.TextView getTitle();
}
+ public abstract class AbstractMediaItemPresenter extends android.support.v17.leanback.widget.RowPresenter {
+ ctor public AbstractMediaItemPresenter(android.content.Context, int);
+ ctor public AbstractMediaItemPresenter();
+ method protected android.support.v17.leanback.widget.RowPresenter.ViewHolder createRowViewHolder(android.view.ViewGroup);
+ method protected abstract void onBindMediaViewHolder(android.support.v17.leanback.widget.AbstractMediaItemPresenter.ViewHolder, java.lang.Object);
+ method public void setBackgroundColor(int);
+ }
+
+ public static class AbstractMediaItemPresenter.ViewHolder extends android.support.v17.leanback.widget.RowPresenter.ViewHolder {
+ ctor public AbstractMediaItemPresenter.ViewHolder(android.view.View);
+ method public android.widget.TextView getTrackDurationView();
+ method public android.widget.TextView getTrackNameView();
+ method public android.widget.TextView getTrackNumberView();
+ }
+
+ 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);
@@ -914,6 +954,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 {
@@ -1219,12 +1260,15 @@
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);
@@ -1256,6 +1300,7 @@
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 B autoSaveRestoreEnabled(boolean);
method public B checkSetId(int);
method public B checked(boolean);
method public B clickAction(long);
diff --git a/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml b/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml
deleted file mode 100644
index fdd6034..0000000
--- a/v17/leanback/res/animator/lb_page_indicator_dot_hide.xml
+++ /dev/null
@@ -1,37 +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:propertyName="alpha"
- android:valueFrom="1.0"
- android:valueTo="0.0"
- android:duration="167"
- android:interpolator="@android:anim/decelerate_interpolator" />
- <objectAnimator
- android:propertyName="diameter"
- android:valueFrom="36dp"
- android:valueTo="10dp"
- android:duration="417"
- android:interpolator="@android:anim/decelerate_interpolator" />
- <objectAnimator
- android:propertyName="translationX"
- android:valueFrom="-16dp"
- android:valueTo="0dp"
- android:duration="417"
- android:interpolator="@android:anim/decelerate_interpolator" />
-</set>
diff --git a/v17/leanback/res/animator/lb_page_indicator_dot_show.xml b/v17/leanback/res/animator/lb_page_indicator_dot_show.xml
deleted file mode 100644
index 392fb2c..0000000
--- a/v17/leanback/res/animator/lb_page_indicator_dot_show.xml
+++ /dev/null
@@ -1,37 +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:propertyName="alpha"
- android:valueFrom="0.0"
- android:valueTo="1.0"
- android:duration="167"
- android:interpolator="@android:anim/decelerate_interpolator" />
- <objectAnimator
- android:propertyName="diameter"
- android:valueFrom="10dp"
- android:valueTo="36dp"
- android:duration="417"
- android:interpolator="@android:anim/decelerate_interpolator" />
- <objectAnimator
- android:propertyName="translationX"
- android:valueFrom="-16dp"
- android:valueTo="0dp"
- android:duration="417"
- android:interpolator="@android:anim/decelerate_interpolator" />
-</set>
diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
index 2ffae70..adf9d4f 100644
--- a/v17/leanback/res/layout/lb_guidedstep_fragment.xml
+++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml
@@ -68,6 +68,8 @@
<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"
diff --git a/v17/leanback/res/layout/lb_onboarding_fragment.xml b/v17/leanback/res/layout/lb_onboarding_fragment.xml
index 0484b87..04bd0ea 100644
--- a/v17/leanback/res/layout/lb_onboarding_fragment.xml
+++ b/v17/leanback/res/layout/lb_onboarding_fragment.xml
@@ -16,7 +16,6 @@
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
- xmlns:lb="http://schemas.android.com/apk/res-auto"
android:id="@+id/onboarding_fragment_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
@@ -53,11 +52,9 @@
<FrameLayout
android:id="@id/navigator_container"
style="?attr/onboardingNavigatorContainerStyle">
- <!-- TODO: Customize dimensions of PagingIndicator -->
<android.support.v17.leanback.widget.PagingIndicator
android:id="@+id/page_indicator"
style="?attr/onboardingPageIndicatorStyle"
- lb:dotBgColor="?attr/onboardingNavigatorDotBgColor"
android:visibility="gone" />
<Button
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_media_item.xml b/v17/leanback/res/layout/lb_row_media_item.xml
new file mode 100644
index 0000000..7f01068
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_media_item.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.
+-->
+
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:id="@+id/rowContainer"
+ style="?attr/playbackMediaItemStyle">
+
+
+ <RelativeLayout
+ android:layout_width="match_parent"
+ android:layout_height="match_parent">
+
+ <TextView
+ android:id="@+id/trackNumber"
+ style="?attr/playbackMediaItemTrackNumberStyle"/>
+
+
+ <TextView
+ android:id="@+id/trackName"
+ android:layout_toEndOf="@+id/trackNumber"
+ android:layout_toLeftOf="@+id/trackDuration"
+ style="?attr/playbackMediaItemTrackNameStyle"/>
+
+
+ <TextView
+ android:id="@+id/trackDuration"
+ android:gravity="center_vertical|right"
+ style="?attr/playbackMediaItemTrackDurationStyle"/>
+ </RelativeLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/v17/leanback/res/layout/lb_row_track_list_header.xml b/v17/leanback/res/layout/lb_row_track_list_header.xml
new file mode 100644
index 0000000..57cedb5
--- /dev/null
+++ b/v17/leanback/res/layout/lb_row_track_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/trackListHeader"
+ style="?attr/playbackMediaListHeaderTitleStyle"
+ />
+</FrameLayout>
\ No newline at end of file
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/attrs.xml b/v17/leanback/res/values/attrs.xml
index 7ac192f..28e3807 100644
--- a/v17/leanback/res/values/attrs.xml
+++ b/v17/leanback/res/values/attrs.xml
@@ -267,7 +267,18 @@
<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="playbackMediaListHeaderStyle" format="reference"/>
+ <attr name="playbackMediaItemStyle" format="reference"/>
+
+ <attr name="playbackMediaListHeaderTitleStyle" format="reference"/>
+ <attr name="playbackMediaItemTrackNumberStyle" format="reference"/>
+ <attr name="playbackMediaItemTrackNameStyle" format="reference"/>
+ <attr name="playbackMediaItemTrackDurationStyle" format="reference"/>
+
<attr name="playbackControlsButtonStyle" format="reference" />
<attr name="playbackControlButtonLabelStyle" format="reference" />
<attr name="playbackControlsTimeStyle" format="reference" />
@@ -520,15 +531,20 @@
<!-- 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" />
-
- <!-- Theme attribute for the style of the background color of the dots in the navigator
- of the onboarding screen. -->
- <attr name="onboardingNavigatorDotBgColor" 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/dimens.xml b/v17/leanback/res/values/dimens.xml
index 063de2e..8b2fc22 100644
--- a/v17/leanback/res/values/dimens.xml
+++ b/v17/leanback/res/values/dimens.xml
@@ -277,14 +277,12 @@
<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 lb_page_indicator_arrow_diameter + 2 * lb_page_indicator_arrow_shadow_radius -->
+ <!-- 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_diameter">36dp</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>
- <!-- This value must be twice lb_page_indicator_dot_radius -->
- <dimen name="lb_page_indicator_dot_diameter">10dp</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>
diff --git a/v17/leanback/res/values/styles.xml b/v17/leanback/res/values/styles.xml
index 9dfdee2..8156b82 100644
--- a/v17/leanback/res/values/styles.xml
+++ b/v17/leanback/res/values/styles.xml
@@ -285,6 +285,95 @@
<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">false</item>
+ <item name="android:clipToPadding">false</item>
+ <item name="android:foreground">@null</item>
+ <item name="android:background">?attr/defaultBrandColor</item>
+ <item name="android:layout_width">match_parent</item>
+ <item name="android:layout_height">48dp</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaItemStyle" parent="Widget.Leanback.PlaybackRow">
+ <item name="android:focusable">true</item>
+ <item name="android:focusableInTouchMode">true</item>
+ <item name="android:foreground">?android:attr/selectableItemBackground</item>
+ </style>
+
+ <style name="Widget.Leanback.PlaybackMediaListHeaderStyle"
+ parent="Widget.Leanback.PlaybackRow">
+ <item name="android:focusable">false</item>
+ <item name="android:focusableInTouchMode">false</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">32dp</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaListHeaderTitle</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemTrackNumber">
+ <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.PlaybackMediaItemTrackNumberStyle">
+ <item name="android:layout_width">56dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_marginLeft">32dp</item>
+ <item name="android:gravity">center_vertical</item>"
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackNumber</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemTrackName">
+ <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.PlaybackMediaItemTrackNameStyle">
+ <item name="android:layout_width">wrap_content</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:gravity">center_vertical</item>"
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackName</item>
+ </style>
+
+ <style name="TextAppearance.Leanback.PlaybackMediaItemTrackDuration">
+ <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.PlaybackMediaItemTrackDurationStyle">
+ <item name="android:layout_width">66dp</item>
+ <item name="android:layout_height">match_parent</item>
+ <item name="android:layout_alignParentEnd">true</item>
+ <item name="android:layout_alignParentTop">true</item>
+ <item name="android:layout_marginRight">32dp</item>
+ <item name="android:singleLine">true</item>
+ <item name="android:gravity">center_vertical|right</item>
+ <item name="android:textAppearance">@style/TextAppearance.Leanback.PlaybackMediaItemTrackDuration</item>
+ </style>
+
+
<style name="Widget.Leanback.DetailsDescriptionTitleStyle">
<item name="android:textAlignment">viewStart</item>
<item name="android:textAppearance">@style/TextAppearance.Leanback.DetailsDescriptionTitle</item>
@@ -460,8 +549,8 @@
<!-- 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="focusOutEnd">false</item>
- <item name="focusOutFront">false</item>
+ <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>
@@ -611,6 +700,12 @@
<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. -->
diff --git a/v17/leanback/res/values/themes.xml b/v17/leanback/res/values/themes.xml
index df5bb67..2af382f 100644
--- a/v17/leanback/res/values/themes.xml
+++ b/v17/leanback/res/values/themes.xml
@@ -64,15 +64,28 @@
<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="playbackMediaListHeaderStyle">@style/Widget.Leanback.PlaybackMediaListHeaderStyle</item>
+ <item name="playbackMediaItemStyle">@style/Widget.Leanback.PlaybackMediaItemStyle</item>
+ <item name="playbackMediaListHeaderTitleStyle">@style/Widget.Leanback.PlaybackMediaListHeaderTitleStyle</item>
+ <item name="playbackMediaItemTrackNumberStyle">@style/Widget.Leanback.PlaybackMediaItemTrackNumberStyle</item>
+ <item name="playbackMediaItemTrackNameStyle">@style/Widget.Leanback.PlaybackMediaItemTrackNameStyle</item>
+ <item name="playbackMediaItemTrackDurationStyle">@style/Widget.Leanback.PlaybackMediaItemTrackDurationStyle</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 -->
@@ -185,6 +198,5 @@
<item name="onboardingPageIndicatorStyle">@style/Widget.Leanback.OnboardingPageIndicatorStyle</item>
<item name="onboardingStartButtonStyle">@style/Widget.Leanback.OnboardingStartButtonStyle</item>
<item name="onboardingLogoStyle">@style/Widget.Leanback.OnboardingLogoStyle</item>
- <item name="onboardingNavigatorDotBgColor">@color/lb_page_indicator_dot</item>
</style>
</resources>
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..a11b623 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedFragment.java
@@ -239,4 +239,13 @@
mTitleView.enableAnimation(true);
}
}
+
+ /**
+ * Returns true/false when it's showing/hiding the title respectively.
+ *
+ * @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..b6c7b63 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrandedSupportFragment.java
@@ -241,4 +241,13 @@
mTitleView.enableAnimation(true);
}
}
+
+ /**
+ * Returns true/false when it's showing/hiding the title respectively.
+ *
+ * @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 ca606f3..8338e5e 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -200,6 +200,53 @@
}
/**
+ * Possible set of actions that {@link BrowseFragment} exposes to clients. Custom
+ * fragments can interact with {@link BrowseFragment} using this interface.
+ */
+ public interface FragmentHost {
+ /**
+ * Clients are required to invoke this callback once their view is created
+ * inside {@link Fragment#onStart} 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);
+
+ /**
+ * Slides in the title view from top in {@link BrowseFragment}. This will only happen
+ * if either a. we are in fully expanded mode OR b. non expanded mode but on the first
+ * 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 {
+
+ @Override
+ public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+ processingPendingEntranceTransition();
+ }
+
+ @Override
+ public void showTitleView(boolean show) {
+ if (mShowingHeaders && !isShowingTitle()) {
+ mPendingShowTitleView = true;
+ }
+ else if (!mShowingHeaders && !isShowingTitle()) {
+ showTitle(true);
+ }
+ }
+ }
+
+ /**
* 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
@@ -224,6 +271,7 @@
public static class MainFragmentAdapter<T extends Fragment> {
private boolean mScalingEnabled;
private final T mFragment;
+ private FragmentHost mFragmentHost;
public MainFragmentAdapter(T fragment) {
this.mFragment = fragment;
@@ -291,6 +339,14 @@
public void setScalingEnabled(boolean scalingEnabled) {
this.mScalingEnabled = scalingEnabled;
}
+
+ void setFragmentHost(FragmentHost fragmentHost) {
+ this.mFragmentHost = fragmentHost;
+ }
+
+ public final FragmentHost getFragmentHost() {
+ return mFragmentHost;
+ }
}
/**
@@ -385,6 +441,7 @@
mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentAdapter.class);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
if (!mIsPageRow) {
mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentRowsAdapter.class);
@@ -481,6 +538,7 @@
private boolean mHeadersBackStackEnabled = true;
private String mWithHeadersBackStackName;
private boolean mShowingHeaders = true;
+ private boolean mPendingShowTitleView;
private boolean mCanShowHeaders = true;
private int mContainerListMarginStart;
private int mContainerListAlignTop;
@@ -628,7 +686,7 @@
* 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;
@@ -908,6 +966,7 @@
// 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();
@@ -917,6 +976,7 @@
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentAdapter.class);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null ?
savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
@@ -991,7 +1051,7 @@
@Override
boolean isReadyForPrepareEntranceTransition() {
- return mMainFragment != null;
+ return mMainFragment != null && mMainFragment.getView() != null;
}
@Override
@@ -1000,14 +1060,7 @@
}
void processingPendingEntranceTransition() {
- // mMainFragment is not null at this point, it can perform prepare entrance transition.
performPendingStates();
- // mMainFragment's view is going to be created in next cycle
- getView().post(new Runnable() {
- public void run() {
- performPendingStates();
- }
- });
}
private void createHeadersTransition() {
@@ -1039,6 +1092,15 @@
headerGridView.requestFocus();
}
}
+
+ // Animate titleview once header animation is complete.
+ if (!mShowingHeaders && mPendingShowTitleView) {
+ mPendingShowTitleView = false;
+ showTitle(true);
+ } else if (mShowingHeaders && isShowingTitle() && mSelectedPosition != 0) {
+ mPendingShowTitleView = true;
+ showTitle(false);
+ }
}
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
@@ -1150,6 +1212,10 @@
return;
}
+ if (position != mSelectedPosition) {
+ mPendingShowTitleView = false;
+ }
+
mHeadersFragment.setSelectedPosition(position, smooth);
replaceMainFragment(position);
@@ -1240,7 +1306,7 @@
if (isEntranceTransitionEnabled()) {
setEntranceTransitionStartState();
}
- processingPendingEntranceTransition();
+ showTitle(false);
}
private void onExpandTransitionStart(boolean expand, final Runnable callback) {
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 124d228..fc943db 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -202,6 +202,53 @@
}
/**
+ * Possible set of actions that {@link BrowseSupportFragment} exposes to clients. Custom
+ * fragments can interact with {@link BrowseSupportFragment} using this interface.
+ */
+ public interface FragmentHost {
+ /**
+ * Clients are required to invoke this callback once their view is created
+ * inside {@link Fragment#onStart} 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);
+
+ /**
+ * Slides in the title view from top in {@link BrowseSupportFragment}. This will only happen
+ * if either a. we are in fully expanded mode OR b. non expanded mode but on the first
+ * 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 {
+
+ @Override
+ public void notifyViewCreated(MainFragmentAdapter fragmentAdapter) {
+ processingPendingEntranceTransition();
+ }
+
+ @Override
+ public void showTitleView(boolean show) {
+ if (mShowingHeaders && !isShowingTitle()) {
+ mPendingShowTitleView = true;
+ }
+ else if (!mShowingHeaders && !isShowingTitle()) {
+ showTitle(true);
+ }
+ }
+ }
+
+ /**
* 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
@@ -226,6 +273,7 @@
public static class MainFragmentAdapter<T extends Fragment> {
private boolean mScalingEnabled;
private final T mFragment;
+ private FragmentHost mFragmentHost;
public MainFragmentAdapter(T fragment) {
this.mFragment = fragment;
@@ -293,6 +341,14 @@
public void setScalingEnabled(boolean scalingEnabled) {
this.mScalingEnabled = scalingEnabled;
}
+
+ void setFragmentHost(FragmentHost fragmentHost) {
+ this.mFragmentHost = fragmentHost;
+ }
+
+ public final FragmentHost getFragmentHost() {
+ return mFragmentHost;
+ }
}
/**
@@ -387,6 +443,7 @@
mMainFragment = mMainFragmentAdapterRegistry.createFragment(item);
mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentAdapter.class);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
if (!mIsPageRow) {
mMainFragmentRowsAdapter = (MainFragmentRowsAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentRowsAdapter.class);
@@ -483,6 +540,7 @@
private boolean mHeadersBackStackEnabled = true;
private String mWithHeadersBackStackName;
private boolean mShowingHeaders = true;
+ private boolean mPendingShowTitleView;
private boolean mCanShowHeaders = true;
private int mContainerListMarginStart;
private int mContainerListAlignTop;
@@ -630,7 +688,7 @@
* 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;
@@ -910,6 +968,7 @@
// 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();
@@ -919,6 +978,7 @@
mMainFragment = getChildFragmentManager().findFragmentById(R.id.scale_frame);
mMainFragmentAdapter = (MainFragmentAdapter) ((Adaptable)mMainFragment)
.getAdapter(MainFragmentAdapter.class);
+ mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl());
mIsPageRow = savedInstanceState != null ?
savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false;
@@ -993,7 +1053,7 @@
@Override
boolean isReadyForPrepareEntranceTransition() {
- return mMainFragment != null;
+ return mMainFragment != null && mMainFragment.getView() != null;
}
@Override
@@ -1002,14 +1062,7 @@
}
void processingPendingEntranceTransition() {
- // mMainFragment is not null at this point, it can perform prepare entrance transition.
performPendingStates();
- // mMainFragment's view is going to be created in next cycle
- getView().post(new Runnable() {
- public void run() {
- performPendingStates();
- }
- });
}
private void createHeadersTransition() {
@@ -1041,6 +1094,15 @@
headerGridView.requestFocus();
}
}
+
+ // Animate titleview once header animation is complete.
+ if (!mShowingHeaders && mPendingShowTitleView) {
+ mPendingShowTitleView = false;
+ showTitle(true);
+ } else if (mShowingHeaders && isShowingTitle() && mSelectedPosition != 0) {
+ mPendingShowTitleView = true;
+ showTitle(false);
+ }
}
if (mBrowseTransitionListener != null) {
mBrowseTransitionListener.onHeadersTransitionStop(mShowingHeaders);
@@ -1152,6 +1214,10 @@
return;
}
+ if (position != mSelectedPosition) {
+ mPendingShowTitleView = false;
+ }
+
mHeadersSupportFragment.setSelectedPosition(position, smooth);
replaceMainFragment(position);
@@ -1242,7 +1308,7 @@
if (isEntranceTransitionEnabled()) {
setEntranceTransitionStartState();
}
- processingPendingEntranceTransition();
+ showTitle(false);
}
private void onExpandTransitionStart(boolean expand, final Runnable callback) {
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 49781a6..5ac5b0d 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepFragment.java
@@ -21,7 +21,6 @@
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;
@@ -33,11 +32,9 @@
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;
@@ -45,14 +42,10 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -146,6 +139,8 @@
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";
@@ -441,7 +436,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);
@@ -462,7 +457,7 @@
* @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);
@@ -590,7 +585,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
@@ -598,12 +596,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();
@@ -957,9 +961,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);
}
@@ -1108,10 +1118,64 @@
@Override
public void onResume() {
super.onResume();
- if (mActionsStylist.isSubActionsExpanded()) {
- mActionsStylist.getSubActionsGridView().requestFocus();
- } else {
- 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));
+ }
}
}
@@ -1121,6 +1185,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);
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 7e6b328..647d1b2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java
@@ -23,7 +23,6 @@
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;
@@ -35,11 +34,9 @@
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;
@@ -47,14 +44,10 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.view.ViewTreeObserver;
import android.widget.FrameLayout;
-import android.widget.ImageView;
import android.widget.LinearLayout;
-import android.widget.TextView;
import java.util.ArrayList;
-import java.util.Collections;
import java.util.List;
/**
@@ -148,6 +141,8 @@
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";
@@ -443,7 +438,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);
@@ -464,7 +459,7 @@
* @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);
@@ -592,7 +587,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
@@ -600,12 +598,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();
@@ -959,9 +963,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);
}
@@ -1110,10 +1120,64 @@
@Override
public void onResume() {
super.onResume();
- if (mActionsStylist.isSubActionsExpanded()) {
- mActionsStylist.getSubActionsGridView().requestFocus();
- } else {
- 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));
+ }
}
}
@@ -1123,6 +1187,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);
diff --git a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
index bc0fee7..e05ca0c 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/ProgressBarManager.java
@@ -28,22 +28,22 @@
private Runnable runnable = new Runnable() {
@Override
public void run() {
- if (!mEnableProgressBar || mIsShowing ||
- (!mUserProvidedProgressBar && rootView == null)) {
+ if (!mEnableProgressBar || (!mUserProvidedProgressBar && rootView == null)) {
return;
}
- mIsShowing = true;
- 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);
+ 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);
+ }
}
}
};
@@ -64,6 +64,7 @@
*/
public void show() {
if (mEnableProgressBar) {
+ mIsShowing = true;
mHandler.postDelayed(runnable, mInitialDelay);
}
}
@@ -72,10 +73,6 @@
* Hides the progress bar.
*/
public void hide() {
- if (!mIsShowing) {
- return;
- }
-
mIsShowing = false;
if (mUserProvidedProgressBar) {
mProgressBarView.setVisibility(View.INVISIBLE);
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 2294903..7721be2 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsFragment.java
@@ -51,14 +51,20 @@
*/
public class RowsFragment extends BaseRowFragment implements Adaptable {
- final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
- final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
@Override
public Object getAdapter(Class clazz) {
if (clazz == BrowseFragment.MainFragmentAdapter.class) {
+ if (mMainFragmentAdapter == null) {
+ mMainFragmentAdapter = new MainFragmentAdapter(this);
+ }
return mMainFragmentAdapter;
} else if (clazz == BrowseFragment.MainFragmentRowsAdapter.class) {
+ if (mMainFragmentRowsAdapter == null) {
+ mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ }
return mMainFragmentRowsAdapter;
}
return null;
@@ -281,6 +287,9 @@
mRecycledViewPool = null;
mPresenterMapper = null;
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+ }
}
@Override
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 197aad2..27ec54b 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/RowsSupportFragment.java
@@ -53,14 +53,20 @@
*/
public class RowsSupportFragment extends BaseRowSupportFragment implements Adaptable {
- final MainFragmentAdapter mMainFragmentAdapter = new MainFragmentAdapter(this);
- final MainFragmentRowsAdapter mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ private MainFragmentAdapter mMainFragmentAdapter;
+ private MainFragmentRowsAdapter mMainFragmentRowsAdapter;
@Override
public Object getAdapter(Class clazz) {
if (clazz == BrowseSupportFragment.MainFragmentAdapter.class) {
+ if (mMainFragmentAdapter == null) {
+ mMainFragmentAdapter = new MainFragmentAdapter(this);
+ }
return mMainFragmentAdapter;
} else if (clazz == BrowseSupportFragment.MainFragmentRowsAdapter.class) {
+ if (mMainFragmentRowsAdapter == null) {
+ mMainFragmentRowsAdapter = new MainFragmentRowsAdapter(this);
+ }
return mMainFragmentRowsAdapter;
}
return null;
@@ -283,6 +289,9 @@
mRecycledViewPool = null;
mPresenterMapper = null;
+ if (mMainFragmentAdapter != null) {
+ mMainFragmentAdapter.getFragmentHost().notifyViewCreated(mMainFragmentAdapter);
+ }
}
@Override
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..eac61a8
--- /dev/null
+++ b/v17/leanback/src/android/support/v17/leanback/widget/AbstractMediaItemPresenter.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.v17.leanback.widget;
+
+import android.content.Context;
+import android.graphics.Color;
+import android.support.v17.leanback.R;
+import android.view.ContextThemeWrapper;
+import android.util.Log;
+import android.util.TypedValue;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+/**
+ * Abstract {@link Presenter} class for rendering media items in a playlist format.
+ * Media items in the playlist are arranged as a vertical list with each row holding each media's
+ * metadata which is provided by the user of this class.
+ * <p>
+ * Subclasses must override {@link #onBindMediaViewHolder} to implement their media item model
+ * data binding to each row view
+ * </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 {
+
+
+ private int mBackgroundColor = Color.TRANSPARENT;
+ private boolean mBackgroundColorSet;
+ private final Context mContext;
+
+ /**
+ * Constructor used for creating an abstract media item 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 AbstractMediaItemPresenter(Context context, int mThemeResId) {
+ mContext = new ContextThemeWrapper(context.getApplicationContext(), mThemeResId);
+ setHeaderPresenter(null);
+ }
+
+ /**
+ * Constructor used for creating an abstract media item presenter.
+ * The styling for this presenter is extracted from Context of parent in
+ * {@link #createRowViewHolder(ViewGroup)}.
+ */
+ public AbstractMediaItemPresenter() {
+ mContext = null;
+ setHeaderPresenter(null);
+ }
+
+ /**
+ * The ViewHolder for the {@link AbstractMediaItemPresenter}. It references different views
+ * that place different meta-data corresponding to a media item.
+ */
+ public static class ViewHolder extends RowPresenter.ViewHolder {
+
+ private final View mContainerView;
+ private final TextView mTrackNumberView;
+ private final TextView mTrackNameView;
+ private final TextView mTrackDurationView;
+
+ View.OnClickListener mOnClickListener = new View.OnClickListener() {
+ @Override
+ public void onClick(View v) {
+ if (getOnItemViewClickedListener() != null) {
+ getOnItemViewClickedListener().onItemClicked(ViewHolder.this,
+ ViewHolder.this.getRowObject(), ViewHolder.this,
+ ViewHolder.this.getRowObject());
+ }
+ }
+ };
+
+ public ViewHolder(View view) {
+ super(view);
+ mContainerView = view.findViewById(R.id.rowContainer);
+ mContainerView.setOnClickListener(mOnClickListener);
+ mTrackNumberView = (TextView) view.findViewById(R.id.trackNumber);
+ mTrackNameView = (TextView) view.findViewById(R.id.trackName);
+ mTrackDurationView = (TextView) view.findViewById(R.id.trackDuration);
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track number
+ */
+ public TextView getTrackNumberView() {
+ return mTrackNumberView;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track name
+ */
+ public TextView getTrackNameView() {
+ return mTrackNameView;
+ }
+
+ /**
+ * @return The TextView responsible for rendering the track duration
+ */
+ public TextView getTrackDurationView() {
+ return mTrackDurationView;
+ }
+ }
+
+ @Override
+ protected RowPresenter.ViewHolder createRowViewHolder(ViewGroup parent) {
+ Context context = mContext != null ? mContext : parent.getContext();
+ View view = LayoutInflater.from(context).
+ inflate(R.layout.lb_row_media_item, parent, false);
+ ViewHolder vh = new ViewHolder(view);
+ if (mBackgroundColorSet) {
+ vh.mContainerView.setBackgroundColor(mBackgroundColor);
+ }
+ return new ViewHolder(view);
+ }
+
+ @Override
+ public boolean isUsingDefaultSelectEffect() {
+ return false;
+ }
+
+ @Override
+ protected void onBindRowViewHolder(RowPresenter.ViewHolder vh, Object item) {
+ super.onBindRowViewHolder(vh, item);
+ onBindMediaViewHolder((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 media list background color.
+ */
+ public void setBackgroundColor(int color) {
+ mBackgroundColorSet = true;
+ mBackgroundColor = color;
+ }
+
+ /**
+ * Binds the media data model provided by the user to the {@link ViewHolder} provided by the
+ * {@link AbstractMediaItemPresenter}.
+ * The subclasses of this presenter can access and bind individual views for TrackNumber,
+ * TrackName, and TrackDuration, by calling {@link ViewHolder#getTrackNumberView},
+ * {@link ViewHolder#getTrackNameView}, and {@link ViewHolder#getTrackDurationView}, on the
+ * {@link ViewHolder} provided as the argument {@code vh} by this presenter.
+ *
+ * @param vh The ViewHolder for this {@link AbstractMediaItemPresenter}.
+ * @param item The media item data object being presented.
+ */
+ protected abstract void onBindMediaViewHolder(ViewHolder vh, Object item);
+
+}
\ 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..9809fa1
--- /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 tracks 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 tracklist 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.trackListHeader);
+ }
+
+ /**
+ *
+ * @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_row_track_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/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 56f81fe..33c764a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -23,6 +23,7 @@
import android.support.v4.view.ViewCompat;
import android.support.v4.view.accessibility.AccessibilityNodeInfoCompat;
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;
@@ -123,32 +124,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;
}
@@ -433,6 +408,7 @@
* The orientation of a "row".
*/
private int mOrientation = HORIZONTAL;
+ private OrientationHelper mOrientationHelper = OrientationHelper.createHorizontalHelper(this);
private RecyclerView.State mState;
private RecyclerView.Recycler mRecycler;
@@ -670,6 +646,7 @@
}
mOrientation = orientation;
+ mOrientationHelper = OrientationHelper.createOrientationHelper(this, mOrientation);
mWindowAlignment.setOrientation(orientation);
mItemAlignment.setOrientation(orientation);
mForceFullLayout = true;
@@ -1009,22 +986,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) {
@@ -1294,6 +1298,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);
@@ -1623,19 +1648,13 @@
right = startSecondary + sizeSecondary;
}
LayoutParams params = (LayoutParams) v.getLayoutParams();
- // layoutDecorated() doesn't handle margins, so we need exclude margin:
- int decoratedLeftExcludeMargin = left + params.leftMargin;
- int decoratedTopExcludeMargin = top + params.topMargin;
- int decoratedRightExcludeMargin = right - params.rightMargin;
- int decoratedBottomExcludeMargin = bottom - params.bottomMargin;
- layoutDecorated(v, decoratedLeftExcludeMargin, decoratedTopExcludeMargin,
- decoratedRightExcludeMargin, decoratedBottomExcludeMargin);
- // Now v.getLeft() includes the extra space for optical bounds, subtracting it from value
- // passed in layoutDecorated(), we can get the optical bounds insets.
- params.setOpticalInsets(decoratedLeftExcludeMargin - getDecoratedLeft(v),
- decoratedTopExcludeMargin - getDecoratedTop(v),
- getDecoratedRight(v) - decoratedRightExcludeMargin,
- getDecoratedBottom(v) - decoratedBottomExcludeMargin);
+ 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();
}
@@ -1934,6 +1953,9 @@
mInLayout = false;
leaveContext();
+ if (!hadFocus && !mInFastRelayout && mBaseGridView.hasFocusable()) {
+ ViewCompat.postOnAnimation(mBaseGridView, mAskFocusRunnable);
+ }
if (DEBUG) Log.v(getTag(), "layoutChildren end");
}
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 30b3b89..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,6 +13,7 @@
*/
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;
@@ -137,7 +138,7 @@
*/
public BuilderBase(Context context) {
mContext = context;
- mActionFlags = PF_ENABLED | PF_FOCUSABLE;
+ mActionFlags = PF_ENABLED | PF_FOCUSABLE | PF_AUTORESTORE;
}
/**
@@ -526,6 +527,19 @@
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;
+ }
+
}
/**
@@ -567,6 +581,7 @@
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;
@@ -860,4 +875,81 @@
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/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 713f716..2689f46 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -417,6 +417,8 @@
R.id.guidedactions_sub_list);
}
}
+ mActionsGridView.setFocusable(false);
+ mActionsGridView.setFocusableInTouchMode(false);
// Cache widths, chevron alpha values, max and min text lines, etc
Context ctx = mMainView.getContext();
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
index 67bf28a..9b9f29f 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedDatePickerAction.java
@@ -14,6 +14,7 @@
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;
@@ -175,4 +176,14 @@
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/PagingIndicator.java b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
index 2148ddd..94ab8d8 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PagingIndicator.java
@@ -17,8 +17,9 @@
package android.support.v17.leanback.widget;
import android.animation.Animator;
-import android.animation.AnimatorInflater;
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;
@@ -33,16 +34,59 @@
import android.support.annotation.VisibleForTesting;
import android.support.v17.leanback.R;
import android.util.AttributeSet;
+import android.util.Property;
import android.view.View;
-
-import java.util.ArrayList;
-import java.util.List;
+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;
@@ -71,8 +115,8 @@
private final int mDotFgSelectColor;
private final Paint mBgPaint;
private final Paint mFgPaint;
- private final Animator mShowAnimator;
- private final Animator mHideAnimator;
+ private final AnimatorSet mShowAnimator;
+ private final AnimatorSet mHideAnimator;
private final AnimatorSet mAnimator = new AnimatorSet();
private Bitmap mArrow;
private final Rect mArrowRect;
@@ -91,23 +135,26 @@
Resources res = getResources();
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.PagingIndicator,
defStyle, 0);
- int bgColor = res.getColor(R.color.lb_page_indicator_dot);
- try {
- bgColor = typedArray.getColor(R.styleable.PagingIndicator_dotBgColor, bgColor);
- } finally {
- typedArray.recycle();
- }
- mIsLtr = res.getConfiguration().getLayoutDirection() == View.LAYOUT_DIRECTION_LTR;
- mDotRadius = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_radius);
+ mDotRadius = getDimensionFromTypedArray(typedArray, R.styleable.PagingIndicator_dotRadius,
+ R.dimen.lb_page_indicator_dot_radius);
mDotDiameter = mDotRadius * 2;
- mDotGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_dot_gap);
- mArrowGap = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_gap);
- mArrowDiameter = res.getDimensionPixelSize(R.dimen.lb_page_indicator_arrow_diameter);
- mArrowRadius = mArrowDiameter / 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);
- mDotFgSelectColor = res.getColor(R.color.lb_page_indicator_arrow_background);
- int shadowColor = res.getColor(R.color.lb_page_indicator_arrow_shadow);
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);
@@ -116,27 +163,26 @@
mArrowRect = new Rect(0, 0, mArrow.getWidth(), mArrow.getHeight());
mArrowToBgRatio = (float) mArrow.getWidth() / (float) mArrowDiameter;
// Initialize animations.
- List<Animator> animators = new ArrayList<>();
- mShowAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.lb_page_indicator_dot_show);
- mHideAnimator = AnimatorInflater.loadAnimator(context,
- R.animator.lb_page_indicator_dot_hide);
- animators.add(mShowAnimator);
- animators.add(mHideAnimator);
- mAnimator.playTogether(animators);
+ 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);
+ }
- // To guard the methods from the proguard, the methods which is called only by the
- // reflection should be called explicitly just once.
- // Without this calls, the animation will not work.
- Dot dot = new Dot();
- dot.setTranslationX(0.0f);
- dot.setAlpha(0.0f);
- dot.setDiameter(0.0f);
- dot.getTranslationX();
- dot.getAlpha();
- dot.getDiameter();
+ 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() {
@@ -151,6 +197,29 @@
}
}
+ 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.
*/
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 1b57fc5..d53e38c 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/RowPresenter.java
@@ -586,6 +586,9 @@
/**
* 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.mRowObject = item;
@@ -602,6 +605,8 @@
/**
* 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) {
diff --git a/v17/preference-leanback/Android.mk b/v17/preference-leanback/Android.mk
index c14c4a6..bd36e32 100644
--- a/v17/preference-leanback/Android.mk
+++ b/v17/preference-leanback/Android.mk
@@ -14,25 +14,27 @@
LOCAL_PATH := $(call my-dir)
+# 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 := $(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/tests/src/android/support/v17/leanback/widget/GridActivity.java b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
index b1f9d52..27c5ad7 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridActivity.java
@@ -57,6 +57,7 @@
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
@@ -102,6 +103,7 @@
int[] mItemLengths;
boolean[] mItemFocusables;
int[] mLayoutMargins;
+ int mNinePatchShadow;
private int mBoundCount;
@@ -119,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;
}
@@ -142,6 +147,7 @@
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)
@@ -385,6 +391,13 @@
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);
}
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 8302cfd..91a114f 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -484,6 +484,76 @@
}
+ 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();
diff --git a/v4/Android.mk b/v4/Android.mk
index dd24664..1e8adad 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -264,12 +264,15 @@
# 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_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/api/current.txt b/v4/api/current.txt
index 2cf8a11..08c19ee 100644
--- a/v4/api/current.txt
+++ b/v4/api/current.txt
@@ -89,6 +89,12 @@
method public void update(android.support.v4.app.ActivityOptionsCompat);
}
+ 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);
@@ -98,6 +104,9 @@
field public static final int MODE_IGNORED = 1; // 0x1
}
+ abstract class BaseFragmentActivityApi24 extends android.support.v4.app.BaseFragmentActivityHoneycomb {
+ }
+
abstract class BaseFragmentActivityDonut extends android.app.Activity {
}
@@ -191,9 +200,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();
@@ -239,7 +250,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.support.v4.app.BaseFragmentActivityApi24 implements android.support.v4.app.ActivityCompat.OnRequestPermissionsResultCallback {
ctor public FragmentActivity();
method public java.lang.Object getLastCustomNonConfigurationInstance();
method public android.support.v4.app.FragmentManager getSupportFragmentManager();
@@ -278,9 +289,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();
@@ -574,11 +587,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);
}
@@ -721,6 +736,7 @@
method public android.app.PendingIntent getDisplayIntent();
method public int getGravity();
method public boolean getHintAvoidBackgroundClipping();
+ method public boolean getHintContentIntentLaunchesActivity();
method public boolean getHintHideIcon();
method public int getHintScreenTimeout();
method public boolean getHintShowBackgroundOnly();
@@ -736,6 +752,7 @@
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 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);
@@ -769,6 +786,7 @@
}
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();
@@ -780,6 +798,16 @@
field public static final java.lang.String EXTRA_USE_SIDE_CHANNEL = "android.support.useSideChannel";
}
+ public class NotificationManagerCompatApi24 {
+ ctor public NotificationManagerCompatApi24();
+ method public static boolean areNotificationsEnabled(android.app.NotificationManager);
+ }
+
+ public class NotificationManagerCompatKitKat {
+ ctor public NotificationManagerCompatKitKat();
+ method public static boolean areNotificationsEnabled(android.content.Context);
+ }
+
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();
@@ -977,6 +1005,7 @@
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";
@@ -2509,6 +2538,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);
@@ -3252,7 +3282,9 @@
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);
@@ -3266,7 +3298,9 @@
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 deprecated void setDrawerListener(android.support.v4.widget.DrawerLayout.DrawerListener);
diff --git a/v4/api21/android/support/v4/view/ViewCompatLollipop.java b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
index fddbccb..167ae17 100644
--- a/v4/api21/android/support/v4/view/ViewCompatLollipop.java
+++ b/v4/api21/android/support/v4/view/ViewCompatLollipop.java
@@ -18,13 +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);
}
@@ -193,4 +197,67 @@
public static float getZ(View view) {
return view.getZ();
}
+
+ 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/api23/android/support/v4/print/PrintHelperApi23.java b/v4/api23/android/support/v4/print/PrintHelperApi23.java
index 5129cd3..ba646e3 100644
--- a/v4/api23/android/support/v4/print/PrintHelperApi23.java
+++ b/v4/api23/android/support/v4/print/PrintHelperApi23.java
@@ -25,7 +25,13 @@
class PrintHelperApi23 extends PrintHelperApi20 {
@Override
protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
- return super.copyAttributes(other).setDuplexMode(other.getDuplexMode());
+ PrintAttributes.Builder b = super.copyAttributes(other);
+
+ if (other.getDuplexMode() != 0) {
+ b.setDuplexMode(other.getDuplexMode());
+ }
+
+ return b;
}
PrintHelperApi23(Context context) {
diff --git a/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.java b/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.java
new file mode 100644
index 0000000..7756648
--- /dev/null
+++ b/v4/api24/android/support/v4/app/BaseFragmentActivityApi24.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.v4.app;
+
+import android.support.annotation.CallSuper;
+import android.support.v4.os.BuildCompat;
+
+/**
+ * Base class for {@code FragmentActivity} to be able to use v24 APIs.
+ */
+abstract class BaseFragmentActivityApi24 extends BaseFragmentActivityHoneycomb {
+
+ @Override
+ @CallSuper
+ public void onMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ if (BuildCompat.isAtLeastN()) {
+ super.onMultiWindowModeChanged(isInMultiWindowMode);
+ }
+ dispatchFragmentsOnMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ @Override
+ @CallSuper
+ public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ if (BuildCompat.isAtLeastN()) {
+ super.onPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+ dispatchFragmentsOnPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ abstract void dispatchFragmentsOnMultiWindowModeChanged(boolean isInMultiWindowMode);
+
+ abstract void dispatchFragmentsOnPictureInPictureModeChanged(boolean isInPictureInPictureMode);
+
+}
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..bbd3bde
--- /dev/null
+++ b/v4/api24/android/support/v4/app/NotificationManagerCompatApi24.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.app;
+
+import android.app.NotificationManager;
+import android.content.Context;
+
+public class NotificationManagerCompatApi24 {
+ public static boolean areNotificationsEnabled(NotificationManager notificationManager) {
+ return notificationManager.areNotificationsEnabled();
+ }
+}
diff --git a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
index 98ea53f..60bb2c4 100644
--- a/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
+++ b/v4/donut/android/support/v4/graphics/drawable/DrawableWrapperDonut.java
@@ -57,16 +57,7 @@
* @param dr the drawable to wrap
*/
DrawableWrapperDonut(@Nullable Drawable dr) {
- // The following is workaround for issues for certain DrawableContainers on some API levels.
- // They expect getConstantState() to always return non-null, which will only happen after
- // we have been mutated. Since most Drawables provided to us will be from Resources,
- // they will nearly always have been mutated, so we should act as if we have been too.
- // This means that we should copy our input's CS to our own state. This satisfies the
- // canConstantState() check below. If the input does not provide a CS, then there's nothing
- // we can do anyway.
- if (dr != null && dr.getConstantState() != null) {
- mState = mutateConstantState();
- }
+ mState = mutateConstantState();
// Now set the drawable...
setWrappedDrawable(dr);
}
@@ -137,7 +128,9 @@
@Override
public boolean isStateful() {
- final ColorStateList tintList = isCompatTintEnabled() ? mState.mTint : null;
+ final ColorStateList tintList = (isCompatTintEnabled() && mState != null)
+ ? mState.mTint
+ : null;
return (tintList != null && tintList.isStateful()) || mDrawable.isStateful();
}
diff --git a/v4/java/android/support/v4/os/BuildCompat.java b/v4/donut/android/support/v4/os/BuildCompat.java
similarity index 100%
rename from v4/java/android/support/v4/os/BuildCompat.java
rename to v4/donut/android/support/v4/os/BuildCompat.java
diff --git a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
index 4804fb6..5067cf7 100644
--- a/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
+++ b/v4/honeycomb/android/support/v4/view/ViewCompatHC.java
@@ -17,6 +17,7 @@
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;
@@ -98,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);
}
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/BackStackRecord.java b/v4/java/android/support/v4/app/BackStackRecord.java
index bec151c..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++;
}
diff --git a/v4/java/android/support/v4/app/Fragment.java b/v4/java/android/support/v4/app/Fragment.java
index 30f92a0..4918dfb 100644
--- a/v4/java/android/support/v4/app/Fragment.java
+++ b/v4/java/android/support/v4/app/Fragment.java
@@ -876,11 +876,12 @@
* false if it is not.
*/
public void setUserVisibleHint(boolean isVisibleToUser) {
- if (!mUserVisibleHint && isVisibleToUser && mState < STARTED && mFragmentManager != null) {
+ if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
+ && mFragmentManager != null && isAdded()) {
mFragmentManager.performPendingDeferredStart(this);
}
mUserVisibleHint = isVisibleToUser;
- mDeferStart = !isVisibleToUser;
+ mDeferStart = mState < STARTED && !isVisibleToUser;
}
/**
@@ -1038,7 +1039,7 @@
if (mHost == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
- mHost.onRequestPermissionsFromFragment(this, permissions,requestCode);
+ mHost.onRequestPermissionsFromFragment(this, permissions, requestCode);
}
/**
@@ -1374,11 +1375,30 @@
*/
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) {
+ }
+
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
@@ -2070,6 +2090,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) {
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index fd35a7a..ea61fff 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -42,8 +42,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
@@ -77,7 +75,7 @@
* 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 BaseFragmentActivityApi24 implements
ActivityCompat.OnRequestPermissionsResultCallback,
ActivityCompatApi23.RequestPermissionsRequestCodeValidator {
private static final String TAG = "FragmentActivity";
@@ -279,6 +277,26 @@
}
/**
+ * Dispatch multi-window mode change to all fragments.
+ *
+ * @see Activity#onMultiWindowModeChanged(boolean)
+ */
+ @Override
+ void dispatchFragmentsOnMultiWindowModeChanged(boolean isInMultiWindowMode) {
+ mFragments.dispatchMultiWindowModeChanged(isInMultiWindowMode);
+ }
+
+ /**
+ * Dispatch picture-in-picture mode change to all fragments.
+ *
+ * @see Activity#onPictureInPictureModeChanged(boolean)
+ */
+ @Override
+ void dispatchFragmentsOnPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
+ mFragments.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
+ }
+
+ /**
* Dispatch configuration change to all fragments.
*/
@Override
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 60beed9..d413a42 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -270,6 +270,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/FragmentManager.java b/v4/java/android/support/v4/app/FragmentManager.java
index ca7e484..11cf28d 100644
--- a/v4/java/android/support/v4/app/FragmentManager.java
+++ b/v4/java/android/support/v4/app/FragmentManager.java
@@ -2172,7 +2172,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/LoaderManager.java b/v4/java/android/support/v4/app/LoaderManager.java
index fad56e0..9ec2cd5 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);
}
}
diff --git a/v4/java/android/support/v4/app/NotificationCompat.java b/v4/java/android/support/v4/app/NotificationCompat.java
index e6cbd79..9a9bdb6 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;
@@ -2001,6 +2002,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;
@@ -2163,6 +2165,30 @@
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 */
@@ -2326,6 +2352,7 @@
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_HINT_CONTENT_INTENT_LAUNCHES_ACTIVITY = 1 << 6;
// Default value for flags integer
private static final int DEFAULT_FLAGS = FLAG_CONTENT_INTENT_AVAILABLE_OFFLINE;
@@ -2897,6 +2924,29 @@
return mHintScreenTimeout;
}
+ /**
+ * 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;
+ }
+
private void setFlag(int mask, boolean value) {
if (value) {
mFlags |= mask;
diff --git a/v4/java/android/support/v4/app/NotificationManagerCompat.java b/v4/java/android/support/v4/app/NotificationManagerCompat.java
index e2b6fc9..b87973f 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;
@@ -118,6 +119,8 @@
Notification notification);
int getSideChannelBindFlags();
+
+ boolean areNotificationsEnabled(Context context, NotificationManager notificationManager);
}
static class ImplBase implements Impl {
@@ -137,6 +140,12 @@
public int getSideChannelBindFlags() {
return Service.BIND_AUTO_CREATE;
}
+
+ @Override
+ public boolean areNotificationsEnabled(Context context,
+ NotificationManager notificationManager) {
+ return true;
+ }
}
static class ImplEclair extends ImplBase {
@@ -161,8 +170,28 @@
}
}
+ 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);
+ }
+ }
+
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,6 +256,13 @@
}
/**
+ * Returns whether notifications from the calling package are not blocked.
+ */
+ public boolean areNotificationsEnabled() {
+ return IMPL.areNotificationsEnabled(mContext, mNotificationManager);
+ }
+
+ /**
* Get the set of packages that have an enabled notification listener component within them.
*/
public static Set<String> getEnabledListenerPackages(Context context) {
diff --git a/v4/java/android/support/v4/content/ContextCompat.java b/v4/java/android/support/v4/content/ContextCompat.java
index efb1e38..ddafb00 100644
--- a/v4/java/android/support/v4/content/ContextCompat.java
+++ b/v4/java/android/support/v4/content/ContextCompat.java
@@ -20,6 +20,7 @@
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;
@@ -29,6 +30,7 @@
import android.support.v4.os.BuildCompat;
import android.support.v4.os.EnvironmentCompat;
import android.util.Log;
+import android.util.TypedValue;
import java.io.File;
@@ -45,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.
*
@@ -330,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);
}
}
diff --git a/v4/java/android/support/v4/content/IntentCompat.java b/v4/java/android/support/v4/content/IntentCompat.java
index 97489cd..eaf7b1f 100644
--- a/v4/java/android/support/v4/content/IntentCompat.java
+++ b/v4/java/android/support/v4/content/IntentCompat.java
@@ -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/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index 02e9a6d..23ab4b7 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -103,7 +103,7 @@
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
} else {
- mImpl = new MediaBrowserServiceImplBase(context, serviceComponent, callback, rootHints);
+ mImpl = new MediaBrowserImplBase(context, serviceComponent, callback, rootHints);
}
}
@@ -723,7 +723,7 @@
void onLoadChildren(Messenger callback, String parentId, List list, Bundle options);
}
- static class MediaBrowserServiceImplBase
+ static class MediaBrowserImplBase
implements MediaBrowserImpl, MediaBrowserServiceCallbackImpl {
private static final boolean DBG = false;
@@ -747,7 +747,7 @@
private MediaSessionCompat.Token mMediaSessionToken;
private Bundle mExtras;
- public MediaBrowserServiceImplBase(Context context, ComponentName serviceComponent,
+ public MediaBrowserImplBase(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
if (context == null) {
throw new IllegalArgumentException("context must not be null");
@@ -863,6 +863,7 @@
mServiceConnection = null;
mServiceBinderWrapper = null;
mCallbacksMessenger = null;
+ mHandler.setCallbacksMessenger(null);
mRootId = null;
mMediaSessionToken = null;
}
@@ -1273,6 +1274,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);
}
@@ -1431,6 +1439,7 @@
public void onConnectionSuspended() {
mServiceBinderWrapper = null;
mCallbacksMessenger = null;
+ mHandler.setCallbacksMessenger(null);
}
@Override
@@ -1683,6 +1692,10 @@
sendRequest(CLIENT_MSG_REGISTER_CALLBACK_MESSENGER, null, 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();
diff --git a/v4/java/android/support/v4/media/MediaBrowserProtocol.java b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
index aea3ddd..b0fee56 100644
--- a/v4/java/android/support/v4/media/MediaBrowserProtocol.java
+++ b/v4/java/android/support/v4/media/MediaBrowserProtocol.java
@@ -143,4 +143,11 @@
* - 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 8122980..f24639a 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -196,6 +196,9 @@
case CLIENT_MSG_REGISTER_CALLBACK_MESSENGER:
mServiceImpl.registerCallbacks(new ServiceCallbacksCompat(msg.replyTo));
break;
+ case CLIENT_MSG_UNREGISTER_CALLBACK_MESSENGER:
+ mServiceImpl.unregisterCallbacks(new ServiceCallbacksCompat(msg.replyTo));
+ break;
default:
Log.w(TAG, "Unhandled message: " + msg
+ "\n Service version: " + SERVICE_VERSION_CURRENT
@@ -440,6 +443,17 @@
}
});
}
+
+ // 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);
+ }
+ });
+ }
}
private class ServiceImplApi21 implements MediaBrowserServiceCompatApi21.ServiceImplApi21 {
diff --git a/v4/java/android/support/v4/view/ViewCompat.java b/v4/java/android/support/v4/view/ViewCompat.java
index 8ef49a9..27dd97f 100644
--- a/v4/java/android/support/v4/view/ViewCompat.java
+++ b/v4/java/android/support/v4/view/ViewCompat.java
@@ -17,6 +17,7 @@
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;
@@ -410,6 +411,7 @@
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);
@@ -739,6 +741,11 @@
}
@Override
+ public Matrix getMatrix(View view) {
+ return null;
+ }
+
+ @Override
public int getMinimumWidth(View view) {
return ViewCompatBase.getMinimumWidth(view);
}
@@ -1180,6 +1187,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);
@@ -1686,6 +1699,16 @@
public float getZ(View view) {
return ViewCompatLollipop.getZ(view);
}
+
+ @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 {
@@ -2507,6 +2530,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>
diff --git a/v4/java/android/support/v4/widget/DrawerLayout.java b/v4/java/android/support/v4/widget/DrawerLayout.java
index e53cbdf..e0b714e 100644
--- a/v4/java/android/support/v4/widget/DrawerLayout.java
+++ b/v4/java/android/support/v4/widget/DrawerLayout.java
@@ -1583,6 +1583,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");
}
@@ -1593,7 +1603,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)) {
@@ -1602,6 +1612,10 @@
mRightDragger.smoothSlideViewTo(drawerView, getWidth() - drawerView.getWidth(),
drawerView.getTop());
}
+ } else {
+ moveDrawerToOffset(drawerView, 1.f);
+ updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+ drawerView.setVisibility(VISIBLE);
}
invalidate();
}
@@ -1613,12 +1627,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);
}
/**
@@ -1627,6 +1652,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");
}
@@ -1635,7 +1670,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)) {
@@ -1644,6 +1679,10 @@
} else {
mRightDragger.smoothSlideViewTo(drawerView, getWidth(), drawerView.getTop());
}
+ } else {
+ moveDrawerToOffset(drawerView, 0.f);
+ updateDrawerState(lp.gravity, STATE_IDLE, drawerView);
+ drawerView.setVisibility(INVISIBLE);
}
invalidate();
}
@@ -1655,12 +1694,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);
}
/**
diff --git a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
index 9666740..0d8f106 100644
--- a/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
+++ b/v4/java/android/support/v4/widget/ExploreByTouchHelper.java
@@ -98,16 +98,16 @@
/** Default class name used for virtual views. */
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();
private final Rect mTempParentRect = new Rect();
private final Rect mTempVisibleRect = new Rect();
private final int[] mTempGlobalRect = new int[2];
- /** Cache of accessibility nodes. This is populated on-demand. */
- private final SparseArrayCompat<AccessibilityNodeInfoCompat> mCachedNodes =
- new SparseArrayCompat<>();
-
/** System accessibility manager, used to check state and send events. */
private final AccessibilityManager mManager;
@@ -370,9 +370,11 @@
* 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 : mCachedNodes.get(focusedNodeId);
+ focusedNodeId == INVALID_ID ? null : allNodes.get(focusedNodeId);
final AccessibilityNodeInfoCompat nextFocusedNode;
switch (direction) {
@@ -380,7 +382,7 @@
case View.FOCUS_BACKWARD:
final boolean isLayoutRtl =
ViewCompat.getLayoutDirection(mHost) == ViewCompat.LAYOUT_DIRECTION_RTL;
- nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(mCachedNodes,
+ nextFocusedNode = FocusStrategy.findNextFocusInRelativeDirection(allNodes,
SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, direction, isLayoutRtl,
false);
break;
@@ -402,7 +404,7 @@
// when moving UP or DOWN).
guessPreviouslyFocusedRect(mHost, direction, selectedRect);
}
- nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(mCachedNodes,
+ nextFocusedNode = FocusStrategy.findNextFocusInAbsoluteDirection(allNodes,
SPARSE_VALUES_ADAPTER, NODE_ADAPTER, focusedNode, selectedRect, direction);
break;
default:
@@ -415,13 +417,26 @@
if (nextFocusedNode == null) {
nextFocusedNodeId = INVALID_ID;
} else {
- final int index = mCachedNodes.indexOfValue(nextFocusedNode);
- nextFocusedNodeId = mCachedNodes.keyAt(index);
+ 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.
@@ -549,21 +564,6 @@
* </ul>
*/
public final void invalidateVirtualView(int virtualViewId, int changeTypes) {
- final SparseArrayCompat<AccessibilityNodeInfoCompat> cachedNodes = mCachedNodes;
- if (virtualViewId == HOST_ID
- && (changeTypes & AccessibilityEventCompat.CONTENT_CHANGE_TYPE_SUBTREE) != 0) {
- for (int i = 0, count = cachedNodes.size(); i < count; i++) {
- cachedNodes.valueAt(i).recycle();
- }
- cachedNodes.clear();
- } else {
- final int index = cachedNodes.indexOfKey(virtualViewId);
- if (index >= 0) {
- cachedNodes.valueAt(index).recycle();
- cachedNodes.removeAt(index);
- }
- }
-
if (virtualViewId != INVALID_ID && mManager.isEnabled()) {
final ViewParent parent = mHost.getParent();
if (parent != null) {
@@ -718,19 +718,11 @@
*/
@NonNull
private AccessibilityNodeInfoCompat obtainAccessibilityNodeInfo(int virtualViewId) {
- final AccessibilityNodeInfoCompat node;
- final int cacheIndex = mCachedNodes.indexOfKey(virtualViewId);
- if (cacheIndex >= 0) {
- node = mCachedNodes.valueAt(cacheIndex);
- } else if (virtualViewId == HOST_ID) {
- node = createNodeForHost();
- } else {
- node = createNodeForChild(virtualViewId);
+ if (virtualViewId == HOST_ID) {
+ return createNodeForHost();
}
- mCachedNodes.put(virtualViewId, node);
-
- return node;
+ return createNodeForChild(virtualViewId);
}
/**
@@ -804,6 +796,7 @@
node.setEnabled(true);
node.setFocusable(true);
node.setClassName(DEFAULT_CLASS_NAME);
+ node.setBoundsInParent(INVALID_PARENT_BOUNDS);
// Allow the client to populate the node.
onPopulateNodeForVirtualView(virtualViewId, node);
@@ -815,7 +808,7 @@
}
node.getBoundsInParent(mTempParentRect);
- if (mTempParentRect.isEmpty()) {
+ if (mTempParentRect.equals(INVALID_PARENT_BOUNDS)) {
throw new RuntimeException("Callbacks must set parent bounds in "
+ "populateNodeForVirtualViewId()");
}
diff --git a/v4/java/android/support/v4/widget/NestedScrollView.java b/v4/java/android/support/v4/widget/NestedScrollView.java
index 32626c4..044c5d3 100644
--- a/v4/java/android/support/v4/widget/NestedScrollView.java
+++ b/v4/java/android/support/v4/widget/NestedScrollView.java
@@ -1647,6 +1647,8 @@
@Override
public void onAttachedToWindow() {
+ super.onAttachedToWindow();
+
mIsLaidOut = false;
}
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..be97634
--- /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;
+
+public 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/print/PrintHelperKitkat.java b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
index 93eb3f4..3a6273e 100644
--- a/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
+++ b/v4/kitkat/android/support/v4/print/PrintHelperKitkat.java
@@ -204,11 +204,16 @@
* @return A builder that will build print attributes that match the other attributes
*/
protected PrintAttributes.Builder copyAttributes(PrintAttributes other) {
- return (new PrintAttributes.Builder())
+ PrintAttributes.Builder b = (new PrintAttributes.Builder())
.setMediaSize(other.getMediaSize())
.setResolution(other.getResolution())
- .setColorMode(other.getColorMode())
.setMinMargins(other.getMinMargins());
+
+ if (other.getColorMode() != 0) {
+ b.setColorMode(other.getColorMode());
+ }
+
+ return b;
}
/**
diff --git a/v4/tests/AndroidManifest.xml b/v4/tests/AndroidManifest.xml
index 89b2335..ca71f7a 100644
--- a/v4/tests/AndroidManifest.xml
+++ b/v4/tests/AndroidManifest.xml
@@ -28,7 +28,9 @@
<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
- <application android:supportsRtl="true">
+ <application
+ android:supportsRtl="true"
+ android:theme="@style/TestActivityTheme">
<uses-library android:name="android.test.runner" />
<activity android:name="android.support.v4.widget.TextViewTestActivity"/>
diff --git a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
index 6f8a525..0e717d8 100644
--- a/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
+++ b/v4/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -19,7 +19,6 @@
import android.content.Intent;
import android.os.Bundle;
-import android.os.Debug;
import android.os.Parcelable;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
@@ -27,10 +26,14 @@
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 org.junit.Assert;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -38,8 +41,13 @@
import java.io.FileDescriptor;
import java.io.PrintWriter;
-import static junit.framework.Assert.*;
-import static junit.framework.TestCase.*;
+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)
@@ -366,6 +374,91 @@
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);
+ }
+
+ 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
@@ -495,4 +588,35 @@
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/content/ContextCompatTest.java b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
index cccf892..56e89a5 100644
--- a/v4/tests/java/android/support/v4/content/ContextCompatTest.java
+++ b/v4/tests/java/android/support/v4/content/ContextCompatTest.java
@@ -15,6 +15,9 @@
*/
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;
@@ -25,8 +28,7 @@
import android.support.v4.test.R;
import android.support.v4.testutils.TestUtils;
import android.test.suitebuilder.annotation.SmallTest;
-import org.junit.Before;
-import org.junit.Test;
+import android.util.DisplayMetrics;
import static org.junit.Assert.assertEquals;
@@ -103,6 +105,29 @@
}
}
+ @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);
diff --git a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
index a2ac8bd..84be6ec 100644
--- a/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
+++ b/v4/tests/java/android/support/v4/view/ViewPropertyAnimatorCompatTest.java
@@ -34,6 +34,8 @@
@MediumTest
public class ViewPropertyAnimatorCompatTest extends BaseInstrumentationTestCase<VpaActivity> {
+ private static final int WAIT_TIMEOUT_MS = 200;
+
private View mView;
private int mNumListenerCalls = 0;
@@ -80,9 +82,7 @@
}
});
assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
- // Now sleep to allow second listener callback to happen, if it will
- Thread.sleep(200);
- assertEquals(1, mNumListenerCalls);
+ waitAndCheckCallCount(1);
}
@Test
@@ -118,8 +118,15 @@
}
});
assertTrue(latch2.await(200, TimeUnit.MILLISECONDS));
- // Now sleep to allow second listener callback to happen, if it will
- Thread.sleep(200);
- assertEquals(1, mNumListenerCalls);
+ 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/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/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/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/styles.xml b/v4/tests/res/values/styles.xml
index c0855d8..447d5ec 100644
--- a/v4/tests/res/values/styles.xml
+++ b/v4/tests/res/values/styles.xml
@@ -14,6 +14,9 @@
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>
diff --git a/v7/appcompat/Android.mk b/v7/appcompat/Android.mk
index 6816f54..6d11fcb 100644
--- a/v7/appcompat/Android.mk
+++ b/v7/appcompat/Android.mk
@@ -15,19 +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 := $(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-vectordrawable \
- android-support-animatedvectordrawable
+LOCAL_STATIC_JAVA_LIBRARIES := \
+ android-support-vectordrawable \
+ android-support-animatedvectordrawable
LOCAL_JAVA_LIBRARIES := android-support-v4
-LOCAL_AAPT_FLAGS += --auto-add-overlay \
- --no-version-vectors
+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/res/drawable/abc_btn_check_material.xml b/v7/appcompat/res/drawable/abc_btn_check_material.xml
index f8e90a9..0ebbf00 100644
--- a/v7/appcompat/res/drawable/abc_btn_check_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_check_material.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:drawable="@drawable/abc_btn_checkbox_checked_mtrl" />
- <item android:drawable="@drawable/abc_btn_checkbox_unchecked_mtrl" />
-</selector>
\ No newline at end of file
+<!-- This is a dummy drawable so that we can refer to the drawable ID -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white"/>
+</shape>
diff --git a/v7/appcompat/res/drawable/abc_btn_radio_material.xml b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
index ac4eead..f20add7 100644
--- a/v7/appcompat/res/drawable/abc_btn_radio_material.xml
+++ b/v7/appcompat/res/drawable/abc_btn_radio_material.xml
@@ -14,7 +14,7 @@
limitations under the License.
-->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
- <item android:state_checked="true" android:drawable="@drawable/abc_btn_radio_on_mtrl" />
- <item android:drawable="@drawable/abc_btn_radio_off_mtrl" />
-</selector>
\ No newline at end of file
+<!-- This is a dummy drawable so that we can refer to the drawable ID -->
+<shape xmlns:android="http://schemas.android.com/apk/res/android">
+ <solid android:color="@android:color/white"/>
+</shape>
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
index 4529093..893e1b5 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatActivity.java
@@ -18,7 +18,6 @@
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;
@@ -33,9 +32,7 @@
import android.support.v4.app.TaskStackBuilder;
import android.support.v4.view.KeyEventCompat;
import android.support.v7.view.ActionMode;
-import android.support.v7.widget.TintResources;
import android.support.v7.widget.Toolbar;
-import android.util.DisplayMetrics;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuInflater;
@@ -64,7 +61,6 @@
private AppCompatDelegate mDelegate;
private int mThemeId = 0;
private boolean mEatKeyUpEvent;
- private Resources mResources;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@@ -159,12 +155,6 @@
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
@@ -532,12 +522,4 @@
}
return super.dispatchKeyEvent(event);
}
-
- @Override
- public Resources getResources() {
- if (mResources == null) {
- mResources = new TintResources(this, super.getResources());
- }
- return mResources;
- }
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
index 43e1f7b..52368c9 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatDelegateImplV7.java
@@ -300,7 +300,6 @@
if (mActionBar != null) {
mActionBar.onDestroy();
- mActionBar = null;
}
}
@@ -971,8 +970,7 @@
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 */
- isPre21 /* Only tint wrap the context pre-L */
+ true /* Read read app:theme as a fallback at all times for legacy reasons */
);
}
diff --git a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
index dcf855c..7477813 100644
--- a/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
+++ b/v7/appcompat/src/android/support/v7/app/AppCompatViewInflater.java
@@ -39,7 +39,6 @@
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;
@@ -79,7 +78,7 @@
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
- boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
+ boolean readAndroidTheme, boolean readAppTheme) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
@@ -91,9 +90,6 @@
// 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;
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/widget/AppCompatDrawableManager.java b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
index 4a2345e..2fa93fb 100644
--- a/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
+++ b/v7/appcompat/src/android/support/v7/widget/AppCompatDrawableManager.java
@@ -28,6 +28,7 @@
import android.graphics.drawable.Drawable;
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;
@@ -162,9 +163,9 @@
private ArrayMap<String, InflateDelegate> mDelegates;
private SparseArray<String> mKnownDrawableIdTags;
- private final Object mDelegateDrawableCacheLock = new Object();
+ private final Object mDrawableCacheLock = new Object();
private final WeakHashMap<Context, LongSparseArray<WeakReference<Drawable.ConstantState>>>
- mDelegateDrawableCaches = new WeakHashMap<>(0);
+ mDrawableCaches = new WeakHashMap<>(0);
private TypedValue mTypedValue;
@@ -180,8 +181,12 @@
Drawable drawable = loadDrawableFromDelegates(context, resId);
if (drawable == null) {
+ drawable = createDrawableIfNeeded(context, resId);
+ }
+ if (drawable == null) {
drawable = ContextCompat.getDrawable(context, resId);
}
+
if (drawable != null) {
// Tint it if needed
drawable = tintDrawable(context, resId, failIfNotKnown, drawable);
@@ -193,6 +198,56 @@
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)
+ });
+ } else if (resId == R.drawable.abc_btn_check_material) {
+ final StateListDrawable sld = new StateListDrawable();
+ sld.addState(ThemeUtils.CHECKED_STATE_SET,
+ getDrawable(context, R.drawable.abc_btn_checkbox_checked_mtrl));
+ sld.addState(ThemeUtils.EMPTY_STATE_SET,
+ getDrawable(context, R.drawable.abc_btn_checkbox_unchecked_mtrl));
+ dr = sld;
+ } else if (resId == R.drawable.abc_btn_radio_material) {
+ final StateListDrawable sld = new StateListDrawable();
+ sld.addState(ThemeUtils.CHECKED_STATE_SET,
+ getDrawable(context, R.drawable.abc_btn_radio_on_mtrl));
+ sld.addState(ThemeUtils.EMPTY_STATE_SET,
+ getDrawable(context, R.drawable.abc_btn_radio_off_mtrl));
+ dr = sld;
+ }
+
+ 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);
@@ -209,11 +264,6 @@
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),
@@ -266,14 +316,13 @@
if (mTypedValue == null) {
mTypedValue = new TypedValue();
}
-
final TypedValue tv = mTypedValue;
final Resources res = context.getResources();
res.getValue(resId, tv, true);
- final long key = (((long) tv.assetCookie) << 32) | tv.data;
+ final long key = createCacheKey(tv);
- Drawable dr = getCachedDelegateDrawable(context, key);
+ Drawable dr = getCachedDrawable(context, key);
if (dr != null) {
if (DEBUG) {
Log.i(TAG, "[loadDrawableFromDelegates] Returning cached drawable: " +
@@ -310,7 +359,7 @@
if (dr != null) {
// Add it to the drawable cache
dr.setChangingConfigurations(tv.changingConfigurations);
- if (addCachedDelegateDrawable(context, key, dr) && DEBUG) {
+ if (addDrawableToCache(context, key, dr) && DEBUG) {
Log.i(TAG, "[loadDrawableFromDelegates] Saved drawable to cache: " +
context.getResources().getResourceName(resId));
}
@@ -330,10 +379,10 @@
return null;
}
- private Drawable getCachedDelegateDrawable(@NonNull final Context context, final long key) {
- synchronized (mDelegateDrawableCacheLock) {
+ private Drawable getCachedDrawable(@NonNull final Context context, final long key) {
+ synchronized (mDrawableCacheLock) {
final LongSparseArray<WeakReference<ConstantState>> cache
- = mDelegateDrawableCaches.get(context);
+ = mDrawableCaches.get(context);
if (cache == null) {
return null;
}
@@ -353,16 +402,15 @@
return null;
}
- private boolean addCachedDelegateDrawable(@NonNull final Context context, final long key,
+ private boolean addDrawableToCache(@NonNull final Context context, final long key,
@NonNull final Drawable drawable) {
final ConstantState cs = drawable.getConstantState();
if (cs != null) {
- synchronized (mDelegateDrawableCacheLock) {
- LongSparseArray<WeakReference<ConstantState>> cache
- = mDelegateDrawableCaches.get(context);
+ synchronized (mDrawableCacheLock) {
+ LongSparseArray<WeakReference<ConstantState>> cache = mDrawableCaches.get(context);
if (cache == null) {
cache = new LongSparseArray<>();
- mDelegateDrawableCaches.put(context, cache);
+ mDrawableCaches.put(context, cache);
}
cache.put(key, new WeakReference<ConstantState>(cs));
}
@@ -371,19 +419,7 @@
return false;
}
- public final Drawable onDrawableLoadedFromResources(@NonNull Context context,
- @NonNull TintResources resources, @DrawableRes final int resId) {
- Drawable drawable = loadDrawableFromDelegates(context, resId);
- if (drawable == null) {
- drawable = resources.superGetDrawable(resId);
- }
- if (drawable != null) {
- return tintDrawable(context, resId, false, drawable);
- }
- return null;
- }
-
- private static boolean tintDrawableUsingColorFilter(@NonNull Context context,
+ static boolean tintDrawableUsingColorFilter(@NonNull Context context,
@DrawableRes final int resId, @NonNull Drawable drawable) {
PorterDuff.Mode tintMode = DEFAULT_MODE;
boolean colorAttrSet = false;
diff --git a/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java b/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
new file mode 100644
index 0000000..828a792
--- /dev/null
+++ b/v7/appcompat/src/android/support/v7/widget/ResourcesWrapper.java
@@ -0,0 +1,279 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v7.widget;
+
+import org.xmlpull.v1.XmlPullParserException;
+
+import android.content.res.AssetFileDescriptor;
+import android.content.res.ColorStateList;
+import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
+import android.content.res.XmlResourceParser;
+import android.graphics.Movie;
+import android.graphics.drawable.Drawable;
+import android.os.Bundle;
+import android.util.AttributeSet;
+import android.util.DisplayMetrics;
+import android.util.TypedValue;
+
+import java.io.IOException;
+import java.io.InputStream;
+
+/**
+ * This extends Resources but delegates the calls to another Resources object. This enables
+ * any customization done by some subclass of Resources to be also picked up.
+ */
+class ResourcesWrapper extends Resources {
+
+ private final Resources mResources;
+
+ public ResourcesWrapper(Resources resources) {
+ super(resources.getAssets(), resources.getDisplayMetrics(), resources.getConfiguration());
+ mResources = resources;
+ }
+
+ @Override
+ public CharSequence getText(int id) throws NotFoundException {
+ return mResources.getText(id);
+ }
+
+ @Override
+ public CharSequence getQuantityText(int id, int quantity) throws NotFoundException {
+ return mResources.getQuantityText(id, quantity);
+ }
+
+ @Override
+ public String getString(int id) throws NotFoundException {
+ return mResources.getString(id);
+ }
+
+ @Override
+ public String getString(int id, Object... formatArgs) throws NotFoundException {
+ return mResources.getString(id, formatArgs);
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity, Object... formatArgs)
+ throws NotFoundException {
+ return mResources.getQuantityString(id, quantity, formatArgs);
+ }
+
+ @Override
+ public String getQuantityString(int id, int quantity) throws NotFoundException {
+ return mResources.getQuantityString(id, quantity);
+ }
+
+ @Override
+ public CharSequence getText(int id, CharSequence def) {
+ return mResources.getText(id, def);
+ }
+
+ @Override
+ public CharSequence[] getTextArray(int id) throws NotFoundException {
+ return mResources.getTextArray(id);
+ }
+
+ @Override
+ public String[] getStringArray(int id) throws NotFoundException {
+ return mResources.getStringArray(id);
+ }
+
+ @Override
+ public int[] getIntArray(int id) throws NotFoundException {
+ return mResources.getIntArray(id);
+ }
+
+ @Override
+ public TypedArray obtainTypedArray(int id) throws NotFoundException {
+ return mResources.obtainTypedArray(id);
+ }
+
+ @Override
+ public float getDimension(int id) throws NotFoundException {
+ return mResources.getDimension(id);
+ }
+
+ @Override
+ public int getDimensionPixelOffset(int id) throws NotFoundException {
+ return mResources.getDimensionPixelOffset(id);
+ }
+
+ @Override
+ public int getDimensionPixelSize(int id) throws NotFoundException {
+ return mResources.getDimensionPixelSize(id);
+ }
+
+ @Override
+ public float getFraction(int id, int base, int pbase) {
+ return mResources.getFraction(id, base, pbase);
+ }
+
+ @Override
+ public Drawable getDrawable(int id) throws NotFoundException {
+ return mResources.getDrawable(id);
+ }
+
+ @Override
+ public Drawable getDrawable(int id, Theme theme) throws NotFoundException {
+ return mResources.getDrawable(id, theme);
+ }
+
+ @Override
+ public Drawable getDrawableForDensity(int id, int density) throws NotFoundException {
+ return mResources.getDrawableForDensity(id, density);
+ }
+
+ @Override
+ public Drawable getDrawableForDensity(int id, int density, Theme theme) {
+ return mResources.getDrawableForDensity(id, density, theme);
+ }
+
+ @Override
+ public Movie getMovie(int id) throws NotFoundException {
+ return mResources.getMovie(id);
+ }
+
+ @Override
+ public int getColor(int id) throws NotFoundException {
+ return mResources.getColor(id);
+ }
+
+ @Override
+ public ColorStateList getColorStateList(int id) throws NotFoundException {
+ return mResources.getColorStateList(id);
+ }
+
+ @Override
+ public boolean getBoolean(int id) throws NotFoundException {
+ return mResources.getBoolean(id);
+ }
+
+ @Override
+ public int getInteger(int id) throws NotFoundException {
+ return mResources.getInteger(id);
+ }
+
+ @Override
+ public XmlResourceParser getLayout(int id) throws NotFoundException {
+ return mResources.getLayout(id);
+ }
+
+ @Override
+ public XmlResourceParser getAnimation(int id) throws NotFoundException {
+ return mResources.getAnimation(id);
+ }
+
+ @Override
+ public XmlResourceParser getXml(int id) throws NotFoundException {
+ return mResources.getXml(id);
+ }
+
+ @Override
+ public InputStream openRawResource(int id) throws NotFoundException {
+ return mResources.openRawResource(id);
+ }
+
+ @Override
+ public InputStream openRawResource(int id, TypedValue value) throws NotFoundException {
+ return mResources.openRawResource(id, value);
+ }
+
+ @Override
+ public AssetFileDescriptor openRawResourceFd(int id) throws NotFoundException {
+ return mResources.openRawResourceFd(id);
+ }
+
+ @Override
+ public void getValue(int id, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValue(id, outValue, resolveRefs);
+ }
+
+ @Override
+ public void getValueForDensity(int id, int density, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValueForDensity(id, density, outValue, resolveRefs);
+ }
+
+ @Override
+ public void getValue(String name, TypedValue outValue, boolean resolveRefs)
+ throws NotFoundException {
+ mResources.getValue(name, outValue, resolveRefs);
+ }
+
+ @Override
+ public TypedArray obtainAttributes(AttributeSet set, int[] attrs) {
+ return mResources.obtainAttributes(set, attrs);
+ }
+
+ @Override
+ public void updateConfiguration(Configuration config, DisplayMetrics metrics) {
+ super.updateConfiguration(config, metrics);
+ if (mResources != null) { // called from super's constructor. So, need to check.
+ mResources.updateConfiguration(config, metrics);
+ }
+ }
+
+ @Override
+ public DisplayMetrics getDisplayMetrics() {
+ return mResources.getDisplayMetrics();
+ }
+
+ @Override
+ public Configuration getConfiguration() {
+ return mResources.getConfiguration();
+ }
+
+ @Override
+ public int getIdentifier(String name, String defType, String defPackage) {
+ return mResources.getIdentifier(name, defType, defPackage);
+ }
+
+ @Override
+ public String getResourceName(int resid) throws NotFoundException {
+ return mResources.getResourceName(resid);
+ }
+
+ @Override
+ public String getResourcePackageName(int resid) throws NotFoundException {
+ return mResources.getResourcePackageName(resid);
+ }
+
+ @Override
+ public String getResourceTypeName(int resid) throws NotFoundException {
+ return mResources.getResourceTypeName(resid);
+ }
+
+ @Override
+ public String getResourceEntryName(int resid) throws NotFoundException {
+ return mResources.getResourceEntryName(resid);
+ }
+
+ @Override
+ public void parseBundleExtras(XmlResourceParser parser, Bundle outBundle)
+ throws XmlPullParserException, IOException {
+ mResources.parseBundleExtras(parser, outBundle);
+ }
+
+ @Override
+ public void parseBundleExtra(String tagName, AttributeSet attrs, Bundle outBundle)
+ throws XmlPullParserException {
+ mResources.parseBundleExtra(tagName, attrs, outBundle);
+ }
+}
+
diff --git a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
index 6647f89..119890d 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintContextWrapper.java
@@ -27,15 +27,13 @@
/**
* A {@link android.content.ContextWrapper} which returns a tint-aware
* {@link android.content.res.Resources} instance from {@link #getResources()}.
- *
- * @hide
*/
-public class TintContextWrapper extends ContextWrapper {
+class TintContextWrapper extends ContextWrapper {
private static final ArrayList<WeakReference<TintContextWrapper>> sCache = new ArrayList<>();
public static Context wrap(@NonNull final Context context) {
- if (shouldWrap(context)) {
+ if (!(context instanceof TintContextWrapper)) {
// First check our instance cache
for (int i = 0, count = sCache.size(); i < count; i++) {
final WeakReference<TintContextWrapper> ref = sCache.get(i);
@@ -54,39 +52,10 @@
return context;
}
- private static boolean shouldWrap(@NonNull final Context context) {
- if (context instanceof TintContextWrapper) {
- // If the Context is already a TintContextWrapper, no needed to wrap again
- return false;
- }
- if (context.getResources() instanceof TintResources) {
- // If the Context already has a TintResources impl, no needed to wrap again
- return false;
- }
- // Else, we should wrap
- return true;
- }
-
private Resources mResources;
- private final Resources.Theme mTheme;
- private TintContextWrapper(@NonNull final Context base) {
+ private TintContextWrapper(Context base) {
super(base);
-
- // We need to create a copy of the Theme so that the Theme references our Resources
- // instance
- mTheme = getResources().newTheme();
- mTheme.setTo(base.getTheme());
- }
-
- @Override
- public Resources.Theme getTheme() {
- return mTheme;
- }
-
- @Override
- public void setTheme(int resid) {
- mTheme.applyStyle(resid, true);
}
@Override
diff --git a/v7/appcompat/src/android/support/v7/widget/TintResources.java b/v7/appcompat/src/android/support/v7/widget/TintResources.java
index 6739c93..39e03a5 100644
--- a/v7/appcompat/src/android/support/v7/widget/TintResources.java
+++ b/v7/appcompat/src/android/support/v7/widget/TintResources.java
@@ -25,14 +25,13 @@
/**
* This class allows us to intercept calls so that we can tint resources (if applicable).
- *
- * @hide
*/
-public class TintResources extends Resources {
+class TintResources extends ResourcesWrapper {
+
private final WeakReference<Context> mContextRef;
- public TintResources(@NonNull final Context context, @NonNull final Resources res) {
- super(res.getAssets(), res.getDisplayMetrics(), res.getConfiguration());
+ public TintResources(@NonNull Context context, @NonNull final Resources res) {
+ super(res);
mContextRef = new WeakReference<>(context);
}
@@ -43,15 +42,11 @@
*/
@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);
+ Drawable d = super.getDrawable(id);
+ Context context = mContextRef.get();
+ if (d != null && context != null) {
+ AppCompatDrawableManager.get().tintDrawableUsingColorFilter(context, id, d);
}
- }
-
- final Drawable superGetDrawable(int id) {
- return super.getDrawable(id);
+ return d;
}
}
\ No newline at end of file
diff --git a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
index 49f3efe..ff63834 100755
--- a/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/DrawerLayoutTest.java
@@ -80,6 +80,20 @@
@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));
@@ -104,6 +118,30 @@
@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));
@@ -239,6 +277,35 @@
@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));
@@ -279,6 +346,38 @@
@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);
diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
index 4feb013..8e839b9 100755
--- a/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
+++ b/v7/appcompat/tests/src/android/support/v7/testutils/DrawerLayoutActions.java
@@ -144,6 +144,31 @@
/**
* 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
@@ -217,6 +242,31 @@
}
/**
+ * 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) {
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..81cac57
--- /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 7fa5cec..cc51239 100644
--- a/v7/cardview/Android.mk
+++ b/v7/cardview/Android.mk
@@ -19,6 +19,7 @@
# 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 := $(SUPPORT_CURRENT_SDK_VERSION)
LOCAL_SRC_FILES := $(call all-java-files-under, dummy)
@@ -72,9 +73,8 @@
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)
@@ -82,17 +82,23 @@
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 \
- android-support-annotations
+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/api21/android/support/v7/widget/CardViewApi21.java b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
index c4af30b..85fc178 100644
--- a/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
+++ b/v7/cardview/api21/android/support/v7/widget/CardViewApi21.java
@@ -24,10 +24,11 @@
@Override
public void initialize(CardViewDelegate cardView, Context context,
- ColorStateList backgroundColor, float radius, float elevation, float maxElevation) {
- final RoundRectDrawable backgroundDrawable = new RoundRectDrawable(backgroundColor, radius);
- cardView.setBackgroundDrawable(backgroundDrawable);
- View view = (View) cardView;
+ 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);
@@ -35,7 +36,7 @@
@Override
public void setRadius(CardViewDelegate cardView, float radius) {
- ((RoundRectDrawable) (cardView.getBackground())).setRadius(radius);
+ getCardBackground(cardView).setRadius(radius);
}
@Override
@@ -44,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
@@ -66,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
@@ -106,6 +107,10 @@
@Override
public void setBackgroundColor(CardViewDelegate cardView, @Nullable ColorStateList color) {
- ((RoundRectDrawable) (cardView.getBackground())).setColor(color);
+ getCardBackground(cardView).setColor(color);
+ }
+
+ 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 68fd714..5a9a462 100644
--- a/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
+++ b/v7/cardview/api21/android/support/v7/widget/RoundRectDrawable.java
@@ -50,7 +50,7 @@
private ColorStateList mBackground;
private PorterDuffColorFilter mTintFilter;
private ColorStateList mTint;
- private PorterDuff.Mode mTintMode;
+ private PorterDuff.Mode mTintMode = PorterDuff.Mode.SRC_IN;
public RoundRectDrawable(ColorStateList backgroundColor, float radius) {
mRadius = radius;
diff --git a/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java b/v7/cardview/base/android/support/v7/widget/CardViewDelegate.java
index 8752b7d..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,11 +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/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
index 384dbfd..5738251 100644
--- a/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
+++ b/v7/cardview/eclair-mr1/android/support/v7/widget/CardViewEclairMr1.java
@@ -81,12 +81,13 @@
RoundRectDrawableWithShadow background = createBackground(context, backgroundColor, radius,
elevation, maxElevation);
background.setAddPaddingForCorners(cardView.getPreventCornerOverlap());
- cardView.setBackgroundDrawable(background);
+ cardView.setCardBackground(background);
updatePadding(cardView);
}
- RoundRectDrawableWithShadow createBackground(Context context, ColorStateList 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);
}
@@ -160,6 +161,6 @@
}
private RoundRectDrawableWithShadow getShadowBackground(CardViewDelegate cardView) {
- return ((RoundRectDrawableWithShadow) cardView.getBackground());
+ return ((RoundRectDrawableWithShadow) cardView.getCardBackground());
}
}
diff --git a/v7/cardview/src/android/support/v7/widget/CardView.java b/v7/cardview/src/android/support/v7/widget/CardView.java
index 68b262f..899e480 100644
--- a/v7/cardview/src/android/support/v7/widget/CardView.java
+++ b/v7/cardview/src/android/support/v7/widget/CardView.java
@@ -21,10 +21,13 @@
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;
/**
@@ -70,7 +73,7 @@
* @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;
@@ -103,7 +106,6 @@
private final Rect mShadowBounds = new Rect();
-
public CardView(Context context) {
super(context);
initialize(context, null, 0);
@@ -134,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;
}
@@ -156,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);
}
/**
@@ -181,7 +181,7 @@
*/
public void setContentPadding(int left, int top, int right, int bottom) {
mContentPadding.set(left, top, right, bottom);
- IMPL.updatePadding(this);
+ IMPL.updatePadding(mCardViewDelegate);
}
@Override
@@ -191,7 +191,7 @@
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;
@@ -201,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;
@@ -252,7 +252,8 @@
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
@@ -268,29 +269,13 @@
}
/**
- * Internal method used by CardView implementations to set min width / height that is necessary
- * to draw shadows on APIs older than 21.
- *
- * @hide
- */
- @Override
- public void setMinWidthHeightInternal(int width, int height) {
- if (width > mUserSetMinWidth) {
- super.setMinimumWidth(width);
- }
- if (height > mUserSetMinHeight) {
- super.setMinimumHeight(height);
- }
- }
-
- /**
* Updates the background color of the CardView
*
* @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, ColorStateList.valueOf(color));
+ public void setCardBackgroundColor(@ColorInt int color) {
+ IMPL.setBackgroundColor(mCardViewDelegate, ColorStateList.valueOf(color));
}
/**
@@ -300,7 +285,7 @@
* @attr ref android.support.v7.cardview.R.styleable#CardView_cardBackgroundColor
*/
public void setCardBackgroundColor(@Nullable ColorStateList color) {
- IMPL.setBackgroundColor(this, color);
+ IMPL.setBackgroundColor(mCardViewDelegate, color);
}
/**
@@ -347,7 +332,7 @@
* @see #setRadius(float)
*/
public void setRadius(float radius) {
- IMPL.setRadius(this, radius);
+ IMPL.setRadius(mCardViewDelegate, radius);
}
/**
@@ -357,19 +342,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);
}
/**
@@ -381,7 +354,7 @@
* @see #setMaxCardElevation(float)
*/
public void setCardElevation(float elevation) {
- IMPL.setElevation(this, elevation);
+ IMPL.setElevation(mCardViewDelegate, elevation);
}
/**
@@ -392,7 +365,7 @@
* @see #getMaxCardElevation()
*/
public float getCardElevation() {
- return IMPL.getElevation(this);
+ return IMPL.getElevation(mCardViewDelegate);
}
/**
@@ -407,7 +380,7 @@
* @see #getMaxCardElevation()
*/
public void setMaxCardElevation(float maxElevation) {
- IMPL.setMaxElevation(this, maxElevation);
+ IMPL.setMaxElevation(mCardViewDelegate, maxElevation);
}
/**
@@ -418,7 +391,7 @@
* @see #getCardElevation()
*/
public float getMaxCardElevation() {
- return IMPL.getMaxElevation(this);
+ return IMPL.getMaxElevation(mCardViewDelegate);
}
/**
@@ -428,7 +401,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;
}
@@ -447,10 +419,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/mediarouter/Android.mk b/v7/mediarouter/Android.mk
index ada74c5..c0c4085 100644
--- a/v7/mediarouter/Android.mk
+++ b/v7/mediarouter/Android.mk
@@ -16,17 +16,14 @@
# 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 := $(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)
@@ -77,18 +74,30 @@
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-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-api24
-LOCAL_JAVA_LIBRARIES := android-support-v4 android-support-v7-mediarouter-res \
+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/current.txt b/v7/mediarouter/api/current.txt
index e1d6ad6..f65dd85 100644
--- a/v7/mediarouter/api/current.txt
+++ b/v7/mediarouter/api/current.txt
@@ -702,7 +702,7 @@
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 setMessageCallback(android.support.v7.media.RemotePlaybackClient.MessageCallback);
+ 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);
@@ -719,9 +719,8 @@
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.MessageCallback {
- ctor public RemotePlaybackClient.MessageCallback();
- method public void onMessageReceived(java.lang.String, android.os.Bundle);
+ 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 {
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 25b1d1d..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,20 +94,23 @@
}
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 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.
@@ -135,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/media/MediaControlIntent.java b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
index 55734c9..c827534 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaControlIntent.java
@@ -893,7 +893,7 @@
public static final String ACTION_END_SESSION = "android.media.intent.action.END_SESSION";
/**
- * Custom media control action: Send {link #EXTRA_MESSAGE}.
+ * Custom media control action: Send {@link #EXTRA_MESSAGE}.
* <p>
* This action asks a route to handle a message described by EXTRA_MESSAGE.
* </p>
@@ -1178,6 +1178,8 @@
* {@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";
diff --git a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
index cc0f75e..343d21b 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouteProviderService.java
@@ -141,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 a321925..896f057 100644
--- a/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
+++ b/v7/mediarouter/src/android/support/v7/media/MediaRouter.java
@@ -408,7 +408,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);
+ }
}
/**
@@ -2430,7 +2437,7 @@
}
}
- 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
diff --git a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
index 113f55d..1716af3 100644
--- a/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
+++ b/v7/mediarouter/src/android/support/v7/media/RemotePlaybackClient.java
@@ -52,7 +52,7 @@
private String mSessionId;
private StatusCallback mStatusCallback;
- private MessageCallback mMessageCallback;
+ private OnMessageReceivedListener mOnMessageReceivedListener;
/**
* Creates a remote playback client for a route.
@@ -250,10 +250,10 @@
* The callback should be set before the session is created.
* </p>
*
- * @param callback The callback to set. May be null to remove the previous callback.
+ * @param listener The callback to set. May be null to remove the previous callback.
*/
- public void setMessageCallback(MessageCallback callback) {
- mMessageCallback = callback;
+ public void setOnMessageReceivedListener(OnMessageReceivedListener listener) {
+ mOnMessageReceivedListener = listener;
}
/**
@@ -573,7 +573,7 @@
* more information about the semantics of this request.
* </p>
*
- * @param message A bundle message denoting {link MediaControlIntent#EXTRA_SESSION_MESSAGE}.
+ * @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.
@@ -933,8 +933,8 @@
Log.d(TAG, "Received message callback: sessionId=" + sessionId);
}
- if (mMessageCallback != null) {
- mMessageCallback.onMessageReceived(sessionId,
+ if (mOnMessageReceivedListener != null) {
+ mOnMessageReceivedListener.onMessageReceived(sessionId,
intent.getBundleExtra(MediaControlIntent.EXTRA_MESSAGE));
}
}
@@ -979,17 +979,6 @@
}
}
- public static abstract class MessageCallback {
- /**
- * Called when a message received.
- *
- * @param sessionId The session id.
- * @param message A bundle message denoting {@link MediaControlIntent#EXTRA_MESSAGE}.
- */
- public void onMessageReceived(String sessionId, Bundle message) {
- }
- }
-
/**
* Base callback type for remote playback requests.
*/
@@ -1038,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/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/preference/Android.mk b/v7/preference/Android.mk
index 85ded02..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 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.
+# 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_SRC_FILES := $(call all-java-files-under, constants)
LOCAL_SDK_VERSION := $(SUPPORT_CURRENT_SDK_VERSION)
-LOCAL_RESOURCE_DIR := \
- frameworks/support/v7/appcompat/res \
- frameworks/support/v7/recyclerview/res \
- $(LOCAL_PATH)/res
-LOCAL_AAPT_FLAGS := \
- --auto-add-overlay
+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/current.txt b/v7/preference/api/current.txt
index b2046ca..c2b5b4f 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -159,6 +159,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();
diff --git a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
index 50ab1db..8ef625b 100644
--- a/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
+++ b/v7/preference/src/android/support/v7/preference/CheckBoxPreference.java
@@ -109,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/DropDownPreference.java b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
index e7a0514..ee1e723 100644
--- a/v7/preference/src/android/support/v7/preference/DropDownPreference.java
+++ b/v7/preference/src/android/support/v7/preference/DropDownPreference.java
@@ -25,6 +25,10 @@
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;
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index 175f855..d9621a2 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -101,6 +101,7 @@
private OnPreferenceClickListener mOnClickListener;
private int mOrder = DEFAULT_ORDER;
+ private int mViewId = 0;
private CharSequence mTitle;
private CharSequence mSummary;
/**
@@ -477,6 +478,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) {
@@ -583,6 +585,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
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
index ecc6712..afb966c 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceFragmentCompat.java
@@ -764,11 +764,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);
@@ -780,32 +775,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/recyclerview/Android.mk b/v7/recyclerview/Android.mk
index 1a0819c..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 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_MODULE := android-support-v7-recyclerview-res
-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)
-
# 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/current.txt b/v7/recyclerview/api/current.txt
index 56c86aa..9a5c15b 100644
--- a/v7/recyclerview/api/current.txt
+++ b/v7/recyclerview/api/current.txt
@@ -240,6 +240,8 @@
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();
@@ -285,6 +287,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();
@@ -315,6 +318,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);
@@ -515,6 +519,7 @@
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();
@@ -550,6 +555,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);
diff --git a/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java b/v7/recyclerview/src/android/support/v7/widget/AdapterHelper.java
index b1dd91c..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;
diff --git a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
index ef100cb..6a56418 100644
--- a/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/GridLayoutManager.java
@@ -172,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() {
@@ -800,6 +803,7 @@
}
mSpanCount = spanCount;
mSpanSizeLookup.invalidateSpanIndexCache();
+ requestLayout();
}
/**
diff --git a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
index 7a7f0f9..0570135 100644
--- a/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/LinearLayoutManager.java
@@ -20,6 +20,7 @@
import android.content.Context;
import android.graphics.PointF;
+import android.graphics.Rect;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.v4.view.ViewCompat;
@@ -619,17 +620,22 @@
}
layoutForPredictiveAnimations(recycler, state, startOffset, endOffset);
if (!state.isPreLayout()) {
- mPendingScrollPosition = NO_POSITION;
- mPendingScrollPositionOffset = INVALID_OFFSET;
mOrientationHelper.onLayoutComplete();
}
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;
+ }
+
/**
* Method called when Anchor position is decided. Extending class can setup accordingly or
* even update anchor info if necessary.
@@ -1227,6 +1233,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
@@ -1247,7 +1255,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;
}
@@ -1255,7 +1265,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;
}
@@ -1266,6 +1278,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
@@ -1285,7 +1299,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;
}
@@ -1293,7 +1309,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;
}
diff --git a/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java b/v7/recyclerview/src/android/support/v7/widget/OrientationHelper.java
index cd1f5e1..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.
*
@@ -265,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();
@@ -351,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();
diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
index d9a1362..c9b672a 100644
--- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
+++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java
@@ -21,8 +21,10 @@
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;
@@ -58,7 +60,6 @@
import android.view.MotionEvent;
import android.view.VelocityTracker;
import android.view.View;
-import android.view.View.MeasureSpec;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
@@ -296,6 +297,7 @@
};
private final Rect mTempRect = new Rect();
+ private final RectF mTempRectF = new RectF();
private Adapter mAdapter;
@VisibleForTesting LayoutManager mLayout;
private RecyclerListener mRecyclerListener;
@@ -376,6 +378,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();
@@ -1446,11 +1449,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();
@@ -2917,6 +2916,75 @@
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
@@ -2930,7 +2998,7 @@
eatRequestLayout();
mViewInfoStore.clear();
onEnterLayoutOrScroll();
-
+ saveFocusInfo();
processAdapterUpdatesAndSetAnimationFlags();
mState.mTrackOldChangeHolders = mState.mRunSimpleAnimations && mItemsChanged;
mItemsAddedOrRemoved = mItemsChanged = false;
@@ -3108,12 +3176,15 @@
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();
}
/**
@@ -3602,6 +3673,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
@@ -3730,6 +3834,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
@@ -3748,6 +3856,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
@@ -3757,25 +3868,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;
}
}
@@ -3783,7 +3906,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;
}
/**
@@ -3794,20 +3917,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;
}
/**
@@ -6429,6 +6561,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
@@ -7063,11 +7209,11 @@
* {@link #setAutoMeasureEnabled(boolean)}.
* <p>
* When RecyclerView is running a layout, this value is always set to
- * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
*
* @return Width measure spec mode.
*
- * @see MeasureSpec#getMode(int)
+ * @see View.MeasureSpec#getMode(int)
* @see View#onMeasure(int, int)
*/
public int getWidthMode() {
@@ -7081,11 +7227,11 @@
* {@link #setAutoMeasureEnabled(boolean)}.
* <p>
* When RecyclerView is running a layout, this value is always set to
- * {@link MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
+ * {@link View.MeasureSpec#EXACTLY} even if it was measured with a different spec mode.
*
* @return Height measure spec mode.
*
- * @see MeasureSpec#getMode(int)
+ * @see View.MeasureSpec#getMode(int)
* @see View#onMeasure(int, int)
*/
public int getHeightMode() {
@@ -7691,6 +7837,43 @@
}
/**
+ * Calculates the bounding box of the View while taking into account its matrix changes
+ * (translation, scale etc) wrt 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
@@ -10152,6 +10335,17 @@
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) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index 2542031..18987ad 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -614,7 +614,7 @@
updateAnchorInfoForLayout(state, anchorInfo);
- if (mPendingSavedState == null) {
+ if (mPendingSavedState == null && mPendingScrollPosition == NO_POSITION) {
if (anchorInfo.mLayoutFromEnd != mLastLayoutFromEnd ||
isLayoutRTL() != mLastLayoutRTL) {
mLazySpanLookup.clear();
@@ -683,17 +683,22 @@
hasGaps = true;
}
}
- mPendingScrollPosition = NO_POSITION;
- mPendingScrollPositionOffset = INVALID_OFFSET;
}
mLastLayoutFromEnd = anchorInfo.mLayoutFromEnd;
mLastLayoutRTL = isLayoutRTL();
- mPendingSavedState = null; // we don't need this anymore
if (hasGaps) {
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
+ }
+
private void repositionToWrapContentIfNecessary() {
if (mSecondaryOrientation.getMode() == View.MeasureSpec.EXACTLY) {
return; // nothing to do
@@ -829,7 +834,6 @@
// child
anchorInfo.mPosition = mShouldReverseLayout ? getLastChildPosition()
: getFirstChildPosition();
-
if (mPendingScrollPositionOffset != INVALID_OFFSET) {
if (anchorInfo.mLayoutFromEnd) {
final int target = mPrimaryOrientation.getEndAfterPadding() -
@@ -1837,7 +1841,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) {
@@ -1867,7 +1872,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) {
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/src/android/support/v7/widget/AsyncListUtilLayoutTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/AsyncListUtilLayoutTest.java
index af2cea7..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,6 +16,8 @@
package android.support.v7.widget;
+import org.hamcrest.CoreMatchers;
+import org.hamcrest.MatcherAssert;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -32,6 +34,8 @@
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 {
@@ -313,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
index cfe6fe1..aa1be7b 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseGridLayoutManagerTest.java
@@ -29,6 +29,9 @@
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";
@@ -194,8 +197,17 @@
mLayoutLatch = new CountDownLatch(layoutCount);
}
- public void waitForLayout(int seconds) throws InterruptedException {
- mLayoutLatch.await(seconds, SECONDS);
+ 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() {
+ }
+ });
}
}
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
index 4e37369..305fa66 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseLinearLayoutManagerTest.java
@@ -35,6 +35,11 @@
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;
@@ -347,8 +352,17 @@
layoutLatch = new CountDownLatch(count);
}
- public void waitForLayout(long timeout) throws InterruptedException {
- waitForLayout(timeout, TimeUnit.SECONDS);
+ 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
@@ -383,13 +397,6 @@
}
}
- 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() {
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 25876a7..57c09fa 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewAnimationsTest.java
@@ -207,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 {
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 ca9fddf..3f02ebe 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseRecyclerViewInstrumentationTest.java
@@ -24,17 +24,13 @@
import org.junit.Before;
import org.junit.Rule;
-import android.app.Activity;
import android.app.Instrumentation;
-import android.content.Intent;
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.support.v7.recyclerview.test.SameActivityTestRule;
-import android.test.ActivityInstrumentationTestCase2;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
@@ -57,6 +53,8 @@
import static org.junit.Assert.*;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
abstract public class BaseRecyclerViewInstrumentationTest {
private static final String TAG = "RecyclerViewTest";
@@ -67,7 +65,9 @@
protected AdapterHelper mAdapterHelper;
- Throwable mainThreadException;
+ private Throwable mMainThreadException;
+
+ private boolean mIgnoreMainThreadException = false;
Thread mInstrumentationThread;
@@ -88,11 +88,19 @@
}
void checkForMainThreadException() throws Throwable {
- if (mainThreadException != null) {
- throw mainThreadException;
+ if (!mIgnoreMainThreadException && mMainThreadException != null) {
+ throw mMainThreadException;
}
}
+ public void setIgnoreMainThreadException(boolean ignoreMainThreadException) {
+ mIgnoreMainThreadException = ignoreMainThreadException;
+ }
+
+ public Throwable getMainThreadException() {
+ return mMainThreadException;
+ }
+
protected TestActivity getActivity() {
return mActivityRule.getActivity();
}
@@ -172,19 +180,21 @@
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();
+ }
}
}
}
@@ -233,10 +243,13 @@
@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();
} catch (Throwable t) {
@@ -503,47 +516,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 {
@@ -556,14 +571,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,
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
index b146355..0fea8d6 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/BaseStaggeredGridLayoutManagerTest.java
@@ -28,6 +28,11 @@
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;
@@ -108,7 +113,7 @@
void waitFirstLayout() throws Throwable {
mLayoutManager.expectLayouts(1);
setRecyclerView(mRecyclerView);
- mLayoutManager.waitForLayout(2);
+ mLayoutManager.waitForLayout(3);
getInstrumentation().waitForIdleSync();
}
@@ -480,15 +485,17 @@
layoutLatch = new CountDownLatch(count);
}
- public void waitForLayout(long timeout) throws Throwable {
- waitForLayout(timeout * (DEBUG ? 1000 : 1), TimeUnit.SECONDS);
- }
-
- public void waitForLayout(long timeout, TimeUnit timeUnit) throws Throwable {
- layoutLatch.await(timeout, timeUnit);
+ public void waitForLayout(int seconds) throws Throwable {
+ layoutLatch.await(seconds * (DEBUG ? 100 : 1), SECONDS);
checkForMainThreadException();
- assertEquals("all expected layouts should be executed at the expected time",
- 0, layoutLatch.getCount());
+ 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 {
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 4a15e48..b38c551 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/GridLayoutManagerTest.java
@@ -775,7 +775,6 @@
assertFalse(mGlm.supportsPredictiveItemAnimations());
}
});
- checkForMainThreadException();
mGlm.waitForLayout(2);
mGlm.expectLayout(2);
mAdapter.deleteAndNotify(3, 2);
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
index 73e913c..9ee7958 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/LinearLayoutManagerBaseConfigSetTest.java
@@ -22,10 +22,12 @@
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;
@@ -35,8 +37,15 @@
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;
@@ -44,6 +53,7 @@
* 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;
@@ -63,7 +73,6 @@
}
@Test
- @MediumTest
public void scrollToPositionWithOffsetTest() throws Throwable {
Config config = ((Config) mConfig.clone()).itemCount(300);
setupByConfig(config, true);
@@ -148,7 +157,6 @@
}
@Test
- @MediumTest
public void getFirstLastChildrenTest() throws Throwable {
final Config config = ((Config) mConfig.clone()).itemCount(300);
setupByConfig(config, true);
@@ -224,6 +232,73 @@
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;
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 3b9f5d6..be0fc10 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityTest.java
@@ -261,14 +261,20 @@
});
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,
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 2b8fb40..2ddbabf 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAnimationsTest.java
@@ -23,8 +23,6 @@
import android.graphics.Rect;
import android.support.annotation.NonNull;
-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;
@@ -52,6 +50,38 @@
final List<TestViewHolder> recycledVHs = new ArrayList<>();
@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);
}
@@ -141,6 +171,7 @@
@Test
public void detectStableIdError() throws Throwable {
+ setIgnoreMainThreadException(true);
final AtomicBoolean useBadIds = new AtomicBoolean(false);
TestAdapter adapter = new TestAdapter(10) {
@Override
@@ -162,14 +193,13 @@
useBadIds.set(true);
adapter.changeAndNotify(4, 2);
mLayoutManager.waitForLayout(2);
- assertTrue(mainThreadException instanceof IllegalStateException);
- assertTrue(mainThreadException.getMessage()
+ 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){}
- mainThreadException = null;
}
@@ -1368,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;
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..ea1573d
--- /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 be43820..3402a3f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java
@@ -25,6 +25,8 @@
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;
@@ -35,6 +37,7 @@
import android.support.v4.view.ViewCompat;
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.MotionEvent;
@@ -60,6 +63,7 @@
import static android.support.v7.widget.RecyclerView.getChildViewHolderInt;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
+import static org.hamcrest.CoreMatchers.is;
@RunWith(AndroidJUnit4.class)
@MediumTest
@@ -77,6 +81,147 @@
}
@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);
}
@@ -123,6 +268,54 @@
});
}
+ @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().mContainer.removeView(recyclerView);
+ getActivity().mContainer.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());
@@ -312,6 +505,87 @@
}
@Test
+ 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() {
@@ -1064,6 +1338,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() {
@@ -2167,6 +2582,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() {
@@ -2198,8 +2614,7 @@
lm.waitForLayout(2);
removeRecyclerView();
assertTrue("Invalid data updates should be caught:" + msg,
- mainThreadException instanceof IllegalStateException);
- mainThreadException = null;
+ getMainThreadException() instanceof IllegalStateException);
}
@Test
@@ -2237,6 +2652,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
@@ -2295,8 +2711,7 @@
lm.waitForLayout(2);
removeRecyclerView();
assertTrue("Invalid data updates should be caught:" + msg,
- mainThreadException instanceof IllegalStateException);
- mainThreadException = null;
+ getMainThreadException() instanceof IllegalStateException);
}
@Test
@@ -2816,7 +3231,7 @@
getActivity().mContainer.addView(recyclerView);
}
});
- testLayoutManager.waitForLayout(2, TimeUnit.SECONDS);
+ testLayoutManager.waitForLayout(2);
assertEquals("item count in state should be correct", adapter.getItemCount()
, itemCount.get());
@@ -3289,6 +3704,48 @@
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);
@@ -3413,7 +3870,7 @@
}
}
- private static interface AdapterRunnable {
+ public interface AdapterRunnable {
void run(TestAdapter adapter) throws Throwable;
}
@@ -3425,4 +3882,36 @@
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..81fa0a7
--- /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.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
index 15331b5..f8d11d8 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/StaggeredGridLayoutManagerBaseConfigSetTest.java
@@ -25,9 +25,11 @@
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;
@@ -38,8 +40,14 @@
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)
@@ -731,6 +739,78 @@
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);
diff --git a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
index 6fc9d5d..85b6763 100644
--- a/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
+++ b/v8/renderscript/java/src/android/support/v8/renderscript/RenderScript.java
@@ -798,12 +798,10 @@
}
mIncLoaded = true;
}
- if (mIncDev == 0) {
- mIncDev = nIncDeviceCreate();
- }
if (mIncCon == 0) {
//Create a dummy compat context (synchronous).
- mIncCon = nIncContextCreate(mIncDev, 0, 0, 0);
+ long device = nIncDeviceCreate();
+ mIncCon = nIncContextCreate(device, 0, 0, 0);
}
return rsnScriptIntrinsicCreate(mIncCon, id, eid, mUseInc);
} else {
@@ -1046,10 +1044,8 @@
return rsnIncAllocationCreateTyped(mContext, mIncCon, alloc, type, xBytesSize);
}
- long mDev;
long mContext;
//Dummy device & context for Inc Support Lib
- long mIncDev;
long mIncCon;
//indicator of whether inc support lib has been loaded or not.
boolean mIncLoaded;
@@ -1350,7 +1346,6 @@
mNativeLibDir = mApplicationContext.getApplicationInfo().nativeLibraryDir;
}
}
- mIncDev = 0;
mIncCon = 0;
mIncLoaded = false;
mRWLock = new ReentrantReadWriteLock();
@@ -1481,8 +1476,8 @@
}
}
- rs.mDev = rs.nDeviceCreate();
- rs.mContext = rs.nContextCreate(rs.mDev, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
+ long device = rs.nDeviceCreate();
+ rs.mContext = rs.nContextCreate(device, 0, sdkVersion, ct.mID, rs.mNativeLibDir);
rs.mContextType = ct;
rs.mContextFlags = flags;
rs.mContextSdkVersion = sdkVersion;
@@ -1703,12 +1698,6 @@
}
nContextDestroy();
- nDeviceDestroy(mDev);
- if (mIncDev != 0) {
- nIncDeviceDestroy(mIncDev);
- mIncDev = 0;
- }
- mDev = 0;
}
boolean isAlive() {