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;
+    }
 }