diff options
author | Dan Pasanen <dan.pasanen@gmail.com> | 2017-04-05 07:25:03 -0500 |
---|---|---|
committer | Dan Pasanen <dan.pasanen@gmail.com> | 2017-04-05 07:25:03 -0500 |
commit | 5a832ea51874a2381564c5329bf47db518d47ff7 (patch) | |
tree | 1a4ee240e75a30745466481244da51e415704136 | |
parent | 0285afecf474df8254879e2c35043d4174713d69 (diff) | |
parent | 9201a902d80346b09042da74bf3639f61622e285 (diff) | |
download | android_frameworks_support-cm-14.1.tar.gz android_frameworks_support-cm-14.1.tar.bz2 android_frameworks_support-cm-14.1.zip |
Merge tag 'android-7.1.2_r2' into cm-14.1staging/cm-14.1_android-7.1.2_r2cm-14.1
Android 7.1.2 Release 2 (N2G47E)
# gpg: Signature made Mon 03 Apr 2017 01:41:48 AM CDT
# gpg: using DSA key E8AD3F819AB10E78
# gpg: Can't check signature: No public key
74 files changed, 1288 insertions, 399 deletions
diff --git a/build.gradle b/build.gradle index 258d4d011e..952f18706f 100644 --- a/build.gradle +++ b/build.gradle @@ -30,8 +30,8 @@ dependencies { doclava project(':doclava') } -ext.supportVersion = '25.0.0' -ext.extraVersion = 39 +ext.supportVersion = '25.0.1' +ext.extraVersion = 40 ext.supportRepoOut = '' ext.buildToolsVersion = '23.0.2' ext.buildNumber = Integer.toString(ext.extraVersion) diff --git a/compat/gingerbread/android/support/v4/os/BuildCompat.java b/compat/gingerbread/android/support/v4/os/BuildCompat.java index 3c73e4dc43..a49b5aa447 100644 --- a/compat/gingerbread/android/support/v4/os/BuildCompat.java +++ b/compat/gingerbread/android/support/v4/os/BuildCompat.java @@ -27,6 +27,12 @@ import android.text.TextUtils; public class BuildCompat { private BuildCompat() { } + /* Boilerplate for isAtLeast${PLATFORM}: + * public static boolean isAtLeast*() { + * return !"REL".equals(VERSION.CODENAME) + * && ("${PLATFORM}".equals(VERSION.CODENAME) || VERSION.CODENAME.startsWith("${PLATFORM}MR")); + * } + */ /** * Check if the device is running on the Android N release or newer. diff --git a/compat/java/android/support/v4/widget/TextViewCompat.java b/compat/java/android/support/v4/widget/TextViewCompat.java index 6c96ef3123..faa252ae19 100644 --- a/compat/java/android/support/v4/widget/TextViewCompat.java +++ b/compat/java/android/support/v4/widget/TextViewCompat.java @@ -158,6 +158,11 @@ public final class TextViewCompat { TextViewCompatJbMr2.setCompoundDrawablesRelativeWithIntrinsicBounds(textView, start, top, end, bottom); } + + @Override + public Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { + return TextViewCompatJbMr2.getCompoundDrawablesRelative(textView); + } } static class Api23TextViewCompatImpl extends JbMr2TextViewCompatImpl { diff --git a/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java b/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java index fc088ed1af..fa473cf6d8 100644 --- a/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java +++ b/compat/jellybean-mr1/android/support/v4/widget/TextViewCompatJbMr1.java @@ -46,8 +46,17 @@ class TextViewCompatJbMr1 { bottom); } - static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { - return textView.getCompoundDrawablesRelative(); + public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { + final boolean rtl = textView.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL; + final Drawable[] compounds = textView.getCompoundDrawables(); + if (rtl) { + // If we're on RTL, we need to invert the horizontal result like above + final Drawable start = compounds[2]; + final Drawable end = compounds[0]; + compounds[0] = start; + compounds[2] = end; + } + return compounds; } } diff --git a/compat/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java b/compat/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java index 73f9666f9e..03b1e153d0 100644 --- a/compat/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java +++ b/compat/jellybean-mr2/android/support/v4/widget/TextViewCompatJbMr2.java @@ -42,4 +42,8 @@ class TextViewCompatJbMr2 { textView.setCompoundDrawablesRelativeWithIntrinsicBounds(start, top, end, bottom); } + public static Drawable[] getCompoundDrawablesRelative(@NonNull TextView textView) { + return textView.getCompoundDrawablesRelative(); + } + } diff --git a/design/res/values/attrs.xml b/design/res/values/attrs.xml index 545a82ea06..41e0c0f0ae 100644 --- a/design/res/values/attrs.xml +++ b/design/res/values/attrs.xml @@ -469,6 +469,7 @@ <attr name="itemIconTint"/> <attr name="itemTextColor"/> <attr name="itemBackground"/> + <attr name="elevation"/> </declare-styleable> </resources> diff --git a/design/res/values/colors.xml b/design/res/values/colors.xml index ebf6412356..eb18f05a17 100644 --- a/design/res/values/colors.xml +++ b/design/res/values/colors.xml @@ -38,4 +38,6 @@ <color name="design_snackbar_background_color">#323232</color> + <color name="design_bottom_navigation_shadow_color">#14000000</color> + </resources>
\ No newline at end of file diff --git a/design/res/values/dimens.xml b/design/res/values/dimens.xml index 63e98b876d..de715ce8a5 100644 --- a/design/res/values/dimens.xml +++ b/design/res/values/dimens.xml @@ -59,6 +59,8 @@ <dimen name="design_bottom_sheet_peek_height_min">64dp</dimen> <dimen name="design_bottom_navigation_height">56dp</dimen> + <dimen name="design_bottom_navigation_elevation">8dp</dimen> + <dimen name="design_bottom_navigation_shadow_height">1dp</dimen> <dimen name="design_bottom_navigation_text_size">12sp</dimen> <dimen name="design_bottom_navigation_active_text_size">14sp</dimen> <dimen name="design_bottom_navigation_margin">8dp</dimen> diff --git a/design/res/values/styles.xml b/design/res/values/styles.xml index c8b9cd584a..820742fa79 100644 --- a/design/res/values/styles.xml +++ b/design/res/values/styles.xml @@ -44,6 +44,7 @@ <style name="Widget.Design.BottomNavigationView" parent=""> <item name="itemBackground">?attr/selectableItemBackgroundBorderless</item> + <item name="elevation">@dimen/design_bottom_navigation_elevation</item> </style> <style name="Base.Widget.Design.TabLayout" parent="android:Widget"> diff --git a/design/src/android/support/design/internal/BottomNavigationItemView.java b/design/src/android/support/design/internal/BottomNavigationItemView.java index ea22399f0a..5403ba12be 100644 --- a/design/src/android/support/design/internal/BottomNavigationItemView.java +++ b/design/src/android/support/design/internal/BottomNavigationItemView.java @@ -16,6 +16,8 @@ package android.support.design.internal; +import static android.support.annotation.RestrictTo.Scope.GROUP_ID; + import android.content.Context; import android.content.res.ColorStateList; import android.content.res.Resources; @@ -35,8 +37,6 @@ import android.widget.FrameLayout; import android.widget.ImageView; import android.widget.TextView; -import static android.support.annotation.RestrictTo.Scope.GROUP_ID; - /** * @hide */ @@ -131,8 +131,6 @@ public class BottomNavigationItemView extends FrameLayout implements MenuView.It @Override public void setChecked(boolean checked) { - mItemData.setChecked(checked); - ViewCompat.setPivotX(mLargeLabel, mLargeLabel.getWidth() / 2); ViewCompat.setPivotY(mLargeLabel, mLargeLabel.getBaseline()); ViewCompat.setPivotX(mSmallLabel, mSmallLabel.getWidth() / 2); diff --git a/design/src/android/support/design/internal/BottomNavigationMenu.java b/design/src/android/support/design/internal/BottomNavigationMenu.java index 883209775e..9862175b25 100644 --- a/design/src/android/support/design/internal/BottomNavigationMenu.java +++ b/design/src/android/support/design/internal/BottomNavigationMenu.java @@ -16,14 +16,15 @@ package android.support.design.internal; +import static android.support.annotation.RestrictTo.Scope.GROUP_ID; + import android.content.Context; import android.support.annotation.RestrictTo; import android.support.v7.view.menu.MenuBuilder; +import android.support.v7.view.menu.MenuItemImpl; import android.view.MenuItem; import android.view.SubMenu; -import static android.support.annotation.RestrictTo.Scope.GROUP_ID; - /** * @hide */ @@ -47,6 +48,12 @@ public final class BottomNavigationMenu extends MenuBuilder { "Maximum number of items supported by BottomNavigationView is " + MAX_ITEM_COUNT + ". Limit can be checked with BottomNavigationView#getMaxItemCount()"); } - return super.addInternal(group, id, categoryOrder, title); + stopDispatchingItemsChanged(); + final MenuItem item = super.addInternal(group, id, categoryOrder, title); + if (item instanceof MenuItemImpl) { + ((MenuItemImpl) item).setExclusiveCheckable(true); + } + startDispatchingItemsChanged(); + return item; } } diff --git a/design/src/android/support/design/internal/BottomNavigationMenuView.java b/design/src/android/support/design/internal/BottomNavigationMenuView.java index 096bdd83aa..ba43b5bf7a 100644 --- a/design/src/android/support/design/internal/BottomNavigationMenuView.java +++ b/design/src/android/support/design/internal/BottomNavigationMenuView.java @@ -97,10 +97,6 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { @Override public void initialize(MenuBuilder menu) { mMenu = menu; - if (mMenu == null) return; - if (mMenu.size() > mActiveButton) { - mMenu.getItem(mActiveButton).setChecked(true); - } } @Override @@ -125,7 +121,7 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } } } else { - final int maxAvailable = width / count; + final int maxAvailable = width / (count == 0 ? 1 : count); final int childWidth = Math.min(maxAvailable, mActiveItemMaxWidth); int extra = width - childWidth * count; for (int i = 0; i < count; i++) { @@ -181,9 +177,9 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } /** - * Set the tint which is applied to the menu items' icons. + * Sets the tint which is applied to the menu items' icons. * - * @param tint the tint to apply. + * @param tint the tint to apply */ public void setIconTintList(ColorStateList tint) { mItemIconTint = tint; @@ -196,7 +192,7 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { /** * Returns the tint which is applied to menu items' icons. * - * @return The ColorStateList that is used to tint menu items' icons. + * @return the ColorStateList that is used to tint menu items' icons */ @Nullable public ColorStateList getIconTintList() { @@ -204,7 +200,7 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } /** - * Set the text color to be used on menu items. + * Sets the text color to be used on menu items. * * @param color the ColorStateList used for menu items' text. */ @@ -219,15 +215,16 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { /** * Returns the text color used on menu items. * - * @return the ColorStateList used for menu items' text. + * @return the ColorStateList used for menu items' text */ public ColorStateList getItemTextColor() { return mItemTextColor; } /** - * Sets the resource id to be used for item background. - * @param background the resource id of the background. + * Sets the resource ID to be used for item background. + * + * @param background the resource ID of the background */ public void setItemBackgroundRes(int background) { mItemBackgroundRes = background; @@ -238,9 +235,9 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } /** - * Returns the background resource of the menu items. + * Returns the resource ID for the background of the menu items. * - * @return the resource id of the background. + * @return the resource ID for the background */ public int getItemBackgroundRes() { return mItemBackgroundRes; @@ -257,6 +254,9 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } } removeAllViews(); + if (mMenu.size() == 0) { + return; + } mButtons = new BottomNavigationItemView[mMenu.size()]; mShiftingMode = mMenu.size() > 3; for (int i = 0; i < mMenu.size(); i++) { @@ -274,6 +274,8 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { child.setOnClickListener(mOnClickListener); addView(child); } + mActiveButton = Math.min(mMenu.size() - 1, mActiveButton); + mMenu.getItem(mActiveButton).setChecked(true); } public void updateMenuView() { @@ -285,6 +287,9 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { } for (int i = 0; i < menuSize; i++) { mPresenter.setUpdateSuspended(true); + if (mMenu.getItem(i).isChecked()) { + mActiveButton = i; + } mButtons[i].initialize((MenuItemImpl) mMenu.getItem(i), 0); mPresenter.setUpdateSuspended(false); } @@ -295,10 +300,7 @@ public class BottomNavigationMenuView extends ViewGroup implements MenuView { mAnimationHelper.beginDelayedTransition(this); - mPresenter.setUpdateSuspended(true); - mButtons[mActiveButton].setChecked(false); - mButtons[newButton].setChecked(true); - mPresenter.setUpdateSuspended(false); + mMenu.getItem(newButton).setChecked(true); mActiveButton = newButton; } diff --git a/design/src/android/support/design/widget/BottomNavigationView.java b/design/src/android/support/design/widget/BottomNavigationView.java index 8a8e0bd052..e3a81b3f77 100644 --- a/design/src/android/support/design/widget/BottomNavigationView.java +++ b/design/src/android/support/design/widget/BottomNavigationView.java @@ -18,6 +18,7 @@ package android.support.design.widget; import android.content.Context; import android.content.res.ColorStateList; +import android.os.Build; import android.support.annotation.DrawableRes; import android.support.annotation.NonNull; import android.support.annotation.Nullable; @@ -25,6 +26,8 @@ import android.support.design.R; import android.support.design.internal.BottomNavigationMenu; import android.support.design.internal.BottomNavigationMenuView; import android.support.design.internal.BottomNavigationPresenter; +import android.support.v4.content.ContextCompat; +import android.support.v4.view.ViewCompat; import android.support.v7.content.res.AppCompatResources; import android.support.v7.view.SupportMenuInflater; import android.support.v7.view.menu.MenuBuilder; @@ -35,6 +38,7 @@ import android.view.Gravity; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; +import android.view.View; import android.view.ViewGroup; import android.widget.FrameLayout; @@ -117,7 +121,7 @@ public class BottomNavigationView extends FrameLayout { mPresenter.setBottomNavigationMenuView(mMenuView); mMenuView.setPresenter(mPresenter); mMenu.addMenuPresenter(mPresenter); - + mPresenter.initForMenu(getContext(), mMenu); // Custom attributes TintTypedArray a = TintTypedArray.obtainStyledAttributes(context, attrs, @@ -138,6 +142,10 @@ public class BottomNavigationView extends FrameLayout { mMenuView.setItemTextColor( createDefaultColorStateList(android.R.attr.textColorSecondary)); } + if (a.hasValue(R.styleable.BottomNavigationView_elevation)) { + ViewCompat.setElevation(this, a.getDimensionPixelSize( + R.styleable.BottomNavigationView_elevation, 0)); + } int itemBackground = a.getResourceId(R.styleable.BottomNavigationView_itemBackground, 0); mMenuView.setItemBackgroundRes(itemBackground); @@ -148,6 +156,9 @@ public class BottomNavigationView extends FrameLayout { a.recycle(); addView(mMenuView, params); + if (Build.VERSION.SDK_INT < 21) { + addCompatibilityTopDivider(context); + } mMenu.setCallback(new MenuBuilder.Callback() { @Override @@ -188,7 +199,6 @@ public class BottomNavigationView extends FrameLayout { public void inflateMenu(int resId) { mPresenter.setUpdateSuspended(true); getMenuInflater().inflate(resId, mMenu); - mPresenter.initForMenu(getContext(), mMenu); mPresenter.setUpdateSuspended(false); mPresenter.updateMenuView(true); } @@ -224,10 +234,13 @@ public class BottomNavigationView extends FrameLayout { } /** - * Returns the text color used on menu items. + * Returns colors used for the different states (normal, selected, focused, etc.) of the menu + * item text. * * @see #setItemTextColor(ColorStateList) * + * @return the ColorStateList of colors used for the different states of the menu items text. + * * @attr ref R.styleable#BottomNavigationView_itemTextColor */ @Nullable @@ -236,7 +249,8 @@ public class BottomNavigationView extends FrameLayout { } /** - * Set the text color to be used on menu items. + * Set the colors to use for the different states (normal, selected, focused, etc.) of the menu + * item text. * * @see #getItemTextColor() * @@ -286,6 +300,18 @@ public class BottomNavigationView extends FrameLayout { boolean onNavigationItemSelected(@NonNull MenuItem item); } + private void addCompatibilityTopDivider(Context context) { + View divider = new View(context); + divider.setBackgroundColor( + ContextCompat.getColor(context, R.color.design_bottom_navigation_shadow_color)); + FrameLayout.LayoutParams dividerParams = new FrameLayout.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + getResources().getDimensionPixelSize( + R.dimen.design_bottom_navigation_shadow_height)); + divider.setLayoutParams(dividerParams); + addView(divider); + } + private MenuInflater getMenuInflater() { if (mMenuInflater == null) { mMenuInflater = new SupportMenuInflater(getContext()); diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java index 95669341cd..249aad2a99 100644 --- a/design/src/android/support/design/widget/CoordinatorLayout.java +++ b/design/src/android/support/design/widget/CoordinatorLayout.java @@ -155,6 +155,7 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen private final Rect mTempRect2 = new Rect(); private final Rect mTempRect3 = new Rect(); private final Rect mTempRect4 = new Rect(); + private final Rect mTempRect5 = new Rect(); private final int[] mTempIntPair = new int[2]; private Paint mScrimPaint; @@ -731,6 +732,11 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); + if (child.getVisibility() == GONE) { + // If the child is GONE, skip... + continue; + } + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); int keylineWidthUsed = 0; @@ -843,6 +849,11 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen final int childCount = mDependencySortedChildren.size(); for (int i = 0; i < childCount; i++) { final View child = mDependencySortedChildren.get(i); + if (child.getVisibility() == GONE) { + // If the child is GONE, skip... + continue; + } + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Behavior behavior = lp.getBehavior(); @@ -1310,42 +1321,51 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen private void offsetChildByInset(final View child, final Rect inset, final int layoutDirection) { if (!ViewCompat.isLaidOut(child)) { - // The view has not been laid out yet, - // so we can't obtain its bounds. + // The view has not been laid out yet, so we can't obtain its bounds. return; } - final LayoutParams lp = (LayoutParams) child.getLayoutParams(); - final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges, - layoutDirection); + final Rect bounds = mTempRect5; + bounds.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + if (bounds.isEmpty()) { + // Bounds are empty so there is nothing to dodge against, skip... + return; + } + final LayoutParams lp = (LayoutParams) child.getLayoutParams(); final Behavior behavior = lp.getBehavior(); - final Rect rect = mTempRect3; - if (behavior != null && behavior.getInsetDodgeRect(this, child, rect)) { - // Make sure that it intersects the views bounds - if (!rect.intersect(child.getLeft(), child.getTop(), - child.getRight(), child.getBottom())) { - throw new IllegalArgumentException("Rect should intersect with child's bounds."); + final Rect dodgeRect = mTempRect3; + dodgeRect.setEmpty(); + + if (behavior != null && behavior.getInsetDodgeRect(this, child, dodgeRect)) { + // Make sure that the rect is within the view's bounds + if (!bounds.contains(dodgeRect)) { + throw new IllegalArgumentException("Rect should be within the child's bounds." + + " Rect:" + dodgeRect.toShortString() + + " | Bounds:" + bounds.toShortString()); } } else { - rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + dodgeRect.set(bounds); } - if (rect.isEmpty()) { + if (dodgeRect.isEmpty()) { // Rect is empty so there is nothing to dodge against, skip... return; } + final int absDodgeInsetEdges = GravityCompat.getAbsoluteGravity(lp.dodgeInsetEdges, + layoutDirection); + boolean offsetY = false; if ((absDodgeInsetEdges & Gravity.TOP) == Gravity.TOP) { - int distance = rect.top - lp.topMargin - lp.mInsetOffsetY; + int distance = dodgeRect.top - lp.topMargin - lp.mInsetOffsetY; if (distance < inset.top) { setInsetOffsetY(child, inset.top - distance); offsetY = true; } } if ((absDodgeInsetEdges & Gravity.BOTTOM) == Gravity.BOTTOM) { - int distance = getHeight() - rect.bottom - lp.bottomMargin + lp.mInsetOffsetY; + int distance = getHeight() - dodgeRect.bottom - lp.bottomMargin + lp.mInsetOffsetY; if (distance < inset.bottom) { setInsetOffsetY(child, distance - inset.bottom); offsetY = true; @@ -1357,14 +1377,14 @@ public class CoordinatorLayout extends ViewGroup implements NestedScrollingParen boolean offsetX = false; if ((absDodgeInsetEdges & Gravity.LEFT) == Gravity.LEFT) { - int distance = rect.left - lp.leftMargin - lp.mInsetOffsetX; + int distance = dodgeRect.left - lp.leftMargin - lp.mInsetOffsetX; if (distance < inset.left) { setInsetOffsetX(child, inset.left - distance); offsetX = true; } } if ((absDodgeInsetEdges & Gravity.RIGHT) == Gravity.RIGHT) { - int distance = getWidth() - rect.right - lp.rightMargin + lp.mInsetOffsetX; + int distance = getWidth() - dodgeRect.right - lp.rightMargin + lp.mInsetOffsetX; if (distance < inset.right) { setInsetOffsetX(child, distance - inset.right); offsetX = true; diff --git a/design/src/android/support/design/widget/TextInputEditText.java b/design/src/android/support/design/widget/TextInputEditText.java index a14143936e..7235ec220d 100644 --- a/design/src/android/support/design/widget/TextInputEditText.java +++ b/design/src/android/support/design/widget/TextInputEditText.java @@ -19,6 +19,7 @@ package android.support.design.widget; import android.content.Context; import android.support.v7.widget.AppCompatEditText; import android.util.AttributeSet; +import android.view.View; import android.view.ViewParent; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; @@ -49,9 +50,13 @@ public class TextInputEditText extends AppCompatEditText { if (ic != null && outAttrs.hintText == null) { // If we don't have a hint and our parent is a TextInputLayout, use it's hint for the // EditorInfo. This allows us to display a hint in 'extract mode'. - final ViewParent parent = getParent(); - if (parent instanceof TextInputLayout) { - outAttrs.hintText = ((TextInputLayout) parent).getHint(); + ViewParent parent = getParent(); + while (parent instanceof View) { + if (parent instanceof TextInputLayout) { + outAttrs.hintText = ((TextInputLayout) parent).getHint(); + break; + } + parent = parent.getParent(); } } return ic; diff --git a/design/src/android/support/design/widget/TextInputLayout.java b/design/src/android/support/design/widget/TextInputLayout.java index 33559565e4..e645a34255 100644 --- a/design/src/android/support/design/widget/TextInputLayout.java +++ b/design/src/android/support/design/widget/TextInputLayout.java @@ -77,6 +77,16 @@ import android.widget.TextView; * {@link #setError(CharSequence)}, and a character counter via * {@link #setCounterEnabled(boolean)}.</p> * + * <p>Password visibility toggling is also supported via the + * {@link #setPasswordVisibilityToggleEnabled(boolean)} API and related attribute. + * If enabled, a button is displayed to toggle between the password being displayed as plain-text + * or disguised, when your EditText is set to display a password.</p> + * + * <p><strong>Note:</strong> When using the password toggle functionality, the 'end' compound + * drawable of the EditText will be overridden while the toggle is enabled. To ensure that any + * existing drawables are restored correctly, you should set those compound drawables relatively + * (start/end), opposed to absolutely (left/right).</p> + * * The {@link TextInputEditText} class is provided to be used as a child of this layout. Using * TextInputEditText allows TextInputLayout greater control over the visual aspects of any * text input. An example usage is as so: @@ -219,7 +229,7 @@ public class TextInputLayout extends LinearLayout { R.styleable.TextInputLayout_counterOverflowTextAppearance, 0); mPasswordToggleEnabled = a.getBoolean( - R.styleable.TextInputLayout_passwordToggleEnabled, true); + R.styleable.TextInputLayout_passwordToggleEnabled, false); mPasswordToggleDrawable = a.getDrawable(R.styleable.TextInputLayout_passwordToggleDrawable); mPasswordToggleContentDesc = a.getText( R.styleable.TextInputLayout_passwordToggleContentDescription); @@ -1041,11 +1051,15 @@ public class TextInputLayout extends LinearLayout { mPasswordToggleView.setVisibility(View.GONE); } - // Make sure that we remove the dummy end compound drawable - final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText); - if (compounds[2] == mPasswordToggleDummyDrawable) { - TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], compounds[1], - mOriginalEditTextEndDrawable, compounds[3]); + if (mPasswordToggleDummyDrawable != null) { + // Make sure that we remove the dummy end compound drawable if it exists, and then + // clear it + final Drawable[] compounds = TextViewCompat.getCompoundDrawablesRelative(mEditText); + if (compounds[2] == mPasswordToggleDummyDrawable) { + TextViewCompat.setCompoundDrawablesRelative(mEditText, compounds[0], + compounds[1], mOriginalEditTextEndDrawable, compounds[3]); + mPasswordToggleDummyDrawable = null; + } } } } diff --git a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java index 0ddbc6d51d..09a9c35ada 100644 --- a/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java +++ b/design/tests/src/android/support/design/widget/BottomNavigationViewTest.java @@ -42,6 +42,7 @@ import android.support.annotation.ColorInt; import android.support.design.test.R; import android.support.design.testutils.TestDrawable; import android.support.design.testutils.TestUtilsMatchers; +import android.support.test.annotation.UiThreadTest; import android.support.v4.content.res.ResourcesCompat; import android.test.suitebuilder.annotation.SmallTest; import android.view.Menu; @@ -77,6 +78,20 @@ public class BottomNavigationViewTest mMenuStringContent.put(R.id.destination_people, res.getString(R.string.navigate_people)); } + @UiThreadTest + @Test + @SmallTest + public void testAddItemsWithoutMenuInflation() { + BottomNavigationView navigation = new BottomNavigationView(mActivityTestRule.getActivity()); + mActivityTestRule.getActivity().setContentView(navigation); + navigation.getMenu().add("Item1"); + navigation.getMenu().add("Item2"); + assertEquals(2, navigation.getMenu().size()); + navigation.getMenu().removeItem(0); + navigation.getMenu().removeItem(0); + assertEquals(0, navigation.getMenu().size()); + } + @Test @SmallTest public void testBasics() { @@ -196,4 +211,27 @@ public class BottomNavigationViewTest onView(allOf(withId(R.id.icon), isDescendantOfA(withId(R.id.destination_people)))).check( matches(TestUtilsMatchers.drawable(blueFill, allowedComponentVariance))); } + + @UiThreadTest + @Test + @SmallTest + public void testItemChecking() throws Throwable { + final Menu menu = mBottomNavigation.getMenu(); + assertTrue(menu.getItem(0).isChecked()); + checkAndVerifyExclusiveItem(menu, R.id.destination_home); + checkAndVerifyExclusiveItem(menu, R.id.destination_profile); + checkAndVerifyExclusiveItem(menu, R.id.destination_people); + } + + private void checkAndVerifyExclusiveItem(final Menu menu, final int id) throws Throwable { + menu.findItem(id).setChecked(true); + for (int i = 0; i < menu.size(); i++) { + final MenuItem item = menu.getItem(i); + if (item.getItemId() == id) { + assertTrue(item.isChecked()); + } else { + assertFalse(item.isChecked()); + } + } + } } diff --git a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java index 9fff6a24ae..47919a75c1 100644 --- a/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java +++ b/design/tests/src/android/support/design/widget/CoordinatorLayoutTest.java @@ -19,6 +19,7 @@ package android.support.design.widget; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import static org.mockito.Matchers.any; import static org.mockito.Matchers.same; import static org.mockito.Mockito.atLeastOnce; @@ -392,4 +393,93 @@ public class CoordinatorLayoutTest extends BaseInstrumentationTestCase<Coordinat // Wait for a layout. mInstrumentation.waitForIdleSync(); } + + @Test + public void testGoneNotMeasuredLaidOut() throws Throwable { + final CoordinatorLayoutActivity activity = mActivityTestRule.getActivity(); + final CoordinatorLayout col = activity.mCoordinatorLayout; + + // Now create a GONE view and add it to the CoordinatorLayout + final View imageView = new View(activity); + imageView.setVisibility(View.GONE); + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + col.addView(imageView, 200, 200); + } + }); + // Wait for a layout and measure pass + mInstrumentation.waitForIdleSync(); + + // And assert that it has not been laid out + assertFalse(imageView.getMeasuredWidth() > 0); + assertFalse(imageView.getMeasuredHeight() > 0); + assertFalse(ViewCompat.isLaidOut(imageView)); + + // Now set the view to INVISIBLE + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + imageView.setVisibility(View.INVISIBLE); + } + }); + // Wait for a layout and measure pass + mInstrumentation.waitForIdleSync(); + + // And assert that it has been laid out + assertTrue(imageView.getMeasuredWidth() > 0); + assertTrue(imageView.getMeasuredHeight() > 0); + assertTrue(ViewCompat.isLaidOut(imageView)); + } + + @Test + public void testDodgeInsetViewWithEmptyBounds() throws Throwable { + final CoordinatorLayout col = mActivityTestRule.getActivity().mCoordinatorLayout; + + // Add a view with zero height/width which is set to dodge its bounds + final View view = new View(col.getContext()); + final Behavior spyBehavior = spy(new DodgeBoundsBehavior()); + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams(); + lp.dodgeInsetEdges = Gravity.BOTTOM; + lp.gravity = Gravity.BOTTOM; + lp.height = 0; + lp.width = 0; + lp.setBehavior(spyBehavior); + col.addView(view, lp); + } + }); + + // Wait for a layout + mInstrumentation.waitForIdleSync(); + + // Now add an non-empty bounds inset view to the bottom of the CoordinatorLayout + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + final View dodge = new View(col.getContext()); + final CoordinatorLayout.LayoutParams lp = col.generateDefaultLayoutParams(); + lp.insetEdge = Gravity.BOTTOM; + lp.gravity = Gravity.BOTTOM; + lp.height = 60; + lp.width = CoordinatorLayout.LayoutParams.MATCH_PARENT; + col.addView(dodge, lp); + } + }); + + // Verify that the Behavior of the view with empty bounds does not have its + // getInsetDodgeRect() called + verify(spyBehavior, never()) + .getInsetDodgeRect(same(col), same(view), any(Rect.class)); + } + + public static class DodgeBoundsBehavior extends Behavior<View> { + @Override + public boolean getInsetDodgeRect(CoordinatorLayout parent, View child, Rect rect) { + rect.set(child.getLeft(), child.getTop(), child.getRight(), child.getBottom()); + return true; + } + } } diff --git a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java index 66ea00b9c0..0d84865585 100755 --- a/design/tests/src/android/support/design/widget/TextInputLayoutTest.java +++ b/design/tests/src/android/support/design/widget/TextInputLayoutTest.java @@ -36,6 +36,7 @@ import static android.support.test.espresso.matcher.ViewMatchers.withText; import static org.hamcrest.Matchers.not; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import android.app.Activity; @@ -46,8 +47,10 @@ import android.support.design.test.R; import android.support.test.annotation.UiThreadTest; import android.support.test.espresso.NoMatchingViewException; import android.support.test.espresso.ViewAssertion; +import android.support.v4.widget.TextViewCompat; import android.test.suitebuilder.annotation.SmallTest; import android.view.View; +import android.view.inputmethod.EditorInfo; import android.widget.EditText; import org.junit.Test; @@ -217,6 +220,25 @@ public class TextInputLayoutTest extends BaseInstrumentationTestCase<TextInputLa onView(withId(R.id.textinput_edittext)).check(matches(not(isEnabled()))); } + @UiThreadTest + @Test + public void testExtractUiHintSet() { + final Activity activity = mActivityTestRule.getActivity(); + + // Set a hint on the TextInputLayout + final TextInputLayout layout = (TextInputLayout) activity.findViewById(R.id.textinput); + layout.setHint(INPUT_TEXT); + + final EditText editText = (EditText) activity.findViewById(R.id.textinput_edittext); + + // Now manually pass in a EditorInfo to the EditText and make sure it updates the + // hintText to our known value + final EditorInfo info = new EditorInfo(); + editText.onCreateInputConnection(info); + + assertEquals(INPUT_TEXT, info.hintText); + } + /** * Regression test for b/31663756. */ @@ -230,6 +252,68 @@ public class TextInputLayoutTest extends BaseInstrumentationTestCase<TextInputLa layout.drawableStateChanged(); } + @Test + public void testMaintainsLeftRightCompoundDrawables() throws Throwable { + final Activity activity = mActivityTestRule.getActivity(); + + // Set a known set of test compound drawables on the EditText + final Drawable left = new ColorDrawable(Color.RED); + final Drawable top = new ColorDrawable(Color.GREEN); + final Drawable right = new ColorDrawable(Color.BLUE); + final Drawable bottom = new ColorDrawable(Color.BLACK); + + final TextInputEditText editText = new TextInputEditText(activity); + editText.setCompoundDrawables(left, top, right, bottom); + + // Now add the EditText to a TextInputLayout + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + TextInputLayout til = (TextInputLayout) + activity.findViewById(R.id.textinput_noedittext); + til.addView(editText); + } + }); + + // Finally assert that all of the drawables are untouched + final Drawable[] compoundDrawables = editText.getCompoundDrawables(); + assertSame(left, compoundDrawables[0]); + assertSame(top, compoundDrawables[1]); + assertSame(right, compoundDrawables[2]); + assertSame(bottom, compoundDrawables[3]); + } + + @Test + public void testMaintainsStartEndCompoundDrawables() throws Throwable { + final Activity activity = mActivityTestRule.getActivity(); + + // Set a known set of test compound drawables on the EditText + final Drawable start = new ColorDrawable(Color.RED); + final Drawable top = new ColorDrawable(Color.GREEN); + final Drawable end = new ColorDrawable(Color.BLUE); + final Drawable bottom = new ColorDrawable(Color.BLACK); + + final TextInputEditText editText = new TextInputEditText(activity); + TextViewCompat.setCompoundDrawablesRelative(editText, start, top, end, bottom); + + // Now add the EditText to a TextInputLayout + mActivityTestRule.runOnUiThread(new Runnable() { + @Override + public void run() { + TextInputLayout til = (TextInputLayout) + activity.findViewById(R.id.textinput_noedittext); + til.addView(editText); + } + }); + + // Finally assert that all of the drawables are untouched + final Drawable[] compoundDrawables = TextViewCompat.getCompoundDrawablesRelative(editText); + assertSame(start, compoundDrawables[0]); + assertSame(top, compoundDrawables[1]); + assertSame(end, compoundDrawables[2]); + assertSame(bottom, compoundDrawables[3]); + } + static ViewAssertion isHintExpanded(final boolean expanded) { return new ViewAssertion() { @Override diff --git a/graphics/drawable/animated/build.gradle b/graphics/drawable/animated/build.gradle index df11178b09..31cb13d280 100644 --- a/graphics/drawable/animated/build.gradle +++ b/graphics/drawable/animated/build.gradle @@ -52,6 +52,10 @@ android { testOptions { unitTests.returnDefaultValues = true } + + buildTypes.all { + consumerProguardFiles 'proguard-rules.pro' + } } android.libraryVariants.all { variant -> diff --git a/graphics/drawable/animated/proguard-rules.pro b/graphics/drawable/animated/proguard-rules.pro new file mode 100644 index 0000000000..4695a39f11 --- /dev/null +++ b/graphics/drawable/animated/proguard-rules.pro @@ -0,0 +1,19 @@ +# Copyright (C) 2016 The Android Open Source Project +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# keep setters in VectorDrawables so that animations can still work. +-keepclassmembers class android.support.graphics.drawable.VectorDrawableCompat$* { + void set*(***); + *** get*(); +} diff --git a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java index 0b0522eb05..599631bd61 100644 --- a/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java +++ b/graphics/drawable/animated/src/android/support/graphics/drawable/AnimatedVectorDrawableCompat.java @@ -55,12 +55,81 @@ import java.util.List; * For older API version, this class uses {@link android.animation.ObjectAnimator} and * {@link android.animation.AnimatorSet} to animate the properties of a * {@link VectorDrawableCompat} to create an animated drawable. - * <p> + * <p/> * AnimatedVectorDrawableCompat are defined in the same XML format as {@link AnimatedVectorDrawable}. - * </p> + * <p/> + * Here are all the animatable attributes in {@link VectorDrawableCompat}: + * <table border="2" align="center" cellpadding="5"> + * <thead> + * <tr> + * <th>Element Name</th> + * <th>Animatable attribute name</th> + * </tr> + * </thead> + * <tr> + * <td><vector></td> + * <td>alpha</td> + * </tr> + * <tr> + * <td rowspan="7"><group></td> + * <td>rotation</td> + * </tr> + * <tr> + * <td>pivotX</td> + * </tr> + * <tr> + * <td>pivotY</td> + * </tr> + * <tr> + * <td>scaleX</td> + * </tr> + * <tr> + * <td>scaleY</td> + * </tr> + * <tr> + * <td>translateX</td> + * </tr> + * <tr> + * <td>translateY</td> + * </tr> + * <tr> + * <td rowspan="8"><path></td> + * <td>fillColor</td> + * </tr> + * <tr> + * <td>strokeColor</td> + * </tr> + * <tr> + * <td>strokeWidth</td> + * </tr> + * <tr> + * <td>strokeAlpha</td> + * </tr> + * <tr> + * <td>fillAlpha</td> + * </tr> + * <tr> + * <td>trimPathStart</td> + * </tr> + * <tr> + * <td>trimPathOffset</td> + * </tr> + * </table> + * <p/> * You can always create a AnimatedVectorDrawableCompat object and use it as a Drawable by the Java * API. In order to refer to AnimatedVectorDrawableCompat inside a XML file, you can use * app:srcCompat attribute in AppCompat library's ImageButton or ImageView. + * <p/> + * Note that the animation in AnimatedVectorDrawableCompat has to be valid and functional based on + * the SDK version the app will be running on. Before SDK version 21, the animation system didn't + * support the following features: + * <ul> + * <li>Path Morphing (PathType evaluator). This is used for morphing one path into another.</li> + * <li>Path Interpolation. This is used to defined a flexible interpolator (represented as a path) + * instead of the system defined ones like LinearInterpolator.</li> + * <li>Animating 2 values in one ObjectAnimator according to one path's X value and Y value. One + * usage is moving one object in both X and Y dimensions along an path.</li> + * </ul> */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) public class AnimatedVectorDrawableCompat extends VectorDrawableCommon implements Animatable { @@ -99,13 +168,18 @@ public class AnimatedVectorDrawableCompat extends VectorDrawableCommon implement } } + /** + * mutate() will be effective only if the getConstantState() is returning non-null. + * Otherwise, it just return the current object without modification. + */ @Override public Drawable mutate() { if (mDelegateDrawable != null) { mDelegateDrawable.mutate(); - return this; } - throw new IllegalStateException("Mutate() is not supported for older platform"); + // For older platforms that there is no delegated drawable, we just return this without + // any modification here, and the getConstantState() will return null in this case. + return this; } diff --git a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java index 2e50823425..306f5107b0 100644 --- a/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java +++ b/graphics/drawable/animated/tests/src/android/support/graphics/drawable/tests/AnimatedVectorDrawableTest.java @@ -268,6 +268,8 @@ public class AnimatedVectorDrawableTest { assertEquals(0x40, d1.getAlpha()); assertEquals(0x20, d2.getAlpha()); assertEquals(originalAlpha, d3.getAlpha()); + } else { + assertEquals(d1.mutate(), d1); } } } diff --git a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java index f4d11985fd..1a0f5af5b8 100644 --- a/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java +++ b/graphics/drawable/static/src/android/support/graphics/drawable/VectorDrawableCompat.java @@ -52,9 +52,6 @@ import android.util.Xml; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; -import org.xmlpull.v1.XmlPullParser; -import org.xmlpull.v1.XmlPullParserException; - import java.io.IOException; import java.util.ArrayList; import java.util.Stack; @@ -84,36 +81,27 @@ import java.util.Stack; * <dl> * <dt><code>android:name</code></dt> * <dd>Defines the name of this vector drawable.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:width</code></dt> * <dd>Used to define the intrinsic width of the drawable. * This support all the dimension units, normally specified with dp.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:height</code></dt> * <dd>Used to define the intrinsic height the drawable. * This support all the dimension units, normally specified with dp.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:viewportWidth</code></dt> * <dd>Used to define the width of the viewport space. Viewport is basically * the virtual canvas where the paths are drawn on.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:viewportHeight</code></dt> * <dd>Used to define the height of the viewport space. Viewport is basically * the virtual canvas where the paths are drawn on.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:tint</code></dt> * <dd>The color to apply to the drawable as a tint. By default, no tint is applied.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:tintMode</code></dt> - * <dd>The Porter-Duff blending mode for the tint color. The default value is src_in.</dd> - * <dd>Animatable : No.</dd> + * <dd>The Porter-Duff blending mode for the tint color. Default is src_in.</dd> * <dt><code>android:autoMirrored</code></dt> * <dd>Indicates if the drawable needs to be mirrored when its layout direction is - * RTL (right-to-left).</dd> - * <dd>Animatable : No.</dd> + * RTL (right-to-left). Default is false.</dd> * <dt><code>android:alpha</code></dt> - * <dd>The opacity of this drawable.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The opacity of this drawable. Default is 1.</dd> * </dl></dd> * </dl> * @@ -125,32 +113,24 @@ import java.util.Stack; * <dl> * <dt><code>android:name</code></dt> * <dd>Defines the name of the group.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:rotation</code></dt> - * <dd>The degrees of rotation of the group.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The degrees of rotation of the group. Default is 0.</dd> * <dt><code>android:pivotX</code></dt> * <dd>The X coordinate of the pivot for the scale and rotation of the group. - * This is defined in the viewport space.</dd> - * <dd>Animatable : Yes.</dd> + * This is defined in the viewport space. Default is 0.</dd> * <dt><code>android:pivotY</code></dt> * <dd>The Y coordinate of the pivot for the scale and rotation of the group. - * This is defined in the viewport space.</dd> - * <dd>Animatable : Yes.</dd> + * This is defined in the viewport space. Default is 0.</dd> * <dt><code>android:scaleX</code></dt> - * <dd>The amount of scale on the X Coordinate.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The amount of scale on the X Coordinate. Default is 1.</dd> * <dt><code>android:scaleY</code></dt> - * <dd>The amount of scale on the Y coordinate.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The amount of scale on the Y coordinate. Default is 1.</dd> * <dt><code>android:translateX</code></dt> * <dd>The amount of translation on the X coordinate. - * This is defined in the viewport space.</dd> - * <dd>Animatable : Yes.</dd> + * This is defined in the viewport space. Default is 0.</dd> * <dt><code>android:translateY</code></dt> * <dd>The amount of translation on the Y coordinate. - * This is defined in the viewport space.</dd> - * <dd>Animatable : Yes.</dd> + * This is defined in the viewport space. Default is 0.</dd> * </dl></dd> * </dl> * @@ -160,49 +140,36 @@ import java.util.Stack; * <dl> * <dt><code>android:name</code></dt> * <dd>Defines the name of the path.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:pathData</code></dt> * <dd>Defines path data using exactly same format as "d" attribute * in the SVG's path data. This is defined in the viewport space.</dd> - * <dd>Animatable : Yes.</dd> * <dt><code>android:fillColor</code></dt> * <dd>Specifies the color used to fill the path. * If this property is animated, any value set by the animation will override the original value. * No path fill is drawn if this property is not specified.</dd> - * <dd>Animatable : Yes.</dd> * <dt><code>android:strokeColor</code></dt> * <dd>Specifies the color used to draw the path outline. * If this property is animated, any value set by the animation will override the original value. * No path outline is drawn if this property is not specified.</dd> - * <dd>Animatable : Yes.</dd> * <dt><code>android:strokeWidth</code></dt> - * <dd>The width a path stroke.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The width a path stroke. Default is 0.</dd> * <dt><code>android:strokeAlpha</code></dt> - * <dd>The opacity of a path stroke.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The opacity of a path stroke. Default is 1.</dd> * <dt><code>android:fillAlpha</code></dt> - * <dd>The opacity to fill the path with.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The opacity to fill the path with. Default is 1.</dd> * <dt><code>android:trimPathStart</code></dt> - * <dd>The fraction of the path to trim from the start, in the range from 0 to 1.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The fraction of the path to trim from the start, in the range from 0 to 1. Default is 0.</dd> * <dt><code>android:trimPathEnd</code></dt> - * <dd>The fraction of the path to trim from the end, in the range from 0 to 1.</dd> - * <dd>Animatable : Yes.</dd> + * <dd>The fraction of the path to trim from the end, in the range from 0 to 1. Default is 1.</dd> * <dt><code>android:trimPathOffset</code></dt> * <dd>Shift trim region (allows showed region to include the start and end), in the range - * from 0 to 1.</dd> - * <dd>Animatable : Yes.</dd> + * from 0 to 1. Default is 0.</dd> * <dt><code>android:strokeLineCap</code></dt> - * <dd>Sets the linecap for a stroked path: butt, round, square.</dd> - * <dd>Animatable : No.</dd> + * <dd>Sets the linecap for a stroked path: butt, round, square. Default is butt.</dd> * <dt><code>android:strokeLineJoin</code></dt> - * <dd>Sets the lineJoin for a stroked path: miter,round,bevel.</dd> - * <dd>Animatable : No.</dd> + * <dd>Sets the lineJoin for a stroked path: miter,round,bevel. Default is miter.</dd> * <dt><code>android:strokeMiterLimit</code></dt> - * <dd>Sets the Miter limit for a stroked path.</dd> - * <dd>Animatable : No.</dd> + * <dd>Sets the Miter limit for a stroked path. Default is 4.</dd> * </dl></dd> * </dl> * @@ -213,13 +180,14 @@ import java.util.Stack; * <dl> * <dt><code>android:name</code></dt> * <dd>Defines the name of the clip path.</dd> - * <dd>Animatable : No.</dd> * <dt><code>android:pathData</code></dt> * <dd>Defines clip path using the same format as "d" attribute * in the SVG's path data.</dd> - * <dd>Animatable : Yes.</dd> * </dl></dd> * </dl> + * <p/> + * Note that theme attributes in XML file are supported through + * <code>{@link #inflate(Resources, XmlPullParser, AttributeSet, Theme)}</code>. */ @TargetApi(Build.VERSION_CODES.LOLLIPOP) diff --git a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java index 1fe0da9d71..79f61c31a5 100644 --- a/media-compat/java/android/support/v4/media/MediaBrowserCompat.java +++ b/media-compat/java/android/support/v4/media/MediaBrowserCompat.java @@ -1784,7 +1784,9 @@ public final class MediaBrowserCompat { @Override protected void onReceiveResult(int resultCode, Bundle resultData) { - resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader()); + if (resultData != null) { + resultData.setClassLoader(MediaBrowserCompat.class.getClassLoader()); + } if (resultCode != 0 || resultData == null || !resultData.containsKey(MediaBrowserServiceCompat.KEY_MEDIA_ITEM)) { mCallback.onError(mMediaId); diff --git a/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml b/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml index aa07328937..1c60d90cbf 100644 --- a/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml +++ b/samples/Support7Demos/res/layout/appcompat_widgets_buttons.xml @@ -16,8 +16,9 @@ --> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" - android:layout_width="match_parent" - android:layout_height="match_parent"> + android:layout_width="match_parent" + android:layout_height="match_parent" + xmlns:app="http://schemas.android.com/apk/res-auto"> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" @@ -77,6 +78,19 @@ android:text="Button (borderless + colored)" style="@style/Widget.AppCompat.Button.Borderless.Colored"/> + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button (colored)" + style="@style/Widget.AppCompat.Button.Colored"/> + + <Button + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Button (colored + tinted)" + app:backgroundTint="#00FF00" + style="@style/Widget.AppCompat.Button.Colored"/> + <RatingBar android:layout_width="wrap_content" android:layout_height="wrap_content"/> diff --git a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml index c86843009a..b2a4d59b09 100644 --- a/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml +++ b/samples/SupportDesignDemos/res/layout/design_bottom_navigation_view.xml @@ -52,11 +52,18 @@ android:text="@string/bottomnavigation_tint" android:layout_below="@+id/button_remove"/> + <Button + android:id="@+id/button_select_next" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/bottomnavigation_select_next" + android:layout_below="@+id/button_tint"/> + <TextView android:id="@+id/selected_item" android:layout_width="wrap_content" android:layout_height="wrap_content" - android:layout_below="@+id/button_tint"/> + android:layout_below="@+id/button_select_next"/> </RelativeLayout> </ScrollView> <android.support.design.widget.BottomNavigationView @@ -64,6 +71,6 @@ android:layout_width="match_parent" android:layout_height="56dp" android:layout_gravity="bottom" - android:background="#eee" + android:background="#fafafa" app:menu="@menu/sample_bottom_menu"/> </FrameLayout> diff --git a/samples/SupportDesignDemos/res/values/strings.xml b/samples/SupportDesignDemos/res/values/strings.xml index 95900ca163..c257f1de5f 100644 --- a/samples/SupportDesignDemos/res/values/strings.xml +++ b/samples/SupportDesignDemos/res/values/strings.xml @@ -127,4 +127,5 @@ <string name="bottomnavigation_add">Add an item</string> <string name="bottomnavigation_remove">Remove an item</string> <string name="bottomnavigation_tint">Toggle tint</string> + <string name="bottomnavigation_select_next">Select next item</string> </resources> diff --git a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java index 34422181ea..fa9b0b6705 100644 --- a/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java +++ b/samples/SupportDesignDemos/src/com/example/android/support/design/widget/BottomNavigationViewUsage.java @@ -76,6 +76,20 @@ public class BottomNavigationViewUsage extends AppCompatActivity { } } }); + Button buttonNext = (Button) findViewById(R.id.button_select_next); + buttonNext.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View view) { + final int menuSize = bottom.getMenu().size(); + int currentlySelected = 0; + for (int i = 0; i < menuSize; i++) { + if (bottom.getMenu().getItem(i).isChecked()) { + currentlySelected = i; + } + } + bottom.getMenu().getItem((currentlySelected + 1) % menuSize).setChecked(true); + } + }); final TextView selectedItem = (TextView) findViewById(R.id.selected_item); bottom.setOnNavigationItemSelectedListener( new BottomNavigationView.OnNavigationItemSelectedListener() { diff --git a/v13/api25/android/support/v13/view/inputmethod/InputContentInfoCompatApi25.java b/v13/api25/android/support/v13/view/inputmethod/InputContentInfoCompatApi25.java index c485ca6724..a9335d084d 100644 --- a/v13/api25/android/support/v13/view/inputmethod/InputContentInfoCompatApi25.java +++ b/v13/api25/android/support/v13/view/inputmethod/InputContentInfoCompatApi25.java @@ -44,6 +44,6 @@ final class InputContentInfoCompatApi25 { } public static void releasePermission(Object inputContentInfo) { - ((InputContentInfo) inputContentInfo).requestPermission(); + ((InputContentInfo) inputContentInfo).releasePermission(); } } diff --git a/v17/leanback/res/layout/lb_guidedstep_fragment.xml b/v17/leanback/res/layout/lb_guidedstep_fragment.xml index adf9d4f404..bbe21ba73f 100644 --- a/v17/leanback/res/layout/lb_guidedstep_fragment.xml +++ b/v17/leanback/res/layout/lb_guidedstep_fragment.xml @@ -33,6 +33,7 @@ <LinearLayout android:id="@+id/content_frame" android:orientation="horizontal" + android:baselineAligned="false" android:layout_width="match_parent" android:layout_height="match_parent"> @@ -40,8 +41,7 @@ android:id="@+id/content_fragment" android:layout_width="0dp" android:layout_weight="1" - android:layout_height="match_parent" - android:layout_alignParentStart="true" /> + android:layout_height="match_parent" /> <android.support.v17.leanback.widget.NonOverlappingFrameLayout android:id="@+id/action_fragment_root" @@ -53,8 +53,7 @@ android:paddingStart="@dimen/lb_guidedactions_section_shadow_width" android:layout_width="0dp" android:layout_weight="?attr/guidedActionContentWidthWeight" - android:layout_height="match_parent" - android:layout_alignParentEnd="true"> + android:layout_height="match_parent" > <android.support.v17.leanback.widget.NonOverlappingView android:id="@+id/action_fragment_background" diff --git a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java index 72cb5a15eb..aef967891d 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/BackgroundSupportFragment.java @@ -15,8 +15,8 @@ */ package android.support.v17.leanback.app; -import android.support.annotation.RestrictTo; import android.support.v4.app.Fragment; +import android.support.annotation.RestrictTo; import static android.support.annotation.RestrictTo.Scope.GROUP_ID; 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 0f7cfa0b2c..be56ea5f34 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java @@ -1006,11 +1006,8 @@ public class BrowseFragment extends BaseFragment { mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) { return true; } - if (getTitleView() != null && - getTitleView().requestFocus(direction, previouslyFocusedRect)) { - return true; - } - return false; + return getTitleView() != null && + getTitleView().requestFocus(direction, previouslyFocusedRect); } @Override @@ -1120,8 +1117,8 @@ public class BrowseFragment extends BaseFragment { .getMainFragmentAdapter(); mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); - mIsPageRow = savedInstanceState != null ? - savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false; + mIsPageRow = savedInstanceState != null + && savedInstanceState.getBoolean(IS_PAGE_ROW, false); mSelectedPosition = savedInstanceState != null ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0; 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 c88438e280..d85a93a9a3 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java @@ -1008,11 +1008,8 @@ public class BrowseSupportFragment extends BaseSupportFragment { mMainFragment.getView().requestFocus(direction, previouslyFocusedRect)) { return true; } - if (getTitleView() != null && - getTitleView().requestFocus(direction, previouslyFocusedRect)) { - return true; - } - return false; + return getTitleView() != null && + getTitleView().requestFocus(direction, previouslyFocusedRect); } @Override @@ -1122,8 +1119,8 @@ public class BrowseSupportFragment extends BaseSupportFragment { .getMainFragmentAdapter(); mMainFragmentAdapter.setFragmentHost(new FragmentHostImpl()); - mIsPageRow = savedInstanceState != null ? - savedInstanceState.getBoolean(IS_PAGE_ROW, false) : false; + mIsPageRow = savedInstanceState != null + && savedInstanceState.getBoolean(IS_PAGE_ROW, false); mSelectedPosition = savedInstanceState != null ? savedInstanceState.getInt(CURRENT_SELECTED_POSITION, 0) : 0; 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 f5b27df281..2552f8000b 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/GuidedStepSupportFragment.java @@ -17,7 +17,6 @@ package android.support.v17.leanback.app; import android.animation.Animator; import android.animation.AnimatorSet; -import android.support.annotation.RestrictTo; import android.support.v4.app.FragmentActivity; import android.support.v4.app.Fragment; import android.support.v4.app.FragmentManager; @@ -27,6 +26,7 @@ import android.content.Context; import android.os.Build; import android.os.Bundle; import android.support.annotation.NonNull; +import android.support.annotation.RestrictTo; import android.support.v17.leanback.R; import android.support.v17.leanback.transition.TransitionHelper; import android.support.v17.leanback.widget.GuidanceStylist; @@ -1317,6 +1317,7 @@ public class GuidedStepSupportFragment extends Fragment implements GuidedActionA * For now clients(subclasses) can call this method inside the constructor. * @hide */ + @RestrictTo(GROUP_ID) public void setEntranceTransitionType(int transitionType) { this.entranceTransitionType = transitionType; } diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java index 2cf897ff25..888e0575aa 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java +++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlGlue.java @@ -21,6 +21,8 @@ import android.view.InputEvent; import android.view.KeyEvent; import android.view.View; +import java.lang.ref.WeakReference; + /** * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and @@ -178,14 +180,21 @@ public abstract class PlaybackControlGlue implements OnActionClickedListener, Vi private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL; private boolean mFadeWhenPlaying = true; - private final Handler mHandler = new Handler() { + static class UpdatePlaybackStateHandler extends Handler { @Override public void handleMessage(Message msg) { if (msg.what == MSG_UPDATE_PLAYBACK_STATE) { - updatePlaybackState(); + PlaybackControlGlue glue = ((WeakReference<PlaybackControlGlue>) msg.obj).get(); + if (glue != null) { + glue.updatePlaybackState(); + } } } - }; + } + + final static Handler sHandler = new UpdatePlaybackStateHandler(); + + final WeakReference<PlaybackControlGlue> mGlueWeakReference = new WeakReference(this); private final OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { @@ -561,16 +570,16 @@ public abstract class PlaybackControlGlue implements OnActionClickedListener, Vi private void updateControlsRow() { updateRowMetadata(); - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); updatePlaybackState(); } private void updatePlaybackStatusAfterUserAction() { updatePlaybackState(mPlaybackSpeed); // Sync playback state after a delay - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE, - UPDATE_PLAYBACK_STATE_DELAY_MS); + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); + sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE, + mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS); } private void updateRowMetadata() { @@ -831,12 +840,12 @@ public abstract class PlaybackControlGlue implements OnActionClickedListener, Vi if (!hasValidMedia()) { return; } - if (mHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE)) { - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); + if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) { + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); if (getCurrentSpeedId() != mPlaybackSpeed) { if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update"); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE, - UPDATE_PLAYBACK_STATE_DELAY_MS); + sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE, + mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS); } else { if (DEBUG) Log.v(TAG, "Update state matches expectation"); updatePlaybackState(); diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java index 1e1eab5094..e8068ec05b 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java +++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackControlSupportGlue.java @@ -23,6 +23,8 @@ import android.view.InputEvent; import android.view.KeyEvent; import android.view.View; +import java.lang.ref.WeakReference; + /** * A helper class for managing a {@link android.support.v17.leanback.widget.PlaybackControlsRow} and @@ -180,14 +182,21 @@ public abstract class PlaybackControlSupportGlue implements OnActionClickedListe private int mPlaybackSpeed = PLAYBACK_SPEED_NORMAL; private boolean mFadeWhenPlaying = true; - private final Handler mHandler = new Handler() { + static class UpdatePlaybackStateHandler extends Handler { @Override public void handleMessage(Message msg) { if (msg.what == MSG_UPDATE_PLAYBACK_STATE) { - updatePlaybackState(); + PlaybackControlSupportGlue glue = ((WeakReference<PlaybackControlSupportGlue>) msg.obj).get(); + if (glue != null) { + glue.updatePlaybackState(); + } } } - }; + } + + final static Handler sHandler = new UpdatePlaybackStateHandler(); + + final WeakReference<PlaybackControlSupportGlue> mGlueWeakReference = new WeakReference(this); private final OnItemViewClickedListener mOnItemViewClickedListener = new OnItemViewClickedListener() { @@ -494,14 +503,18 @@ public abstract class PlaybackControlSupportGlue implements OnActionClickedListe boolean canPause = keyEvent == null || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE || keyEvent.getKeyCode() == KeyEvent.KEYCODE_MEDIA_PAUSE; - if (mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) { - if (canPlay) { - mPlaybackSpeed = PLAYBACK_SPEED_NORMAL; - startPlayback(mPlaybackSpeed); - } - } else if (canPause) { + // PLAY_PAUSE PLAY PAUSE + // playing paused paused + // paused playing playing + // ff/rw playing playing paused + if (canPause && + (canPlay ? mPlaybackSpeed == PLAYBACK_SPEED_NORMAL : + mPlaybackSpeed != PLAYBACK_SPEED_PAUSED)) { mPlaybackSpeed = PLAYBACK_SPEED_PAUSED; pausePlayback(); + } else if (canPlay && mPlaybackSpeed != PLAYBACK_SPEED_NORMAL) { + mPlaybackSpeed = PLAYBACK_SPEED_NORMAL; + startPlayback(mPlaybackSpeed); } updatePlaybackStatusAfterUserAction(); handled = true; @@ -559,16 +572,16 @@ public abstract class PlaybackControlSupportGlue implements OnActionClickedListe private void updateControlsRow() { updateRowMetadata(); - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); updatePlaybackState(); } private void updatePlaybackStatusAfterUserAction() { updatePlaybackState(mPlaybackSpeed); // Sync playback state after a delay - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE, - UPDATE_PLAYBACK_STATE_DELAY_MS); + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); + sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE, + mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS); } private void updateRowMetadata() { @@ -829,12 +842,12 @@ public abstract class PlaybackControlSupportGlue implements OnActionClickedListe if (!hasValidMedia()) { return; } - if (mHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE)) { - mHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE); + if (sHandler.hasMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference)) { + sHandler.removeMessages(MSG_UPDATE_PLAYBACK_STATE, mGlueWeakReference); if (getCurrentSpeedId() != mPlaybackSpeed) { if (DEBUG) Log.v(TAG, "Status expectation mismatch, delaying update"); - mHandler.sendEmptyMessageDelayed(MSG_UPDATE_PLAYBACK_STATE, - UPDATE_PLAYBACK_STATE_DELAY_MS); + sHandler.sendMessageDelayed(sHandler.obtainMessage(MSG_UPDATE_PLAYBACK_STATE, + mGlueWeakReference), UPDATE_PLAYBACK_STATE_DELAY_MS); } else { if (DEBUG) Log.v(TAG, "Update state matches expectation"); updatePlaybackState(); diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java index 58eb2cb560..86e171b996 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlayFragment.java @@ -47,6 +47,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -131,9 +132,7 @@ public class PlaybackOverlayFragment extends DetailsFragment { private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator; private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator; private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator; - private boolean mTranslateAnimationEnabled; boolean mResetControlsToPrimaryActionsPending; - private RecyclerView.ItemAnimator mItemAnimator; private final Animator.AnimatorListener mFadeListener = new Animator.AnimatorListener() { @@ -170,14 +169,22 @@ public class PlaybackOverlayFragment extends DetailsFragment { } }; - private final Handler mHandler = new Handler() { + static class FadeHandler extends Handler { @Override public void handleMessage(Message message) { - if (message.what == START_FADE_OUT && mFadingEnabled) { - fade(false); + PlaybackOverlayFragment fragment; + if (message.what == START_FADE_OUT) { + fragment = ((WeakReference<PlaybackOverlayFragment>) message.obj).get(); + if (fragment != null && fragment.mFadingEnabled) { + fragment.fade(false); + } } } - }; + } + + final static Handler sHandler = new FadeHandler(); + + final WeakReference<PlaybackOverlayFragment> mFragmentReference = new WeakReference(this); private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener = new VerticalGridView.OnTouchInterceptListener() { @@ -234,12 +241,12 @@ public class PlaybackOverlayFragment extends DetailsFragment { mFadingEnabled = enabled; if (mFadingEnabled) { if (isResumed() && mFadingStatus == IDLE - && !mHandler.hasMessages(START_FADE_OUT)) { + && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) { startFadeTimer(); } } else { // Ensure fully opaque - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(true); } } @@ -290,7 +297,7 @@ public class PlaybackOverlayFragment extends DetailsFragment { if (!mFadingEnabled || !isResumed()) { return; } - if (mHandler.hasMessages(START_FADE_OUT)) { + if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) { // Restart the timer startFadeTimer(); } else { @@ -302,7 +309,7 @@ public class PlaybackOverlayFragment extends DetailsFragment { * Fades out the playback overlay immediately. */ public void fadeOut() { - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(false); } @@ -342,7 +349,7 @@ public class PlaybackOverlayFragment extends DetailsFragment { // them out (even if the key was consumed by the handler). if (mFadingEnabled && !controlsHidden) { consumeEvent = true; - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(false); } else if (consumeEvent) { tickle(); @@ -368,10 +375,9 @@ public class PlaybackOverlayFragment extends DetailsFragment { } void startFadeTimer() { - if (mHandler != null) { - mHandler.removeMessages(START_FADE_OUT); - mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs); - } + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); + sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference), + mShowTimeMs); } private static ValueAnimator loadAnimator(Context context, int resId) { diff --git a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java index 2c103a7846..baf2b89111 100644 --- a/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java +++ b/v17/leanback/src/android/support/v17/leanback/app/PlaybackOverlaySupportFragment.java @@ -49,6 +49,7 @@ import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import java.lang.ref.WeakReference; import java.util.ArrayList; /** @@ -133,9 +134,7 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { private ValueAnimator mControlRowFadeInAnimator, mControlRowFadeOutAnimator; private ValueAnimator mDescriptionFadeInAnimator, mDescriptionFadeOutAnimator; private ValueAnimator mOtherRowFadeInAnimator, mOtherRowFadeOutAnimator; - private boolean mTranslateAnimationEnabled; boolean mResetControlsToPrimaryActionsPending; - private RecyclerView.ItemAnimator mItemAnimator; private final Animator.AnimatorListener mFadeListener = new Animator.AnimatorListener() { @@ -172,14 +171,22 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { } }; - private final Handler mHandler = new Handler() { + static class FadeHandler extends Handler { @Override public void handleMessage(Message message) { - if (message.what == START_FADE_OUT && mFadingEnabled) { - fade(false); + PlaybackOverlaySupportFragment fragment; + if (message.what == START_FADE_OUT) { + fragment = ((WeakReference<PlaybackOverlaySupportFragment>) message.obj).get(); + if (fragment != null && fragment.mFadingEnabled) { + fragment.fade(false); + } } } - }; + } + + final static Handler sHandler = new FadeHandler(); + + final WeakReference<PlaybackOverlaySupportFragment> mFragmentReference = new WeakReference(this); private final VerticalGridView.OnTouchInterceptListener mOnTouchInterceptListener = new VerticalGridView.OnTouchInterceptListener() { @@ -236,12 +243,12 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { mFadingEnabled = enabled; if (mFadingEnabled) { if (isResumed() && mFadingStatus == IDLE - && !mHandler.hasMessages(START_FADE_OUT)) { + && !sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) { startFadeTimer(); } } else { // Ensure fully opaque - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(true); } } @@ -292,7 +299,7 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { if (!mFadingEnabled || !isResumed()) { return; } - if (mHandler.hasMessages(START_FADE_OUT)) { + if (sHandler.hasMessages(START_FADE_OUT, mFragmentReference)) { // Restart the timer startFadeTimer(); } else { @@ -304,7 +311,7 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { * Fades out the playback overlay immediately. */ public void fadeOut() { - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(false); } @@ -344,7 +351,7 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { // them out (even if the key was consumed by the handler). if (mFadingEnabled && !controlsHidden) { consumeEvent = true; - mHandler.removeMessages(START_FADE_OUT); + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); fade(false); } else if (consumeEvent) { tickle(); @@ -370,10 +377,9 @@ public class PlaybackOverlaySupportFragment extends DetailsSupportFragment { } void startFadeTimer() { - if (mHandler != null) { - mHandler.removeMessages(START_FADE_OUT); - mHandler.sendEmptyMessageDelayed(START_FADE_OUT, mShowTimeMs); - } + sHandler.removeMessages(START_FADE_OUT, mFragmentReference); + sHandler.sendMessageDelayed(sHandler.obtainMessage(START_FADE_OUT, mFragmentReference), + mShowTimeMs); } private static ValueAnimator loadAnimator(Context context, int resId) { diff --git a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java index 5b72643817..4837a3b642 100644 --- a/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java +++ b/v17/leanback/src/android/support/v17/leanback/transition/TransitionHelper.java @@ -48,11 +48,8 @@ public final class TransitionHelper { * @return True if Transition animations are supported. */ public static boolean systemSupportsTransitions() { - if (Build.VERSION.SDK_INT >= 19) { - // Supported on Android 4.4 or later. - return true; - } - return false; + // Supported on Android 4.4 or later. + return Build.VERSION.SDK_INT >= 19; } /** diff --git a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java index 82f5cfb5c0..1db870a39d 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/BaseGridView.java @@ -847,10 +847,7 @@ abstract class BaseGridView extends RecyclerView { if (super.dispatchKeyEvent(event)) { return true; } - if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) { - return true; - } - return false; + return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event); } @Override diff --git a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java index 937d328d53..e18276397e 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/DetailsOverviewSharedElementHelper.java @@ -32,13 +32,34 @@ import android.view.View.MeasureSpec; import android.widget.ImageView; import android.widget.ImageView.ScaleType; +import java.lang.ref.WeakReference; import java.util.List; final class DetailsOverviewSharedElementHelper extends SharedElementCallback { - static final String TAG = "DetailsOverviewSharedElementHelper"; + static final String TAG = "DetailsTransitionHelper"; static final boolean DEBUG = false; + static class TransitionTimeOutRunnable implements Runnable { + WeakReference<DetailsOverviewSharedElementHelper> mHelperRef; + + TransitionTimeOutRunnable(DetailsOverviewSharedElementHelper helper) { + mHelperRef = new WeakReference<DetailsOverviewSharedElementHelper>(helper); + } + + @Override + public void run() { + DetailsOverviewSharedElementHelper helper = mHelperRef.get(); + if (helper == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "timeout " + helper.mActivityToRunTransition); + } + helper.startPostponedEnterTransition(); + } + } + ViewHolder mViewHolder; Activity mActivityToRunTransition; boolean mStartedPostpone; @@ -182,18 +203,7 @@ final class DetailsOverviewSharedElementHelper extends SharedElementCallback { ActivityCompat.setEnterSharedElementCallback(mActivityToRunTransition, this); ActivityCompat.postponeEnterTransition(mActivityToRunTransition); if (timeoutMs > 0) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (mStartedPostpone) { - return; - } - if (DEBUG) { - Log.d(TAG, "timeout " + mActivityToRunTransition); - } - startPostponedEnterTransition(); - } - }, timeoutMs); + new Handler().postDelayed(new TransitionTimeOutRunnable(this), timeoutMs); } } diff --git a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java index 9fd8815612..7fbb0e6ea9 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/FullWidthDetailsOverviewSharedElementHelper.java @@ -34,6 +34,8 @@ import android.widget.ImageView.ScaleType; import java.util.List; +import java.lang.ref.WeakReference; + /** * Helper class to assist delayed shared element activity transition for view created by * {@link FullWidthDetailsOverviewRowPresenter}. User must call @@ -46,11 +48,31 @@ import java.util.List; public class FullWidthDetailsOverviewSharedElementHelper extends FullWidthDetailsOverviewRowPresenter.Listener { - static final String TAG = "FullWidthDetailsOverviewSharedElementHelper"; + static final String TAG = "DetailsTransitionHelper"; static final boolean DEBUG = false; private static final long DEFAULT_TIMEOUT = 5000; + static class TransitionTimeOutRunnable implements Runnable { + WeakReference<FullWidthDetailsOverviewSharedElementHelper> mHelperRef; + + TransitionTimeOutRunnable(FullWidthDetailsOverviewSharedElementHelper helper) { + mHelperRef = new WeakReference<FullWidthDetailsOverviewSharedElementHelper>(helper); + } + + @Override + public void run() { + FullWidthDetailsOverviewSharedElementHelper helper = mHelperRef.get(); + if (helper == null) { + return; + } + if (DEBUG) { + Log.d(TAG, "timeout " + helper.mActivityToRunTransition); + } + helper.startPostponedEnterTransition(); + } + } + ViewHolder mViewHolder; Activity mActivityToRunTransition; private boolean mStartedPostpone; @@ -80,15 +102,7 @@ public class FullWidthDetailsOverviewSharedElementHelper extends setAutoStartSharedElementTransition(transition != null); ActivityCompat.postponeEnterTransition(mActivityToRunTransition); if (timeoutMs > 0) { - new Handler().postDelayed(new Runnable() { - @Override - public void run() { - if (DEBUG) { - Log.d(TAG, "timeout " + mActivityToRunTransition); - } - startPostponedEnterTransitionInternal(); - } - }, timeoutMs); + new Handler().postDelayed(new TransitionTimeOutRunnable(this), timeoutMs); } } diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java index 7f28705008..888cc8fa00 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidanceStylist.java @@ -184,11 +184,17 @@ public class GuidanceStylist implements FragmentAnimationProvider { if (mGuidanceContainer != null) { CharSequence contentDescription = mGuidanceContainer.getContentDescription(); if (TextUtils.isEmpty(contentDescription)) { - mGuidanceContainer.setContentDescription(new StringBuilder() - .append(guidance.getBreadcrumb()).append('\n') - .append(guidance.getTitle()).append('\n') - .append(guidance.getDescription()) - .toString()); + StringBuilder builder = new StringBuilder(); + if (!TextUtils.isEmpty(guidance.getBreadcrumb())) { + builder.append(guidance.getBreadcrumb()).append('\n'); + } + if (!TextUtils.isEmpty(guidance.getTitle())) { + builder.append(guidance.getTitle()).append('\n'); + } + if (!TextUtils.isEmpty(guidance.getDescription())) { + builder.append(guidance.getDescription()).append('\n'); + } + mGuidanceContainer.setContentDescription(builder); } } diff --git a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java index 200b6d93a5..9d86bfee78 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/ListRowPresenter.java @@ -437,12 +437,9 @@ public class ListRowPresenter extends RowPresenter { new BaseGridView.OnUnhandledKeyListener() { @Override public boolean onUnhandledKey(KeyEvent event) { - if (rowViewHolder.getOnKeyListener() != null && + return rowViewHolder.getOnKeyListener() != null && rowViewHolder.getOnKeyListener().onKey( - rowViewHolder.view, event.getKeyCode(), event)) { - return true; - } - return false; + rowViewHolder.view, event.getKeyCode(), event); } }); rowViewHolder.mGridView.setNumRows(mNumRows); diff --git a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java index 8242caa8b5..e08a0ec241 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/NonOverlappingLinearLayoutWithForeground.java @@ -60,6 +60,7 @@ class NonOverlappingLinearLayoutWithForeground extends LinearLayout { if (d != null) { setForegroundCompat(d); } + a.recycle(); } } diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java index 277f427a12..c10d202871 100644 --- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java +++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackControlsRowView.java @@ -57,10 +57,7 @@ class PlaybackControlsRowView extends LinearLayout { if (super.dispatchKeyEvent(event)) { return true; } - if (mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event)) { - return true; - } - return false; + return mOnUnhandledKeyListener != null && mOnUnhandledKeyListener.onUnhandledKey(event); } @Override diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java index 5e52a2215b..e01a168c61 100644 --- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java +++ b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseFragmentTestActivity.java @@ -36,25 +36,19 @@ public class BrowseFragmentTestActivity extends Activity { super.onCreate(savedInstanceState); Intent intent = getIntent(); - BrowseTestFragment.NUM_ROWS = intent.getIntExtra(EXTRA_NUM_ROWS, - BrowseTestFragment.DEFAULT_NUM_ROWS); - BrowseTestFragment.REPEAT_PER_ROW = intent.getIntExtra(EXTRA_REPEAT_PER_ROW, - BrowseTestFragment.DEFAULT_REPEAT_PER_ROW); - BrowseTestFragment.LOAD_DATA_DELAY = intent.getLongExtra(EXTRA_LOAD_DATA_DELAY, - BrowseTestFragment.DEFAULT_LOAD_DATA_DELAY); - BrowseTestFragment.TEST_ENTRANCE_TRANSITION = intent.getBooleanExtra( - EXTRA_TEST_ENTRANCE_TRANSITION, - BrowseTestFragment.DEFAULT_TEST_ENTRANCE_TRANSITION); - BrowseTestFragment.SET_ADAPTER_AFTER_DATA_LOAD = intent.getBooleanExtra( - EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, - BrowseTestFragment.DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD); setContentView(R.layout.browse); - FragmentTransaction ft = getFragmentManager().beginTransaction(); - ft.replace(R.id.main_frame, new BrowseTestFragment()); - if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) { - ft.addToBackStack(null); + if (savedInstanceState == null) { + Bundle arguments = new Bundle(); + arguments.putAll(intent.getExtras()); + BrowseTestFragment fragment = new BrowseTestFragment(); + fragment.setArguments(arguments); + FragmentTransaction ft = getFragmentManager().beginTransaction(); + ft.replace(R.id.main_frame, fragment); + if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) { + ft.addToBackStack(null); + } + ft.commit(); } - ft.commit(); } public BrowseTestFragment getBrowseTestFragment() { diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java index d92b58deca..14f72bc957 100644 --- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java +++ b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseSupportFragmentTestActivity.java @@ -38,25 +38,19 @@ public class BrowseSupportFragmentTestActivity extends FragmentActivity { super.onCreate(savedInstanceState); Intent intent = getIntent(); - BrowseTestSupportFragment.NUM_ROWS = intent.getIntExtra(EXTRA_NUM_ROWS, - BrowseTestSupportFragment.DEFAULT_NUM_ROWS); - BrowseTestSupportFragment.REPEAT_PER_ROW = intent.getIntExtra(EXTRA_REPEAT_PER_ROW, - BrowseTestSupportFragment.DEFAULT_REPEAT_PER_ROW); - BrowseTestSupportFragment.LOAD_DATA_DELAY = intent.getLongExtra(EXTRA_LOAD_DATA_DELAY, - BrowseTestSupportFragment.DEFAULT_LOAD_DATA_DELAY); - BrowseTestSupportFragment.TEST_ENTRANCE_TRANSITION = intent.getBooleanExtra( - EXTRA_TEST_ENTRANCE_TRANSITION, - BrowseTestSupportFragment.DEFAULT_TEST_ENTRANCE_TRANSITION); - BrowseTestSupportFragment.SET_ADAPTER_AFTER_DATA_LOAD = intent.getBooleanExtra( - EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, - BrowseTestSupportFragment.DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD); setContentView(R.layout.browse); - FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); - ft.replace(R.id.main_frame, new BrowseTestSupportFragment()); - if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) { - ft.addToBackStack(null); + if (savedInstanceState == null) { + Bundle arguments = new Bundle(); + arguments.putAll(intent.getExtras()); + BrowseTestSupportFragment fragment = new BrowseTestSupportFragment(); + fragment.setArguments(arguments); + FragmentTransaction ft = getSupportFragmentManager().beginTransaction(); + ft.replace(R.id.main_frame, fragment); + if (intent.getBooleanExtra(EXTRA_ADD_TO_BACKSTACK, false)) { + ft.addToBackStack(null); + } + ft.commit(); } - ft.commit(); } public BrowseTestSupportFragment getBrowseTestSupportFragment() { diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java index 62fa32e963..8de428b7c0 100644 --- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java +++ b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestFragment.java @@ -28,6 +28,11 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.util.Log; import android.view.View; +import static android.support.v17.leanback.app.BrowseFragmentTestActivity.*; + +/** + * @hide from javadoc + */ public class BrowseTestFragment extends BrowseFragment { private static final String TAG = "BrowseTestFragment"; @@ -37,23 +42,33 @@ public class BrowseTestFragment extends BrowseFragment { final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true; final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false; - static int NUM_ROWS = DEFAULT_NUM_ROWS; - static int REPEAT_PER_ROW = DEFAULT_REPEAT_PER_ROW; - static long LOAD_DATA_DELAY = DEFAULT_LOAD_DATA_DELAY; - static boolean TEST_ENTRANCE_TRANSITION = DEFAULT_TEST_ENTRANCE_TRANSITION; - static boolean SET_ADAPTER_AFTER_DATA_LOAD = DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD; - private ArrayObjectAdapter mRowsAdapter; // For good performance, it's important to use a single instance of // a card presenter for all rows using that presenter. final static StringPresenter sCardPresenter = new StringPresenter(); + int NUM_ROWS; + int REPEAT_PER_ROW; + @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); + Bundle arguments = getArguments(); + NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestFragment.DEFAULT_NUM_ROWS); + REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW, + DEFAULT_REPEAT_PER_ROW); + long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY, + DEFAULT_LOAD_DATA_DELAY); + boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean( + EXTRA_TEST_ENTRANCE_TRANSITION, + DEFAULT_TEST_ENTRANCE_TRANSITION); + final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean( + EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, + DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD); + if (!SET_ADAPTER_AFTER_DATA_LOAD) { setupRows(); } @@ -86,6 +101,9 @@ public class BrowseTestFragment extends BrowseFragment { new Handler().postDelayed(new Runnable() { @Override public void run() { + if (getActivity() == null || getActivity().isDestroyed()) { + return; + } if (SET_ADAPTER_AFTER_DATA_LOAD) { setupRows(); } diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java index 6031dfaaa8..0f9ec2c07c 100644 --- a/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java +++ b/v17/leanback/tests/java/android/support/v17/leanback/app/BrowseTestSupportFragment.java @@ -30,6 +30,11 @@ import android.support.v17.leanback.widget.VerticalGridView; import android.util.Log; import android.view.View; +import static android.support.v17.leanback.app.BrowseSupportFragmentTestActivity.*; + +/** + * @hide from javadoc + */ public class BrowseTestSupportFragment extends BrowseSupportFragment { private static final String TAG = "BrowseTestSupportFragment"; @@ -39,23 +44,33 @@ public class BrowseTestSupportFragment extends BrowseSupportFragment { final static boolean DEFAULT_TEST_ENTRANCE_TRANSITION = true; final static boolean DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD = false; - static int NUM_ROWS = DEFAULT_NUM_ROWS; - static int REPEAT_PER_ROW = DEFAULT_REPEAT_PER_ROW; - static long LOAD_DATA_DELAY = DEFAULT_LOAD_DATA_DELAY; - static boolean TEST_ENTRANCE_TRANSITION = DEFAULT_TEST_ENTRANCE_TRANSITION; - static boolean SET_ADAPTER_AFTER_DATA_LOAD = DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD; - private ArrayObjectAdapter mRowsAdapter; // For good performance, it's important to use a single instance of // a card presenter for all rows using that presenter. final static StringPresenter sCardPresenter = new StringPresenter(); + int NUM_ROWS; + int REPEAT_PER_ROW; + @Override public void onCreate(Bundle savedInstanceState) { Log.i(TAG, "onCreate"); super.onCreate(savedInstanceState); + Bundle arguments = getArguments(); + NUM_ROWS = arguments.getInt(EXTRA_NUM_ROWS, BrowseTestSupportFragment.DEFAULT_NUM_ROWS); + REPEAT_PER_ROW = arguments.getInt(EXTRA_REPEAT_PER_ROW, + DEFAULT_REPEAT_PER_ROW); + long LOAD_DATA_DELAY = arguments.getLong(EXTRA_LOAD_DATA_DELAY, + DEFAULT_LOAD_DATA_DELAY); + boolean TEST_ENTRANCE_TRANSITION = arguments.getBoolean( + EXTRA_TEST_ENTRANCE_TRANSITION, + DEFAULT_TEST_ENTRANCE_TRANSITION); + final boolean SET_ADAPTER_AFTER_DATA_LOAD = arguments.getBoolean( + EXTRA_SET_ADAPTER_AFTER_DATA_LOAD, + DEFAULT_SET_ADAPTER_AFTER_DATA_LOAD); + if (!SET_ADAPTER_AFTER_DATA_LOAD) { setupRows(); } @@ -88,6 +103,9 @@ public class BrowseTestSupportFragment extends BrowseSupportFragment { new Handler().postDelayed(new Runnable() { @Override public void run() { + if (getActivity() == null || getActivity().isDestroyed()) { + return; + } if (SET_ADAPTER_AFTER_DATA_LOAD) { setupRows(); } diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java index 69e61dca1b..b7cb75dcae 100644 --- a/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java +++ b/v17/leanback/tests/java/android/support/v17/leanback/app/PlaybackControlSupportGlueTest.java @@ -350,4 +350,161 @@ public class PlaybackControlSupportGlueTest { assertEquals(0, rewind.getIndex()); } } + + @Test + public void testMediaPauseButtonOnFF() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_FAST_FORWARD); + + glue.onActionClicked(playPause); + glue.onActionClicked(fastForward); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPauseButtonOnPlay() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPauseButtonOnPause() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayButtonOnFF() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_FAST_FORWARD); + + glue.onActionClicked(playPause); + glue.onActionClicked(fastForward); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayButtonOnPlay() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayButtonOnPause() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayPauseButtonOnFF() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + PlaybackControlsRow.MultiAction fastForward = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_FAST_FORWARD); + + glue.onActionClicked(playPause); + glue.onActionClicked(fastForward); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_FAST_L0, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayPauseButtonOnPlay() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + } + + @Test + public void testMediaPlayPauseButtonOnPause() { + PlaybackControlsRow row = new PlaybackControlsRow(); + glue.setControlsRow(row); + SparseArrayObjectAdapter adapter = (SparseArrayObjectAdapter) + row.getPrimaryActionsAdapter(); + PlaybackControlsRow.MultiAction playPause = (PlaybackControlsRow.MultiAction) adapter + .lookup(PlaybackControlSupportGlue.ACTION_PLAY_PAUSE); + + glue.onActionClicked(playPause); + glue.onActionClicked(playPause); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_PAUSED, glue.getCurrentSpeedId()); + glue.onKey(null, KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, new KeyEvent(KeyEvent.ACTION_DOWN, + KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE)); + assertEquals(PlaybackControlSupportGlue.PLAYBACK_SPEED_NORMAL, glue.getCurrentSpeedId()); + } + } diff --git a/v7/appcompat/res/values-es-rUS/strings.xml b/v7/appcompat/res/values-es-rUS/strings.xml index 9bc27a945f..804941ce4e 100644 --- a/v7/appcompat/res/values-es-rUS/strings.xml +++ b/v7/appcompat/res/values-es-rUS/strings.xml @@ -25,7 +25,7 @@ <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string> <string name="abc_searchview_description_search" msgid="8264924765203268293">"Búsqueda"</string> <string name="abc_search_hint" msgid="7723749260725869598">"Buscar…"</string> - <string name="abc_searchview_description_query" msgid="2550479030709304392">"Consulta de búsqueda"</string> + <string name="abc_searchview_description_query" msgid="2550479030709304392">"Búsqueda"</string> <string name="abc_searchview_description_clear" msgid="3691816814315814921">"Eliminar la consulta"</string> <string name="abc_searchview_description_submit" msgid="8928215447528550784">"Enviar consulta"</string> <string name="abc_searchview_description_voice" msgid="893419373245838918">"Búsqueda por voz"</string> diff --git a/v7/appcompat/res/values-hy-rAM/strings.xml b/v7/appcompat/res/values-hy-rAM/strings.xml index c714b9cfaf..dd526113ce 100644 --- a/v7/appcompat/res/values-hy-rAM/strings.xml +++ b/v7/appcompat/res/values-hy-rAM/strings.xml @@ -16,7 +16,7 @@ <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2"> - <string name="abc_action_mode_done" msgid="4076576682505996667">"Կատարված է"</string> + <string name="abc_action_mode_done" msgid="4076576682505996667">"Պատրաստ է"</string> <string name="abc_action_bar_home_description" msgid="4600421777120114993">"Ուղղվել տուն"</string> <string name="abc_action_bar_up_description" msgid="1594238315039666878">"Ուղղվել վերև"</string> <string name="abc_action_menu_overflow_description" msgid="3588849162933574182">"Այլ ընտրանքներ"</string> diff --git a/v7/appcompat/res/values-kn-rIN/strings.xml b/v7/appcompat/res/values-kn-rIN/strings.xml index e240f96af3..ce95380cb9 100644 --- a/v7/appcompat/res/values-kn-rIN/strings.xml +++ b/v7/appcompat/res/values-kn-rIN/strings.xml @@ -23,7 +23,7 @@ <string name="abc_toolbar_collapse_description" msgid="1603543279005712093">"ಸಂಕುಚಿಸು"</string> <string name="abc_action_bar_home_description_format" msgid="1397052879051804371">"%1$s, %2$s"</string> <string name="abc_action_bar_home_subtitle_description_format" msgid="6623331958280229229">"%1$s, %2$s, %3$s"</string> - <string name="abc_searchview_description_search" msgid="8264924765203268293">"ಹುಡುಕು"</string> + <string name="abc_searchview_description_search" msgid="8264924765203268293">"ಹುಡುಕಿ"</string> <string name="abc_search_hint" msgid="7723749260725869598">"ಹುಡುಕಿ…"</string> <string name="abc_searchview_description_query" msgid="2550479030709304392">"ಪ್ರಶ್ನೆಯನ್ನು ಹುಡುಕಿ"</string> <string name="abc_searchview_description_clear" msgid="3691816814315814921">"ಪ್ರಶ್ನೆಯನ್ನು ತೆರವುಗೊಳಿಸು"</string> @@ -36,5 +36,5 @@ <string name="status_bar_notification_info_overflow" msgid="2869576371154716097">"999+"</string> <string name="abc_capital_on" msgid="3405795526292276155">"ಆನ್"</string> <string name="abc_capital_off" msgid="121134116657445385">"ಆಫ್"</string> - <string name="search_menu_title" msgid="146198913615257606">"ಹುಡುಕು"</string> + <string name="search_menu_title" msgid="146198913615257606">"ಹುಡುಕಿ"</string> </resources> diff --git a/v7/appcompat/res/values-v11/themes_base.xml b/v7/appcompat/res/values-v11/themes_base.xml index e0ac24dc21..d38ef32ce0 100644 --- a/v7/appcompat/res/values-v11/themes_base.xml +++ b/v7/appcompat/res/values-v11/themes_base.xml @@ -158,4 +158,29 @@ <style name="Base.ThemeOverlay.AppCompat.Dialog" parent="Base.V11.ThemeOverlay.AppCompat.Dialog" /> + <style name="Base.ThemeOverlay.AppCompat.Dialog.Alert"> + <item name="android:windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> + <item name="android:windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> + </style> + + <style name="Base.Theme.AppCompat.Dialog.Alert"> + <item name="android:windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> + <item name="android:windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> + </style> + + <style name="Base.Theme.AppCompat.Light.Dialog.Alert"> + <item name="android:windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> + <item name="android:windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> + </style> + + <style name="Base.Theme.AppCompat.Dialog.MinWidth"> + <item name="android:windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> + <item name="android:windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> + </style> + + <style name="Base.Theme.AppCompat.Light.Dialog.MinWidth"> + <item name="android:windowMinWidthMajor">@dimen/abc_dialog_min_width_major</item> + <item name="android:windowMinWidthMinor">@dimen/abc_dialog_min_width_minor</item> + </style> + </resources> diff --git a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java index 2ee07d8166..cff87c5507 100644 --- a/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java +++ b/v7/appcompat/src/android/support/v7/view/menu/MenuBuilder.java @@ -16,6 +16,8 @@ package android.support.v7.view.menu; +import static android.support.annotation.RestrictTo.Scope.GROUP_ID; + import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -47,8 +49,6 @@ import java.util.ArrayList; import java.util.List; import java.util.concurrent.CopyOnWriteArrayList; -import static android.support.annotation.RestrictTo.Scope.GROUP_ID; - /** * Implementation of the {@link android.support.v4.internal.view.SupportMenu} interface for creating a * standard menu UI. @@ -165,6 +165,8 @@ public class MenuBuilder implements SupportMenu { private boolean mItemsChangedWhileDispatchPrevented = false; + private boolean mStructureChangedWhileDispatchPrevented = false; + private boolean mOptionalIconsVisible = false; private boolean mIsClosing = false; @@ -582,6 +584,7 @@ public class MenuBuilder implements SupportMenu { clearHeader(); mPreventDispatchingItemsChanged = false; mItemsChangedWhileDispatchPrevented = false; + mStructureChangedWhileDispatchPrevented = false; onItemsChanged(true); } @@ -599,6 +602,7 @@ public class MenuBuilder implements SupportMenu { final int group = item.getGroupId(); final int N = mItems.size(); + stopDispatchingItemsChanged(); for (int i = 0; i < N; i++) { MenuItemImpl curItem = mItems.get(i); if (curItem.getGroupId() == group) { @@ -609,6 +613,7 @@ public class MenuBuilder implements SupportMenu { curItem.setCheckedInt(curItem == item); } } + startDispatchingItemsChanged(); } @Override @@ -1042,6 +1047,9 @@ public class MenuBuilder implements SupportMenu { dispatchPresenterUpdate(structureChanged); } else { mItemsChangedWhileDispatchPrevented = true; + if (structureChanged) { + mStructureChangedWhileDispatchPrevented = true; + } } } @@ -1054,6 +1062,7 @@ public class MenuBuilder implements SupportMenu { if (!mPreventDispatchingItemsChanged) { mPreventDispatchingItemsChanged = true; mItemsChangedWhileDispatchPrevented = false; + mStructureChangedWhileDispatchPrevented = false; } } @@ -1062,7 +1071,7 @@ public class MenuBuilder implements SupportMenu { if (mItemsChangedWhileDispatchPrevented) { mItemsChangedWhileDispatchPrevented = false; - onItemsChanged(true); + onItemsChanged(mStructureChangedWhileDispatchPrevented); } } diff --git a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java index 09e4a28471..3fffb9f3c9 100644 --- a/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java +++ b/v7/appcompat/src/android/support/v7/widget/AppCompatBackgroundHelper.java @@ -155,10 +155,9 @@ class AppCompatBackgroundHelper { void applySupportBackgroundTint() { final Drawable background = mView.getBackground(); if (background != null) { - if (Build.VERSION.SDK_INT == 21 && applyFrameworkTintUsingColorFilter(background)) { - // GradientDrawable doesn't implement setTintList on API 21, and since there is - // no nice way to unwrap DrawableContainers we have to blanket apply this - // on API 21. This needs to be called before the internal tints below so it takes + if (shouldApplyFrameworkTintUsingColorFilter() + && applyFrameworkTintUsingColorFilter(background)) { + // This needs to be called before the internal tints below so it takes // effect on any widgets using the compat tint on API 21 (EditText) return; } @@ -186,6 +185,23 @@ class AppCompatBackgroundHelper { applySupportBackgroundTint(); } + private boolean shouldApplyFrameworkTintUsingColorFilter() { + final int sdk = Build.VERSION.SDK_INT; + if (sdk < 21) { + // API 19 and below doesn't have framework tint + return false; + } else if (sdk == 21) { + // GradientDrawable doesn't implement setTintList on API 21, and since there is + // no nice way to unwrap DrawableContainers we have to blanket apply this + // on API 21 + return true; + } else { + // On API 22+, if we're using an internal compat background tint, we're also + // responsible for applying any custom tint set via the framework impl + return mInternalBackgroundTint != null; + } + } + /** * Applies the framework background tint to a view, but using the compat method (ColorFilter) * diff --git a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml index 72d6500a3f..7708d0c476 100644 --- a/v7/appcompat/tests/res/layout/appcompat_button_activity.xml +++ b/v7/appcompat/tests/res/layout/appcompat_button_activity.xml @@ -76,6 +76,13 @@ android:text="@string/sample_text2" android:background="@drawable/test_background_green" /> + <android.support.v7.widget.AppCompatButton + android:id="@+id/button_colored_untinted" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:text="@string/sample_text2" + style="@style/Widget.AppCompat.Button.Colored"/> + </LinearLayout> </ScrollView> diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java index 37a3732caf..b68d0efc41 100644 --- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java +++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtils.java @@ -173,6 +173,36 @@ public class TestUtils { } /** + * Checks whether the center pixel in the specified drawable is of the same specified color. + * + * In case there is a color mismatch, the behavior of this method depends on the + * <code>throwExceptionIfFails</code> parameter. If it is <code>true</code>, this method will + * throw an <code>Exception</code> describing the mismatch. Otherwise this method will call + * <code>Assert.fail</code> with detailed description of the mismatch. + */ + public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Drawable drawable, + int drawableWidth, int drawableHeight, boolean callSetBounds, @ColorInt int color, + int allowedComponentVariance, boolean throwExceptionIfFails) { + // Create a bitmap + Bitmap bitmap = Bitmap.createBitmap(drawableWidth, drawableHeight, Bitmap.Config.ARGB_8888); + // Create a canvas that wraps the bitmap + Canvas canvas = new Canvas(bitmap); + if (callSetBounds) { + // Configure the drawable to have bounds that match the passed size + drawable.setBounds(0, 0, drawableWidth, drawableHeight); + } + // And ask the drawable to draw itself to the canvas / bitmap + drawable.draw(canvas); + + try { + assertCenterPixelOfColor(failMessagePrefix, bitmap, color, allowedComponentVariance, + throwExceptionIfFails); + } finally { + bitmap.recycle(); + } + } + + /** * Checks whether the center pixel in the specified bitmap is of the same specified color. * * In case there is a color mismatch, the behavior of this method depends on the @@ -181,8 +211,7 @@ public class TestUtils { * <code>Assert.fail</code> with detailed description of the mismatch. */ public static void assertCenterPixelOfColor(String failMessagePrefix, @NonNull Bitmap bitmap, - @ColorInt int color, - int allowedComponentVariance, boolean throwExceptionIfFails) { + @ColorInt int color, int allowedComponentVariance, boolean throwExceptionIfFails) { final int centerX = bitmap.getWidth() / 2; final int centerY = bitmap.getHeight() / 2; final @ColorInt int colorAtCenterPixel = bitmap.getPixel(centerX, centerY); diff --git a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java index ac10d3b546..3e092c4ab3 100644 --- a/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java +++ b/v7/appcompat/tests/src/android/support/v7/testutils/TestUtilsMatchers.java @@ -81,6 +81,14 @@ public class TestUtilsMatchers { * with the specific color. */ public static Matcher isBackground(@ColorInt final int color) { + return isBackground(color, false); + } + + /** + * Returns a matcher that matches <code>View</code>s which have background flat-filled + * with the specific color. + */ + public static Matcher isBackground(@ColorInt final int color, final boolean onlyTestCenter) { return new BoundedMatcher<View, View>(View.class) { private String failedComparisonDescription; @@ -97,10 +105,14 @@ public class TestUtilsMatchers { if (drawable == null) { return false; } - try { - TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(), - view.getHeight(), false, color, 0, true); + if (onlyTestCenter) { + TestUtils.assertCenterPixelOfColor("", drawable, view.getWidth(), + view.getHeight(), false, color, 0, true); + } else { + TestUtils.assertAllPixelsOfColor("", drawable, view.getWidth(), + view.getHeight(), false, color, 0, true); + } // If we are here, the color comparison has passed. failedComparisonDescription = null; return true; diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java index 315c12d78a..dca0f2a9c6 100644 --- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java +++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatBaseViewTest.java @@ -15,6 +15,19 @@ */ package android.support.v7.widget; +import static android.support.test.espresso.Espresso.onView; +import static android.support.test.espresso.assertion.ViewAssertions.matches; +import static android.support.test.espresso.matcher.ViewMatchers.withId; +import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundResource; +import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundTintList; +import static android.support.v7.testutils.AppCompatTintableViewActions.setBackgroundTintMode; +import static android.support.v7.testutils.AppCompatTintableViewActions.setEnabled; +import static android.support.v7.testutils.TestUtilsActions.setBackgroundTintListViewCompat; +import static android.support.v7.testutils.TestUtilsActions.setBackgroundTintModeViewCompat; +import static android.support.v7.testutils.TestUtilsMatchers.isBackground; + +import static org.junit.Assert.assertNull; + import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.PorterDuff; @@ -29,17 +42,13 @@ import android.support.v7.appcompat.test.R; import android.support.v7.testutils.AppCompatTintableViewActions; import android.support.v7.testutils.BaseTestActivity; import android.support.v7.testutils.TestUtils; -import android.support.v7.testutils.TestUtilsActions; import android.test.suitebuilder.annotation.SmallTest; import android.view.View; import android.view.ViewGroup; + import org.junit.Before; import org.junit.Test; -import static android.support.test.espresso.Espresso.onView; -import static android.support.test.espresso.matcher.ViewMatchers.withId; -import static org.junit.Assert.assertNull; - /** * Base class for testing custom view extensions in appcompat-v7 that implement the * <code>TintableBackgroundView</code> interface. Extensions of this class run all tests @@ -100,11 +109,11 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend assertNull("No background after XML loading", view.getBackground()); // Disable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); assertNull("No background after disabling", view.getBackground()); // Enable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); assertNull("No background after re-enabling", view.getBackground()); // Load a new color state list, set it on the view and check that the background @@ -112,14 +121,14 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintList(sandColor)); + setBackgroundTintList(sandColor)); // Disable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); assertNull("No background after disabling", view.getBackground()); // Enable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); assertNull("No background after re-enabling", view.getBackground()); } @@ -144,26 +153,25 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend assertNull("No background after XML loading", view.getBackground()); // Disable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); assertNull("No background after disabling", view.getBackground()); // Enable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); assertNull("No background after re-enabling", view.getBackground()); // Load a new color state list, set it on the view and check that the background // is still null. final ColorStateList lilacColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_lilac, null); - onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintListViewCompat(lilacColor)); + onView(withId(viewId)).perform(setBackgroundTintListViewCompat(lilacColor)); // Disable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); assertNull("No background after disabling", view.getBackground()); // Enable the view and check that the background is still null. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); assertNull("No background after re-enabling", view.getBackground()); } @@ -197,13 +205,13 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view, lilacDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view, lilacDefault, 0); @@ -212,19 +220,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintList(sandColor)); + setBackgroundTintList(sandColor)); verifyBackgroundIsColoredAs("New sand tinting in enabled state", view, sandDefault, 0); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New sand tinting in disabled state", view, sandDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view, sandDefault, 0); @@ -232,20 +240,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // switched to the matching entry in newly set color state list. final ColorStateList oceanColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_ocean, null); - onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintList(oceanColor)); + onView(withId(viewId)).perform(setBackgroundTintList(oceanColor)); verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view, oceanDefault, 0); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view, oceanDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view, oceanDefault, 0); } @@ -280,13 +287,13 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Default lilac tinting in disabled state", view, lilacDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state", view, lilacDefault, 0); @@ -294,20 +301,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // switched to the matching entry in newly set color state list. final ColorStateList sandColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_sand, null); - onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintListViewCompat(sandColor)); + onView(withId(viewId)).perform(setBackgroundTintListViewCompat(sandColor)); verifyBackgroundIsColoredAs("New sand tinting in enabled state", view, sandDefault, 0); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New sand tinting in disabled state", view, sandDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New sand tinting in re-enabled state", view, sandDefault, 0); @@ -316,19 +322,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend final ColorStateList oceanColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_ocean, null); onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintListViewCompat(oceanColor)); + setBackgroundTintListViewCompat(oceanColor)); verifyBackgroundIsColoredAs("New ocean tinting in enabled state", view, oceanDefault, 0); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New ocean tinting in disabled state", view, oceanDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New ocean tinting in re-enabled state", view, oceanDefault, 0); } @@ -365,21 +371,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend final int allowedComponentVariance = 2; // Set src_in tint mode on our view - onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_IN)); + onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_IN)); // Load a new color state list, set it on the view and check that the background has // switched to the matching entry in newly set color state list. final ColorStateList emeraldColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_emerald_translucent, null); - onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintList(emeraldColor)); + onView(withId(viewId)).perform(setBackgroundTintList(emeraldColor)); verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view, emeraldDefault, allowedComponentVariance); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view, emeraldDisabled, allowedComponentVariance); @@ -387,19 +391,18 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // translucent colors, we expect the actual background of the view to be different under // this new mode (unlike src_in and src_over that behave identically when the destination is // a fully filled rectangle and the source is an opaque color). - onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER)); + onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_OVER)); // Enable the view and check that the background has switched to the matching entry // in the color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view, ColorUtils.compositeColors(emeraldDefault, backgroundColor), allowedComponentVariance); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over", view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor), allowedComponentVariance); @@ -437,21 +440,19 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend final int allowedComponentVariance = 2; // Set src_in tint mode on our view - onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN)); + onView(withId(viewId)).perform(setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_IN)); // Load a new color state list, set it on the view and check that the background has // switched to the matching entry in newly set color state list. final ColorStateList emeraldColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_emerald_translucent, null); - onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintListViewCompat(emeraldColor)); + onView(withId(viewId)).perform(setBackgroundTintListViewCompat(emeraldColor)); verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_in", view, emeraldDefault, allowedComponentVariance); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_in", view, emeraldDisabled, allowedComponentVariance); @@ -459,19 +460,18 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // translucent colors, we expect the actual background of the view to be different under // this new mode (unlike src_in and src_over that behave identically when the destination is // a fully filled rectangle and the source is an opaque color). - onView(withId(viewId)).perform( - TestUtilsActions.setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER)); + onView(withId(viewId)).perform(setBackgroundTintModeViewCompat(PorterDuff.Mode.SRC_OVER)); // Enable the view and check that the background has switched to the matching entry // in the color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("New emerald tinting in enabled state under src_over", view, ColorUtils.compositeColors(emeraldDefault, backgroundColor), allowedComponentVariance); // Disable the view and check that the background has switched to the matching entry // in the newly set color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("New emerald tinting in disabled state under src_over", view, ColorUtils.compositeColors(emeraldDisabled, backgroundColor), allowedComponentVariance); @@ -497,8 +497,7 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend } // Set background on our view - onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable( - ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null))); + onView(withId(viewId)).perform(setBackgroundResource(R.drawable.test_background_green)); // Test the default state for tinting set up in the layout XML file. verifyBackgroundIsColoredAs("Default lilac tinting in enabled state on green background", @@ -506,13 +505,13 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on green background", view, lilacDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on green background", view, lilacDefault, 0); @@ -526,13 +525,13 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Default lilac tinting in disabled state on red background", view, lilacDisabled, 0); // Enable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Default lilac tinting in re-enabled state on red background", view, lilacDefault, 0); } @@ -566,17 +565,15 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // translucent colors, we expect the actual background of the view to be different under // this new mode (unlike src_in and src_over that behave identically when the destination is // a fully filled rectangle and the source is an opaque color). - onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintMode(PorterDuff.Mode.SRC_OVER)); + onView(withId(viewId)).perform(setBackgroundTintMode(PorterDuff.Mode.SRC_OVER)); // Load and set a translucent color state list as the background tint list final ColorStateList emeraldColor = ResourcesCompat.getColorStateList( mResources, R.color.color_state_list_emerald_translucent, null); onView(withId(viewId)).perform( - AppCompatTintableViewActions.setBackgroundTintList(emeraldColor)); + setBackgroundTintList(emeraldColor)); // Set background on our view - onView(withId(viewId)).perform(AppCompatTintableViewActions.setBackgroundDrawable( - ResourcesCompat.getDrawable(mResources, R.drawable.test_background_green, null))); + onView(withId(viewId)).perform(setBackgroundResource(R.drawable.test_background_green)); // From this point on in this method we're allowing a margin of error in checking the // color of the view background. This is due to both translucent colors being used @@ -592,14 +589,14 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Emerald tinting in disabled state on green background", view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorGreen), allowedComponentVariance); // Enable the view and check that the background has switched to the matching entry // in the default color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on green background", view, ColorUtils.compositeColors(emeraldDefault, backgroundColorGreen), allowedComponentVariance); @@ -615,16 +612,39 @@ public abstract class AppCompatBaseViewTest<A extends BaseTestActivity, T extend // Disable the view and check that the background has switched to the matching entry // in our current color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(false)); + onView(withId(viewId)).perform(setEnabled(false)); verifyBackgroundIsColoredAs("Emerald tinting in disabled state on red background", view, ColorUtils.compositeColors(emeraldDisabled, backgroundColorRed), allowedComponentVariance); // Enable the view and check that the background has switched to the matching entry // in our current color state list. - onView(withId(viewId)).perform(AppCompatTintableViewActions.setEnabled(true)); + onView(withId(viewId)).perform(setEnabled(true)); verifyBackgroundIsColoredAs("Emerald tinting in re-enabled state on red background", view, ColorUtils.compositeColors(emeraldDefault, backgroundColorRed), allowedComponentVariance); } + + protected void testUntintedBackgroundTintingViewCompatAcrossStateChange(@IdRes int viewId) { + final T view = (T) mContainer.findViewById(viewId); + + final @ColorInt int oceanDefault = ResourcesCompat.getColor( + mResources, R.color.ocean_default, null); + final @ColorInt int oceanDisabled = ResourcesCompat.getColor( + mResources, R.color.ocean_disabled, null); + + final ColorStateList oceanColor = ResourcesCompat.getColorStateList( + mResources, R.color.color_state_list_ocean, null); + onView(withId(viewId)).perform(setBackgroundTintListViewCompat(oceanColor)); + + // Disable the view and check that the background has switched to the matching entry + // in the newly set color state list. + onView(withId(viewId)).perform(setEnabled(false)) + .check(matches(isBackground(oceanDisabled, true))); + + // Enable the view and check that the background has switched to the matching entry + // in the newly set color state list. + onView(withId(viewId)).perform(setEnabled(true)) + .check(matches(isBackground(oceanDefault, true))); + } } diff --git a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java index 340737e15c..d3d0a48bcf 100644 --- a/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java +++ b/v7/appcompat/tests/src/android/support/v7/widget/AppCompatButtonTest.java @@ -15,16 +15,18 @@ */ package android.support.v7.widget; -import android.support.v7.appcompat.test.R; -import android.support.v7.testutils.TestUtilsActions; -import android.test.suitebuilder.annotation.SmallTest; -import org.junit.Test; - import static android.support.test.espresso.Espresso.onView; import static android.support.test.espresso.matcher.ViewMatchers.withId; import static android.support.v7.testutils.TestUtilsActions.setTextAppearance; + import static org.junit.Assert.assertEquals; +import android.support.test.filters.SdkSuppress; +import android.support.v7.appcompat.test.R; +import android.test.suitebuilder.annotation.SmallTest; + +import org.junit.Test; + /** * In addition to all tinting-related tests done by the base class, this class provides * tests specific to {@link AppCompatButton} class. @@ -80,4 +82,13 @@ public class AppCompatButtonTest assertEquals("Button is not in all caps", text, button.getLayout().getText()); } + + /** + * Currently only runs on API 22+ due to http://b.android.com/221469 + */ + @Test + @SdkSuppress(minSdkVersion = 22) + public void testBackgroundTintListOnColoredButton() { + testUntintedBackgroundTintingViewCompatAcrossStateChange(R.id.button_colored_untinted); + } } diff --git a/v7/mediarouter/res/values-b+sr+Latn/strings.xml b/v7/mediarouter/res/values-b+sr+Latn/strings.xml index 8075d2e91a..4bc9baa185 100644 --- a/v7/mediarouter/res/values-b+sr+Latn/strings.xml +++ b/v7/mediarouter/res/values-b+sr+Latn/strings.xml @@ -22,7 +22,7 @@ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Dugme Prebaci. Veza je prekinuta"</string> <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Dugme Prebaci. Povezuje se"</string> <string name="mr_cast_button_connected" msgid="5088427771788648085">"Dugme Prebaci. Povezan je"</string> - <string name="mr_chooser_title" msgid="414301941546135990">"Prebacujte na"</string> + <string name="mr_chooser_title" msgid="414301941546135990">"Prebacuj na"</string> <string name="mr_chooser_searching" msgid="6349900579507521956">"Pronalaženje uređaja"</string> <string name="mr_controller_disconnect" msgid="1227264889412989580">"Prekini vezu"</string> <string name="mr_controller_stop" msgid="4570331844078181931">"Zaustavi prebacivanje"</string> diff --git a/v7/mediarouter/res/values-gl-rES/strings.xml b/v7/mediarouter/res/values-gl-rES/strings.xml index 83489ac06e..d6d1888375 100644 --- a/v7/mediarouter/res/values-gl-rES/strings.xml +++ b/v7/mediarouter/res/values-gl-rES/strings.xml @@ -25,7 +25,7 @@ <string name="mr_chooser_title" msgid="414301941546135990">"Emitir en"</string> <string name="mr_chooser_searching" msgid="6349900579507521956">"Buscando dispositivos"</string> <string name="mr_controller_disconnect" msgid="1227264889412989580">"Desconectar"</string> - <string name="mr_controller_stop" msgid="4570331844078181931">"Parar de emitir"</string> + <string name="mr_controller_stop" msgid="4570331844078181931">"Deter emisión"</string> <string name="mr_controller_close_description" msgid="7333862312480583260">"Pechar"</string> <string name="mr_controller_play" msgid="683634565969987458">"Reproduce"</string> <string name="mr_controller_pause" msgid="5451884435510905406">"Pausa"</string> diff --git a/v7/mediarouter/res/values-hi/strings.xml b/v7/mediarouter/res/values-hi/strings.xml index e74967ef55..5b01c7c2da 100644 --- a/v7/mediarouter/res/values-hi/strings.xml +++ b/v7/mediarouter/res/values-hi/strings.xml @@ -19,12 +19,12 @@ <string name="mr_system_route_name" msgid="5441529851481176817">"सिस्टम"</string> <string name="mr_user_route_category_name" msgid="7498112907524977311">"डिवाइस"</string> <string name="mr_button_content_description" msgid="3698378085901466129">"कास्ट करें बटन"</string> - <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिस्कनेक्ट है"</string> + <string name="mr_cast_button_disconnected" msgid="816305490427819240">"कास्ट करें बटन. डिसकनेक्ट है"</string> <string name="mr_cast_button_connecting" msgid="2187642765091873834">"कास्ट करें बटन. कनेक्ट हो रहा है"</string> <string name="mr_cast_button_connected" msgid="5088427771788648085">"कास्ट करें बटन. कनेक्ट है"</string> <string name="mr_chooser_title" msgid="414301941546135990">"इस पर कास्ट करें"</string> <string name="mr_chooser_searching" msgid="6349900579507521956">"डिवाइस ढूंढ रहा है"</string> - <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिस्कनेक्ट करें"</string> + <string name="mr_controller_disconnect" msgid="1227264889412989580">"डिसकनेक्ट करें"</string> <string name="mr_controller_stop" msgid="4570331844078181931">"कास्ट करना बंद करें"</string> <string name="mr_controller_close_description" msgid="7333862312480583260">"बंद करें"</string> <string name="mr_controller_play" msgid="683634565969987458">"चलाएं"</string> diff --git a/v7/mediarouter/res/values-pa-rIN/strings.xml b/v7/mediarouter/res/values-pa-rIN/strings.xml index 258529d00b..c207246430 100644 --- a/v7/mediarouter/res/values-pa-rIN/strings.xml +++ b/v7/mediarouter/res/values-pa-rIN/strings.xml @@ -25,7 +25,7 @@ <string name="mr_chooser_title" msgid="414301941546135990">"ਇਸ ਨਾਲ ਕਾਸਟ ਕਰੋ"</string> <string name="mr_chooser_searching" msgid="6349900579507521956">"ਡਿਵਾਈਸਾਂ ਲੱਭ ਰਿਹਾ ਹੈ"</string> <string name="mr_controller_disconnect" msgid="1227264889412989580">"ਡਿਸਕਨੈਕਟ ਕਰੋ"</string> - <string name="mr_controller_stop" msgid="4570331844078181931">"ਜੋੜਨਾ ਰੋਕੋ"</string> + <string name="mr_controller_stop" msgid="4570331844078181931">"ਕਾਸਟ ਕਰਨਾ ਰੋਕੋ"</string> <string name="mr_controller_close_description" msgid="7333862312480583260">"ਬੰਦ ਕਰੋ"</string> <string name="mr_controller_play" msgid="683634565969987458">"ਪਲੇ ਕਰੋ"</string> <string name="mr_controller_pause" msgid="5451884435510905406">"ਰੋਕੋ"</string> diff --git a/v7/mediarouter/res/values-sr/strings.xml b/v7/mediarouter/res/values-sr/strings.xml index bddc045113..ef1b2b1d57 100644 --- a/v7/mediarouter/res/values-sr/strings.xml +++ b/v7/mediarouter/res/values-sr/strings.xml @@ -22,7 +22,7 @@ <string name="mr_cast_button_disconnected" msgid="816305490427819240">"Дугме Пребаци. Веза је прекинута"</string> <string name="mr_cast_button_connecting" msgid="2187642765091873834">"Дугме Пребаци. Повезује се"</string> <string name="mr_cast_button_connected" msgid="5088427771788648085">"Дугме Пребаци. Повезан је"</string> - <string name="mr_chooser_title" msgid="414301941546135990">"Пребацујте на"</string> + <string name="mr_chooser_title" msgid="414301941546135990">"Пребацуј на"</string> <string name="mr_chooser_searching" msgid="6349900579507521956">"Проналажење уређаја"</string> <string name="mr_controller_disconnect" msgid="1227264889412989580">"Прекини везу"</string> <string name="mr_controller_stop" msgid="4570331844078181931">"Заустави пребацивање"</string> diff --git a/v7/mediarouter/res/values-zh-rCN/strings.xml b/v7/mediarouter/res/values-zh-rCN/strings.xml index c22a91c31e..30c7dcc539 100644 --- a/v7/mediarouter/res/values-zh-rCN/strings.xml +++ b/v7/mediarouter/res/values-zh-rCN/strings.xml @@ -30,7 +30,7 @@ <string name="mr_controller_play" msgid="683634565969987458">"播放"</string> <string name="mr_controller_pause" msgid="5451884435510905406">"暂停"</string> <string name="mr_controller_expand_group" msgid="8062427022744266907">"展开"</string> - <string name="mr_controller_collapse_group" msgid="7924809056904240926">"折叠"</string> + <string name="mr_controller_collapse_group" msgid="7924809056904240926">"收起"</string> <string name="mr_controller_album_art" msgid="6422801843540543585">"专辑封面"</string> <string name="mr_controller_volume_slider" msgid="2361785992211841709">"音量滑块"</string> <string name="mr_controller_no_media_selected" msgid="6547130360349182381">"未选择任何媒体"</string> diff --git a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java index 9c7747c38f..1e78f94210 100644 --- a/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java +++ b/v7/palette/src/main/java/android/support/v7/graphics/ColorCutQuantizer.java @@ -503,9 +503,8 @@ final class ColorCutQuantizer { private static int modifyWordWidth(int value, int currentWidth, int targetWidth) { final int newValue; if (targetWidth > currentWidth) { - // If we're approximating up in word width, we'll use scaling to approximate the - // new value - newValue = value * ((1 << targetWidth) - 1) / ((1 << currentWidth) - 1); + // If we're approximating up in word width, we'll shift up + newValue = value << (targetWidth - currentWidth); } else { // Else, we will just shift and keep the MSB newValue = value >> (currentWidth - targetWidth); diff --git a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java index 46da1b8aa3..b640f79c62 100644 --- a/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java +++ b/v7/recyclerview/src/android/support/v7/widget/RecyclerView.java @@ -453,7 +453,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro * until the end of the layout because a11y service may make sync calls back to the RV while * the View's state is undefined. */ - private final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList(); + @VisibleForTesting + final List<ViewHolder> mPendingAccessibilityImportanceChange = new ArrayList(); private Runnable mItemAnimatorRunner = new Runnable() { @Override @@ -1577,6 +1578,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro .hasAnyUpdateTypes(UpdateOp.ADD | UpdateOp.REMOVE | UpdateOp.MOVE)) { TraceCompat.beginSection(TRACE_HANDLE_ADAPTER_UPDATES_TAG); eatRequestLayout(); + onEnterLayoutOrScroll(); mAdapterHelper.preProcess(); if (!mLayoutRequestEaten) { if (hasUpdatedView()) { @@ -1587,6 +1589,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro } } resumeRequestLayout(true); + onExitLayoutOrScroll(); TraceCompat.endSection(); } else if (mAdapterHelper.hasPendingUpdates()) { TraceCompat.beginSection(TRACE_ON_DATA_SET_CHANGE_LAYOUT_TAG); @@ -4493,15 +4496,15 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro public void run() { try { TraceCompat.beginSection(TRACE_PREFETCH_TAG); - final int prefetchCount = mLayout.getItemPrefetchCount(); if (mAdapter == null || mLayout == null || !mLayout.isItemPrefetchEnabled() - || prefetchCount < 1 + || mLayout.getItemPrefetchCount() < 1 || hasPendingAdapterUpdates()) { // abort - no work return; } + final int prefetchCount = mLayout.getItemPrefetchCount(); // Query last vsync so we can predict next one. Note that drawing time not yet // valid in animation/input callbacks, so query it here to be safe. @@ -9712,7 +9715,8 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro private int mWasImportantForAccessibilityBeforeHidden = ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO; // set if we defer the accessibility state change of the view holder - private int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; + @VisibleForTesting + int mPendingAccessibilityState = PENDING_ACCESSIBILITY_STATE_NOT_SET; /** * Is set when VH is bound from the adapter and cleaned right before it is sent to @@ -10104,7 +10108,7 @@ public class RecyclerView extends ViewGroup implements ScrollingView, NestedScro for (int i = mPendingAccessibilityImportanceChange.size() - 1; i >= 0; i --) { ViewHolder viewHolder = mPendingAccessibilityImportanceChange.get(i); if (viewHolder.itemView.getParent() != this || viewHolder.shouldIgnore()) { - return; + continue; } int state = viewHolder.mPendingAccessibilityState; if (state != ViewHolder.PENDING_ACCESSIBILITY_STATE_NOT_SET) { diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java index 7ba99a5453..4a7cb27d68 100644 --- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java +++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewAccessibilityLifecycleTest.java @@ -19,18 +19,31 @@ package android.support.v7.widget; import org.junit.Test; import org.junit.runner.RunWith; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.mockito.Matchers.anyInt; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.annotation.TargetApi; +import android.content.Context; +import android.os.Build; +import android.support.test.filters.SdkSuppress; import android.support.test.runner.AndroidJUnit4; import android.support.v4.view.ViewCompat; +import android.view.View; import android.view.ViewGroup; +import org.mockito.Mockito; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.concurrent.atomic.AtomicBoolean; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; - @RunWith(AndroidJUnit4.class) public class RecyclerViewAccessibilityLifecycleTest extends BaseRecyclerViewInstrumentationTest { @Test @@ -86,6 +99,39 @@ public class RecyclerViewAccessibilityLifecycleTest extends BaseRecyclerViewInst assertThat(calledA11DuringLayout.get(), is(false)); } + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) + @TargetApi(Build.VERSION_CODES.JELLY_BEAN) + @Test + public void processAllViewHolders() { + RecyclerView rv = new RecyclerView(getActivity()); + rv.setLayoutManager(new LinearLayoutManager(getActivity())); + View itemView1 = spy(new View(getActivity())); + View itemView2 = spy(new View(getActivity())); + View itemView3 = spy(new View(getActivity())); + + rv.addView(itemView1); + // do not add 2 + rv.addView(itemView3); + + RecyclerView.ViewHolder vh1 = new RecyclerView.ViewHolder(itemView1) {}; + vh1.mPendingAccessibilityState = View.IMPORTANT_FOR_ACCESSIBILITY_YES; + RecyclerView.ViewHolder vh2 = new RecyclerView.ViewHolder(itemView2) {}; + vh2.mPendingAccessibilityState = View.IMPORTANT_FOR_ACCESSIBILITY_YES; + RecyclerView.ViewHolder vh3 = new RecyclerView.ViewHolder(itemView3) {}; + vh3.mPendingAccessibilityState = View.IMPORTANT_FOR_ACCESSIBILITY_AUTO; + + rv.mPendingAccessibilityImportanceChange.add(vh1); + rv.mPendingAccessibilityImportanceChange.add(vh2); + rv.mPendingAccessibilityImportanceChange.add(vh3); + rv.dispatchPendingImportantForAccessibilityChanges(); + + verify(itemView1).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); + //noinspection WrongConstant + verify(itemView2, never()).setImportantForAccessibility(anyInt()); + verify(itemView3).setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); + assertThat(rv.mPendingAccessibilityImportanceChange.size(), is(0)); + } + public class LayoutAllLayoutManager extends TestLayoutManager { private final boolean mAllowNullLayoutLatch; diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java index 97f9b92676..9166210c05 100644 --- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java +++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewBasicTest.java @@ -364,6 +364,12 @@ public class RecyclerViewBasicTest { assertEquals(RecyclerView.Recycler.DEFAULT_CACHE_SIZE + 3, recycler.mViewCacheMax); } + @SdkSuppress(minSdkVersion = Build.VERSION_CODES.LOLLIPOP) + @Test + public void prefetcherWithoutLayout() { + mRecyclerView.mViewPrefetcher.run(); + } + static class MockLayoutManager extends RecyclerView.LayoutManager { int mLayoutCount = 0; 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 770ab71ea3..80adf64dfe 100644 --- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java +++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewLayoutTest.java @@ -97,6 +97,64 @@ public class RecyclerViewLayoutTest extends BaseRecyclerViewInstrumentationTest } @Test + public void triggerFocusSearchInOnRecycledCallback() throws Throwable { + final RecyclerView rv = new RecyclerView(getActivity()) { + @Override + void consumePendingUpdateOperations() { + try { + super.consumePendingUpdateOperations(); + } catch (Throwable t) { + postExceptionToInstrumentation(t); + } + } + }; + final AtomicBoolean receivedOnRecycled = new AtomicBoolean(false); + final TestAdapter adapter = new TestAdapter(20) { + @Override + public void onViewRecycled(TestViewHolder holder) { + super.onViewRecycled(holder); + if (receivedOnRecycled.getAndSet(true)) { + return; + } + rv.focusSearch(rv.getChildAt(0), View.FOCUS_FORWARD); + } + }; + final AtomicInteger layoutCnt = new AtomicInteger(5); + TestLayoutManager tlm = new TestLayoutManager() { + @Override + public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) { + detachAndScrapAttachedViews(recycler); + layoutRange(recycler, 0, layoutCnt.get()); + layoutLatch.countDown(); + } + }; + rv.setLayoutManager(tlm); + rv.setAdapter(adapter); + tlm.expectLayouts(1); + setRecyclerView(rv); + tlm.waitForLayout(2); + + layoutCnt.set(4); + tlm.expectLayouts(1); + requestLayoutOnUIThread(rv); + tlm.waitForLayout(1); + + assertThat("test sanity", rv.mRecycler.mCachedViews.size(), is(1)); + tlm.expectLayouts(1); + runTestOnUiThread(new Runnable() { + @Override + public void run() { + adapter.notifyItemChanged(4); + rv.smoothScrollBy(0, 1); + } + }); + checkForMainThreadException(); + tlm.waitForLayout(2); + assertThat("test sanity", rv.mRecycler.mCachedViews.size(), is(0)); + assertThat(receivedOnRecycled.get(), is(true)); + } + + @Test public void detachAttachGetReadyWithoutChanges() throws Throwable { detachAttachGetReady(false, false, false); } |