Merge "Clarify dangers of LLM#getExtraLayoutSpace" into nyc-support-25.1-dev
diff --git a/PREUPLOAD.cfg b/PREUPLOAD.cfg
index 4caef90..2811ea9 100644
--- a/PREUPLOAD.cfg
+++ b/PREUPLOAD.cfg
@@ -1,5 +1,5 @@
 [Hook Scripts]
-checkstyle_hook = ../../development/tools/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
+checkstyle_hook = ${REPO_ROOT}/prebuilts/checkstyle/checkstyle.py --sha ${PREUPLOAD_COMMIT}
 
 [Builtin Hooks]
 commit_msg_changeid_field = true
diff --git a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
index 78b0c50..d835985 100644
--- a/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
+++ b/media-compat/java/android/support/v4/media/session/MediaSessionCompat.java
@@ -2302,7 +2302,9 @@
         public void setCallback(Callback callback, Handler handler) {
             MediaSessionCompatApi21.setCallback(mSessionObj,
                     callback == null ? null : callback.mCallbackObj, handler);
-            callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+            if (callback != null) {
+                callback.mSessionImpl = new WeakReference<MediaSessionImpl>(this);
+            }
         }
 
         @Override
diff --git a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
index ca6ee77..7d2904a 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GuidedActionAdapter.java
@@ -151,6 +151,9 @@
      * @param actions The list of actions to be managed.
      */
     public void setActions(List<GuidedAction> actions) {
+        if (!mIsSubAdapter) {
+            mStylist.collapseAction(false);
+        }
         mActionOnFocusListener.unFocus();
         mActions.clear();
         mActions.addAll(actions);
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
index 192b20e..50aaa62 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepFragmentTest.java
@@ -29,6 +29,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.testutils.PollingCheck;
 import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -354,4 +355,58 @@
         PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
         verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
     }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestFragment.Provider obj = (GuidedStepTestFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
 }
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
index 879292f..7d6b54f 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/app/GuidedStepSupportFragmentTest.java
@@ -32,6 +32,7 @@
 import android.support.test.runner.AndroidJUnit4;
 import android.support.v17.leanback.testutils.PollingCheck;
 import android.support.v17.leanback.widget.GuidedAction;
+import android.support.v17.leanback.widget.GuidedActionsStylist;
 import android.view.KeyEvent;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -357,4 +358,58 @@
         PollingCheck.waitFor(new PollingCheck.ActivityDestroy(activity));
         verify(first, timeout(ON_DESTROY_TIMEOUT).times(1)).onDestroy();
     }
+
+    @Test
+    public void setActionsWhenSubActionsExpanded() throws Throwable {
+        final String firstFragmentName = generateMethodTestName("first");
+        GuidedStepTestSupportFragment.Provider first = mockProvider(firstFragmentName);
+        doAnswer(new Answer<Void>() {
+            public Void answer(InvocationOnMock invocation) {
+                List actions = (List) invocation.getArguments()[0];
+                List<GuidedAction> subActions = new ArrayList<GuidedAction>();
+                subActions.add(new GuidedAction.Builder().id(2000).title("item1").build());
+                actions.add(new GuidedAction.Builder().id(1000).subActions(subActions)
+                        .title("list").build());
+                return null;
+            }
+        }).when(first).onCreateActions(any(List.class), any(Bundle.class));
+        doAnswer(new Answer<Boolean>() {
+            public Boolean answer(InvocationOnMock invocation) {
+                GuidedStepTestSupportFragment.Provider obj = (GuidedStepTestSupportFragment.Provider)
+                        invocation.getMock();
+                GuidedAction action = (GuidedAction) invocation.getArguments()[0];
+                if (action.getId() == 2000) {
+                    List<GuidedAction> newActions = new ArrayList<GuidedAction>();
+                    newActions.add(new GuidedAction.Builder().id(1001).title("item2").build());
+                    obj.getFragment().setActions(newActions);
+                    return false;
+                }
+                return false;
+            }
+        }).when(first).onSubGuidedActionClicked(any(GuidedAction.class));
+
+        final GuidedStepSupportFragmentTestActivity activity = launchTestActivity(firstFragmentName);
+
+        // after clicked, it sub actions list should expand
+        View firstView = first.getFragment().getActionItemView(0);
+        assertTrue(firstView.hasFocus());
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(firstView.hasFocus());
+
+        sendKey(KeyEvent.KEYCODE_DPAD_CENTER);
+        ArgumentCaptor<GuidedAction> actionCapture = ArgumentCaptor.forClass(GuidedAction.class);
+        verify(first, times(1)).onSubGuidedActionClicked(actionCapture.capture());
+        // after clicked a sub action, whole action list is replaced.
+        PollingCheck.waitFor(new ExpandTransitionFinish(first));
+        assertFalse(first.getFragment().isExpanded());
+        View newFirstView  = first.getFragment().getActionItemView(0);
+        assertTrue(newFirstView.hasFocus());
+        assertTrue(newFirstView.getVisibility() == View.VISIBLE);
+        GuidedActionsStylist.ViewHolder vh = (GuidedActionsStylist.ViewHolder) first.getFragment()
+                .getGuidedActionsStylist().getActionsGridView().getChildViewHolder(newFirstView);
+        assertEquals(1001, vh.getAction().getId());
+
+    }
+
 }
