Merge "Add WAD/WND sample to :support-wear-demos" into oc-support-26.1-dev
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 45d69ef..f2dae95 100644
--- a/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
+++ b/v17/leanback/src/android/support/v17/leanback/widget/GridLayoutManager.java
@@ -1269,8 +1269,7 @@
         if (TRACE) TraceCompat.beginSection("processRowSizeSecondary");
         CircularIntArray[] rows = mGrid == null ? null : mGrid.getItemPositionsInRows();
         boolean changed = false;
-        int scrapChildWidth = -1;
-        int scrapChildHeight = -1;
+        int scrapeChildSize = -1;
 
         for (int rowIndex = 0; rowIndex < mNumRows; rowIndex++) {
             CircularIntArray row = rows == null ? null : rows[rowIndex];
@@ -1281,7 +1280,7 @@
                 final int rowIndexStart = row.get(rowItemPairIndex);
                 final int rowIndexEnd = row.get(rowItemPairIndex + 1);
                 for (int i = rowIndexStart; i <= rowIndexEnd; i++) {
-                    final View view = findViewByPosition(i);
+                    final View view = findViewByPosition(i - mPositionDeltaInPreLayout);
                     if (view == null) {
                         continue;
                     }
@@ -1299,27 +1298,49 @@
 
             final int itemCount = mState.getItemCount();
             if (!mBaseGridView.hasFixedSize() && measure && rowSize < 0 && itemCount > 0) {
-                if (scrapChildWidth < 0 && scrapChildHeight < 0) {
-                    int position;
-                    if (mFocusPosition == NO_POSITION) {
+                if (scrapeChildSize < 0) {
+                    // measure a child that is close to mFocusPosition but not currently visible
+                    int position = mFocusPosition;
+                    if (position < 0) {
                         position = 0;
-                    } else if (mFocusPosition >= itemCount) {
+                    } else if (position >= itemCount) {
                         position = itemCount - 1;
-                    } else {
-                        position = mFocusPosition;
                     }
-                    measureScrapChild(position,
-                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                            MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
-                            mMeasuredDimension);
-                    scrapChildWidth = mMeasuredDimension[0];
-                    scrapChildHeight = mMeasuredDimension[1];
-                    if (DEBUG) {
-                        Log.v(TAG, "measured scrap child: " + scrapChildWidth + " "
-                                + scrapChildHeight);
+                    if (getChildCount() > 0) {
+                        int firstPos = mBaseGridView.getChildViewHolder(
+                                getChildAt(0)).getLayoutPosition();
+                        int lastPos = mBaseGridView.getChildViewHolder(
+                                getChildAt(getChildCount() - 1)).getLayoutPosition();
+                        // if mFocusPosition is between first and last, choose either
+                        // first - 1 or last + 1
+                        if (position >= firstPos && position <= lastPos) {
+                            position = (position - firstPos <= lastPos - position)
+                                    ? (firstPos - 1) : (lastPos + 1);
+                            // try the other value if the position is invalid. if both values are
+                            // invalid, skip measureScrapChild below.
+                            if (position < 0 && lastPos < itemCount - 1) {
+                                position = lastPos + 1;
+                            } else if (position >= itemCount && firstPos > 0) {
+                                position = firstPos - 1;
+                            }
+                        }
+                    }
+                    if (position >= 0 && position < itemCount) {
+                        measureScrapChild(position,
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED),
+                                mMeasuredDimension);
+                        scrapeChildSize = mOrientation == HORIZONTAL ? mMeasuredDimension[0] :
+                                mMeasuredDimension[1];
+                        if (DEBUG) {
+                            Log.v(TAG, "measured scrap child: " + mMeasuredDimension[0] + " "
+                                    + mMeasuredDimension[1]);
+                        }
                     }
                 }
-                rowSize = mOrientation == HORIZONTAL ? scrapChildHeight : scrapChildWidth;
+                if (scrapeChildSize >= 0) {
+                    rowSize = scrapeChildSize;
+                }
             }
             if (rowSize < 0) {
                 rowSize = 0;
@@ -1404,7 +1425,10 @@
                 mRowSizeSecondary = new int[mNumRows];
             }
 
-            // Measure all current children and update cached row heights
+            if (mState.isPreLayout()) {
+                updatePositionDeltaInPreLayout();
+            }
+            // Measure all current children and update cached row height or column width
             processRowSizeSecondary(true);
 
             switch (modeSecondary) {
@@ -2057,6 +2081,22 @@
         mPositionToRowInPostLayout.clear();
     }
 
+    // in prelayout, first child's getViewPosition can be smaller than old adapter position
+    // if there were items removed before first visible index. For example:
+    // visible items are 3, 4, 5, 6, deleting 1, 2, 3 from adapter; the view position in
+    // prelayout are not 3(deleted), 4, 5, 6. Instead it's 1(deleted), 2, 3, 4.
+    // So there is a delta (2 in this case) between last cached position and prelayout position.
+    void updatePositionDeltaInPreLayout() {
+        if (getChildCount() > 0) {
+            View view = getChildAt(0);
+            LayoutParams lp = (LayoutParams) view.getLayoutParams();
+            mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
+                    - lp.getViewLayoutPosition();
+        } else {
+            mPositionDeltaInPreLayout = 0;
+        }
+    }
+
     // Lays out items based on the current scroll position
     @Override
     public void onLayoutChildren(RecyclerView.Recycler recycler, RecyclerView.State state) {
@@ -2094,6 +2134,7 @@
 
         saveContext(recycler, state);
         if (state.isPreLayout()) {
+            updatePositionDeltaInPreLayout();
             int childCount = getChildCount();
             if (mGrid != null && childCount > 0) {
                 int minChangedEdge = Integer.MAX_VALUE;
@@ -2105,12 +2146,6 @@
                 for (int i = 0; i < childCount; i++) {
                     View view = getChildAt(i);
                     LayoutParams lp = (LayoutParams) view.getLayoutParams();
-                    if (i == 0) {
-                        // first child's layout position can be smaller than index if there were
-                        // items removed before first visible index.
-                        mPositionDeltaInPreLayout = mGrid.getFirstVisibleIndex()
-                                - lp.getViewLayoutPosition();
-                    }
                     int newAdapterPosition = mBaseGridView.getChildAdapterPosition(view);
                     // if either of following happening
                     // 1. item itself has changed or layout parameter changed
diff --git a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
index 1f6bdb2..61974cc 100644
--- a/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
+++ b/v17/leanback/tests/java/android/support/v17/leanback/widget/GridWidgetTest.java
@@ -920,8 +920,7 @@
 
     void preparePredictiveLayout() throws Throwable {
         Intent intent = new Intent();
-        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
-                R.layout.horizontal_linear);
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID, R.layout.horizontal_linear);
         intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, 100);
         initActivity(intent);
         mOrientation = BaseGridView.HORIZONTAL;
@@ -1016,6 +1015,50 @@
     }
 
     @Test
+    public void testPredictiveOnMeasureWrapContent() throws Throwable {
+        Intent intent = new Intent();
+        intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
+                R.layout.horizontal_linear_wrap_content);
+        int count = 50;
+        intent.putExtra(GridActivity.EXTRA_NUM_ITEMS, count);
+        initActivity(intent);
+        mOrientation = BaseGridView.HORIZONTAL;
+        mNumRows = 1;
+
+        waitForScrollIdle(mVerifyLayout);
+        mActivityTestRule.runOnUiThread(new Runnable() {
+            @Override
+            public void run() {
+                mGridView.setHasFixedSize(false);
+            }
+        });
+
+        for (int i = 0; i < 30; i++) {
+            final int oldCount = count;
+            final int newCount = i;
+            mActivityTestRule.runOnUiThread(new Runnable() {
+                @Override
+                public void run() {
+                    if (oldCount > 0) {
+                        mActivity.removeItems(0, oldCount);
+                    }
+                    if (newCount > 0) {
+                        int[] newItems = new int[newCount];
+                        for (int i = 0; i < newCount; i++) {
+                            newItems[i] = 400;
+                        }
+                        mActivity.addItems(0, newItems);
+                    }
+                }
+            });
+            waitForItemAnimationStart();
+            waitForItemAnimation();
+            count = newCount;
+        }
+
+    }
+
+    @Test
     public void testPredictiveLayoutRemove4() throws Throwable {
         Intent intent = new Intent();
         intent.putExtra(GridActivity.EXTRA_LAYOUT_RESOURCE_ID,
diff --git a/v17/leanback/tests/res/layout/horizontal_linear_wrap_content.xml b/v17/leanback/tests/res/layout/horizontal_linear_wrap_content.xml
new file mode 100644
index 0000000..c0e2715
--- /dev/null
+++ b/v17/leanback/tests/res/layout/horizontal_linear_wrap_content.xml
@@ -0,0 +1,39 @@
+<!--
+  ~ Copyright (C) 2017 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.
+  -->
+
+<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="wrap_content"
+    android:layout_height="wrap_content"
+    >
+  <android.support.v17.leanback.widget.HorizontalGridViewEx
+      android:id="@+id/gridview"
+      android:layout_width="match_parent"
+      android:layout_height="wrap_content"
+      android:clipToPadding="false"
+      android:focusable="true"
+      android:focusableInTouchMode="true"
+      android:background="#00ffff"
+      android:horizontalSpacing="12dip"
+      android:verticalSpacing="24dip"
+      lb:rowHeight="wrap_content"
+      android:paddingBottom="12dip"
+      android:paddingLeft="12dip"
+      android:paddingRight="12dip"
+      android:paddingTop="12dip" />
+</LinearLayout>