Merge "BottomSheet supports nested scrolling" into mnc-ub-dev
diff --git a/design/api/current.txt b/design/api/current.txt
index cfeed81..18c6056 100644
--- a/design/api/current.txt
+++ b/design/api/current.txt
@@ -188,8 +188,8 @@
ctor public CoordinatorLayout.Behavior();
ctor public CoordinatorLayout.Behavior(android.content.Context, android.util.AttributeSet);
method public boolean blocksInteractionBelow(android.support.design.widget.CoordinatorLayout, V);
- method public final int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
- method public final float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
+ method public int getScrimColor(android.support.design.widget.CoordinatorLayout, V);
+ method public float getScrimOpacity(android.support.design.widget.CoordinatorLayout, V);
method public static java.lang.Object getTag(android.view.View);
method public boolean isDirty(android.support.design.widget.CoordinatorLayout, V);
method public boolean layoutDependsOn(android.support.design.widget.CoordinatorLayout, V, android.view.View);
diff --git a/design/build.gradle b/design/build.gradle
index 59d8a53..7148e50 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -14,8 +14,8 @@
exclude module: 'support-annotations'
}
androidTestCompile 'org.mockito:mockito-core:1.9.5'
- androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
- androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'junit:junit:4.12'
}
diff --git a/design/src/android/support/design/widget/AppBarLayout.java b/design/src/android/support/design/widget/AppBarLayout.java
index e5b8f46..2208988 100644
--- a/design/src/android/support/design/widget/AppBarLayout.java
+++ b/design/src/android/support/design/widget/AppBarLayout.java
@@ -165,8 +165,7 @@
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
- setWindowInsets(insets);
- return insets.consumeSystemWindowInsets();
+ return setWindowInsets(insets);
}
});
}
@@ -424,7 +423,7 @@
}
final int getMinimumHeightForVisibleOverlappingContent() {
- final int topInset = mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
+ final int topInset = getTopInset();
final int minHeight = ViewCompat.getMinimumHeight(this);
if (minHeight != 0) {
// If this layout has a min height, use it (doubled)
@@ -474,19 +473,11 @@
return mLastInsets != null ? mLastInsets.getSystemWindowInsetTop() : 0;
}
- private void setWindowInsets(WindowInsetsCompat insets) {
+ private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
// Invalidate the total scroll range...
mTotalScrollRange = INVALID_SCROLL_RANGE;
mLastInsets = insets;
-
- // Now dispatch them to our children
- for (int i = 0, z = getChildCount(); i < z; i++) {
- final View child = getChildAt(i);
- insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
- if (insets.isConsumed()) {
- break;
- }
- }
+ return insets;
}
public static class LayoutParams extends LinearLayout.LayoutParams {
diff --git a/design/src/android/support/design/widget/CollapsingToolbarLayout.java b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
index ec07441..100150e 100644
--- a/design/src/android/support/design/widget/CollapsingToolbarLayout.java
+++ b/design/src/android/support/design/widget/CollapsingToolbarLayout.java
@@ -23,7 +23,6 @@
import android.graphics.Typeface;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
-import android.os.Build;
import android.support.annotation.ColorInt;
import android.support.annotation.DrawableRes;
import android.support.annotation.IntDef;
@@ -32,6 +31,7 @@
import android.support.annotation.StyleRes;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.view.ViewCompat;
import android.support.v4.view.WindowInsetsCompat;
@@ -65,7 +65,8 @@
* <h4>Status bar scrim</h4>
* A scrim which is show or hidden behind the status bar when the scroll position has hit a certain
* threshold. You can change this via {@link #setStatusBarScrim(Drawable)}. This only works
- * on {@link Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system windows.
+ * on {@link android.os.Build.VERSION_CODES#LOLLIPOP LOLLIPOP} devices when we set to fit system
+ * windows.
*
* <h4>Parallax scrolling children</h4>
* Child views can opt to be scrolled within this layout in a parallax fashion.
@@ -206,9 +207,7 @@
@Override
public WindowInsetsCompat onApplyWindowInsets(View v,
WindowInsetsCompat insets) {
- mLastInsets = insets;
- requestLayout();
- return insets.consumeSystemWindowInsets();
+ return setWindowInsets(insets);
}
});
}
@@ -225,6 +224,9 @@
}
((AppBarLayout) parent).addOnOffsetChangedListener(mOnOffsetChangedListener);
}
+
+ // We're attached, so lets request an inset dispatch
+ ViewCompat.requestApplyInsets(this);
}
@Override
@@ -238,6 +240,14 @@
super.onDetachedFromWindow();
}
+ private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
+ if (mLastInsets != insets) {
+ mLastInsets = insets;
+ requestLayout();
+ }
+ return insets.consumeSystemWindowInsets();
+ }
+
@Override
public void draw(Canvas canvas) {
super.draw(canvas);
@@ -409,7 +419,7 @@
if (child.getTop() < insetTop) {
// If the child isn't set to fit system windows but is drawing within the inset
// offset it down
- child.offsetTopAndBottom(insetTop);
+ ViewCompat.offsetTopAndBottom(child, insetTop);
}
}
@@ -583,13 +593,11 @@
if (mContentScrim != null) {
mContentScrim.setCallback(null);
}
- if (drawable != null) {
- mContentScrim = drawable.mutate();
- drawable.setBounds(0, 0, getWidth(), getHeight());
- drawable.setCallback(this);
- drawable.setAlpha(mScrimAlpha);
- } else {
- mContentScrim = null;
+ mContentScrim = drawable != null ? drawable.mutate() : null;
+ if (mContentScrim != null) {
+ mContentScrim.setBounds(0, 0, getWidth(), getHeight());
+ mContentScrim.setCallback(this);
+ mContentScrim.setAlpha(mScrimAlpha);
}
ViewCompat.postInvalidateOnAnimation(this);
}
@@ -626,6 +634,7 @@
* @attr ref R.styleable#CollapsingToolbarLayout_contentScrim
* @see #setContentScrim(Drawable)
*/
+ @Nullable
public Drawable getContentScrim() {
return mContentScrim;
}
@@ -646,14 +655,60 @@
if (mStatusBarScrim != null) {
mStatusBarScrim.setCallback(null);
}
-
- mStatusBarScrim = drawable;
- drawable.setCallback(this);
- drawable.mutate().setAlpha(mScrimAlpha);
+ mStatusBarScrim = drawable != null ? drawable.mutate() : null;
+ if (mStatusBarScrim != null) {
+ if (mStatusBarScrim.isStateful()) {
+ mStatusBarScrim.setState(getDrawableState());
+ }
+ DrawableCompat.setLayoutDirection(mStatusBarScrim,
+ ViewCompat.getLayoutDirection(this));
+ mStatusBarScrim.setVisible(getVisibility() == VISIBLE, false);
+ mStatusBarScrim.setCallback(this);
+ mStatusBarScrim.setAlpha(mScrimAlpha);
+ }
ViewCompat.postInvalidateOnAnimation(this);
}
}
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final int[] state = getDrawableState();
+ boolean changed = false;
+
+ Drawable d = mStatusBarScrim;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+ d = mContentScrim;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+
+ if (changed) {
+ invalidate();
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mContentScrim || who == mStatusBarScrim;
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ final boolean visible = visibility == VISIBLE;
+ if (mStatusBarScrim != null && mStatusBarScrim.isVisible() != visible) {
+ mStatusBarScrim.setVisible(visible, false);
+ }
+ if (mContentScrim != null && mContentScrim.isVisible() != visible) {
+ mContentScrim.setVisible(visible, false);
+ }
+ }
+
/**
* Set the color to use for the status bar scrim.
*
@@ -686,6 +741,7 @@
* @attr ref R.styleable#CollapsingToolbarLayout_statusBarScrim
* @see #setStatusBarScrim(Drawable)
*/
+ @Nullable
public Drawable getStatusBarScrim() {
return mStatusBarScrim;
}
diff --git a/design/src/android/support/design/widget/CoordinatorLayout.java b/design/src/android/support/design/widget/CoordinatorLayout.java
index ea5b0c4..f5d8797 100644
--- a/design/src/android/support/design/widget/CoordinatorLayout.java
+++ b/design/src/android/support/design/widget/CoordinatorLayout.java
@@ -29,8 +29,12 @@
import android.os.Parcel;
import android.os.Parcelable;
import android.os.SystemClock;
+import android.support.annotation.ColorInt;
+import android.support.annotation.DrawableRes;
+import android.support.annotation.Nullable;
import android.support.design.R;
import android.support.v4.content.ContextCompat;
+import android.support.v4.graphics.drawable.DrawableCompat;
import android.support.v4.os.ParcelableCompat;
import android.support.v4.os.ParcelableCompatCreatorCallbacks;
import android.support.v4.view.GravityCompat;
@@ -242,9 +246,23 @@
*
* @param bg Background drawable to draw behind the status bar
*/
- public void setStatusBarBackground(Drawable bg) {
- mStatusBarBackground = bg;
- invalidate();
+ public void setStatusBarBackground(@Nullable final Drawable bg) {
+ if (mStatusBarBackground != bg) {
+ if (mStatusBarBackground != null) {
+ mStatusBarBackground.setCallback(null);
+ }
+ mStatusBarBackground = bg != null ? bg.mutate() : null;
+ if (mStatusBarBackground != null) {
+ if (mStatusBarBackground.isStateful()) {
+ mStatusBarBackground.setState(getDrawableState());
+ }
+ DrawableCompat.setLayoutDirection(mStatusBarBackground,
+ ViewCompat.getLayoutDirection(this));
+ mStatusBarBackground.setVisible(getVisibility() == VISIBLE, false);
+ mStatusBarBackground.setCallback(this);
+ }
+ ViewCompat.postInvalidateOnAnimation(this);
+ }
}
/**
@@ -252,17 +270,50 @@
*
* @return The status bar background drawable, or null if none set
*/
+ @Nullable
public Drawable getStatusBarBackground() {
return mStatusBarBackground;
}
+ @Override
+ protected void drawableStateChanged() {
+ super.drawableStateChanged();
+
+ final int[] state = getDrawableState();
+ boolean changed = false;
+
+ Drawable d = mStatusBarBackground;
+ if (d != null && d.isStateful()) {
+ changed |= d.setState(state);
+ }
+
+ if (changed) {
+ invalidate();
+ }
+ }
+
+ @Override
+ protected boolean verifyDrawable(Drawable who) {
+ return super.verifyDrawable(who) || who == mStatusBarBackground;
+ }
+
+ @Override
+ public void setVisibility(int visibility) {
+ super.setVisibility(visibility);
+
+ final boolean visible = visibility == VISIBLE;
+ if (mStatusBarBackground != null && mStatusBarBackground.isVisible() != visible) {
+ mStatusBarBackground.setVisible(visible, false);
+ }
+ }
+
/**
* Set a drawable to draw in the insets area for the status bar.
* Note that this will only be activated if this DrawerLayout fitsSystemWindows.
*
* @param resId Resource id of a background drawable to draw behind the status bar
*/
- public void setStatusBarBackgroundResource(int resId) {
+ public void setStatusBarBackgroundResource(@DrawableRes int resId) {
setStatusBarBackground(resId != 0 ? ContextCompat.getDrawable(getContext(), resId) : null);
}
@@ -273,18 +324,21 @@
* @param color Color to use as a background drawable to draw behind the status bar
* in 0xAARRGGBB format.
*/
- public void setStatusBarBackgroundColor(int color) {
+ public void setStatusBarBackgroundColor(@ColorInt int color) {
setStatusBarBackground(new ColorDrawable(color));
}
- private void setWindowInsets(WindowInsetsCompat insets) {
+ private WindowInsetsCompat setWindowInsets(WindowInsetsCompat insets) {
if (mLastInsets != insets) {
mLastInsets = insets;
mDrawStatusBarBackground = insets != null && insets.getSystemWindowInsetTop() > 0;
setWillNotDraw(!mDrawStatusBarBackground && getBackground() == null);
- dispatchChildApplyWindowInsets(insets);
+
+ // Now dispatch to the Behaviors
+ insets = dispatchApplyWindowInsetsToBehaviors(insets);
requestLayout();
}
+ return insets;
}
/**
@@ -694,9 +748,9 @@
setMeasuredDimension(width, height);
}
- private void dispatchChildApplyWindowInsets(WindowInsetsCompat insets) {
+ private WindowInsetsCompat dispatchApplyWindowInsetsToBehaviors(WindowInsetsCompat insets) {
if (insets.isConsumed()) {
- return;
+ return insets;
}
for (int i = 0, z = getChildCount(); i < z; i++) {
@@ -713,14 +767,10 @@
break;
}
}
-
- // Now let the view try and consume them
- insets = ViewCompat.dispatchApplyWindowInsets(child, insets);
- if (insets.isConsumed()) {
- break;
- }
}
}
+
+ return insets;
}
/**
@@ -1698,7 +1748,7 @@
* {@link Color#BLACK}.
* @see #getScrimOpacity(CoordinatorLayout, android.view.View)
*/
- public final int getScrimColor(CoordinatorLayout parent, V child) {
+ public int getScrimColor(CoordinatorLayout parent, V child) {
return Color.BLACK;
}
@@ -1715,7 +1765,7 @@
* @param child the child view above the scrim
* @return the desired scrim opacity from 0.0f to 1.0f. The default return value is 0.0f.
*/
- public final float getScrimOpacity(CoordinatorLayout parent, V child) {
+ public float getScrimOpacity(CoordinatorLayout parent, V child) {
return 0.f;
}
@@ -2528,15 +2578,15 @@
}
}
- final class ApplyInsetsListener implements android.support.v4.view.OnApplyWindowInsetsListener {
+ private class ApplyInsetsListener
+ implements android.support.v4.view.OnApplyWindowInsetsListener {
@Override
public WindowInsetsCompat onApplyWindowInsets(View v, WindowInsetsCompat insets) {
- setWindowInsets(insets);
- return insets.consumeSystemWindowInsets();
+ return setWindowInsets(insets);
}
}
- final class HierarchyChangeListener implements OnHierarchyChangeListener {
+ private class HierarchyChangeListener implements OnHierarchyChangeListener {
@Override
public void onChildViewAdded(View parent, View child) {
if (mOnHierarchyChangeListener != null) {
diff --git a/v17/leanback/api/current.txt b/v17/leanback/api/current.txt
index d90f9f6..e573a55 100644
--- a/v17/leanback/api/current.txt
+++ b/v17/leanback/api/current.txt
@@ -330,11 +330,11 @@
method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersFragment.OnHeaderViewSelectedListener);
}
- static abstract interface HeadersFragment.OnHeaderClickedListener {
- method public abstract void onHeaderClicked();
+ public static abstract interface HeadersFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
+ public static abstract interface HeadersFragment.OnHeaderViewSelectedListener {
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
@@ -344,11 +344,11 @@
method public void setOnHeaderViewSelectedListener(android.support.v17.leanback.app.HeadersSupportFragment.OnHeaderViewSelectedListener);
}
- static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
- method public abstract void onHeaderClicked();
+ public static abstract interface HeadersSupportFragment.OnHeaderClickedListener {
+ method public abstract void onHeaderClicked(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
- static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
+ public static abstract interface HeadersSupportFragment.OnHeaderViewSelectedListener {
method public abstract void onHeaderSelected(android.support.v17.leanback.widget.RowHeaderPresenter.ViewHolder, android.support.v17.leanback.widget.Row);
}
diff --git a/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java b/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
index e9f8505..f76acf5 100644
--- a/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
+++ b/v17/leanback/api21/android/support/v17/leanback/transition/FadeAndShortSlide.java
@@ -18,11 +18,15 @@
import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.TimeInterpolator;
+import android.content.Context;
+import android.content.res.TypedArray;
import android.graphics.Rect;
+import android.support.v17.leanback.R;
import android.transition.Fade;
import android.transition.Transition;
import android.transition.TransitionValues;
import android.transition.Visibility;
+import android.util.AttributeSet;
import android.view.Gravity;
import android.view.View;
import android.view.ViewGroup;
@@ -146,6 +150,14 @@
setSlideEdge(slideEdge);
}
+ public FadeAndShortSlide(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.lbSlide);
+ int edge = a.getInt(R.styleable.lbSlide_lb_slideEdge, Gravity.START);
+ setSlideEdge(edge);
+ a.recycle();
+ }
+
@Override
public void setEpicenterCallback(EpicenterCallback epicenterCallback) {
super.setEpicenterCallback(epicenterCallback);
@@ -159,17 +171,22 @@
transitionValues.values.put(PROPNAME_SCREEN_POSITION, position);
}
+ // We should not be calling mFade.captureStartValues (or captureEndValues)
+ // as it will duplicate the work done by {@code super.captureStartValues} and
+ // as a side effect, will wrongly override visibility attribute to visible
+ // as forceVisibility isn't set on Fade. As both start and end views will have
+ // the same value for visibility, framework will skip any animation. To avoid
+ // that, we only use it during onAppear and onDisappear calls, to run fade and
+ // slide animations together.
@Override
public void captureStartValues(TransitionValues transitionValues) {
super.captureStartValues(transitionValues);
- mFade.captureStartValues(transitionValues);
captureValues(transitionValues);
}
@Override
public void captureEndValues(TransitionValues transitionValues) {
super.captureEndValues(transitionValues);
- mFade.captureEndValues(transitionValues);
captureValues(transitionValues);
}
@@ -196,9 +213,6 @@
default:
throw new IllegalArgumentException("Invalid slide direction");
}
- // SidePropagation propagation = new SidePropagation();
- // propagation.setSide(slideEdge);
- // setPropagation(propagation);
}
@Override
@@ -221,6 +235,7 @@
final Animator slideAnimator = TranslationAnimationCreator.createAnimation(view, endValues,
left, top, startX, startY, endX, endY, sDecelerate, this);
final Animator fadeAnimator = mFade.onAppear(sceneRoot, view, startValues, endValues);
+
if (slideAnimator == null) {
return fadeAnimator;
} else if (fadeAnimator == null) {
diff --git a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
index 08a80f3..3a097e8 100644
--- a/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
+++ b/v17/leanback/res/transition-v21/lb_guidedstep_activity_enter_bottom.xml
@@ -15,7 +15,9 @@
limitations under the License.
-->
-<slide xmlns:android="http://schemas.android.com/apk/res/android"
+<transition class="android.support.v17.leanback.transition.FadeAndShortSlide"
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:custom="http://schemas.android.com/apk/res-auto"
android:interpolator="@android:interpolator/fast_out_linear_in"
android:duration="@integer/lb_guidedstep_activity_background_fade_duration_ms"
- android:slideEdge="bottom" />
+ custom:lb_slideEdge="bottom" />
diff --git a/v17/leanback/res/values-af/strings.xml b/v17/leanback/res/values-af/strings.xml
index ab85099..2ae8338 100644
--- a/v17/leanback/res/values-af/strings.xml
+++ b/v17/leanback/res/values-af/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Gaan voort"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BEGIN HIER"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-am/strings.xml b/v17/leanback/res/values-am/strings.xml
index d14d201..6f7fb1a 100644
--- a/v17/leanback/res/values-am/strings.xml
+++ b/v17/leanback/res/values-am/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"፦"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ይጀምሩ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-ar/strings.xml b/v17/leanback/res/values-ar/strings.xml
index 08e4c8f..30fba61 100644
--- a/v17/leanback/res/values-ar/strings.xml
+++ b/v17/leanback/res/values-ar/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"البدء"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-az-rAZ/strings.xml b/v17/leanback/res/values-az-rAZ/strings.xml
index a1d9fe4..9d9de73 100644
--- a/v17/leanback/res/values-az-rAZ/strings.xml
+++ b/v17/leanback/res/values-az-rAZ/strings.xml
@@ -51,4 +51,5 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"BAŞLAYIN"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Növbəti"</string>
</resources>
diff --git a/v17/leanback/res/values-bg/strings.xml b/v17/leanback/res/values-bg/strings.xml
index 33ed443..376b287 100644
--- a/v17/leanback/res/values-bg/strings.xml
+++ b/v17/leanback/res/values-bg/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Напред"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ПЪРВИ СТЪПКИ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-bn-rBD/strings.xml b/v17/leanback/res/values-bn-rBD/strings.xml
index 2f628c2..c16d7bb 100644
--- a/v17/leanback/res/values-bn-rBD/strings.xml
+++ b/v17/leanback/res/values-bn-rBD/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"চালিয়ে যান"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"শুরু করা যাক"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ca/strings.xml b/v17/leanback/res/values-ca/strings.xml
index 376474b..6bd9def 100644
--- a/v17/leanback/res/values-ca/strings.xml
+++ b/v17/leanback/res/values-ca/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continua"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMENÇA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-cs/strings.xml b/v17/leanback/res/values-cs/strings.xml
index d7efc86..9721d10 100644
--- a/v17/leanback/res/values-cs/strings.xml
+++ b/v17/leanback/res/values-cs/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Pokračovat"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČÍNÁME"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-da/strings.xml b/v17/leanback/res/values-da/strings.xml
index 62b5517..3fb7091 100644
--- a/v17/leanback/res/values-da/strings.xml
+++ b/v17/leanback/res/values-da/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsæt"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM GODT I GANG"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-de/strings.xml b/v17/leanback/res/values-de/strings.xml
index aef533f..2860679 100644
--- a/v17/leanback/res/values-de/strings.xml
+++ b/v17/leanback/res/values-de/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Weiter"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"JETZT STARTEN"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-el/strings.xml b/v17/leanback/res/values-el/strings.xml
index 82ac5f9..a77d13e 100644
--- a/v17/leanback/res/values-el/strings.xml
+++ b/v17/leanback/res/values-el/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ΕΝΑΡΞΗ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-en-rAU/strings.xml b/v17/leanback/res/values-en-rAU/strings.xml
index 69c0ad9..829375a 100644
--- a/v17/leanback/res/values-en-rAU/strings.xml
+++ b/v17/leanback/res/values-en-rAU/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-en-rGB/strings.xml b/v17/leanback/res/values-en-rGB/strings.xml
index 69c0ad9..829375a 100644
--- a/v17/leanback/res/values-en-rGB/strings.xml
+++ b/v17/leanback/res/values-en-rGB/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-en-rIN/strings.xml b/v17/leanback/res/values-en-rIN/strings.xml
index 69c0ad9..829375a 100644
--- a/v17/leanback/res/values-en-rIN/strings.xml
+++ b/v17/leanback/res/values-en-rIN/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"GET STARTED"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-es-rUS/strings.xml b/v17/leanback/res/values-es-rUS/strings.xml
index 399df2d..51668f6 100644
--- a/v17/leanback/res/values-es-rUS/strings.xml
+++ b/v17/leanback/res/values-es-rUS/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMENZAR"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-es/strings.xml b/v17/leanback/res/values-es/strings.xml
index e87e627..6420a61 100644
--- a/v17/leanback/res/values-es/strings.xml
+++ b/v17/leanback/res/values-es/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"EMPEZAR"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Siguiente"</string>
</resources>
diff --git a/v17/leanback/res/values-et-rEE/strings.xml b/v17/leanback/res/values-et-rEE/strings.xml
index f45d6c3..de4b0e1 100644
--- a/v17/leanback/res/values-et-rEE/strings.xml
+++ b/v17/leanback/res/values-et-rEE/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jätka"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ALUSTAGE"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-eu-rES/strings.xml b/v17/leanback/res/values-eu-rES/strings.xml
index 843197a..9b13d3d 100644
--- a/v17/leanback/res/values-eu-rES/strings.xml
+++ b/v17/leanback/res/values-eu-rES/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jarraitu"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"LEHEN URRATSAK"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-fa/strings.xml b/v17/leanback/res/values-fa/strings.xml
index 13b74f9..cebaf3e 100644
--- a/v17/leanback/res/values-fa/strings.xml
+++ b/v17/leanback/res/values-fa/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"شروع به کار"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-fi/strings.xml b/v17/leanback/res/values-fi/strings.xml
index 00c6a41..8198784 100644
--- a/v17/leanback/res/values-fi/strings.xml
+++ b/v17/leanback/res/values-fi/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Jatka"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"."</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ALOITA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-fr-rCA/strings.xml b/v17/leanback/res/values-fr-rCA/strings.xml
index 7aa8278..c5adcd4 100644
--- a/v17/leanback/res/values-fr-rCA/strings.xml
+++ b/v17/leanback/res/values-fr-rCA/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuer"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMMENCER"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-fr/strings.xml b/v17/leanback/res/values-fr/strings.xml
index a96195c..b2822e8 100644
--- a/v17/leanback/res/values-fr/strings.xml
+++ b/v17/leanback/res/values-fr/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"COMMENCER"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-gl-rES/strings.xml b/v17/leanback/res/values-gl-rES/strings.xml
index 332fc8e..85dafcb 100644
--- a/v17/leanback/res/values-gl-rES/strings.xml
+++ b/v17/leanback/res/values-gl-rES/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"INTRODUCIÓN"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-gu-rIN/strings.xml b/v17/leanback/res/values-gu-rIN/strings.xml
index 077c3e2..34acc78 100644
--- a/v17/leanback/res/values-gu-rIN/strings.xml
+++ b/v17/leanback/res/values-gu-rIN/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"પ્રારંભ કરો"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-hi/strings.xml b/v17/leanback/res/values-hi/strings.xml
index b19cc96..71e1b06 100644
--- a/v17/leanback/res/values-hi/strings.xml
+++ b/v17/leanback/res/values-hi/strings.xml
@@ -51,4 +51,5 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"प्रारंभ करें"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"अगला"</string>
</resources>
diff --git a/v17/leanback/res/values-hr/strings.xml b/v17/leanback/res/values-hr/strings.xml
index a9fe185..fc17908 100644
--- a/v17/leanback/res/values-hr/strings.xml
+++ b/v17/leanback/res/values-hr/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Nastavi"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"."</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"POČETAK"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-hu/strings.xml b/v17/leanback/res/values-hu/strings.xml
index 79d594b..ed12504 100644
--- a/v17/leanback/res/values-hu/strings.xml
+++ b/v17/leanback/res/values-hu/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"KEZDŐ LÉPÉSEK"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-hy-rAM/strings.xml b/v17/leanback/res/values-hy-rAM/strings.xml
index 9144125..1515f5d 100644
--- a/v17/leanback/res/values-hy-rAM/strings.xml
+++ b/v17/leanback/res/values-hy-rAM/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Շարունակել"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ՍԿՍԵԼ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-in/strings.xml b/v17/leanback/res/values-in/strings.xml
index 414e656..f66a247 100644
--- a/v17/leanback/res/values-in/strings.xml
+++ b/v17/leanback/res/values-in/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Lanjutkan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"."</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MULAI"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Berikutnya"</string>
</resources>
diff --git a/v17/leanback/res/values-is-rIS/strings.xml b/v17/leanback/res/values-is-rIS/strings.xml
index bce3b94..60fc7af 100644
--- a/v17/leanback/res/values-is-rIS/strings.xml
+++ b/v17/leanback/res/values-is-rIS/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Halda áfram"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"HEFJAST HANDA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-it/strings.xml b/v17/leanback/res/values-it/strings.xml
index 18e64f1..71b029f 100644
--- a/v17/leanback/res/values-it/strings.xml
+++ b/v17/leanback/res/values-it/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"INIZIA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-iw/strings.xml b/v17/leanback/res/values-iw/strings.xml
index b0e622e..4094b49 100644
--- a/v17/leanback/res/values-iw/strings.xml
+++ b/v17/leanback/res/values-iw/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"המשך"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"התחל"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ja/strings.xml b/v17/leanback/res/values-ja/strings.xml
index 174a85a..4d1c83b 100644
--- a/v17/leanback/res/values-ja/strings.xml
+++ b/v17/leanback/res/values-ja/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"使ってみる"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-ka-rGE/strings.xml b/v17/leanback/res/values-ka-rGE/strings.xml
index 521637d..66e8fd7 100644
--- a/v17/leanback/res/values-ka-rGE/strings.xml
+++ b/v17/leanback/res/values-ka-rGE/strings.xml
@@ -54,6 +54,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"გაგრძელება"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"დაწყება"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-kk-rKZ/strings.xml b/v17/leanback/res/values-kk-rKZ/strings.xml
index 658ee13..258e6bb 100644
--- a/v17/leanback/res/values-kk-rKZ/strings.xml
+++ b/v17/leanback/res/values-kk-rKZ/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ІСКЕ КІРІСУ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-km-rKH/strings.xml b/v17/leanback/res/values-km-rKH/strings.xml
index 8535aaa..9b1ca69 100644
--- a/v17/leanback/res/values-km-rKH/strings.xml
+++ b/v17/leanback/res/values-km-rKH/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"៖"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ចាប់ផ្ដើម"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-kn-rIN/strings.xml b/v17/leanback/res/values-kn-rIN/strings.xml
index 824368c..11d743a 100644
--- a/v17/leanback/res/values-kn-rIN/strings.xml
+++ b/v17/leanback/res/values-kn-rIN/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ಪ್ರಾರಂಭಿಸಿ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-ko/strings.xml b/v17/leanback/res/values-ko/strings.xml
index eaeba5d..5b36514 100644
--- a/v17/leanback/res/values-ko/strings.xml
+++ b/v17/leanback/res/values-ko/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"계속"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"시작하기"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ky-rKG/strings.xml b/v17/leanback/res/values-ky-rKG/strings.xml
index d70ff69..94e70f3 100644
--- a/v17/leanback/res/values-ky-rKG/strings.xml
+++ b/v17/leanback/res/values-ky-rKG/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Улантуу"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"БАШТАДЫК"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-lo-rLA/strings.xml b/v17/leanback/res/values-lo-rLA/strings.xml
index 24db67b..4525e31 100644
--- a/v17/leanback/res/values-lo-rLA/strings.xml
+++ b/v17/leanback/res/values-lo-rLA/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ສືບຕໍ່"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ເລີ່ມຕົ້ນນຳໃຊ້"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-lt/strings.xml b/v17/leanback/res/values-lt/strings.xml
index a7b68c0..397fcff 100644
--- a/v17/leanback/res/values-lt/strings.xml
+++ b/v17/leanback/res/values-lt/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Tęsti"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRADĖTI"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-lv/strings.xml b/v17/leanback/res/values-lv/strings.xml
index 2adc01c..7445495 100644
--- a/v17/leanback/res/values-lv/strings.xml
+++ b/v17/leanback/res/values-lv/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"SĀKT DARBU"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-mk-rMK/strings.xml b/v17/leanback/res/values-mk-rMK/strings.xml
index 1101cdc..d321a69 100644
--- a/v17/leanback/res/values-mk-rMK/strings.xml
+++ b/v17/leanback/res/values-mk-rMK/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Продолжи"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЗАПОЧНИ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ml-rIN/strings.xml b/v17/leanback/res/values-ml-rIN/strings.xml
index fe85def..0cda0b0 100644
--- a/v17/leanback/res/values-ml-rIN/strings.xml
+++ b/v17/leanback/res/values-ml-rIN/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"തുടരുക"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ആരംഭിക്കുക"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-mn-rMN/strings.xml b/v17/leanback/res/values-mn-rMN/strings.xml
index 1e4726e..a5ee5d3 100644
--- a/v17/leanback/res/values-mn-rMN/strings.xml
+++ b/v17/leanback/res/values-mn-rMN/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЭХЭЛЦГЭЭЕ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-mr-rIN/strings.xml b/v17/leanback/res/values-mr-rIN/strings.xml
index 8428f6d..15222be 100644
--- a/v17/leanback/res/values-mr-rIN/strings.xml
+++ b/v17/leanback/res/values-mr-rIN/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"सुरू ठेवा"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"प्रारंभ करा"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"पुढील"</string>
</resources>
diff --git a/v17/leanback/res/values-ms-rMY/strings.xml b/v17/leanback/res/values-ms-rMY/strings.xml
index c045b0f..244bf7f 100644
--- a/v17/leanback/res/values-ms-rMY/strings.xml
+++ b/v17/leanback/res/values-ms-rMY/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Teruskan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MULAKAN"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-my-rMM/strings.xml b/v17/leanback/res/values-my-rMM/strings.xml
index 6ab8f40..1e2b15a 100644
--- a/v17/leanback/res/values-my-rMM/strings.xml
+++ b/v17/leanback/res/values-my-rMM/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">"−"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"စတင်ပါ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-nb/strings.xml b/v17/leanback/res/values-nb/strings.xml
index 7f9f38f..c65b834 100644
--- a/v17/leanback/res/values-nb/strings.xml
+++ b/v17/leanback/res/values-nb/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Fortsett"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">"."</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM I GANG"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ne-rNP/strings.xml b/v17/leanback/res/values-ne-rNP/strings.xml
index 0944c75..11d4c3f 100644
--- a/v17/leanback/res/values-ne-rNP/strings.xml
+++ b/v17/leanback/res/values-ne-rNP/strings.xml
@@ -52,6 +52,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"जारी राख्नुहोस्"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"सुरु गरौँ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-nl/strings.xml b/v17/leanback/res/values-nl/strings.xml
index 49139a0..0eceed4 100644
--- a/v17/leanback/res/values-nl/strings.xml
+++ b/v17/leanback/res/values-nl/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Doorgaan"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"-"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"AAN DE SLAG"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Volgende"</string>
</resources>
diff --git a/v17/leanback/res/values-pa-rIN/strings.xml b/v17/leanback/res/values-pa-rIN/strings.xml
index 894698c..db3b36b 100644
--- a/v17/leanback/res/values-pa-rIN/strings.xml
+++ b/v17/leanback/res/values-pa-rIN/strings.xml
@@ -51,4 +51,5 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ਸ਼ੁਰੂਆਤ ਕਰੋ"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"ਅੱਗੇ"</string>
</resources>
diff --git a/v17/leanback/res/values-pl/strings.xml b/v17/leanback/res/values-pl/strings.xml
index 12d5ef5..c5d91e9 100644
--- a/v17/leanback/res/values-pl/strings.xml
+++ b/v17/leanback/res/values-pl/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Dalej"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ROZPOCZNIJ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-pt-rBR/strings.xml b/v17/leanback/res/values-pt-rBR/strings.xml
index 64ceb32..ac84cf5 100644
--- a/v17/leanback/res/values-pt-rBR/strings.xml
+++ b/v17/leanback/res/values-pt-rBR/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRIMEIROS PASSOS"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-pt-rPT/strings.xml b/v17/leanback/res/values-pt-rPT/strings.xml
index f48d004..aae336f 100644
--- a/v17/leanback/res/values-pt-rPT/strings.xml
+++ b/v17/leanback/res/values-pt-rPT/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"INICIAR"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Seguinte"</string>
</resources>
diff --git a/v17/leanback/res/values-pt/strings.xml b/v17/leanback/res/values-pt/strings.xml
index 64ceb32..ac84cf5 100644
--- a/v17/leanback/res/values-pt/strings.xml
+++ b/v17/leanback/res/values-pt/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuar"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"PRIMEIROS PASSOS"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ro/strings.xml b/v17/leanback/res/values-ro/strings.xml
index c304419..4de7821 100644
--- a/v17/leanback/res/values-ro/strings.xml
+++ b/v17/leanback/res/values-ro/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Continuați"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ÎNCEPEȚI"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ru/strings.xml b/v17/leanback/res/values-ru/strings.xml
index f63b067..9463acf 100644
--- a/v17/leanback/res/values-ru/strings.xml
+++ b/v17/leanback/res/values-ru/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Далее"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"НАЧАТЬ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-si-rLK/strings.xml b/v17/leanback/res/values-si-rLK/strings.xml
index a669c40..7d07b85 100644
--- a/v17/leanback/res/values-si-rLK/strings.xml
+++ b/v17/leanback/res/values-si-rLK/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ආරම්භ කරන්න"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-sk/strings.xml b/v17/leanback/res/values-sk/strings.xml
index 21aab9f..904fd6c 100644
--- a/v17/leanback/res/values-sk/strings.xml
+++ b/v17/leanback/res/values-sk/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČÍNAME"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-sl/strings.xml b/v17/leanback/res/values-sl/strings.xml
index e11ad17..9ba52da 100644
--- a/v17/leanback/res/values-sl/strings.xml
+++ b/v17/leanback/res/values-sl/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Naprej"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ZAČNITE"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-sq-rAL/strings.xml b/v17/leanback/res/values-sq-rAL/strings.xml
index e7b0abc..bf0ec07 100644
--- a/v17/leanback/res/values-sq-rAL/strings.xml
+++ b/v17/leanback/res/values-sq-rAL/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Vazhdo"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"FILLO"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-sr/strings.xml b/v17/leanback/res/values-sr/strings.xml
index 0b56efe..60ba968 100644
--- a/v17/leanback/res/values-sr/strings.xml
+++ b/v17/leanback/res/values-sr/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Настави"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ЗАПОЧНИТЕ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-sv/strings.xml b/v17/leanback/res/values-sv/strings.xml
index c310d6a..23d839c 100644
--- a/v17/leanback/res/values-sv/strings.xml
+++ b/v17/leanback/res/values-sv/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"KOM IGÅNG"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-sw/strings.xml b/v17/leanback/res/values-sw/strings.xml
index d5f66e0..c5af384 100644
--- a/v17/leanback/res/values-sw/strings.xml
+++ b/v17/leanback/res/values-sw/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Endelea"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ANZA KUTUMIA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-ta-rIN/strings.xml b/v17/leanback/res/values-ta-rIN/strings.xml
index f80fce3..dbf342d 100644
--- a/v17/leanback/res/values-ta-rIN/strings.xml
+++ b/v17/leanback/res/values-ta-rIN/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"தொடர்க"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"தொடங்குக"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-te-rIN/strings.xml b/v17/leanback/res/values-te-rIN/strings.xml
index 3d2af21..bf9f96b 100644
--- a/v17/leanback/res/values-te-rIN/strings.xml
+++ b/v17/leanback/res/values-te-rIN/strings.xml
@@ -50,6 +50,6 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"కొనసాగించు"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
- <skip />
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"ప్రారంభించు"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"తదుపరి"</string>
</resources>
diff --git a/v17/leanback/res/values-th/strings.xml b/v17/leanback/res/values-th/strings.xml
index 38160b2..3f275b1 100644
--- a/v17/leanback/res/values-th/strings.xml
+++ b/v17/leanback/res/values-th/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"ต่อไป"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"เริ่มต้นใช้งาน"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-tl/strings.xml b/v17/leanback/res/values-tl/strings.xml
index c6c6f5d..287db86 100644
--- a/v17/leanback/res/values-tl/strings.xml
+++ b/v17/leanback/res/values-tl/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Magpatuloy"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"MAGSIMULA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-tr/strings.xml b/v17/leanback/res/values-tr/strings.xml
index 35c5614..cf59d41 100644
--- a/v17/leanback/res/values-tr/strings.xml
+++ b/v17/leanback/res/values-tr/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Devam"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BAŞLA"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-uk/strings.xml b/v17/leanback/res/values-uk/strings.xml
index eca26e7..c2c81cd 100644
--- a/v17/leanback/res/values-uk/strings.xml
+++ b/v17/leanback/res/values-uk/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"."</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"ПОЧАТИ"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-ur-rPK/strings.xml b/v17/leanback/res/values-ur-rPK/strings.xml
index d751132..efed186 100644
--- a/v17/leanback/res/values-ur-rPK/strings.xml
+++ b/v17/leanback/res/values-ur-rPK/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"جاری رکھیں"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"شروع کریں"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-uz-rUZ/strings.xml b/v17/leanback/res/values-uz-rUZ/strings.xml
index fdc9b71..bfcd306 100644
--- a/v17/leanback/res/values-uz-rUZ/strings.xml
+++ b/v17/leanback/res/values-uz-rUZ/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"Davom etish"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"BOSHLADIK"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-vi/strings.xml b/v17/leanback/res/values-vi/strings.xml
index 357d3ff..6b2009b 100644
--- a/v17/leanback/res/values-vi/strings.xml
+++ b/v17/leanback/res/values-vi/strings.xml
@@ -51,4 +51,6 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"BẮT ĐẦU"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
+ <skip />
</resources>
diff --git a/v17/leanback/res/values-zh-rCN/strings.xml b/v17/leanback/res/values-zh-rCN/strings.xml
index b3eaa26..b5adfcd 100644
--- a/v17/leanback/res/values-zh-rCN/strings.xml
+++ b/v17/leanback/res/values-zh-rCN/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"继续"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"开始使用"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-zh-rHK/strings.xml b/v17/leanback/res/values-zh-rHK/strings.xml
index b8b4fbf..95d0be2 100644
--- a/v17/leanback/res/values-zh-rHK/strings.xml
+++ b/v17/leanback/res/values-zh-rHK/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"開始使用"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-zh-rTW/strings.xml b/v17/leanback/res/values-zh-rTW/strings.xml
index 51c44be..e25fc21 100644
--- a/v17/leanback/res/values-zh-rTW/strings.xml
+++ b/v17/leanback/res/values-zh-rTW/strings.xml
@@ -50,6 +50,7 @@
<string name="lb_guidedaction_continue_title" msgid="8842094924543063706">"繼續"</string>
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
- <!-- no translation found for lb_onboarding_get_started (6961440391306351139) -->
+ <string name="lb_onboarding_get_started" msgid="6961440391306351139">"開始使用"</string>
+ <!-- no translation found for lb_onboarding_accessibility_next (2918313444257732434) -->
<skip />
</resources>
diff --git a/v17/leanback/res/values-zu/strings.xml b/v17/leanback/res/values-zu/strings.xml
index 8e8f78c..aefea61 100644
--- a/v17/leanback/res/values-zu/strings.xml
+++ b/v17/leanback/res/values-zu/strings.xml
@@ -51,4 +51,5 @@
<string name="lb_date_separator" msgid="2440386660906697298">"/"</string>
<string name="lb_time_separator" msgid="2763247350845477227">":"</string>
<string name="lb_onboarding_get_started" msgid="6961440391306351139">"QALISA"</string>
+ <string name="lb_onboarding_accessibility_next" msgid="2918313444257732434">"Okulandelayo"</string>
</resources>
diff --git a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
index 574e9a6..9021732 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseFragment.java
@@ -733,7 +733,7 @@
private HeadersFragment.OnHeaderClickedListener mHeaderClickedListener =
new HeadersFragment.OnHeaderClickedListener() {
@Override
- public void onHeaderClicked() {
+ public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
return;
}
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 5d3cde7..7e5b70a 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/BrowseSupportFragment.java
@@ -735,7 +735,7 @@
private HeadersSupportFragment.OnHeaderClickedListener mHeaderClickedListener =
new HeadersSupportFragment.OnHeaderClickedListener() {
@Override
- public void onHeaderClicked() {
+ public void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row) {
if (!mCanShowHeaders || !mShowingHeaders || isInHeadersTransition()) {
return;
}
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
index fda899f..a004307 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersFragment.java
@@ -41,11 +41,29 @@
*/
public class HeadersFragment extends BaseRowFragment {
- interface OnHeaderClickedListener {
- void onHeaderClicked();
+ /**
+ * Interface definition for a callback to be invoked when a header item is clicked.
+ */
+ public interface OnHeaderClickedListener {
+ /**
+ * Called when a header item has been clicked.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
+ void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
- interface OnHeaderViewSelectedListener {
+ /**
+ * Interface definition for a callback to be invoked when a header item is selected.
+ */
+ public interface OnHeaderViewSelectedListener {
+ /**
+ * Called when a header item has been selected.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
@@ -81,10 +99,9 @@
int position, int subposition) {
if (mOnHeaderViewSelectedListener != null) {
if (viewHolder != null && position >= 0) {
- Row row = (Row) getAdapter().get(position);
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
mOnHeaderViewSelectedListener.onHeaderSelected(
- (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), row);
+ (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
} else {
mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
}
@@ -94,13 +111,15 @@
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
- public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
View headerView = viewHolder.getViewHolder().view;
headerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnHeaderClickedListener != null) {
- mOnHeaderClickedListener.onHeaderClicked();
+ mOnHeaderClickedListener.onHeaderClicked(
+ (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
+ (Row) viewHolder.getItem());
}
}
});
diff --git a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
index d998495..a3b0f44 100644
--- a/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
+++ b/v17/leanback/src/android/support/v17/leanback/app/HeadersSupportFragment.java
@@ -43,11 +43,29 @@
*/
public class HeadersSupportFragment extends BaseRowSupportFragment {
- interface OnHeaderClickedListener {
- void onHeaderClicked();
+ /**
+ * Interface definition for a callback to be invoked when a header item is clicked.
+ */
+ public interface OnHeaderClickedListener {
+ /**
+ * Called when a header item has been clicked.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
+ void onHeaderClicked(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
- interface OnHeaderViewSelectedListener {
+ /**
+ * Interface definition for a callback to be invoked when a header item is selected.
+ */
+ public interface OnHeaderViewSelectedListener {
+ /**
+ * Called when a header item has been selected.
+ *
+ * @param viewHolder Row ViewHolder object corresponding to the selected Header.
+ * @param row Row object corresponding to the selected Header.
+ */
void onHeaderSelected(RowHeaderPresenter.ViewHolder viewHolder, Row row);
}
@@ -83,10 +101,9 @@
int position, int subposition) {
if (mOnHeaderViewSelectedListener != null) {
if (viewHolder != null && position >= 0) {
- Row row = (Row) getAdapter().get(position);
ItemBridgeAdapter.ViewHolder vh = (ItemBridgeAdapter.ViewHolder) viewHolder;
mOnHeaderViewSelectedListener.onHeaderSelected(
- (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), row);
+ (RowHeaderPresenter.ViewHolder) vh.getViewHolder(), (Row) vh.getItem());
} else {
mOnHeaderViewSelectedListener.onHeaderSelected(null, null);
}
@@ -96,13 +113,15 @@
private final ItemBridgeAdapter.AdapterListener mAdapterListener =
new ItemBridgeAdapter.AdapterListener() {
@Override
- public void onCreate(ItemBridgeAdapter.ViewHolder viewHolder) {
+ public void onCreate(final ItemBridgeAdapter.ViewHolder viewHolder) {
View headerView = viewHolder.getViewHolder().view;
headerView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (mOnHeaderClickedListener != null) {
- mOnHeaderClickedListener.onHeaderClicked();
+ mOnHeaderClickedListener.onHeaderClicked(
+ (RowHeaderPresenter.ViewHolder) viewHolder.getViewHolder(),
+ (Row) viewHolder.getItem());
}
}
});
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
index 535dbc9..84e5107 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -2800,11 +2800,11 @@
}
}
} else {
+ int focusableCount = views.size();
if (mFocusScrollStrategy != BaseGridView.FOCUS_SCROLL_ALIGNED) {
// adding views not overlapping padding area to avoid scrolling in gaining focus
int left = mWindowAlignment.mainAxis().getPaddingLow();
int right = mWindowAlignment.mainAxis().getClientSize() + left;
- int focusableCount = views.size();
for (int i = 0, count = getChildCount(); i < count; i++) {
View child = getChildAt(i);
if (child.getVisibility() == View.VISIBLE) {
@@ -2821,13 +2821,16 @@
child.addFocusables(views, direction, focusableMode);
}
}
- if (views.size() != focusableCount) {
- return true;
- }
- } else {
- return true;
}
- // if still cannot find any, fall through and add itself
+ } else {
+ View view = findViewByPosition(mFocusPosition);
+ if (view != null) {
+ view.addFocusables(views, direction, focusableMode);
+ }
+ }
+ // if still cannot find any, fall through and add itself
+ if (views.size() != focusableCount) {
+ return true;
}
if (recyclerView.isFocusable()) {
views.add(recyclerView);
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
index 1d99ecb..0f5e2f3 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionsStylist.java
@@ -627,7 +627,7 @@
if (action.hasMultilineDescription()) {
if (vh.mTitleView != null) {
- vh.mTitleView.setMaxLines(mTitleMaxLines);
+ setMaxLines(vh.mTitleView, mTitleMaxLines);
if (vh.mDescriptionView != null) {
vh.mDescriptionView.setMaxHeight(getDescriptionMaxHeight(
vh.itemView.getContext(), vh.mTitleView));
@@ -635,10 +635,10 @@
}
} else {
if (vh.mTitleView != null) {
- vh.mTitleView.setMaxLines(mTitleMinLines);
+ setMaxLines(vh.mTitleView, mTitleMinLines);
}
if (vh.mDescriptionView != null) {
- vh.mDescriptionView.setMaxLines(mDescriptionMinLines);
+ setMaxLines(vh.mDescriptionView, mDescriptionMinLines);
}
}
if (vh.mActivatorView != null) {
@@ -657,6 +657,17 @@
updateChevronAndVisibility(vh);
}
+ private static void setMaxLines(TextView view, int maxLines) {
+ // setSingleLine must be called before setMaxLines because it resets maximum to
+ // Integer.MAX_VALUE.
+ if (maxLines == 1) {
+ view.setSingleLine(true);
+ } else {
+ view.setSingleLine(false);
+ view.setMaxLines(maxLines);
+ }
+ }
+
/**
* Called by {@link #onBindViewHolder(ViewHolder, GuidedAction)} to setup IME options. Default
* implementation assigns {@link EditorInfo#IME_ACTION_DONE}. Subclass may override.
diff --git a/v17/tests/res/layout/vertical_linear_with_button.xml b/v17/tests/res/layout/vertical_linear_with_button.xml
new file mode 100644
index 0000000..f37bdc3
--- /dev/null
+++ b/v17/tests/res/layout/vertical_linear_with_button.xml
@@ -0,0 +1,31 @@
+<LinearLayout
+ xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:lb="http://schemas.android.com/apk/res-auto"
+ android:orientation="vertical"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ >
+ <Button android:focusable="true"
+ android:id="@+id/button"
+ android:text="button"
+ android:layout_width="wrap_content"
+ android:layout_height="wrap_content">
+ <requestFocus />
+ </Button>
+ <android.support.v17.leanback.widget.VerticalGridViewEx
+ android:id="@+id/gridview"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:clipToPadding="false"
+ android:focusable="true"
+ android:focusableInTouchMode="true"
+ android:background="#00ffff"
+ lb:horizontalMargin="12dip"
+ lb:verticalMargin="24dip"
+ lb:numberOfColumns="1"
+ lb:columnWidth="150dip"
+ android:paddingBottom="12dip"
+ android:paddingLeft="12dip"
+ android:paddingRight="12dip"
+ android:paddingTop="12dip" />
+</LinearLayout>
diff --git a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
index dba1754..ef7b019 100644
--- a/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/tests/src/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -2267,6 +2267,52 @@
assertTrue(v.getBottom() >= windowSize - mGridView.getVerticalMargin());
}
+ public void testFocusFinder() throws Throwable {
+ mInstrumentation = getInstrumentation();
+ Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
+ intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+ R.layout.vertical_linear_with_button);
+ intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 3);
+ intent.putExtra(GridActivity.EXTRA_STAGGERED, false);
+ initActivity(intent);
+ mOrientation = BaseGridView.VERTICAL;
+ mNumRows = 1;
+
+ // test focus from button to vertical grid view
+ final View button = mActivity.findViewById(R.id.button);
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertFalse(mGridView.isFocused());
+ assertTrue(mGridView.hasFocus());
+
+ // FocusFinder should find last focused(2nd) item on DPAD_DOWN
+ final View secondChild = mGridView.getChildAt(1);
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ secondChild.requestFocus();
+ button.requestFocus();
+ }
+ });
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(secondChild.isFocused());
+
+ // Bug 26918143 Even VerticalGridView is not focusable, FocusFinder should find last focused
+ // (2nd) item on DPAD_DOWN.
+ runTestOnUiThread(new Runnable() {
+ @Override
+ public void run() {
+ button.requestFocus();
+ }
+ });
+ mGridView.setFocusable(false);
+ mGridView.setFocusableInTouchMode(false);
+ assertTrue(button.isFocused());
+ sendKeys(KeyEvent.KEYCODE_DPAD_DOWN);
+ assertTrue(secondChild.isFocused());
+ }
+
public void testAccessibility() throws Throwable {
mInstrumentation = getInstrumentation();
Intent intent = new Intent(mInstrumentation.getContext(), GridActivity.class);
diff --git a/v4/Android.mk b/v4/Android.mk
index c47a708..42b3ba2 100644
--- a/v4/Android.mk
+++ b/v4/Android.mk
@@ -229,19 +229,6 @@
support_module_src_files += $(LOCAL_SRC_FILES)
-
-# -----------------------------------------------------------------------
-
-# A helper sub-library that makes direct use of V24 APIs.
-include $(CLEAR_VARS)
-LOCAL_MODULE := android-support-v4-api24
-LOCAL_SDK_VERSION := current
-LOCAL_SRC_FILES := $(call all-java-files-under, api24)
-LOCAL_STATIC_JAVA_LIBRARIES := android-support-v4-api23
-include $(BUILD_STATIC_JAVA_LIBRARY)
-
-support_module_src_files += $(LOCAL_SRC_FILES)
-
# -----------------------------------------------------------------------
# Here is the final static library that apps can link against.
@@ -251,7 +238,7 @@
LOCAL_AIDL_INCLUDES := frameworks/support/v4/java
LOCAL_SRC_FILES := $(call all-java-files-under, java) \
$(call all-Iaidl-files-under, java)
-LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api24
+LOCAL_STATIC_JAVA_LIBRARIES += android-support-v4-api23
include $(BUILD_STATIC_JAVA_LIBRARY)
support_module_src_files += $(LOCAL_SRC_FILES)
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java b/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
index b95a9bb..8db5fcb 100644
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
+++ b/v4/api21/android/support/v4/media/IMediaBrowserServiceAdapterApi21.java
@@ -22,99 +22,98 @@
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;
+import android.os.ResultReceiver;
/**
- * A class for replacing the auto generated hidden class, IMediaBrowserService.Stub
+ * An adapter class for replacing the auto generated hidden class, IMediaBrowserService.Stub
*/
-class IMediaBrowserServiceAdapterApi21 extends Binder implements IInterface {
- // Following TRANSACTION_XXX values are synchronized with the auto generated java file
- // from IMediaBrowserService.aidl
- private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
- private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
- private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
- private static final int TRANSACTION_removeSubscription =
- IBinder.FIRST_CALL_TRANSACTION + 3;
+class IMediaBrowserServiceAdapterApi21 {
+ static abstract class Stub extends Binder implements IInterface {
+ private static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
+ // Following TRANSACTION_XXX values are synchronized with the auto generated java file
+ // from IMediaBrowserService.aidl
+ private static final int TRANSACTION_connect = IBinder.FIRST_CALL_TRANSACTION + 0;
+ private static final int TRANSACTION_disconnect = IBinder.FIRST_CALL_TRANSACTION + 1;
+ private static final int TRANSACTION_addSubscription = IBinder.FIRST_CALL_TRANSACTION + 2;
+ private static final int TRANSACTION_removeSubscription =
+ IBinder.FIRST_CALL_TRANSACTION + 3;
+ private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
- static final String DESCRIPTOR = "android.service.media.IMediaBrowserService";
- final MediaBrowserServiceCompatApi21.ServiceImplApi21 mServiceImpl;
-
- public IMediaBrowserServiceAdapterApi21(
- MediaBrowserServiceCompatApi21.ServiceImplApi21 serviceImpl) {
- mServiceImpl = serviceImpl;
- attachInterface(this, DESCRIPTOR);
- }
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case IBinder.INTERFACE_TRANSACTION: {
- reply.writeString(DESCRIPTOR);
- return true;
- }
- case TRANSACTION_connect: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- Bundle arg1;
- if (data.readInt() != 0) {
- arg1 = Bundle.CREATOR.createFromParcel(data);
- } else {
- arg1 = null;
- }
- Object arg2 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
- data.readStrongBinder());
- connect(arg0, arg1, arg2);
- return true;
- }
- case TRANSACTION_disconnect: {
- data.enforceInterface(DESCRIPTOR);
- Object arg0 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
- data.readStrongBinder());
- disconnect(arg0);
- return true;
- }
- case TRANSACTION_addSubscription: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
- data.readStrongBinder());
- addSubscription(arg0, arg1);
- return true;
- }
- case TRANSACTION_removeSubscription: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
- data.readStrongBinder());
- removeSubscription(arg0, arg1);
- return true;
- }
+ public Stub() {
+ attachInterface(this, DESCRIPTOR);
}
- return super.onTransact(code, data, reply, flags);
- }
- void connect(final String pkg, final Bundle rootHints, final Object callbacks) {
- mServiceImpl.connect(pkg, rootHints,
- new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
- }
+ @Override
+ public IBinder asBinder() {
+ return this;
+ }
- void disconnect(final Object callbacks) {
- mServiceImpl.disconnect(
- new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
- }
+ @Override
+ public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
+ throws RemoteException {
+ switch (code) {
+ case IBinder.INTERFACE_TRANSACTION: {
+ reply.writeString(DESCRIPTOR);
+ return true;
+ }
+ case TRANSACTION_connect: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ Bundle arg1;
+ if (data.readInt() != 0) {
+ arg1 = Bundle.CREATOR.createFromParcel(data);
+ } else {
+ arg1 = null;
+ }
+ Object arg2 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
+ data.readStrongBinder());
+ connect(arg0, arg1, arg2);
+ return true;
+ }
+ case TRANSACTION_disconnect: {
+ data.enforceInterface(DESCRIPTOR);
+ Object arg0 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
+ data.readStrongBinder());
+ disconnect(arg0);
+ return true;
+ }
+ case TRANSACTION_addSubscription: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
+ data.readStrongBinder());
+ addSubscription(arg0, arg1);
+ return true;
+ }
+ case TRANSACTION_removeSubscription: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ Object arg1 = IMediaBrowserServiceCallbacksAdapterApi21.Stub.asInterface(
+ data.readStrongBinder());
+ removeSubscription(arg0, arg1);
+ return true;
+ }
+ case TRANSACTION_getMediaItem: {
+ data.enforceInterface(DESCRIPTOR);
+ String arg0 = data.readString();
+ ResultReceiver arg1;
+ if (data.readInt() != 0) {
+ arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
+ } else {
+ arg1 = null;
+ }
+ getMediaItem(arg0, arg1);
+ return true;
+ }
+ }
+ return super.onTransact(code, data, reply, flags);
+ }
- void addSubscription(final String id, final Object callbacks) {
- mServiceImpl.addSubscription(id,
- new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
- }
-
- void removeSubscription(final String id, final Object callbacks) {
- mServiceImpl.removeSubscription(id,
- new MediaBrowserServiceCompatApi21.ServiceCallbacksImplApi21(callbacks));
+ public abstract void connect(final String pkg, final Bundle rootHints,
+ final Object callbacks);
+ public abstract void disconnect(final Object callbacks);
+ public abstract void addSubscription(final String id, final Object callbacks);
+ public abstract void removeSubscription(final String id, final Object callbacks);
+ public abstract void getMediaItem(final String mediaId, final ResultReceiver receiver);
}
}
diff --git a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java b/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
index 7cbaea1..b190e16 100644
--- a/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
+++ b/v4/api21/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi21.java
@@ -17,7 +17,6 @@
package android.support.v4.media;
import android.media.session.MediaSession;
-import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
@@ -30,6 +29,7 @@
* IMediaBrowserServiceCallbacks.Stub using reflection.
*/
class IMediaBrowserServiceCallbacksAdapterApi21 {
+
Object mCallbackObject;
private Method mAsBinderMethod;
private Method mOnConnectMethod;
diff --git a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
index 9a4d980..c6ccb14 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserCompatApi21.java
@@ -129,26 +129,25 @@
@Override
public void onChildrenLoaded(@NonNull String parentId,
List<MediaBrowser.MediaItem> children) {
- mSubscriptionCallback.onChildrenLoaded(parentId, itemListToParcelList(children));
+ List<Parcel> parcelList = null;
+ if (children != null && children.size() == 1
+ && children.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID)) {
+ children = null;
+ }
+ if (children != null) {
+ parcelList = new ArrayList<>();
+ for (MediaBrowser.MediaItem item : children) {
+ Parcel parcel = Parcel.obtain();
+ item.writeToParcel(parcel, 0);
+ parcelList.add(parcel);
+ }
+ }
+ mSubscriptionCallback.onChildrenLoaded(parentId, parcelList);
}
@Override
public void onError(@NonNull String parentId) {
mSubscriptionCallback.onError(parentId);
}
-
- static List<Parcel> itemListToParcelList(List<MediaBrowser.MediaItem> itemList) {
- if (itemList != null && itemList.size() == 1
- && itemList.get(0).getMediaId().equals(NULL_MEDIA_ITEM_ID)) {
- return null;
- }
- List<Parcel> parcelList = new ArrayList<>();
- for (MediaBrowser.MediaItem item : itemList) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
- }
- return parcelList;
- }
}
}
diff --git a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
index 69fe5b9..a09a74b 100644
--- a/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
+++ b/v4/api21/android/support/v4/media/MediaBrowserServiceCompatApi21.java
@@ -19,27 +19,18 @@
import android.content.Intent;
import android.media.MediaDescription;
import android.media.browse.MediaBrowser;
-import android.os.Binder;
import android.os.Build;
import android.os.Bundle;
import android.os.IBinder;
import android.os.Parcel;
import android.os.RemoteException;
+import android.os.ResultReceiver;
import android.service.media.MediaBrowserService;
import java.util.ArrayList;
import java.util.List;
class MediaBrowserServiceCompatApi21 {
- private static Object sNullParceledListSliceObj;
- static {
- MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
- MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
- MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
- List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
- nullMediaItemList.add(nullMediaItem);
- sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
- }
public static Object createService() {
return new MediaBrowserServiceAdaptorApi21();
@@ -53,41 +44,35 @@
return ((MediaBrowserServiceAdaptorApi21) serviceObj).onBind(intent);
}
- public static Object parcelListToParceledListSliceObject(List<Parcel> list) {
- if (list == null) {
- if (Build.VERSION.SDK_INT <= 23) {
- return sNullParceledListSliceObj;
- }
- return null;
- }
- List<MediaBrowser.MediaItem> itemList = new ArrayList<>();
- for (Parcel parcel : list) {
- parcel.setDataPosition(0);
- itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- }
- return ParceledListSliceAdapterApi21.newInstance(itemList);
- }
-
public interface ServiceImplApi21 {
- void connect(String pkg, Bundle rootHints, ServiceCallbacksApi21 callbacks);
- void disconnect(ServiceCallbacksApi21 callbacks);
- void addSubscription(String id, ServiceCallbacksApi21 callbacks);
- void removeSubscription(String id, ServiceCallbacksApi21 callbacks);
+ void connect(final String pkg, final Bundle rootHints, final ServiceCallbacks callbacks);
+ void disconnect(final ServiceCallbacks callbacks);
+ void addSubscription(final String id, final ServiceCallbacks callbacks);
+ void removeSubscription(final String id, final ServiceCallbacks callbacks);
}
- public interface ServiceCallbacksApi21 {
+ public interface ServiceCallbacks {
IBinder asBinder();
void onConnect(String root, Object session, Bundle extras) throws RemoteException;
void onConnectFailed() throws RemoteException;
void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException;
}
- public static class ServiceCallbacksImplApi21 implements ServiceCallbacksApi21 {
- final IMediaBrowserServiceCallbacksAdapterApi21 mCallbacks;
+ public static class ServiceCallbacksApi21 implements ServiceCallbacks {
+ private static Object sNullParceledListSliceObj;
+ static {
+ MediaDescription nullDescription = new MediaDescription.Builder().setMediaId(
+ MediaBrowserCompatApi21.NULL_MEDIA_ITEM_ID).build();
+ MediaBrowser.MediaItem nullMediaItem = new MediaBrowser.MediaItem(nullDescription, 0);
+ List<MediaBrowser.MediaItem> nullMediaItemList = new ArrayList<>();
+ nullMediaItemList.add(nullMediaItem);
+ sNullParceledListSliceObj = ParceledListSliceAdapterApi21.newInstance(nullMediaItemList);
+ }
- ServiceCallbacksImplApi21(Object callbacksObj) {
- mCallbacks = createCallbacks(callbacksObj);
+ private final IMediaBrowserServiceCallbacksAdapterApi21 mCallbacks;
+
+ ServiceCallbacksApi21(Object callbacksObj) {
+ mCallbacks = new IMediaBrowserServiceCallbacksAdapterApi21(callbacksObj);
}
public IBinder asBinder() {
@@ -103,19 +88,31 @@
}
public void onLoadChildren(String mediaId, List<Parcel> list) throws RemoteException {
- mCallbacks.onLoadChildren(mediaId, parcelListToParceledListSliceObject(list));
- }
-
- IMediaBrowserServiceCallbacksAdapterApi21 createCallbacks(Object callbacksObj) {
- return new IMediaBrowserServiceCallbacksAdapterApi21(callbacksObj);
+ List<MediaBrowser.MediaItem> itemList = null;
+ if (list != null) {
+ itemList = new ArrayList<>();
+ for (Parcel parcel : list) {
+ parcel.setDataPosition(0);
+ itemList.add(MediaBrowser.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ }
+ Object pls;
+ if (Build.VERSION.SDK_INT > 23) {
+ pls = itemList == null ? null : ParceledListSliceAdapterApi21.newInstance(itemList);
+ } else {
+ pls = itemList == null ? sNullParceledListSliceObj
+ : ParceledListSliceAdapterApi21.newInstance(itemList);
+ }
+ mCallbacks.onLoadChildren(mediaId, pls);
}
}
static class MediaBrowserServiceAdaptorApi21 {
- Binder mBinder;
+ ServiceBinderProxyApi21 mBinder;
public void onCreate(ServiceImplApi21 serviceImpl) {
- mBinder = createServiceBinder(serviceImpl);
+ mBinder = new ServiceBinderProxyApi21(serviceImpl);
}
public IBinder onBind(Intent intent) {
@@ -125,8 +122,39 @@
return null;
}
- protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
- return new IMediaBrowserServiceAdapterApi21(serviceImpl);
+ static class ServiceBinderProxyApi21 extends IMediaBrowserServiceAdapterApi21.Stub {
+ final ServiceImplApi21 mServiceImpl;
+
+ ServiceBinderProxyApi21(ServiceImplApi21 serviceImpl) {
+ super();
+ mServiceImpl = serviceImpl;
+ }
+
+ @Override
+ public void connect(final String pkg, final Bundle rootHints, final Object callbacks) {
+ mServiceImpl.connect(pkg, rootHints, new ServiceCallbacksApi21(callbacks));
+ }
+
+ @Override
+ public void disconnect(final Object callbacks) {
+ mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
+ }
+
+ @Override
+ public void addSubscription(final String id, final Object callbacks) {
+ mServiceImpl.addSubscription(id, new ServiceCallbacksApi21(callbacks));
+ }
+
+ @Override
+ public void removeSubscription(final String id,
+ final Object callbacks) {
+ mServiceImpl.removeSubscription(id, new ServiceCallbacksApi21(callbacks));
+ }
+
+ @Override
+ public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
+ // No operation since this method is added in API 23.
+ }
}
}
}
diff --git a/v4/api23/android/support/v4/media/IMediaBrowserServiceAdapterApi23.java b/v4/api23/android/support/v4/media/IMediaBrowserServiceAdapterApi23.java
deleted file mode 100644
index bf79264..0000000
--- a/v4/api23/android/support/v4/media/IMediaBrowserServiceAdapterApi23.java
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.RemoteException;
-import android.os.ResultReceiver;
-import android.service.media.MediaBrowserService;
-import android.util.Log;
-
-/**
- * A class for replacing the auto generated hidden class, IMediaBrowserService.Stub
- */
-class IMediaBrowserServiceAdapterApi23 extends IMediaBrowserServiceAdapterApi21 {
- private static final String TAG = "IMediaBrowserServiceAdapterApi23";
-
- // Following TRANSACTION_XXX values are synchronized with the auto generated java file
- // from IMediaBrowserService.aidl
- private static final int TRANSACTION_getMediaItem = IBinder.FIRST_CALL_TRANSACTION + 4;
-
- final MediaBrowserServiceCompatApi23.ServiceImplApi23 mServiceImpl;
-
- public IMediaBrowserServiceAdapterApi23(
- MediaBrowserServiceCompatApi23.ServiceImplApi23 serviceImpl) {
- super(serviceImpl);
- mServiceImpl = serviceImpl;
- }
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case TRANSACTION_getMediaItem: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- ResultReceiver arg1;
- if (data.readInt() != 0) {
- arg1 = android.os.ResultReceiver.CREATOR.createFromParcel(data);
- } else {
- arg1 = null;
- }
- getMediaItem(arg0, arg1);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- void getMediaItem(final String mediaId, final ResultReceiver receiver) {
- final String KEY_MEDIA_ITEM;
- try {
- KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
- "KEY_MEDIA_ITEM").get(null);
- } catch (IllegalAccessException | NoSuchFieldException e) {
- Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
- return;
- }
-
- mServiceImpl.getMediaItem(mediaId, new MediaBrowserServiceCompatApi23.ItemCallback() {
- @Override
- public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
- if (itemParcel != null) {
- itemParcel.setDataPosition(0);
- MediaBrowser.MediaItem item =
- MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
- resultData.putParcelable(KEY_MEDIA_ITEM, item);
- itemParcel.recycle();
- }
- receiver.send(resultCode, resultData);
- }
- });
-
- }
-}
-
diff --git a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
index 4156935..fcaea40 100644
--- a/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
+++ b/v4/api23/android/support/v4/media/MediaBrowserServiceCompatApi23.java
@@ -16,17 +16,26 @@
package android.support.v4.media;
-import android.os.Binder;
+import android.media.browse.MediaBrowser;
import android.os.Bundle;
import android.os.Parcel;
+import android.os.ResultReceiver;
+import android.service.media.MediaBrowserService;
+import android.util.Log;
class MediaBrowserServiceCompatApi23 extends MediaBrowserServiceCompatApi21 {
+ private static final String TAG = "MediaBrowserServiceCompatApi21";
+
public static Object createService() {
return new MediaBrowserServiceAdaptorApi23();
}
+ public static void onCreate(Object serviceObj, ServiceImplApi23 serviceImpl) {
+ ((MediaBrowserServiceAdaptorApi23) serviceObj).onCreate(serviceImpl);
+ }
+
public interface ServiceImplApi23 extends ServiceImplApi21 {
- void getMediaItem(String mediaId, ItemCallback cb);
+ void getMediaItem(final String mediaId, final ItemCallback cb);
}
public interface ItemCallback {
@@ -34,8 +43,44 @@
}
static class MediaBrowserServiceAdaptorApi23 extends MediaBrowserServiceAdaptorApi21 {
- protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
- return new IMediaBrowserServiceAdapterApi23((ServiceImplApi23) serviceImpl);
+
+ public void onCreate(ServiceImplApi23 serviceImpl) {
+ mBinder = new ServiceBinderProxyApi23(serviceImpl);
+ }
+
+ private static class ServiceBinderProxyApi23 extends ServiceBinderProxyApi21 {
+ ServiceImplApi23 mServiceImpl;
+
+ ServiceBinderProxyApi23(ServiceImplApi23 serviceImpl) {
+ super(serviceImpl);
+ mServiceImpl = serviceImpl;
+ }
+
+ @Override
+ public void getMediaItem(final String mediaId, final ResultReceiver receiver) {
+ final String KEY_MEDIA_ITEM;
+ try {
+ KEY_MEDIA_ITEM = (String) MediaBrowserService.class.getDeclaredField(
+ "KEY_MEDIA_ITEM").get(null);
+ } catch (IllegalAccessException | NoSuchFieldException e) {
+ Log.i(TAG, "Failed to get KEY_MEDIA_ITEM via reflection", e);
+ return;
+ }
+
+ mServiceImpl.getMediaItem(mediaId, new ItemCallback() {
+ @Override
+ public void onItemLoaded(int resultCode, Bundle resultData, Parcel itemParcel) {
+ if (itemParcel != null) {
+ itemParcel.setDataPosition(0);
+ MediaBrowser.MediaItem item =
+ MediaBrowser.MediaItem.CREATOR.createFromParcel(itemParcel);
+ resultData.putParcelable(KEY_MEDIA_ITEM, item);
+ itemParcel.recycle();
+ }
+ receiver.send(resultCode, resultData);
+ }
+ });
+ }
}
}
}
diff --git a/v4/api24/android/support/v4/media/IMediaBrowserServiceAdapterApi24.java b/v4/api24/android/support/v4/media/IMediaBrowserServiceAdapterApi24.java
deleted file mode 100644
index 755d926..0000000
--- a/v4/api24/android/support/v4/media/IMediaBrowserServiceAdapterApi24.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Bundle;
-import android.os.IBinder;
-import android.os.Parcel;
-import android.os.RemoteException;
-
-/**
- * A class for replacing the auto generated hidden class, IMediaBrowserService.Stub
- */
-class IMediaBrowserServiceAdapterApi24 extends IMediaBrowserServiceAdapterApi23 {
- // Following TRANSACTION_XXX values are synchronized with the auto generated java file
- // from IMediaBrowserService.aidl
- private static final int TRANSACTION_addSubscriptionWithOptions =
- IBinder.FIRST_CALL_TRANSACTION + 5;
- private static final int TRANSACTION_removeSubscriptionWithOptions =
- IBinder.FIRST_CALL_TRANSACTION + 6;
-
- final MediaBrowserServiceCompatApi24.ServiceImplApi24 mServiceImpl;
-
- public IMediaBrowserServiceAdapterApi24(
- MediaBrowserServiceCompatApi24.ServiceImplApi24 serviceImpl) {
- super(serviceImpl);
- mServiceImpl = serviceImpl;
- }
-
- @Override
- public IBinder asBinder() {
- return this;
- }
-
- @Override
- public boolean onTransact(int code, Parcel data, Parcel reply, int flags)
- throws RemoteException {
- switch (code) {
- case TRANSACTION_addSubscriptionWithOptions: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- Bundle arg1 = (data.readInt() == 0)
- ? null : Bundle.CREATOR.createFromParcel(data);
- Object arg2 = IMediaBrowserServiceCallbacksAdapterApi24.Stub.asInterface(
- data.readStrongBinder());
- addSubscription(arg0, arg1, arg2);
- return true;
- }
- case TRANSACTION_removeSubscriptionWithOptions: {
- data.enforceInterface(DESCRIPTOR);
- String arg0 = data.readString();
- Bundle arg1 = (data.readInt() == 0)
- ? null : Bundle.CREATOR.createFromParcel(data);
- Object arg2 = IMediaBrowserServiceCallbacksAdapterApi24.Stub.asInterface(
- data.readStrongBinder());
- removeSubscription(arg0, arg1, arg2);
- return true;
- }
- }
- return super.onTransact(code, data, reply, flags);
- }
-
- @Override
- void connect(String pkg, Bundle rootHints, Object callbacks) {
- mServiceImpl.connect(pkg, rootHints,
- new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
- }
-
- void addSubscription(final String id, final Bundle options, final Object callbacks) {
- mServiceImpl.addSubscription(id, options,
- new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
- }
-
- void removeSubscription(final String id, final Bundle options, final Object callbacks) {
- mServiceImpl.removeSubscription(id, options,
- new MediaBrowserServiceCompatApi24.ServiceCallbacksImplApi24(callbacks));
- }
-}
-
-
diff --git a/v4/api24/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi24.java b/v4/api24/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi24.java
deleted file mode 100644
index 1e319bd..0000000
--- a/v4/api24/android/support/v4/media/IMediaBrowserServiceCallbacksAdapterApi24.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Bundle;
-import android.os.RemoteException;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-
-/**
- * An adapter class for accessing the hidden framework classes, IMediaBrowserServiceCallbacks and
- * IMediaBrowserServiceCallbacks.Stub using reflection.
- */
-class IMediaBrowserServiceCallbacksAdapterApi24 extends IMediaBrowserServiceCallbacksAdapterApi21 {
- private Method mOnLoadChildrenMethodWithOptionsMethod;
-
- IMediaBrowserServiceCallbacksAdapterApi24(Object callbackObject) {
- super(callbackObject);
- try {
- Class theClass = Class.forName("android.service.media.IMediaBrowserServiceCallbacks");
- Class parceledListSliceClass = Class.forName("android.content.pm.ParceledListSlice");
- mOnLoadChildrenMethodWithOptionsMethod = theClass.getMethod("onLoadChildrenWithOptions",
- new Class[] { String.class, parceledListSliceClass, Bundle.class });
- } catch (ClassNotFoundException | NoSuchMethodException e) {
- e.printStackTrace();
- }
- }
-
- void onLoadChildrenWithOptions(String mediaId, Object parceledListSliceObj, Bundle options)
- throws RemoteException {
- try {
- mOnLoadChildrenMethodWithOptionsMethod.invoke(
- mCallbackObject, mediaId, parceledListSliceObj, options);
- } catch (IllegalAccessException | InvocationTargetException | NullPointerException e) {
- e.printStackTrace();
- }
- }
-}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
deleted file mode 100644
index 0b4f34b..0000000
--- a/v4/api24/android/support/v4/media/MediaBrowserCompatApi24.java
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.media.browse.MediaBrowser;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.support.annotation.NonNull;
-
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.util.List;
-
-class MediaBrowserCompatApi24 {
- // TODO: Remove reflections once N released.
- private static Method sSubscribeMethod;
- private static Method sUnsubscribeMethod;
- static {
- try {
- sSubscribeMethod = MediaBrowser.class.getMethod("subscribe", new Class[] {
- String.class, Bundle.class, MediaBrowser.SubscriptionCallback.class});
- sUnsubscribeMethod = MediaBrowser.class.getMethod("unsubscribe",
- new Class[] {String.class, Bundle.class});
- } catch (NoSuchMethodException e) {
- e.printStackTrace();
- }
- }
-
- public static Object createSubscriptionCallback(SubscriptionCallback callback) {
- return new SubscriptionCallbackProxy<>(callback);
- }
-
- public static void subscribe(
- Object browserObj, String parentId, Bundle options, Object subscriptionCallbackObj) {
- try {
- sSubscribeMethod.invoke(browserObj, parentId, options, subscriptionCallbackObj);
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
- }
-
- public static void unsubscribe(Object browserObj, String parentId, Bundle options) {
- try {
- sUnsubscribeMethod.invoke(browserObj, parentId, options);
- } catch (IllegalAccessException | InvocationTargetException e) {
- e.printStackTrace();
- }
- }
-
- interface SubscriptionCallback extends MediaBrowserCompatApi21.SubscriptionCallback {
- void onChildrenLoaded(@NonNull String parentId, List<Parcel> children,
- @NonNull Bundle options);
- void onError(@NonNull String parentId, @NonNull Bundle options);
- }
-
- static class SubscriptionCallbackProxy<T extends SubscriptionCallback>
- extends MediaBrowserCompatApi21.SubscriptionCallbackProxy<T> {
- public SubscriptionCallbackProxy(T callback) {
- super(callback);
- }
-
- public void onChildrenLoaded(@NonNull String parentId,
- List<MediaBrowser.MediaItem> children, @NonNull Bundle options) {
- mSubscriptionCallback.onChildrenLoaded(
- parentId, itemListToParcelList(children), options);
- }
-
- public void onError(@NonNull String parentId, @NonNull Bundle options) {
- mSubscriptionCallback.onError(parentId, options);
- }
- }
-}
diff --git a/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java b/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
deleted file mode 100644
index db6e3b7..0000000
--- a/v4/api24/android/support/v4/media/MediaBrowserServiceCompatApi24.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.support.v4.media;
-
-import android.os.Binder;
-import android.os.Bundle;
-import android.os.Parcel;
-import android.os.RemoteException;
-
-import java.util.List;
-
-class MediaBrowserServiceCompatApi24 extends MediaBrowserServiceCompatApi23 {
- public static Object createService() {
- return new MediaBrowserServiceAdaptorApi24();
- }
-
- public interface ServiceImplApi24 extends ServiceImplApi23 {
- void connect(String pkg, Bundle rootHints, ServiceCallbacksApi24 callbacks);
- void addSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
- void removeSubscription(String id, Bundle options, ServiceCallbacksApi24 callbacks);
- }
-
- public interface ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
- void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
- throws RemoteException;
- }
-
- public static class ServiceCallbacksImplApi24 extends ServiceCallbacksImplApi21
- implements ServiceCallbacksApi24 {
- ServiceCallbacksImplApi24(Object callbacksObj) {
- super(callbacksObj);
- }
-
- @Override
- public void onLoadChildren(String mediaId, List<Parcel> list, Bundle options)
- throws RemoteException {
- ((IMediaBrowserServiceCallbacksAdapterApi24)mCallbacks).onLoadChildrenWithOptions(
- mediaId, parcelListToParceledListSliceObject(list), options);
- }
-
- @Override
- IMediaBrowserServiceCallbacksAdapterApi24 createCallbacks(Object callbacksObj) {
- return new IMediaBrowserServiceCallbacksAdapterApi24(callbacksObj);
- }
- }
-
- static class MediaBrowserServiceAdaptorApi24 extends MediaBrowserServiceAdaptorApi23 {
- protected Binder createServiceBinder(ServiceImplApi21 serviceImpl) {
- return new IMediaBrowserServiceAdapterApi24((ServiceImplApi24) serviceImpl);
- }
- }
-}
diff --git a/v4/build.gradle b/v4/build.gradle
index 1bb53ca..7d911c0 100644
--- a/v4/build.gradle
+++ b/v4/build.gradle
@@ -71,6 +71,9 @@
androidTestCompile ('com.android.support.test.espresso:espresso-core:2.2.1') {
exclude module: 'support-annotations'
}
+ androidTestCompile 'org.mockito:mockito-core:1.9.5'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'junit:junit:4.12'
}
diff --git a/v4/java/android/support/v4/app/FragmentActivity.java b/v4/java/android/support/v4/app/FragmentActivity.java
index 0fbe357..5bde380 100644
--- a/v4/java/android/support/v4/app/FragmentActivity.java
+++ b/v4/java/android/support/v4/app/FragmentActivity.java
@@ -29,6 +29,7 @@
import android.support.annotation.Nullable;
import android.support.v4.media.session.MediaControllerCompat;
import android.support.v4.util.SimpleArrayMap;
+import android.support.v4.util.SparseArrayCompat;
import android.util.AttributeSet;
import android.util.Log;
import android.view.KeyEvent;
@@ -82,6 +83,10 @@
private static final String TAG = "FragmentActivity";
static final String FRAGMENTS_TAG = "android:support:fragments";
+ static final String NEXT_CANDIDATE_REQUEST_INDEX_TAG = "android:support:next_request_index";
+ static final String ALLOCATED_REQUEST_INDICIES_TAG = "android:support:request_indicies";
+ static final String REQUEST_FRAGMENT_WHO_TAG = "android:support:request_fragment_who";
+ static final int MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS = 0xffff - 1;
// This is the SDK API version of Honeycomb (3.0).
private static final int HONEYCOMB = 11;
@@ -118,12 +123,23 @@
boolean mOptionsMenuInvalidated;
boolean mRequestedPermissionsFromFragment;
+
+ // A hint for the next candidate request index. Request indicies are ints between 0 and 2^16-1
+ // which are encoded into the upper 16 bits of the requestCode for
+ // Fragment.startActivityForResult(...) calls. This allows us to dispatch onActivityResult(...)
+ // to the appropriate Fragment. Request indicies are allocated by allocateRequestIndex(...).
+ int mNextCandidateRequestIndex;
// We need to keep track of whether startActivityForResult originated from a Fragment, so we
// can conditionally check whether the requestCode collides with our reserved ID space for the
- // fragment index. Unfortunately we can't just call super.startActivityForResult(...) to
- // bypass the check when the call didn't come from a fragment, since we need to use the
- // ActivityCompat version for backward compatibility.
+ // request index (see above). Unfortunately we can't just call
+ // super.startActivityForResult(...) to bypass the check when the call didn't come from a
+ // fragment, since we need to use the ActivityCompat version for backward compatibility.
boolean mStartedActivityFromFragment;
+ // A map from request index to Fragment "who" (i.e. a Fragment's unique identifier). Used to
+ // keep track of the originating Fragment for Fragment.startActivityForResult(...) calls, so we
+ // can dispatch the onActivityResult(...) to the appropriate Fragment. Will only contain entries
+ // for startActivityForResult calls where a result has not yet been delivered.
+ SparseArrayCompat<String> mPendingFragmentActivityResults;
static final class NonConfigurationInstances {
Object custom;
@@ -143,23 +159,21 @@
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
mFragments.noteStateNotSaved();
- int index = requestCode>>16;
- if (index != 0) {
- index--;
- final int activeFragmentsCount = mFragments.getActiveFragmentsCount();
- if (activeFragmentsCount == 0 || index < 0 || index >= activeFragmentsCount) {
- Log.w(TAG, "Activity result fragment index out of range: 0x"
- + Integer.toHexString(requestCode));
+ int requestIndex = requestCode>>16;
+ if (requestIndex != 0) {
+ requestIndex--;
+
+ String who = mPendingFragmentActivityResults.get(requestIndex);
+ mPendingFragmentActivityResults.remove(requestIndex);
+ if (who == null) {
+ Log.w(TAG, "Activity result delivered for unknown Fragment.");
return;
}
- final List<Fragment> activeFragments =
- mFragments.getActiveFragments(new ArrayList<Fragment>(activeFragmentsCount));
- Fragment frag = activeFragments.get(index);
- if (frag == null) {
- Log.w(TAG, "Activity result no fragment exists for index: 0x"
- + Integer.toHexString(requestCode));
+ Fragment targetFragment = mFragments.findFragmentByWho(who);
+ if (targetFragment == null) {
+ Log.w(TAG, "Activity result no fragment exists for who: " + who);
} else {
- frag.onActivityResult(requestCode&0xffff, resultCode, data);
+ targetFragment.onActivityResult(requestCode&0xffff, resultCode, data);
}
return;
}
@@ -291,7 +305,30 @@
if (savedInstanceState != null) {
Parcelable p = savedInstanceState.getParcelable(FRAGMENTS_TAG);
mFragments.restoreAllState(p, nc != null ? nc.fragments : null);
+
+ // Check if there are any pending onActivityResult calls to descendent Fragments.
+ if (savedInstanceState.containsKey(NEXT_CANDIDATE_REQUEST_INDEX_TAG)) {
+ mNextCandidateRequestIndex =
+ savedInstanceState.getInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG);
+ int[] requestCodes = savedInstanceState.getIntArray(ALLOCATED_REQUEST_INDICIES_TAG);
+ String[] fragmentWhos = savedInstanceState.getStringArray(REQUEST_FRAGMENT_WHO_TAG);
+ if (requestCodes == null || fragmentWhos == null ||
+ requestCodes.length != fragmentWhos.length) {
+ Log.w(TAG, "Invalid requestCode mapping in savedInstanceState.");
+ } else {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>(requestCodes.length);
+ for (int i = 0; i < requestCodes.length; i++) {
+ mPendingFragmentActivityResults.put(requestCodes[i], fragmentWhos[i]);
+ }
+ }
+ }
}
+
+ if (mPendingFragmentActivityResults == null) {
+ mPendingFragmentActivityResults = new SparseArrayCompat<>();
+ mNextCandidateRequestIndex = 0;
+ }
+
mFragments.dispatchCreate();
}
@@ -530,6 +567,18 @@
if (p != null) {
outState.putParcelable(FRAGMENTS_TAG, p);
}
+ if (mPendingFragmentActivityResults.size() > 0) {
+ outState.putInt(NEXT_CANDIDATE_REQUEST_INDEX_TAG, mNextCandidateRequestIndex);
+
+ int[] requestCodes = new int[mPendingFragmentActivityResults.size()];
+ String[] fragmentWhos = new String[mPendingFragmentActivityResults.size()];
+ for (int i = 0; i < mPendingFragmentActivityResults.size(); i++) {
+ requestCodes[i] = mPendingFragmentActivityResults.keyAt(i);
+ fragmentWhos[i] = mPendingFragmentActivityResults.valueAt(i);
+ }
+ outState.putIntArray(ALLOCATED_REQUEST_INDICIES_TAG, requestCodes);
+ outState.putStringArray(REQUEST_FRAGMENT_WHO_TAG, fragmentWhos);
+ }
}
/**
@@ -872,13 +921,35 @@
if ((requestCode&0xffff0000) != 0) {
throw new IllegalArgumentException("Can only use lower 16 bits for requestCode");
}
+ int requestIndex = allocateRequestIndex(fragment);
ActivityCompat.startActivityForResult(
- this, intent, ((fragment.mIndex+1)<<16) + (requestCode&0xffff), options);
+ this, intent, ((requestIndex+1)<<16) + (requestCode&0xffff), options);
} finally {
mStartedActivityFromFragment = false;
}
}
+ // Allocates the next available startActivityForResult request index.
+ private int allocateRequestIndex(Fragment fragment) {
+ // Sanity check that we havn't exhaused the request index space.
+ if (mPendingFragmentActivityResults.size() >= MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS) {
+ throw new IllegalStateException("Too many pending Fragment activity results.");
+ }
+
+ // Find an unallocated request index in the mPendingFragmentActivityResults map.
+ while (mPendingFragmentActivityResults.indexOfKey(mNextCandidateRequestIndex) >= 0) {
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+ }
+
+ int requestIndex = mNextCandidateRequestIndex;
+ mPendingFragmentActivityResults.put(requestIndex, fragment.mWho);
+ mNextCandidateRequestIndex =
+ (mNextCandidateRequestIndex + 1) % MAX_NUM_PENDING_FRAGMENT_ACTIVITY_RESULTS;
+
+ return requestIndex;
+ }
+
/**
* Called by Fragment.requestPermissions() to implement its behavior.
*/
diff --git a/v4/java/android/support/v4/app/FragmentController.java b/v4/java/android/support/v4/app/FragmentController.java
index 5d647b0..4a80fcc 100644
--- a/v4/java/android/support/v4/app/FragmentController.java
+++ b/v4/java/android/support/v4/app/FragmentController.java
@@ -19,6 +19,7 @@
import android.content.Context;
import android.content.res.Configuration;
import android.os.Parcelable;
+import android.support.annotation.Nullable;
import android.support.v4.util.SimpleArrayMap;
import android.util.AttributeSet;
import android.view.Menu;
@@ -66,6 +67,14 @@
}
/**
+ * Returns a fragment with the given identifier.
+ */
+ @Nullable
+ Fragment findFragmentByWho(String who) {
+ return mHost.mFragmentManager.findFragmentByWho(who);
+ }
+
+ /**
* Returns the number of active fragments.
*/
public int getActiveFragmentsCount() {
diff --git a/v4/java/android/support/v4/media/MediaBrowserCompat.java b/v4/java/android/support/v4/media/MediaBrowserCompat.java
index e416299..e4b309a 100644
--- a/v4/java/android/support/v4/media/MediaBrowserCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserCompat.java
@@ -19,7 +19,6 @@
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
-import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -93,11 +92,9 @@
*/
public MediaBrowserCompat(Context context, ComponentName serviceComponent,
ConnectionCallback callback, Bundle rootHints) {
- if (Build.VERSION.SDK_INT >= 24) {
- mImpl = new MediaBrowserImplApi24(context, serviceComponent, callback, rootHints);
- } else if (Build.VERSION.SDK_INT >= 23) {
+ if (android.os.Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserImplApi23(context, serviceComponent, callback, rootHints);
- } else if (Build.VERSION.SDK_INT >= 21) {
+ } else if (android.os.Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserImplApi21(context, serviceComponent, callback, rootHints);
} else {
mImpl = new MediaBrowserServiceImplBase(context, serviceComponent, callback, rootHints);
@@ -405,7 +402,7 @@
private ConnectionCallbackInternal mConnectionCallbackInternal;
public ConnectionCallback() {
- if (Build.VERSION.SDK_INT >= 21) {
+ if (android.os.Build.VERSION.SDK_INT >= 21) {
mConnectionCallbackObj =
MediaBrowserCompatApi21.createConnectionCallback(new StubApi21());
} else {
@@ -528,7 +525,7 @@
/**
* Callbacks for subscription related events.
*/
- public static class SubscriptionCallbackApi21 extends SubscriptionCallback {
+ static class SubscriptionCallbackApi21 extends SubscriptionCallback {
SubscriptionCallback mSubscriptionCallback;
private final Object mSubscriptionCallbackObj;
private Bundle mOptions;
@@ -536,13 +533,8 @@
public SubscriptionCallbackApi21(SubscriptionCallback callback, Bundle options) {
mSubscriptionCallback = callback;
mOptions = options;
- if (Build.VERSION.SDK_INT >= 24) {
- mSubscriptionCallbackObj =
- MediaBrowserCompatApi24.createSubscriptionCallback(new StubApi24());
- } else {
- mSubscriptionCallbackObj =
- MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
- }
+ mSubscriptionCallbackObj =
+ MediaBrowserCompatApi21.createSubscriptionCallback(new StubApi21());
}
/**
@@ -603,7 +595,16 @@
private class StubApi21 implements MediaBrowserCompatApi21.SubscriptionCallback {
@Override
public void onChildrenLoaded(@NonNull String parentId, List<Parcel> children) {
- List<MediaBrowserCompat.MediaItem> mediaItems = parcelListToItemList(children);
+ List<MediaBrowserCompat.MediaItem> mediaItems = null;
+ if (children != null) {
+ mediaItems = new ArrayList<>();
+ for (Parcel parcel : children) {
+ parcel.setDataPosition(0);
+ mediaItems.add(
+ MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
+ parcel.recycle();
+ }
+ }
if (mOptions != null) {
SubscriptionCallbackApi21.this.onChildrenLoaded(parentId,
MediaBrowserCompatUtils.applyOptions(mediaItems, mOptions),
@@ -621,35 +622,6 @@
SubscriptionCallbackApi21.this.onError(parentId);
}
}
-
- List<MediaBrowserCompat.MediaItem> parcelListToItemList(
- List<Parcel> parcelList) {
- if (parcelList == null) {
- return null;
- }
- List<MediaBrowserCompat.MediaItem> items = new ArrayList<>();
- for (Parcel parcel : parcelList) {
- parcel.setDataPosition(0);
- items.add(MediaBrowserCompat.MediaItem.CREATOR.createFromParcel(parcel));
- parcel.recycle();
- }
- return items;
- }
- }
-
- private class StubApi24 extends StubApi21
- implements MediaBrowserCompatApi24.SubscriptionCallback {
- @Override
- public void onChildrenLoaded(@NonNull String parentId, @NonNull List<Parcel> children,
- @NonNull Bundle options) {
- SubscriptionCallbackApi21.this.onChildrenLoaded(
- parentId, parcelListToItemList(children), mOptions);
- }
-
- @Override
- public void onError(@NonNull String parentId, @NonNull Bundle options) {
- SubscriptionCallbackApi21.this.onError(parentId, mOptions);
- }
}
}
@@ -660,7 +632,7 @@
final Object mItemCallbackObj;
public ItemCallback() {
- if (Build.VERSION.SDK_INT >= 23) {
+ if (android.os.Build.VERSION.SDK_INT >= 23) {
mItemCallbackObj = MediaBrowserCompatApi23.createItemCallback(new StubApi23());
} else {
mItemCallbackObj = null;
@@ -1508,35 +1480,6 @@
}
}
- static class MediaBrowserImplApi24 extends MediaBrowserImplApi23 {
- public MediaBrowserImplApi24(Context context, ComponentName serviceComponent,
- ConnectionCallback callback, Bundle rootHints) {
- super(context, serviceComponent, callback, rootHints);
- }
-
- @Override
- public void subscribe(@NonNull final String parentId, @NonNull final Bundle options,
- @NonNull final SubscriptionCallback callback) {
- SubscriptionCallbackApi21 cb21 = new SubscriptionCallbackApi21(callback, options);
- if (options == null) {
- MediaBrowserCompatApi21.subscribe(
- mBrowserObj, parentId, cb21.mSubscriptionCallbackObj);
- } else {
- MediaBrowserCompatApi24.subscribe(
- mBrowserObj, parentId, options, cb21.mSubscriptionCallbackObj);
- }
- }
-
- @Override
- public void unsubscribe(@NonNull String parentId, Bundle options) {
- if (options == null) {
- MediaBrowserCompatApi21.unsubscribe(mBrowserObj, parentId);
- } else {
- MediaBrowserCompatApi24.unsubscribe(mBrowserObj, parentId, options);
- }
- }
- }
-
private static class Subscription {
private final List<SubscriptionCallback> mCallbacks;
private final List<Bundle> mOptionsList;
diff --git a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
index 49d1303..e9d6311 100644
--- a/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
+++ b/v4/java/android/support/v4/media/MediaBrowserServiceCompat.java
@@ -151,21 +151,6 @@
}
}
- class MediaBrowserServiceImplApi24 implements MediaBrowserServiceImpl {
- private Object mServiceObj;
-
- @Override
- public void onCreate() {
- mServiceObj = MediaBrowserServiceCompatApi24.createService();
- MediaBrowserServiceCompatApi24.onCreate(mServiceObj, new ServiceImplApi24());
- }
-
- @Override
- public IBinder onBind(Intent intent) {
- return MediaBrowserServiceCompatApi23.onBind(mServiceObj, intent);
- }
- }
-
private final class ServiceHandler extends Handler {
private final ServiceImpl mServiceImpl = new ServiceImpl();
@@ -450,27 +435,27 @@
}
@Override
- public void connect(String pkg, Bundle rootHints,
- final MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
+ public void connect(final String pkg, final Bundle rootHints,
+ final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
new ServiceCallbacksApi21(callbacks));
}
@Override
- public void disconnect(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
+ public void disconnect(final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
mServiceImpl.disconnect(new ServiceCallbacksApi21(callbacks));
}
@Override
public void addSubscription(
- String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
+ final String id, final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
mServiceImpl.addSubscription(id, null, new ServiceCallbacksApi21(callbacks));
}
@Override
- public void removeSubscription(
- String id, MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
+ public void removeSubscription(final String id,
+ final MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
mServiceImpl.removeSubscription(id, null, new ServiceCallbacksApi21(callbacks));
}
}
@@ -478,8 +463,8 @@
private class ServiceImplApi23 extends ServiceImplApi21
implements MediaBrowserServiceCompatApi23.ServiceImplApi23 {
@Override
- public void getMediaItem(
- String mediaId, final MediaBrowserServiceCompatApi23.ItemCallback cb) {
+ public void getMediaItem(final String mediaId,
+ final MediaBrowserServiceCompatApi23.ItemCallback cb) {
ResultReceiver receiverCompat = new ResultReceiver(mHandler) {
@Override
protected void onReceiveResult(int resultCode, Bundle resultData) {
@@ -496,27 +481,6 @@
}
}
- private class ServiceImplApi24 extends ServiceImplApi23
- implements MediaBrowserServiceCompatApi24.ServiceImplApi24 {
- @Override
- public void connect(String pkg, Bundle rootHints,
- final MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
- mServiceImpl.connect(pkg, Binder.getCallingUid(), rootHints,
- new ServiceCallbacksApi24(callbacks));
- }
-
- @Override
- public void addSubscription(String id, Bundle options,
- MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
- mServiceImpl.addSubscription(id, options, new ServiceCallbacksApi24(callbacks));
- }
-
- public void removeSubscription(String id, Bundle options,
- MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
- mServiceImpl.removeSubscription(id, options, new ServiceCallbacksApi24(callbacks));
- }
- }
-
private interface ServiceCallbacks {
IBinder asBinder();
void onConnect(String root, MediaSessionCompat.Token session, Bundle extras)
@@ -576,10 +540,10 @@
}
private class ServiceCallbacksApi21 implements ServiceCallbacks {
- final MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 mCallbacks;
+ final MediaBrowserServiceCompatApi21.ServiceCallbacks mCallbacks;
Messenger mMessenger;
- ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacksApi21 callbacks) {
+ ServiceCallbacksApi21(MediaBrowserServiceCompatApi21.ServiceCallbacks callbacks) {
mCallbacks = callbacks;
}
@@ -617,39 +581,10 @@
}
}
- private class ServiceCallbacksApi24 extends ServiceCallbacksApi21 {
- final MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 mCallbacks;
-
- ServiceCallbacksApi24(MediaBrowserServiceCompatApi24.ServiceCallbacksApi24 callbacks) {
- super(callbacks);
- mCallbacks = callbacks;
- }
-
- public void onLoadChildren(String mediaId, List<MediaBrowserCompat.MediaItem> list,
- Bundle options) throws RemoteException {
- List<Parcel> parcelList = null;
- if (list != null) {
- parcelList = new ArrayList<>();
- for (MediaBrowserCompat.MediaItem item : list) {
- Parcel parcel = Parcel.obtain();
- item.writeToParcel(parcel, 0);
- parcelList.add(parcel);
- }
- }
- if (options == null) {
- mCallbacks.onLoadChildren(mediaId, parcelList);
- } else {
- mCallbacks.onLoadChildren(mediaId, parcelList, options);
- }
- }
- }
-
@Override
public void onCreate() {
super.onCreate();
- if (Build.VERSION.SDK_INT >= 24) {
- mImpl = new MediaBrowserServiceImplApi24();
- } else if (Build.VERSION.SDK_INT >= 23) {
+ if (Build.VERSION.SDK_INT >= 23) {
mImpl = new MediaBrowserServiceImplApi23();
} else if (Build.VERSION.SDK_INT >= 21) {
mImpl = new MediaBrowserServiceImplApi21();
diff --git a/v4/java/android/support/v4/view/ViewPager.java b/v4/java/android/support/v4/view/ViewPager.java
index 3b0bb61..f134b09 100644
--- a/v4/java/android/support/v4/view/ViewPager.java
+++ b/v4/java/android/support/v4/view/ViewPager.java
@@ -386,6 +386,55 @@
ViewCompat.setImportantForAccessibility(this,
ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
+
+ ViewCompat.setOnApplyWindowInsetsListener(this,
+ new android.support.v4.view.OnApplyWindowInsetsListener() {
+ private final Rect mTempRect = new Rect();
+
+ @Override
+ public WindowInsetsCompat onApplyWindowInsets(final View v,
+ final WindowInsetsCompat originalInsets) {
+ // First let the ViewPager itself try and consume them...
+ final WindowInsetsCompat applied =
+ ViewCompat.onApplyWindowInsets(v, originalInsets);
+ if (applied.isConsumed()) {
+ // If the ViewPager consumed all insets, return now
+ return applied;
+ }
+
+ // Now we'll manually dispatch the insets to our children. Since ViewPager
+ // children are always full-height, we do not want to use the standard
+ // ViewGroup dispatchApplyWindowInsets since if child 0 consumes them,
+ // the rest of the children will not receive any insets. To workaround this
+ // we manually dispatch the applied insets, not allowing children to
+ // consume them from each other. We do however keep track of any insets
+ // which are consumed, returning the union of our children's consumption
+ final Rect res = mTempRect;
+ res.left = applied.getSystemWindowInsetLeft();
+ res.top = applied.getSystemWindowInsetTop();
+ res.right = applied.getSystemWindowInsetRight();
+ res.bottom = applied.getSystemWindowInsetBottom();
+
+ for (int i = 0, count = getChildCount(); i < count; i++) {
+ final WindowInsetsCompat childInsets = ViewCompat
+ .dispatchApplyWindowInsets(getChildAt(i), applied);
+ // Now keep track of any consumed by tracking each dimension's min
+ // value
+ res.left = Math.min(childInsets.getSystemWindowInsetLeft(),
+ res.left);
+ res.top = Math.min(childInsets.getSystemWindowInsetTop(),
+ res.top);
+ res.right = Math.min(childInsets.getSystemWindowInsetRight(),
+ res.right);
+ res.bottom = Math.min(childInsets.getSystemWindowInsetBottom(),
+ res.bottom);
+ }
+
+ // Now return a new WindowInsets, using the consumed window insets
+ return applied.replaceSystemWindowInsets(
+ res.left, res.top, res.right, res.bottom);
+ }
+ });
}
@Override
diff --git a/v4/tests/java/android/support/v4/app/NestedFragmentTest.java b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
new file mode 100644
index 0000000..fdc60b7
--- /dev/null
+++ b/v4/tests/java/android/support/v4/app/NestedFragmentTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.support.v4.app;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.os.Bundle;
+import android.support.v4.app.test.FragmentTestActivity;
+import android.support.v4.app.test.FragmentTestActivity.ParentFragment;
+import android.test.ActivityInstrumentationTestCase2;
+import android.test.UiThreadTest;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+public class NestedFragmentTest extends ActivityInstrumentationTestCase2<FragmentTestActivity> {
+
+ ParentFragment mParentFragment;
+
+ public NestedFragmentTest() {
+ super(FragmentTestActivity.class);
+ }
+
+ @Override
+ protected void setUp() throws Exception {
+ super.setUp();
+
+ final FragmentManager fragmentManager = getActivity().getSupportFragmentManager();
+ mParentFragment = new ParentFragment();
+ fragmentManager.beginTransaction().add(mParentFragment, "parent").commit();
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ fragmentManager.executePendingTransactions();
+ latch.countDown();
+ }
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+ }
+
+ @UiThreadTest
+ public void testThrowsWhenUsingReservedRequestCode() {
+ try {
+ mParentFragment.childFragment.startActivityForResult(
+ new Intent(Intent.ACTION_CALL), 16777216 /* requestCode */);
+ fail("Expected IllegalArgumentException");
+ } catch (IllegalArgumentException expected) {}
+ }
+
+ public void testNestedFragmentStartActivityForResult() throws Exception {
+ Instrumentation.ActivityResult activityResult = new Instrumentation.ActivityResult(
+ Activity.RESULT_OK, new Intent());
+
+ Instrumentation.ActivityMonitor activityMonitor =
+ getInstrumentation().addMonitor(
+ new IntentFilter(Intent.ACTION_CALL), activityResult, true /* block */);
+
+ // Sanity check that onActivityResult hasn't been called yet.
+ assertFalse(mParentFragment.childFragment.onActivityResultCalled);
+
+ final CountDownLatch latch = new CountDownLatch(1);
+ getActivity().runOnUiThread(new Runnable() {
+ public void run() {
+ mParentFragment.childFragment.startActivityForResult(
+ new Intent(Intent.ACTION_CALL),
+ 5 /* requestCode */);
+ latch.countDown();
+ }
+ });
+ assertTrue(latch.await(1, TimeUnit.SECONDS));
+
+ assertTrue(getInstrumentation().checkMonitorHit(activityMonitor, 1));
+
+ assertTrue(mParentFragment.childFragment.onActivityResultCalled);
+ assertEquals(5, mParentFragment.childFragment.onActivityResultRequestCode);
+ assertEquals(Activity.RESULT_OK,
+ mParentFragment.childFragment.onActivityResultResultCode);
+ }
+}
diff --git a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
index 85203f8..ca84498 100644
--- a/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
+++ b/v4/tests/java/android/support/v4/app/test/FragmentTestActivity.java
@@ -17,6 +17,7 @@
import android.app.Activity;
import android.content.Context;
+import android.content.Intent;
import android.os.Build.VERSION;
import android.os.Build.VERSION_CODES;
import android.os.Bundle;
@@ -199,23 +200,36 @@
public static class ParentFragment extends Fragment {
public boolean wasAttachedInTime;
+ public ChildFragment childFragment;
+
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
- ChildFragment f = new ChildFragment();
+ childFragment = new ChildFragment();
FragmentManager fm = getChildFragmentManager();
- fm.beginTransaction().add(f, "foo").commit();
+ fm.beginTransaction().add(childFragment, "foo").commit();
fm.executePendingTransactions();
- wasAttachedInTime = f.attached;
+ wasAttachedInTime = childFragment.attached;
}
}
public static class ChildFragment extends Fragment {
public boolean attached;
+ public boolean onActivityResultCalled;
+ public int onActivityResultRequestCode;
+ public int onActivityResultResultCode;
+
@Override
public void onAttach(Context activity) {
super.onAttach(activity);
attached = true;
}
+
+ @Override
+ public void onActivityResult(int requestCode, int resultCode, Intent data) {
+ onActivityResultCalled = true;
+ onActivityResultRequestCode = requestCode;
+ onActivityResultResultCode = resultCode;
+ }
}
}
diff --git a/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java b/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java
index f61fd0e..c6f07e5 100644
--- a/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java
+++ b/v4/tests/java/android/support/v4/testutils/TestUtilsMatchers.java
@@ -17,6 +17,7 @@
package android.support.v4.testutils;
import java.lang.String;
+import java.util.List;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
@@ -228,4 +229,44 @@
}
};
}
+
+ /**
+ * Returns a matcher that matches lists of integer values that match the specified sequence
+ * of values.
+ */
+ public static Matcher<List<Integer>> matches(final int ... expectedValues) {
+ return new TypeSafeMatcher<List<Integer>>() {
+ private String mFailedDescription;
+
+ @Override
+ public void describeTo(Description description) {
+ description.appendText(mFailedDescription);
+ }
+
+ @Override
+ protected boolean matchesSafely(List<Integer> item) {
+ int actualCount = item.size();
+ int expectedCount = expectedValues.length;
+
+ if (actualCount != expectedCount) {
+ mFailedDescription = "Expected " + expectedCount + " values, but got " +
+ actualCount;
+ return false;
+ }
+
+ for (int i = 0; i < expectedCount; i++) {
+ int curr = item.get(i);
+
+ if (curr != expectedValues[i]) {
+ mFailedDescription = "At #" + i + " got " + curr + " but should be " +
+ expectedValues[i];
+ return false;
+ }
+ }
+
+ return true;
+ }
+ };
+ }
+
}
diff --git a/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
index e990257..58042bc 100644
--- a/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
+++ b/v4/tests/java/android/support/v4/view/BaseViewPagerTest.java
@@ -19,7 +19,6 @@
import android.graphics.Color;
import android.support.v4.BaseInstrumentationTestCase;
import android.support.v4.test.R;
-import android.support.v4.testutils.TestUtilsAssertions;
import android.support.v4.testutils.TestUtilsMatchers;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
@@ -30,6 +29,7 @@
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import java.util.ArrayList;
@@ -46,6 +46,7 @@
import static org.hamcrest.Matchers.allOf;
import static org.hamcrest.core.IsNot.not;
import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.*;
/**
* Base class for testing <code>ViewPager</code>. Most of the testing logic should be in this
@@ -191,33 +192,59 @@
private void verifyPageSelections(boolean smoothScroll) {
assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
assertEquals("Scroll right", 1, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(1);
onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
assertEquals("Scroll right", 2, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
// Try "scrolling" beyond the last page and test that we're still on the last page.
onView(withId(R.id.pager)).perform(scrollRight(smoothScroll));
assertEquals("Scroll right beyond last page", 2, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
assertEquals("Scroll left", 1, mViewPager.getCurrentItem());
+ // Verify that this is the second time we're called on index 1
+ verify(mockPageChangeListener, times(2)).onPageSelected(1);
onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
assertEquals("Scroll left", 0, mViewPager.getCurrentItem());
+ // Verify that this is the first time we're called on index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
// Try "scrolling" beyond the first page and test that we're still on the first page.
onView(withId(R.id.pager)).perform(scrollLeft(smoothScroll));
assertEquals("Scroll left beyond first page", 0, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Unregister our listener
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
// Go from index 0 to index 2
onView(withId(R.id.pager)).perform(scrollToPage(2, smoothScroll));
assertEquals("Scroll to last page", 2, mViewPager.getCurrentItem());
+ // Our listener is not registered anymore, so we shouldn't have been called with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
// And back to 0
onView(withId(R.id.pager)).perform(scrollToPage(0, smoothScroll));
assertEquals("Scroll to first page", 0, mViewPager.getCurrentItem());
+ // Our listener is not registered anymore, so we shouldn't have been called with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ // Verify the overall sequence of calls to onPageSelected of our listener
+ ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture());
+ assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0));
}
@Test
@@ -237,25 +264,46 @@
public void testPageSwipes() {
assertEquals("Initial state", 0, mViewPager.getCurrentItem());
+ ViewPager.OnPageChangeListener mockPageChangeListener =
+ mock(ViewPager.OnPageChangeListener.class);
+ mViewPager.addOnPageChangeListener(mockPageChangeListener);
+
onView(withId(R.id.pager)).perform(swipeLeft());
assertEquals("Swipe left", 1, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(1);
onView(withId(R.id.pager)).perform(swipeLeft());
assertEquals("Swipe left", 2, mViewPager.getCurrentItem());
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
// Try swiping beyond the last page and test that we're still on the last page.
onView(withId(R.id.pager)).perform(swipeLeft());
assertEquals("Swipe left beyond last page", 2, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 2
+ verify(mockPageChangeListener, times(1)).onPageSelected(2);
onView(withId(R.id.pager)).perform(swipeRight());
assertEquals("Swipe right", 1, mViewPager.getCurrentItem());
+ // Verify that this is the second time we're called on index 1
+ verify(mockPageChangeListener, times(2)).onPageSelected(1);
onView(withId(R.id.pager)).perform(swipeRight());
assertEquals("Swipe right", 0, mViewPager.getCurrentItem());
+ // Verify that this is the first time we're called on index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
// Try swiping beyond the first page and test that we're still on the first page.
onView(withId(R.id.pager)).perform(swipeRight());
assertEquals("Swipe right beyond first page", 0, mViewPager.getCurrentItem());
+ // We're still on this page, so we shouldn't have been called again with index 0
+ verify(mockPageChangeListener, times(1)).onPageSelected(0);
+
+ mViewPager.removeOnPageChangeListener(mockPageChangeListener);
+
+ // Verify the overall sequence of calls to onPageSelected of our listener
+ ArgumentCaptor<Integer> pageSelectedCaptor = ArgumentCaptor.forClass(int.class);
+ verify(mockPageChangeListener, times(4)).onPageSelected(pageSelectedCaptor.capture());
+ assertThat(pageSelectedCaptor.getAllValues(), TestUtilsMatchers.matches(1, 2, 1, 0));
}
@Test
diff --git a/v7/appcompat/build.gradle b/v7/appcompat/build.gradle
index d59332c..faa8439 100644
--- a/v7/appcompat/build.gradle
+++ b/v7/appcompat/build.gradle
@@ -14,8 +14,8 @@
exclude module: 'support-annotations'
}
androidTestCompile 'org.mockito:mockito-core:1.9.5'
- androidTestCompile 'com.google.dexmaker:dexmaker:1.0'
- androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.0'
+ androidTestCompile 'com.google.dexmaker:dexmaker:1.2'
+ androidTestCompile 'com.google.dexmaker:dexmaker-mockito:1.2'
testCompile 'junit:junit:4.12'
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
index c48b221..50346b9 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogCursorTest.java
@@ -50,14 +50,14 @@
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
public class AlertDialogCursorTest
extends BaseInstrumentationTestCase<AlertDialogTestActivity> {
private Button mButton;
- private int mClickedItemIndex = -1;
-
private static final String TEXT_COLUMN_NAME = "text";
private static final String CHECKED_COLUMN_NAME = "checked";
@@ -146,7 +146,8 @@
});
}
- private void verifySimpleItemsContent(String[] expectedContent) {
+ private void verifySimpleItemsContent(String[] expectedContent,
+ DialogInterface.OnClickListener onClickListener) {
final int expectedCount = expectedContent.length;
onView(withId(R.id.test_button)).perform(click());
@@ -167,15 +168,16 @@
rowInteraction.inRoot(isDialog()).check(matches(isDisplayed()));
}
+ // Verify that our click listener hasn't been called yet
+ verify(onClickListener, never()).onClick(any(DialogInterface.class), any(int.class));
// Test that a click on an item invokes the registered listener
- assertEquals("Before list item click", -1, mClickedItemIndex);
int indexToClick = expectedCount - 2;
DataInteraction interactionForClick = onData(allOf(
is(instanceOf(SQLiteCursor.class)),
TestUtilsMatchers.withCursorItemContent(
TEXT_COLUMN_NAME, expectedContent[indexToClick])));
interactionForClick.inRoot(isDialog()).perform(click());
- assertEquals("List item clicked", indexToClick, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, indexToClick);
}
@Test
@@ -185,18 +187,14 @@
null, null, null, null, null);
assertNotNull(mCursor);
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setCursor(mCursor,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- }, "text");
+ .setCursor(mCursor, mockClickListener, "text");
wireBuilder(builder);
- verifySimpleItemsContent(mTextContent);
+ verifySimpleItemsContent(mTextContent, mockClickListener);
}
/**
@@ -354,7 +352,7 @@
}
private void verifySingleChoiceItemsContent(String[] expectedContent,
- int initialSelectionIndex) {
+ int initialSelectionIndex, DialogInterface.OnClickListener onClickListener) {
final int expectedCount = expectedContent.length;
int currentlyExpectedSelectionIndex = initialSelectionIndex;
@@ -372,14 +370,12 @@
TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME,
expectedContent[currentlyExpectedSelectionIndex])));
interactionForClick.inRoot(isDialog()).perform(click());
- assertEquals("Selected first single-choice item",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
// Now click the same item again and test that the selection has not changed
interactionForClick.inRoot(isDialog()).perform(click());
- assertEquals("Selected first single-choice item again",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(2)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
// Now we're going to click the last item and test that the click listener has been invoked
@@ -390,8 +386,7 @@
TestUtilsMatchers.withCursorItemContent(TEXT_COLUMN_NAME,
expectedContent[currentlyExpectedSelectionIndex])));
interactionForClick.inRoot(isDialog()).perform(click());
- assertEquals("Selected last single-choice item",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
}
@@ -402,17 +397,13 @@
null, null, null, null, null);
assertNotNull(mCursor);
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setSingleChoiceItems(mCursor, 2, TEXT_COLUMN_NAME,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ .setSingleChoiceItems(mCursor, 2, TEXT_COLUMN_NAME, mockClickListener);
wireBuilder(builder);
- verifySingleChoiceItemsContent(mTextContent, 2);
+ verifySingleChoiceItemsContent(mTextContent, 2, mockClickListener);
}
}
diff --git a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
index 40f3dba..213a898 100644
--- a/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/app/AlertDialogTest.java
@@ -19,7 +19,6 @@
import android.content.DialogInterface;
import android.graphics.drawable.ColorDrawable;
import android.os.Handler;
-import android.os.Looper;
import android.os.Message;
import android.support.annotation.ColorInt;
import android.support.annotation.StringRes;
@@ -28,7 +27,6 @@
import android.support.test.espresso.ViewInteraction;
import android.support.v7.appcompat.test.R;
import android.support.v7.testutils.TestUtilsMatchers;
-import android.test.ActivityInstrumentationTestCase2;
import android.test.suitebuilder.annotation.MediumTest;
import android.test.suitebuilder.annotation.SmallTest;
import android.text.TextUtils;
@@ -43,6 +41,7 @@
import org.hamcrest.Matcher;
import org.junit.Before;
import org.junit.Test;
+import org.mockito.ArgumentCaptor;
import static android.support.test.espresso.Espresso.onData;
import static android.support.test.espresso.Espresso.onView;
@@ -58,6 +57,7 @@
import static org.hamcrest.core.AllOf.allOf;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;
+import static org.mockito.Mockito.*;
/**
* Tests in this class make a few assumptions about the underlying implementation of
@@ -78,18 +78,8 @@
public class AlertDialogTest extends BaseInstrumentationTestCase<AlertDialogTestActivity> {
private Button mButton;
- private boolean mIsCanceledCalled = false;
-
- private boolean mIsDismissedCalled = false;
-
- private int mWhichButtonClicked = -1;
-
- private int mClickedItemIndex = -1;
-
private AlertDialog mAlertDialog;
- private Handler mClickHandler;
-
public AlertDialogTest() {
super(AlertDialogTestActivity.class);
}
@@ -98,12 +88,6 @@
public void setUp() {
final AlertDialogTestActivity activity = mActivityTestRule.getActivity();
mButton = (Button) activity.findViewById(R.id.test_button);
- mClickHandler = new Handler(Looper.getMainLooper()) {
- @Override
- public void handleMessage(Message msg) {
- mWhichButtonClicked = msg.what;
- }
- };
}
private void wireBuilder(final AlertDialog.Builder builder) {
@@ -346,16 +330,13 @@
@Test
@SmallTest
public void testCancelCancelableDialog() {
+ DialogInterface.OnCancelListener mockCancelListener =
+ mock(DialogInterface.OnCancelListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
.setMessage(R.string.alert_dialog_content)
.setCancelable(true)
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mIsCanceledCalled = true;
- }
- });
+ .setOnCancelListener(mockCancelListener);
wireBuilder(builder);
onView(withId(R.id.test_button)).perform(click());
@@ -364,22 +345,19 @@
Espresso.pressBack();
// Since our dialog is cancelable, check that the cancel listener has been invoked
- assertTrue("Dialog is canceled", mIsCanceledCalled);
+ verify(mockCancelListener, times(1)).onCancel(mAlertDialog);
}
@Test
@SmallTest
public void testCancelNonCancelableDialog() {
+ DialogInterface.OnCancelListener mockCancelListener =
+ mock(DialogInterface.OnCancelListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
.setMessage(R.string.alert_dialog_content)
.setCancelable(false)
- .setOnCancelListener(new DialogInterface.OnCancelListener() {
- @Override
- public void onCancel(DialogInterface dialog) {
- mIsCanceledCalled = true;
- }
- });
+ .setOnCancelListener(mockCancelListener);
wireBuilder(builder);
onView(withId(R.id.test_button)).perform(click());
@@ -388,12 +366,13 @@
Espresso.pressBack();
// Since our dialog is not cancelable, check that the cancel listener has not been invoked
- assertFalse("Dialog is not canceled", mIsCanceledCalled);
+ verify(mockCancelListener, never()).onCancel(mAlertDialog);
}
// Tests for items content logic (simple, single-choice, multi-choice)
- private void verifySimpleItemsContent(String[] expectedContent) {
+ private void verifySimpleItemsContent(String[] expectedContent,
+ DialogInterface.OnClickListener onClickListener) {
final int expectedCount = expectedContent.length;
onView(withId(R.id.test_button)).perform(click());
@@ -415,12 +394,13 @@
check(matches(isDisplayed()));
}
+ // Verify that our click listener hasn't been called yet
+ verify(onClickListener, never()).onClick(any(DialogInterface.class), any(int.class));
// Test that a click on an item invokes the registered listener
- assertEquals("Before list item click", -1, mClickedItemIndex);
int indexToClick = expectedCount - 2;
onData(allOf(is(instanceOf(String.class)), is(expectedContent[indexToClick]))).
inRoot(isDialog()).perform(click());
- assertEquals("List item clicked", indexToClick, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, indexToClick);
}
@Test
@@ -428,56 +408,44 @@
public void testCustomAdapter() {
final Context context = mActivityTestRule.getActivity();
final String[] content = context.getResources().getStringArray(R.array.alert_dialog_items);
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
.setAdapter(
- new ArrayAdapter<>(context, android.R.layout.simple_list_item_1,
- content),
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, content),
+ mockClickListener);
wireBuilder(builder);
- verifySimpleItemsContent(content);
+ verifySimpleItemsContent(content, mockClickListener);
}
@Test
@SmallTest
public void testSimpleItemsFromRuntimeArray() {
final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setItems(content,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ .setItems(content, mockClickListener);
wireBuilder(builder);
- verifySimpleItemsContent(content);
+ verifySimpleItemsContent(content, mockClickListener);
}
@Test
@SmallTest
public void testSimpleItemsFromResourcesArray() {
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setItems(R.array.alert_dialog_items,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ .setItems(R.array.alert_dialog_items, mockClickListener);
wireBuilder(builder);
verifySimpleItemsContent(mActivityTestRule.getActivity().getResources().getStringArray(
- R.array.alert_dialog_items));
+ R.array.alert_dialog_items), mockClickListener);
}
/**
@@ -645,7 +613,7 @@
}
private void verifySingleChoiceItemsContent(String[] expectedContent,
- int initialSelectionIndex) {
+ int initialSelectionIndex, DialogInterface.OnClickListener onClickListener) {
final int expectedCount = expectedContent.length;
int currentlyExpectedSelectionIndex = initialSelectionIndex;
@@ -661,16 +629,14 @@
onData(allOf(is(instanceOf(String.class)),
is(expectedContent[currentlyExpectedSelectionIndex]))).
inRoot(isDialog()).perform(click());
- assertEquals("Selected first single-choice item",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
// Now click the same item again and test that the selection has not changed
onData(allOf(is(instanceOf(String.class)),
is(expectedContent[currentlyExpectedSelectionIndex]))).
inRoot(isDialog()).perform(click());
- assertEquals("Selected first single-choice item again",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(2)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
// Now we're going to click the last item and test that the click listener has been invoked
@@ -679,8 +645,7 @@
onData(allOf(is(instanceOf(String.class)),
is(expectedContent[currentlyExpectedSelectionIndex]))).
inRoot(isDialog()).perform(click());
- assertEquals("Selected last single-choice item",
- currentlyExpectedSelectionIndex, mClickedItemIndex);
+ verify(onClickListener, times(1)).onClick(mAlertDialog, currentlyExpectedSelectionIndex);
verifySingleChoiceItemsState(expectedContent, currentlyExpectedSelectionIndex);
}
@@ -688,36 +653,28 @@
@SmallTest
public void testSingleChoiceItemsFromRuntimeArray() {
final String[] content = new String[] { "Alice", "Bob", "Charlie", "Delta" };
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setSingleChoiceItems(
- content, 2,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ .setSingleChoiceItems(content, 2, mockClickListener);
wireBuilder(builder);
- verifySingleChoiceItemsContent(content, 2);
+ verifySingleChoiceItemsContent(content, 2, mockClickListener);
}
@Test
@SmallTest
public void testSingleChoiceItemsFromResourcesArray() {
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title)
- .setSingleChoiceItems(R.array.alert_dialog_items, 1,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- mClickedItemIndex = which;
- }
- });
+ .setSingleChoiceItems(R.array.alert_dialog_items, 1, mockClickListener);
wireBuilder(builder);
- verifySingleChoiceItemsContent(new String[] { "Albania", "Belize", "Chad", "Djibouti" }, 1);
+ verifySingleChoiceItemsContent(new String[] { "Albania", "Belize", "Chad", "Djibouti" }, 1,
+ mockClickListener);
}
// Tests for icon logic
@@ -999,10 +956,35 @@
/**
* Helper method to verify dialog state after a button has been clicked.
*/
- private void verifyPostButtonClickState(int whichButtonClicked) {
- assertEquals("Button clicked", whichButtonClicked, mWhichButtonClicked);
+ private void verifyPostButtonClickState(int whichButtonClicked,
+ DialogInterface.OnDismissListener onDismissListener,
+ Handler messageHandler) {
+ // Verify that a Message with expected 'what' field has been posted on our mock handler
+ ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(messageHandler, times(1)).sendMessageDelayed(
+ messageArgumentCaptor.capture(), anyInt());
+ assertEquals("Button clicked", whichButtonClicked, messageArgumentCaptor.getValue().what);
+ // Verify that the dialog is no longer showing
assertFalse("Dialog is not showing", mAlertDialog.isShowing());
- assertTrue("Dialog dismiss listener called", mIsDismissedCalled);
+ if (onDismissListener != null) {
+ // And that our mock listener has been called when the dialog was dismissed
+ verify(onDismissListener, times(1)).onDismiss(mAlertDialog);
+ }
+ }
+
+ /**
+ * Helper method to verify dialog state after a button has been clicked.
+ */
+ private void verifyPostButtonClickState(int whichButtonClicked,
+ DialogInterface.OnClickListener onClickListener,
+ DialogInterface.OnDismissListener onDismissListener) {
+ if (onClickListener != null) {
+ verify(onClickListener, times(1)).onClick(mAlertDialog, whichButtonClicked);
+ }
+ assertFalse("Dialog is not showing", mAlertDialog.isShowing());
+ if (onDismissListener != null) {
+ verify(onDismissListener, times(1)).onDismiss(mAlertDialog);
+ }
}
/**
@@ -1017,40 +999,21 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title);
// Configure buttons with non-empty texts
+ DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
if (!TextUtils.isEmpty(positiveButtonText)) {
- builder.setPositiveButton(positiveButtonText, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Positive button clicked", AlertDialog.BUTTON_POSITIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setPositiveButton(positiveButtonText, mockClickListener);
}
if (!TextUtils.isEmpty(negativeButtonText)) {
- builder.setNegativeButton(negativeButtonText, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Negative button clicked", AlertDialog.BUTTON_NEGATIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setNegativeButton(negativeButtonText, mockClickListener);
}
if (!TextUtils.isEmpty(neutralButtonText)) {
- builder.setNeutralButton(neutralButtonText, new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Neutral button clicked", AlertDialog.BUTTON_NEUTRAL, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setNeutralButton(neutralButtonText, mockClickListener);
}
// Set a dismiss listener to verify that the dialog is dismissed on clicking any button
- builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mIsDismissedCalled = true;
- }
- });
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
// Wire the builder to the button click and click that button to show the dialog
wireBuilder(builder);
@@ -1073,7 +1036,7 @@
break;
}
onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
- verifyPostButtonClickState(whichButtonToClick);
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener);
}
/**
@@ -1093,50 +1056,25 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(context)
.setTitle(R.string.alert_dialog_title);
+ DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
// Configure buttons with non-zero text resource IDs
if (positiveButtonTextResId != 0) {
positiveButtonText = context.getString(positiveButtonTextResId);
- builder.setPositiveButton(positiveButtonTextResId,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Positive button clicked",
- AlertDialog.BUTTON_POSITIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setPositiveButton(positiveButtonTextResId, mockClickListener);
}
if (negativeButtonTextResId != 0) {
negativeButtonText = context.getString(negativeButtonTextResId);
- builder.setNegativeButton(negativeButtonTextResId,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Negative button clicked",
- AlertDialog.BUTTON_NEGATIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setNegativeButton(negativeButtonTextResId, mockClickListener);
}
if (neutralButtonTextResId != 0) {
neutralButtonText = context.getString(neutralButtonTextResId);
- builder.setNeutralButton(neutralButtonTextResId,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Neutral button clicked",
- AlertDialog.BUTTON_NEUTRAL, which);
- mWhichButtonClicked = which;
- }
- });
+ builder.setNeutralButton(neutralButtonTextResId, mockClickListener);
}
// Set a dismiss listener to verify that the dialog is dismissed on clicking any button
- builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mIsDismissedCalled = true;
- }
- });
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
// Wire the builder to the button click and click that button to show the dialog
wireBuilder(builder);
@@ -1159,7 +1097,7 @@
break;
}
onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
- verifyPostButtonClickState(whichButtonToClick);
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, mockDismissListener);
}
/**
@@ -1175,12 +1113,12 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title);
// Set a dismiss listener to verify that the dialog is dismissed on clicking any button
- builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mIsDismissedCalled = true;
- }
- });
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+
+ final DialogInterface.OnClickListener mockClickListener =
+ mock(DialogInterface.OnClickListener.class);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
@@ -1189,36 +1127,15 @@
// Configure buttons with non-empty texts
if (!TextUtils.isEmpty(positiveButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Positive button clicked",
- AlertDialog.BUTTON_POSITIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ mockClickListener);
}
if (!TextUtils.isEmpty(negativeButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Negative button clicked",
- AlertDialog.BUTTON_NEGATIVE, which);
- mWhichButtonClicked = which;
- }
- });
+ mockClickListener);
}
if (!TextUtils.isEmpty(neutralButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText,
- new DialogInterface.OnClickListener() {
- @Override
- public void onClick(DialogInterface dialog, int which) {
- assertEquals("Neutral button clicked",
- AlertDialog.BUTTON_NEUTRAL, which);
- mWhichButtonClicked = which;
- }
- });
+ mockClickListener);
}
mAlertDialog.show();
@@ -1245,7 +1162,7 @@
break;
}
onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
- verifyPostButtonClickState(whichButtonToClick);
+ verifyPostButtonClickState(whichButtonToClick, mockClickListener, null);
}
/**
@@ -1261,13 +1178,11 @@
final AlertDialog.Builder builder = new AlertDialog.Builder(mActivityTestRule.getActivity())
.setTitle(R.string.alert_dialog_title);
// Set a dismiss listener to verify that the dialog is dismissed on clicking any button
- builder.setOnDismissListener(new DialogInterface.OnDismissListener() {
- @Override
- public void onDismiss(DialogInterface dialog) {
- mIsDismissedCalled = true;
- }
- });
+ DialogInterface.OnDismissListener mockDismissListener =
+ mock(DialogInterface.OnDismissListener.class);
+ builder.setOnDismissListener(mockDismissListener);
+ final Handler mockMessageHandler = mock(Handler.class);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
@@ -1275,15 +1190,15 @@
// Configure buttons with non-empty texts
if (!TextUtils.isEmpty(positiveButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_POSITIVE, positiveButtonText,
- Message.obtain(mClickHandler, DialogInterface.BUTTON_POSITIVE));
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_POSITIVE));
}
if (!TextUtils.isEmpty(negativeButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_NEGATIVE, negativeButtonText,
- Message.obtain(mClickHandler, DialogInterface.BUTTON_NEGATIVE));
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEGATIVE));
}
if (!TextUtils.isEmpty(neutralButtonText)) {
mAlertDialog.setButton(DialogInterface.BUTTON_NEUTRAL, neutralButtonText,
- Message.obtain(mClickHandler, DialogInterface.BUTTON_NEUTRAL));
+ Message.obtain(mockMessageHandler, DialogInterface.BUTTON_NEUTRAL));
}
mAlertDialog.show();
@@ -1310,7 +1225,7 @@
break;
}
onView(withText(textOfButtonToClick)).inRoot(isDialog()).perform(click());
- verifyPostButtonClickState(whichButtonToClick);
+ verifyPostButtonClickState(whichButtonToClick, mockDismissListener, mockMessageHandler);
}
@Test
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
index 9072100..7545981 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/ListPopupWindowTest.java
@@ -23,6 +23,7 @@
import android.support.v7.appcompat.test.R;
import android.test.suitebuilder.annotation.SmallTest;
import android.view.LayoutInflater;
+import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
@@ -47,6 +48,8 @@
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
public class ListPopupWindowTest extends BaseInstrumentationTestCase<PopupTestActivity> {
private FrameLayout mContainer;
@@ -57,11 +60,20 @@
private BaseAdapter mListPopupAdapter;
- private int mListPopupClickedItem = -1;
+ private AdapterView.OnItemClickListener mItemClickListener;
- private boolean mIsDismissedCalled = false;
-
- private boolean mIsContainerClicked = false;
+ /**
+ * Item click listener that dismisses our <code>ListPopupWindow</code> when any item
+ * is clicked. Note that this needs to be a separate class that is also protected (not
+ * private) so that Mockito can "spy" on it.
+ */
+ protected class PopupItemClickListener implements AdapterView.OnItemClickListener {
+ @Override
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ mListPopupWindow.dismiss();
+ }
+ }
public ListPopupWindowTest() {
super(PopupTestActivity.class);
@@ -72,6 +84,7 @@
final PopupTestActivity activity = mActivityTestRule.getActivity();
mContainer = (FrameLayout) activity.findViewById(R.id.container);
mButton = (Button) mContainer.findViewById(R.id.test_button);
+ mItemClickListener = new PopupItemClickListener();
}
@Test
@@ -143,7 +156,8 @@
}
});
- assertTrue("Dismiss listener called", mIsDismissedCalled);
+ // Verify that our dismiss listener has been called
+ verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
assertFalse("Popup window not showing after dismissal", mListPopupWindow.isShowing());
}
@@ -152,12 +166,8 @@
popupBuilder.wireToActionButton();
// Also register a click listener on the top-level container
- mContainer.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- mIsContainerClicked = true;
- }
- });
+ View.OnClickListener mockContainerClickListener = mock(View.OnClickListener.class);
+ mContainer.setOnClickListener(mockContainerClickListener);
onView(withId(R.id.test_button)).perform(click());
assertTrue("Popup window showing", mListPopupWindow.isShowing());
@@ -210,12 +220,12 @@
// At this point our popup should not be showing and should have notified its
// dismiss listener
- assertTrue("Dismiss listener called", mIsDismissedCalled);
+ verify(popupBuilder.mOnDismissListener, times(1)).onDismiss();
assertFalse("Popup window not showing after outside click", mListPopupWindow.isShowing());
// Also test that the click outside the popup bounds has been "delivered" to the main
// container only if the popup is not modal
- assertEquals("Click on underlying container", !setupAsModal, mIsContainerClicked);
+ verify(mockContainerClickListener, times(setupAsModal ? 0 : 1)).onClick(mContainer);
}
@Test
@@ -239,13 +249,20 @@
onView(withId(R.id.test_button)).perform(click());
assertTrue("Popup window showing", mListPopupWindow.isShowing());
- assertEquals("Clicked item before click", -1, mListPopupClickedItem);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(popupBuilder.mOnItemClickListener, never()).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
final View mainDecorView = mActivityTestRule.getActivity().getWindow().getDecorView();
onView(withText("Charlie"))
.inRoot(withDecorView(not(is(mainDecorView))))
.perform(click());
- assertEquals("Clicked item after click", 2, mListPopupClickedItem);
+ // Verify that out menu item click listener has been called with the expected item
+ // position. Note that we use any() for other parameters, as we don't want to tie ourselves
+ // to the specific implementation details of how ListPopupWindow displays its content.
+ verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+ any(AdapterView.class), any(View.class), eq(2), any(int.class));
+
// Our item click listener also dismisses the popup
assertFalse("Popup window not showing after click", mListPopupWindow.isShowing());
}
@@ -259,7 +276,10 @@
onView(withId(R.id.test_button)).perform(click());
assertTrue("Popup window showing", mListPopupWindow.isShowing());
- assertEquals("Clicked item before click", -1, mListPopupClickedItem);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(popupBuilder.mOnItemClickListener, never()).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
+
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
@@ -267,7 +287,11 @@
}
});
- assertEquals("Clicked item after click", 1, mListPopupClickedItem);
+ // Verify that out menu item click listener has been called with the expected item
+ // position. Note that we use any() for other parameters, as we don't want to tie ourselves
+ // to the specific implementation details of how ListPopupWindow displays its content.
+ verify(popupBuilder.mOnItemClickListener, times(1)).onItemClick(
+ any(AdapterView.class), any(View.class), eq(1), any(int.class));
// Our item click listener also dismisses the popup
assertFalse("Popup window not showing after click", mListPopupWindow.isShowing());
}
@@ -279,13 +303,13 @@
* we can't add logic that is specific to a certain test (such as dismissing a non-modal
* popup window) once it's shown and we have a reference to a displayed ListPopupWindow.
*/
- private class Builder {
+ public class Builder {
private boolean mIsModal;
private boolean mHasDismissListener;
private boolean mHasItemClickListener;
- public Builder() {
- }
+ private AdapterView.OnItemClickListener mOnItemClickListener;
+ private PopupWindow.OnDismissListener mOnDismissListener;
public Builder setModal(boolean isModal) {
mIsModal = isModal;
@@ -346,26 +370,23 @@
mListPopupWindow.setAdapter(mListPopupAdapter);
mListPopupWindow.setAnchorView(mButton);
- // The following listeners have to be set before the call to show() as
+ // The following mock listeners have to be set before the call to show() as
// they are set on the internally constructed drop down.
if (mHasItemClickListener) {
- mListPopupWindow.setOnItemClickListener(new AdapterView.OnItemClickListener() {
- @Override
- public void onItemClick(AdapterView<?> parent, View view, int position,
- long id) {
- mListPopupClickedItem = position;
- mListPopupWindow.dismiss();
- }
- });
+ // Wrap our item click listener with a Mockito spy
+ mOnItemClickListener = spy(mItemClickListener);
+ // Register that spy as the item click listener on the ListPopupWindow
+ mListPopupWindow.setOnItemClickListener(mOnItemClickListener);
+ // And configure Mockito to call our original listener with onItemClick.
+ // This way we can have both our item click listener running to dismiss the popup
+ // window, and track the invocations of onItemClick with Mockito APIs.
+ doCallRealMethod().when(mOnItemClickListener).onItemClick(
+ any(AdapterView.class), any(View.class), any(int.class), any(int.class));
}
if (mHasDismissListener) {
- mListPopupWindow.setOnDismissListener(new PopupWindow.OnDismissListener() {
- @Override
- public void onDismiss() {
- mIsDismissedCalled = true;
- }
- });
+ mOnDismissListener = mock(PopupWindow.OnDismissListener.class);
+ mListPopupWindow.setOnDismissListener(mOnDismissListener);
}
mListPopupWindow.setModal(mIsModal);
diff --git a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
index 4218775..fa1bf8a 100644
--- a/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
+++ b/v7/appcompat/tests/src/android/support/v7/widget/PopupMenuTest.java
@@ -58,6 +58,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.*;
public class PopupMenuTest extends BaseInstrumentationTestCase<PopupTestActivity> {
// Since PopupMenu doesn't expose any access to the underlying
@@ -73,10 +74,6 @@
private PopupMenu mPopupMenu;
- private int mPopupClickedMenuItemId = -1;
-
- private boolean mIsDismissedCalled = false;
-
private Resources mResources;
private View mMainDecorView;
@@ -289,7 +286,8 @@
}
});
- assertTrue("Dismiss listener called", mIsDismissedCalled);
+ verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
+
// Unlike ListPopupWindow, PopupMenu doesn't have an API to check whether it is showing.
// Use a custom matcher to check the visibility of the drop down list view instead.
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
@@ -347,7 +345,7 @@
// At this point our popup should not be showing and should have notified its
// dismiss listener
- assertTrue("Dismiss listener called", mIsDismissedCalled);
+ verify(menuBuilder.mOnDismissListener, times(1)).onDismiss(mPopupMenu);
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
}
@@ -359,12 +357,16 @@
onView(withId(R.id.test_button)).perform(click());
- assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
onView(withText(mResources.getString(R.string.popup_menu_delete)))
.inRoot(withDecorView(not(is(mMainDecorView))))
.perform(click());
- assertEquals("Clicked item after click", R.id.action_delete, mPopupClickedMenuItemId);
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_delete));
// Popup menu should be automatically dismissed on selecting an item
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
@@ -378,7 +380,9 @@
onView(withId(R.id.test_button)).perform(click());
- assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
@@ -386,7 +390,9 @@
}
});
- assertEquals("Clicked item after click", R.id.action_highlight, mPopupClickedMenuItemId);
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_highlight));
// Popup menu should be automatically dismissed on selecting an item
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
@@ -400,12 +406,16 @@
onView(withId(R.id.test_button)).perform(click());
- assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
onView(withText(mResources.getString(R.string.popup_menu_share)))
.inRoot(withDecorView(not(is(mMainDecorView))))
.perform(click());
- assertEquals("Clicked item after click", R.id.action_share, mPopupClickedMenuItemId);
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share));
// Sleep for a bit to allow the menu -> submenu transition to complete
Thread.sleep(1000);
@@ -441,8 +451,10 @@
onView(withText(mResources.getString(R.string.popup_menu_share_circles)))
.inRoot(allOf(withDecorView(not(is(mMainDecorView))), hasWindowFocus()))
.perform(click());
- assertEquals("Clicked submenu item after click", R.id.action_share_circles,
- mPopupClickedMenuItemId);
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share_circles));
// Popup menu should be automatically dismissed on selecting an item in the submenu
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
@@ -456,7 +468,9 @@
onView(withId(R.id.test_button)).perform(click());
- assertEquals("Clicked item before click", -1, mPopupClickedMenuItemId);
+ // Verify that our menu item click listener hasn't been called yet
+ verify(menuBuilder.mOnMenuItemClickListener, never()).onMenuItemClick(any(MenuItem.class));
+
InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
@Override
public void run() {
@@ -464,7 +478,9 @@
}
});
- assertEquals("Clicked item after click", R.id.action_share, mPopupClickedMenuItemId);
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share));
// Sleep for a bit to allow the menu -> submenu transition to complete
Thread.sleep(1000);
@@ -504,8 +520,10 @@
performIdentifierAction(R.id.action_share_email, 0);
}
});
- assertEquals("Clicked submenu item after click", R.id.action_share_email,
- mPopupClickedMenuItemId);
+
+ // Verify that out menu item click listener has been called with the expected menu item
+ verify(menuBuilder.mOnMenuItemClickListener, times(1)).onMenuItemClick(
+ mPopupMenu.getMenu().findItem(R.id.action_share_email));
// Popup menu should be automatically dismissed on selecting an item in the submenu
onView(withClassName(Matchers.is(DROP_DOWN_CLASS_NAME))).check(doesNotExist());
@@ -518,12 +536,12 @@
* we can't add logic that is specific to a certain test once it's shown and we have a
* reference to a displayed PopupMenu.
*/
- private class Builder {
+ public class Builder {
private boolean mHasDismissListener;
private boolean mHasMenuItemClickListener;
- public Builder() {
- }
+ private PopupMenu.OnMenuItemClickListener mOnMenuItemClickListener;
+ private PopupMenu.OnDismissListener mOnDismissListener;
public Builder withMenuItemClickListener() {
mHasMenuItemClickListener = true;
@@ -541,25 +559,16 @@
menuInflater.inflate(R.menu.popup_menu, mPopupMenu.getMenu());
if (mHasMenuItemClickListener) {
- // Register a listener to be notified when a menu item in our popup menu has
+ // Register a mock listener to be notified when a menu item in our popup menu has
// been clicked.
- mPopupMenu.setOnMenuItemClickListener(new PopupMenu.OnMenuItemClickListener() {
- @Override
- public boolean onMenuItemClick(MenuItem item) {
- mPopupClickedMenuItemId = item.getItemId();
- return true;
- }
- });
+ mOnMenuItemClickListener = mock(PopupMenu.OnMenuItemClickListener.class);
+ mPopupMenu.setOnMenuItemClickListener(mOnMenuItemClickListener);
}
if (mHasDismissListener) {
- // Register a listener to be notified when our popup menu is dismissed.
- mPopupMenu.setOnDismissListener(new PopupMenu.OnDismissListener() {
- @Override
- public void onDismiss(PopupMenu menu) {
- mIsDismissedCalled = true;
- }
- });
+ // Register a mock listener to be notified when our popup menu is dismissed.
+ mOnDismissListener = mock(PopupMenu.OnDismissListener.class);
+ mPopupMenu.setOnDismissListener(mOnDismissListener);
}
// Show the popup menu
diff --git a/v7/preference/api/current.txt b/v7/preference/api/current.txt
index 9034fd4..26a3e12 100644
--- a/v7/preference/api/current.txt
+++ b/v7/preference/api/current.txt
@@ -278,6 +278,8 @@
}
public final class PreferenceScreen extends android.support.v7.preference.PreferenceGroup {
+ method public void setShouldUseGeneratedIds(boolean);
+ method public boolean shouldUseGeneratedIds();
}
public class PreferenceViewHolder extends android.support.v7.widget.RecyclerView.ViewHolder {
diff --git a/v7/preference/src/android/support/v7/preference/Preference.java b/v7/preference/src/android/support/v7/preference/Preference.java
index f386592..ebff2d7 100644
--- a/v7/preference/src/android/support/v7/preference/Preference.java
+++ b/v7/preference/src/android/support/v7/preference/Preference.java
@@ -91,6 +91,12 @@
*/
private long mId;
+ /**
+ * Set true temporarily to keep {@link #onAttachedToHierarchy(PreferenceManager)} from
+ * overwriting mId
+ */
+ private boolean mHasId;
+
private OnPreferenceChangeListener mOnChangeListener;
private OnPreferenceClickListener mOnClickListener;
@@ -1066,12 +1072,28 @@
protected void onAttachedToHierarchy(PreferenceManager preferenceManager) {
mPreferenceManager = preferenceManager;
- mId = preferenceManager.getNextId();
+ if (!mHasId) {
+ mId = preferenceManager.getNextId();
+ }
dispatchSetInitialValue();
}
/**
+ * Called from {@link PreferenceGroup} to pass in an ID for reuse
+ * @hide
+ */
+ protected void onAttachedToHierarchy(PreferenceManager preferenceManager, long id) {
+ mId = id;
+ mHasId = true;
+ try {
+ onAttachedToHierarchy(preferenceManager);
+ } finally {
+ mHasId = false;
+ }
+ }
+
+ /**
* Called when the Preference hierarchy has been attached to the
* list of preferences. This can also be called when this
* Preference has been attached to a group that was already attached
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
index 968b777..a1ead97 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroup.java
@@ -19,7 +19,9 @@
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Bundle;
+import android.os.Handler;
import android.support.v4.content.res.TypedArrayUtils;
+import android.support.v4.util.SimpleArrayMap;
import android.text.TextUtils;
import android.util.AttributeSet;
@@ -55,6 +57,17 @@
private boolean mAttachedToHierarchy = false;
+ private final SimpleArrayMap<String, Long> mIdRecycleCache = new SimpleArrayMap<>();
+ private final Handler mHandler = new Handler();
+ private final Runnable mClearRecycleCacheRunnable = new Runnable() {
+ @Override
+ public void run() {
+ synchronized (this) {
+ mIdRecycleCache.clear();
+ }
+ }
+ };
+
public PreferenceGroup(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
@@ -166,7 +179,16 @@
mPreferenceList.add(insertionIndex, preference);
}
- preference.onAttachedToHierarchy(getPreferenceManager());
+ final PreferenceManager preferenceManager = getPreferenceManager();
+ final String key = preference.getKey();
+ final long id;
+ if (key != null && mIdRecycleCache.containsKey(key)) {
+ id = mIdRecycleCache.get(key);
+ mIdRecycleCache.remove(key);
+ } else {
+ id = preferenceManager.getNextId();
+ }
+ preference.onAttachedToHierarchy(preferenceManager, id);
if (mAttachedToHierarchy) {
preference.onAttached();
@@ -192,7 +214,28 @@
private boolean removePreferenceInt(Preference preference) {
synchronized(this) {
preference.onPrepareForRemoval();
- return mPreferenceList.remove(preference);
+ boolean success = mPreferenceList.remove(preference);
+ if (success) {
+ // If this preference, or another preference with the same key, gets re-added
+ // immediately, we want it to have the same id so that it can be correctly tracked
+ // in the adapter by RecyclerView, to make it appear as if it has only been
+ // seamlessly updated. If the preference is not re-added by the time the handler
+ // runs, we take that as a signal that the preference will not be re-added soon
+ // in which case it does not need to retain the same id.
+
+ // If two (or more) preferences have the same (or null) key and both are removed
+ // and then re-added, only one id will be recycled and the second (and later)
+ // preferences will receive a newly generated id. This use pattern of the preference
+ // API is strongly discouraged.
+ final String key = preference.getKey();
+ if (key != null) {
+ mIdRecycleCache.put(key, preference.getId());
+ mHandler.removeCallbacks(mClearRecycleCacheRunnable);
+ mHandler.post(mClearRecycleCacheRunnable);
+ }
+ }
+
+ return success;
}
}
@@ -269,6 +312,14 @@
return true;
}
+ /**
+ * Returns true if we're between {@link #onAttached()} and {@link #onPrepareForRemoval()}
+ * @hide
+ */
+ public boolean isAttached() {
+ return mAttachedToHierarchy;
+ }
+
@Override
public void onAttached() {
super.onAttached();
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
index c4ccc66..d9f3dd6 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceGroupAdapter.java
@@ -22,7 +22,6 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.ListView;
import java.util.ArrayList;
import java.util.List;
@@ -117,7 +116,11 @@
mPreferenceListInternal = new ArrayList<>();
mPreferenceLayouts = new ArrayList<>();
- setHasStableIds(true);
+ if (mPreferenceGroup instanceof PreferenceScreen) {
+ setHasStableIds(((PreferenceScreen) mPreferenceGroup).shouldUseGeneratedIds());
+ } else {
+ setHasStableIds(true);
+ }
syncMyPreferences();
}
@@ -204,7 +207,9 @@
}
public long getItemId(int position) {
- if (position < 0 || position >= getItemCount()) return ListView.INVALID_ROW_ID;
+ if (!hasStableIds()) {
+ return RecyclerView.NO_ID;
+ }
return this.getItem(position).getId();
}
diff --git a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
index e64ebcc..4efa58e 100644
--- a/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
+++ b/v7/preference/src/android/support/v7/preference/PreferenceScreen.java
@@ -74,6 +74,8 @@
*/
public final class PreferenceScreen extends PreferenceGroup {
+ private boolean mShouldUseGeneratedIds = true;
+
/**
* Do NOT use this constructor, use {@link PreferenceManager#createPreferenceScreen(Context)}.
* @hide-
@@ -99,4 +101,34 @@
return false;
}
+ /**
+ * See {@link #setShouldUseGeneratedIds(boolean)}
+ * @return {@code true} if the adapter should use the preference IDs generated by
+ * {@link PreferenceGroup#addPreference(Preference)} as stable item IDs
+ */
+ public boolean shouldUseGeneratedIds() {
+ return mShouldUseGeneratedIds;
+ }
+
+ /**
+ * Set whether the adapter created for this screen should attempt to use the preference IDs
+ * generated by {@link PreferenceGroup#addPreference(Preference)} as stable item IDs. Setting
+ * this to false can suppress unwanted animations if {@link Preference} objects are frequently
+ * removed from and re-added to their containing {@link PreferenceGroup}.
+ * <p>
+ * This method may only be called when the preference screen is not attached to the hierarchy.
+ * <p>
+ * Default value is {@code true}.
+ *
+ * @param shouldUseGeneratedIds {@code true} if the adapter should use the preference ID as a
+ * stable ID, or {@code false} to disable the use of
+ * stable IDs
+ */
+ public void setShouldUseGeneratedIds(boolean shouldUseGeneratedIds) {
+ if (isAttached()) {
+ throw new IllegalStateException("Cannot change the usage of generated IDs while" +
+ " attached to the preference hierarchy");
+ }
+ mShouldUseGeneratedIds = shouldUseGeneratedIds;
+ }
}