diff --git a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
index 85071a4..7ade21d 100644
--- a/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
+++ b/v7/mediarouter/jellybean/android/support/v7/media/MediaRouterJellybean.java
@@ -121,9 +121,9 @@
             Field globalRouterField = routerObj.getClass().getDeclaredField("sStatic");
             globalRouterField.setAccessible(true);
             Object globalRouterObj = globalRouterField.get(null);
-            Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn", null);
+            Method method = globalRouterObj.getClass().getDeclaredMethod("isBluetoothA2dpOn");
             method.setAccessible(true);
-            Object result = method.invoke(globalRouterObj, null);
+            Object result = method.invoke(globalRouterObj);
             return (Boolean) result;
         } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException
                 | NoSuchMethodException | InvocationTargetException e) {
diff --git a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
index adbe4dd..19f31f8 100644
--- a/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
+++ b/v7/recyclerview/src/android/support/v7/widget/StaggeredGridLayoutManager.java
@@ -2097,15 +2097,23 @@
         if (mPrefetchDistances == null || mPrefetchDistances.length < mSpanCount) {
             mPrefetchDistances = new int[mSpanCount];
         }
+
+        int itemPrefetchCount = 0;
         for (int i = 0; i < mSpanCount; i++) {
-            mPrefetchDistances[i] = mLayoutState.mItemDirection == LAYOUT_START
+            // compute number of pixels past the edge of the viewport that the current span extends
+            int distance = mLayoutState.mItemDirection == LAYOUT_START
                     ? mLayoutState.mStartLine - mSpans[i].getStartLine(mLayoutState.mStartLine)
                     : mSpans[i].getEndLine(mLayoutState.mEndLine) - mLayoutState.mEndLine;
+            if (distance >= 0) {
+                // span extends to the edge, so prefetch next item
+                mPrefetchDistances[itemPrefetchCount] = distance;
+                itemPrefetchCount++;
+            }
         }
-        Arrays.sort(mPrefetchDistances, 0, mSpanCount);
+        Arrays.sort(mPrefetchDistances, 0, itemPrefetchCount);
 
         // then assign them in order to the next N views (where N = span count)
-        for (int i = 0; i < mSpanCount && mLayoutState.hasMore(state); i++) {
+        for (int i = 0; i < itemPrefetchCount && mLayoutState.hasMore(state); i++) {
             layoutPrefetchRegistry.addPosition(mLayoutState.mCurrentPosition, mPrefetchDistances[i]);
             mLayoutState.mCurrentPosition += mLayoutState.mItemDirection;
         }
diff --git a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
index cc39a87..7ab616f 100644
--- a/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
+++ b/v7/recyclerview/tests/src/android/support/v7/widget/RecyclerViewCacheTest.java
@@ -515,6 +515,50 @@
     }
 
     @Test
+    public void prefetchStaggeredPastBoundary() {
+        StaggeredGridLayoutManager sglm =
+                new StaggeredGridLayoutManager(2, StaggeredGridLayoutManager.VERTICAL);
+        mRecyclerView.setLayoutManager(sglm);
+        mRecyclerView.setAdapter(new RecyclerView.Adapter() {
+            @Override
+            public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
+                return new RecyclerView.ViewHolder(new View(getContext())) {};
+            }
+
+            @Override
+            public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
+                holder.itemView.setMinimumWidth(100);
+                holder.itemView.setMinimumHeight(position == 0 ? 100 : 200);
+            }
+
+            @Override
+            public int getItemCount() {
+                return 2;
+            }
+        });
+
+        layout(200, 100);
+        mRecyclerView.scrollBy(0, 50);
+
+        /* Each row is 50 pixels:
+         * ------------- *
+         *___0___|___1___*
+         *   0   |   1   *
+         *_______|___1___*
+         *       |   1   *
+         */
+        assertEquals(2, mRecyclerView.getChildCount());
+        assertEquals(0, sglm.getFirstChildPosition());
+        assertEquals(1, sglm.getLastChildPosition());
+
+        // prefetch upward gets nothing
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, -10);
+
+        // prefetch downward gets nothing (and doesn't crash...)
+        CacheUtils.verifyPositionsPrefetched(mRecyclerView, 0, 10);
+    }
+
+    @Test
     public void prefetchItemsSkipAnimations() {
         LinearLayoutManager llm = new LinearLayoutManager(getContext());
         mRecyclerView.setLayoutManager(llm);