Merge "Set version to alpha 7." into oc-support-26.0-dev
diff --git a/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java b/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java
index f8739f6..dbd4d5f 100644
--- a/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java
+++ b/app-toolkit/common/src/main/java/android/arch/core/internal/FastSafeIterableMap.java
@@ -51,6 +51,13 @@
         return null;
     }
 
+    @Override
+    public V remove(@NonNull K key) {
+        V removed = super.remove(key);
+        mHashMap.remove(key);
+        return removed;
+    }
+
     /**
      * Returns {@code true} if this map contains a mapping for the specified
      * key.
diff --git a/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java b/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java
index a55b939..41b1497 100644
--- a/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java
+++ b/app-toolkit/common/src/test/java/android/arch/core/internal/FastSafeIterableMapTest.java
@@ -20,9 +20,11 @@
 import static org.hamcrest.CoreMatchers.nullValue;
 import static org.junit.Assert.assertThat;
 
-import org.hamcrest.MatcherAssert;
 import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
 
+@RunWith(JUnit4.class)
 public class FastSafeIterableMapTest {
     @Test
     public void testCeil() {
@@ -42,8 +44,8 @@
         map.putIfAbsent(10, 20);
         map.putIfAbsent(20, 40);
         map.putIfAbsent(30, 60);
-        MatcherAssert.assertThat(map.putIfAbsent(5, 10), is((Integer) null));
-        MatcherAssert.assertThat(map.putIfAbsent(10, 30), is(20));
+        assertThat(map.putIfAbsent(5, 10), is((Integer) null));
+        assertThat(map.putIfAbsent(10, 30), is(20));
     }
 
     @Test
@@ -52,10 +54,22 @@
         map.putIfAbsent(10, 20);
         map.putIfAbsent(20, 40);
         map.putIfAbsent(30, 60);
-        MatcherAssert.assertThat(map.contains(10), is(true));
-        MatcherAssert.assertThat(map.contains(11), is(false));
+        assertThat(map.contains(10), is(true));
+        assertThat(map.contains(11), is(false));
+        assertThat(new FastSafeIterableMap<Integer, Integer>().contains(0), is(false));
+    }
 
-        MatcherAssert.assertThat(new FastSafeIterableMap<Integer, Integer>().contains(0),
-                is(false));
+
+    @Test
+    public void testRemove() {
+        FastSafeIterableMap<Integer, Integer> map = new FastSafeIterableMap<>();
+        map.putIfAbsent(10, 20);
+        map.putIfAbsent(20, 40);
+        assertThat(map.contains(10), is(true));
+        assertThat(map.contains(20), is(true));
+        assertThat(map.remove(10), is(20));
+        assertThat(map.contains(10), is(false));
+        assertThat(map.putIfAbsent(10, 30), nullValue());
+        assertThat(map.putIfAbsent(10, 40), is(30));
     }
 }
diff --git a/buildSrc/dependencies.gradle b/buildSrc/dependencies.gradle
index 53f0672..8a7d69a 100644
--- a/buildSrc/dependencies.gradle
+++ b/buildSrc/dependencies.gradle
@@ -19,7 +19,6 @@
 // Testing dependencies
 libs.mockito_core = 'org.mockito:mockito-core:2.7.6'
 libs.dexmaker_mockito = 'com.linkedin.dexmaker:dexmaker-mockito:2.2.0'
-libs.multidex = 'com.android.support:multidex:1.0.1'
 libs.junit = 'junit:junit:4.12'
 libs.test_runner = 'com.android.support.test:runner:0.6-alpha'
 libs.espresso_core = 'com.android.support.test.espresso:espresso-core:2.3-alpha'
diff --git a/design/build.gradle b/design/build.gradle
index 0bc6809..23cbeb7 100644
--- a/design/build.gradle
+++ b/design/build.gradle
@@ -7,19 +7,12 @@
     api project(':support-recyclerview-v7')
     api project(':support-transition')
 
-    androidTestImplementation (libs.test_runner) {
-        exclude module: 'support-annotations'
-    }
-    androidTestImplementation (libs.espresso_core) {
-        exclude module: 'support-annotations'
-    }
-    androidTestImplementation (libs.espresso_contrib) {
-        exclude group: 'com.android.support'
-    }
-    androidTestImplementation libs.mockito_core
-    androidTestImplementation libs.dexmaker_mockito
+    androidTestImplementation libs.test_runner,      { exclude module: 'support-annotations' }
+    androidTestImplementation libs.espresso_core,    { exclude module: 'support-annotations' }
+    androidTestImplementation libs.espresso_contrib, { exclude group: 'com.android.support' }
+    androidTestImplementation libs.mockito_core,     { exclude group: 'net.bytebuddy' } // DexMaker has it"s own MockMaker
+    androidTestImplementation libs.dexmaker_mockito, { exclude group: 'net.bytebuddy' } // DexMaker has it"s own MockMaker
     androidTestImplementation project(':support-testutils')
-    androidTestImplementation libs.multidex
 
     testImplementation libs.junit
     testImplementation (libs.test_runner) {
@@ -32,7 +25,6 @@
         minSdkVersion 14
         // This disables the builds tools automatic vector -> PNG generation
         generatedDensities = []
-        multiDexEnabled true
     }
 
     sourceSets {
diff --git a/design/tests/AndroidManifest.xml b/design/tests/AndroidManifest.xml
index d5dbf91..c4673b3 100755
--- a/design/tests/AndroidManifest.xml
+++ b/design/tests/AndroidManifest.xml
@@ -19,7 +19,6 @@
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application
-        android:name="android.support.multidex.MultiDexApplication"
         android:supportsRtl="true"
         android:theme="@style/Theme.Design">
 
diff --git a/fragment/java/android/support/v4/app/Fragment.java b/fragment/java/android/support/v4/app/Fragment.java
index 1df0f37..f8e3d47 100644
--- a/fragment/java/android/support/v4/app/Fragment.java
+++ b/fragment/java/android/support/v4/app/Fragment.java
@@ -340,6 +340,11 @@
     // getLayoutInflater()
     LayoutInflater mLayoutInflater;
 
+    // Keep track of whether or not this Fragment has run performCreate(). Retained instance
+    // fragments can have mRetaining set to true without going through creation, so we must
+    // track it separately.
+    boolean mIsCreated;
+
     /**
      * State information that has been retrieved from a fragment instance
      * through {@link FragmentManager#saveFragmentInstanceState(Fragment)
@@ -2322,6 +2327,7 @@
         mState = CREATED;
         mCalled = false;
         onCreate(savedInstanceState);
+        mIsCreated = true;
         if (!mCalled) {
             throw new SuperNotCalledException("Fragment " + this
                     + " did not call through to super.onCreate()");
@@ -2583,6 +2589,7 @@
         }
         mState = INITIALIZING;
         mCalled = false;
+        mIsCreated = false;
         onDestroy();
         if (!mCalled) {
             throw new SuperNotCalledException("Fragment " + this
diff --git a/fragment/java/android/support/v4/app/FragmentManager.java b/fragment/java/android/support/v4/app/FragmentManager.java
index 460d474..288fe57 100644
--- a/fragment/java/android/support/v4/app/FragmentManager.java
+++ b/fragment/java/android/support/v4/app/FragmentManager.java
@@ -1372,7 +1372,7 @@
                         }
                         dispatchOnFragmentAttached(f, mHost.getContext(), false);
 
-                        if (!f.mRetaining) {
+                        if (!f.mIsCreated) {
                             dispatchOnFragmentPreCreated(f, f.mSavedFragmentState, false);
                             f.performCreate(f.mSavedFragmentState);
                             dispatchOnFragmentCreated(f, f.mSavedFragmentState, false);
diff --git a/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java b/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
index a312b9b..09f071e 100644
--- a/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
+++ b/fragment/tests/java/android/support/v4/app/FragmentLifecycleTest.java
@@ -1107,6 +1107,86 @@
         }
     }
 
+    /**
+     * When a retained instance fragment is saved while in the back stack, it should go
+     * through onCreate() when it is popped back.
+     */
+    @Test
+    @UiThreadTest
+    public void retainInstanceWithOnCreate() throws Throwable {
+        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, null);
+        FragmentManager fm = fc.getSupportFragmentManager();
+
+        OnCreateFragment fragment1 = new OnCreateFragment();
+
+        fm.beginTransaction()
+                .add(fragment1, "1")
+                .commit();
+        fm.beginTransaction()
+                .remove(fragment1)
+                .addToBackStack(null)
+                .commit();
+
+        Pair<Parcelable, FragmentManagerNonConfig> savedState =
+                FragmentTestUtil.destroy(mActivityRule, fc);
+        Pair<Parcelable, FragmentManagerNonConfig> restartState =
+                Pair.create(savedState.first, null);
+
+        fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, restartState);
+
+        // Save again, but keep the state
+        savedState = FragmentTestUtil.destroy(mActivityRule, fc);
+
+        fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, savedState);
+
+        fm = fc.getSupportFragmentManager();
+
+        fm.popBackStackImmediate();
+        OnCreateFragment fragment2 = (OnCreateFragment) fm.findFragmentByTag("1");
+        assertTrue(fragment2.onCreateCalled);
+        fm.popBackStackImmediate();
+    }
+
+    /**
+     * A retained instance fragment should go through onCreate() once, even through save and
+     * restore.
+     */
+    @Test
+    @UiThreadTest
+    public void retainInstanceOneOnCreate() throws Throwable {
+        FragmentController fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, null);
+        FragmentManager fm = fc.getSupportFragmentManager();
+
+        OnCreateFragment fragment = new OnCreateFragment();
+
+        fm.beginTransaction()
+                .add(fragment, "fragment")
+                .commit();
+        fm.executePendingTransactions();
+
+        fm.beginTransaction()
+                .remove(fragment)
+                .addToBackStack(null)
+                .commit();
+
+        assertTrue(fragment.onCreateCalled);
+        fragment.onCreateCalled = false;
+
+        Pair<Parcelable, FragmentManagerNonConfig> savedState =
+                FragmentTestUtil.destroy(mActivityRule, fc);
+
+        fc = FragmentTestUtil.createController(mActivityRule);
+        FragmentTestUtil.resume(mActivityRule, fc, savedState);
+        fm = fc.getSupportFragmentManager();
+
+        fm.popBackStackImmediate();
+        assertFalse(fragment.onCreateCalled);
+    }
+
     private void assertAnimationsMatch(FragmentManager fm, int enter, int exit, int popEnter,
             int popExit) {
         FragmentManagerImpl fmImpl = (FragmentManagerImpl) fm;
@@ -1440,4 +1520,18 @@
             }
         }
     }
+
+    public static class OnCreateFragment extends Fragment {
+        public boolean onCreateCalled;
+
+        public OnCreateFragment() {
+            setRetainInstance(true);
+        }
+
+        @Override
+        public void onCreate(Bundle savedInstanceState) {
+            super.onCreate(savedInstanceState);
+            onCreateCalled = true;
+        }
+    }
 }
diff --git a/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java b/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
index b3cf753..ef498ee 100644
--- a/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
+++ b/lifecycle/runtime/src/test/java/android/arch/lifecycle/LifecycleRegistryTest.java
@@ -559,6 +559,16 @@
         inOrder.verify(observer4).onResume();
     }
 
+    @Test
+    public void sameObserverReAddition() {
+        TestObserver observer = mock(TestObserver.class);
+        mRegistry.addObserver(observer);
+        mRegistry.removeObserver(observer);
+        mRegistry.addObserver(observer);
+        dispatchEvent(ON_CREATE);
+        verify(observer).onCreate();
+    }
+
     private void dispatchEvent(Lifecycle.Event event) {
         when(mLifecycle.getCurrentState()).thenReturn(LifecycleRegistry.getStateAfter(event));
         mRegistry.handleLifecycleEvent(event);
diff --git a/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java b/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
index 0023a2e..907e624 100644
--- a/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
+++ b/room/runtime/src/main/java/android/arch/persistence/room/migration/Migration.java
@@ -52,6 +52,9 @@
      * Should run the necessary migrations.
      * <p>
      * This class cannot access any generated Dao in this method.
+     * <p>
+     * This method is already called inside a transaction and that transaction might actually be a
+     * composite transaction of all necessary {@code Migration}s.
      *
      * @param database The database instance
      */
diff --git a/v17/leanback/build.gradle b/v17/leanback/build.gradle
index 4fecc60..ea11627 100644
--- a/v17/leanback/build.gradle
+++ b/v17/leanback/build.gradle
@@ -8,21 +8,15 @@
     api project(':support-fragment')
     api project(':support-recyclerview-v7')
 
-    androidTestImplementation (libs.test_runner) {
-        exclude module: 'support-annotations'
-    }
-    androidTestImplementation (libs.espresso_core) {
-        exclude module: 'support-annotations'
-    }
-    androidTestImplementation libs.mockito_core
-    androidTestImplementation libs.dexmaker_mockito
-    androidTestImplementation libs.multidex
+    androidTestImplementation libs.test_runner,      { exclude module: 'support-annotations' }
+    androidTestImplementation libs.espresso_core,    { exclude module: 'support-annotations' }
+    androidTestImplementation libs.mockito_core,     { exclude group: 'net.bytebuddy' } // DexMaker has it"s own MockMaker
+    androidTestImplementation libs.dexmaker_mockito, { exclude group: 'net.bytebuddy' } // DexMaker has it"s own MockMaker
 }
 
 android {
     defaultConfig {
         minSdkVersion 17
-        multiDexEnabled true
     }
 
     sourceSets {
diff --git a/v17/leanback/res/layout/lb_playback_transport_controls_row.xml b/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
index c8852e8..8b692f3 100644
--- a/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
+++ b/v17/leanback/res/layout/lb_playback_transport_controls_row.xml
@@ -18,7 +18,7 @@
 <!-- Note: clipChildren/clipToPadding false are needed to apply shadows to child
      views with no padding of their own. Also to allow for negative margin on description. -->
 
-<android.support.v17.leanback.widget.PlaybackTransportRowView
+<LinearLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
@@ -68,9 +68,10 @@
             android:layout_marginBottom="@dimen/lb_playback_transport_thumbs_bottom_margin" />
     </FrameLayout>
 
-    <LinearLayout
+    <android.support.v17.leanback.widget.PlaybackTransportRowView
         android:layout_width="match_parent"
         android:layout_height="match_parent"
+        android:id="@+id/transport_row"
         android:orientation="vertical"
         android:paddingStart="?attr/browsePaddingStart"
         android:paddingEnd="?attr/browsePaddingEnd"
@@ -134,5 +135,5 @@
         </RelativeLayout>
 
 
-    </LinearLayout>
-</android.support.v17.leanback.widget.PlaybackTransportRowView>
+    </android.support.v17.leanback.widget.PlaybackTransportRowView>
+</LinearLayout>
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
index e6db33b..4505944 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/PlaybackTransportRowPresenter.java
@@ -666,8 +666,8 @@
         vh.mSecondaryControlsVh = (ControlBarPresenter.ViewHolder) mSecondaryControlsPresenter
                 .onCreateViewHolder(vh.mSecondaryControlsDock);
         vh.mSecondaryControlsDock.addView(vh.mSecondaryControlsVh.view);
-        ((PlaybackTransportRowView) vh.view).setOnUnhandledKeyListener(
-                new PlaybackTransportRowView.OnUnhandledKeyListener() {
+        ((PlaybackTransportRowView) vh.view.findViewById(R.id.transport_row))
+                .setOnUnhandledKeyListener(new PlaybackTransportRowView.OnUnhandledKeyListener() {
                 @Override
                 public boolean onUnhandledKey(KeyEvent event) {
                     if (vh.getOnKeyListener() != null) {
diff --git a/v17/leanback/tests/AndroidManifest.xml b/v17/leanback/tests/AndroidManifest.xml
index d719485..b21246e 100644
--- a/v17/leanback/tests/AndroidManifest.xml
+++ b/v17/leanback/tests/AndroidManifest.xml
@@ -18,7 +18,6 @@
     <uses-sdk android:targetSdkVersion="${target-sdk-version}"/>
 
     <application
-        android:name="android.support.multidex.MultiDexApplication"
         android:supportsRtl="true">
         <activity android:name="android.support.v17.leanback.widget.GridActivity"
                   android:exported="true"/>
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
index d6a1f86..2cee649 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackGlueHostImplWithViewHolder.java
@@ -56,12 +56,13 @@
         if (mViewHolder == null && mPlaybackRowPresenter != null && mRow != null) {
             mViewHolder = (PlaybackRowPresenter.ViewHolder)
                     mPlaybackRowPresenter.onCreateViewHolder(mRootView = new FrameLayout(mContext));
+            // Bind ViewHolder before measure/layout so child views will get proper size
+            mPlaybackRowPresenter.onBindViewHolder(mViewHolder, mRow);
             mRootView.addView(mViewHolder.view, mLayoutWidth, mLayoutHeight);
             mRootView.measure(
                     View.MeasureSpec.makeMeasureSpec(1920, View.MeasureSpec.AT_MOST),
                     View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
             mRootView.layout(0, 0, mRootView.getMeasuredWidth(), mRootView.getMeasuredHeight());
-            mPlaybackRowPresenter.onBindViewHolder(mViewHolder, mRow);
             if (mViewHolder instanceof PlaybackSeekUi) {
                 ((PlaybackSeekUi) mViewHolder).setPlaybackSeekUiClient(mChainedClient);
             }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
index 54d27a3..db55725 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/PlaybackTransportRowPresenterTest.java
@@ -37,6 +37,8 @@
 import android.support.v17.leanback.widget.PlaybackSeekDataProvider.ResultCallback;
 import android.view.ContextThemeWrapper;
 import android.view.KeyEvent;
+import android.view.View;
+import android.view.ViewParent;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -65,7 +67,24 @@
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                mGlue = new PlaybackTransportControlGlue(mContext, mImpl);
+                mGlue = new PlaybackTransportControlGlue(mContext, mImpl) {
+                    @Override
+                    protected void onCreatePrimaryActions(ArrayObjectAdapter
+                            primaryActionsAdapter) {
+                        super.onCreatePrimaryActions(primaryActionsAdapter);
+                        primaryActionsAdapter.add(
+                                new PlaybackControlsRow.ClosedCaptioningAction(mContext));
+                    }
+
+                    @Override
+                    protected void onCreateSecondaryActions(ArrayObjectAdapter
+                            secondaryActionsAdapter) {
+                        secondaryActionsAdapter.add(
+                                new PlaybackControlsRow.HighQualityAction(mContext));
+                        secondaryActionsAdapter.add(
+                                new PlaybackControlsRow.PictureInPictureAction(mContext));
+                    }
+                };
                 mGlue.setHost(mHost);
 
             }
@@ -195,6 +214,107 @@
         assertSame(art, mViewHolder.mImageView.getDrawable());
     }
 
+    static boolean isDescendant(View view, View descendant) {
+        while (descendant != view) {
+            ViewParent p = descendant.getParent();
+            if (!(p instanceof View)) {
+                return false;
+            }
+            descendant = (View) p;
+        }
+        return true;
+    }
+
+    @Test
+    public void navigateRightInPrimary() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mControlsVh.mControlBar.getChildAt(0).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
+        assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(1),
+                view.focusSearch(View.FOCUS_RIGHT)));
+    }
+
+    @Test
+    public void navigateRightInSecondary() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
+        assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(1),
+                view.focusSearch(View.FOCUS_RIGHT)));
+    }
+
+    @Test
+    public void navigatePrimaryDownToProgress() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mControlsVh.mControlBar.getChildAt(0).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
+        assertSame(mViewHolder.mProgressBar, view.focusSearch(View.FOCUS_DOWN));
+    }
+
+    @Test
+    public void navigateProgressUpToPrimary() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mProgressBar.requestFocus();
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mProgressBar.focusSearch(View.FOCUS_UP).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mControlsVh.mControlBar.getChildAt(0), view));
+    }
+
+    @Test
+    public void navigateProgressDownToSecondary() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mProgressBar.requestFocus();
+            }
+        });
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mProgressBar.focusSearch(View.FOCUS_DOWN).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
+    }
+
+    @Test
+    public void navigateSecondaryUpToProgress() {
+        InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
+            @Override
+            public void run() {
+                mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0).requestFocus();
+            }
+        });
+        View view = mViewHolder.view.findFocus();
+        assertTrue(isDescendant(mViewHolder.mSecondaryControlsVh.mControlBar.getChildAt(0), view));
+        assertSame(mViewHolder.mProgressBar, view.focusSearch(View.FOCUS_UP));
+    }
+
     @Test
     public void seekAndConfirm() {
         when(mImpl.isPrepared()).thenReturn(true);