Merge changes I5e500724,I40cfb12c into nyc-dev

* changes:
  Restrict selection to 1000 items in DocumentsUI.
  Cancel band selection on directory change.
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml
index be21b55..e67cc8a 100644
--- a/packages/DocumentsUI/res/values/strings.xml
+++ b/packages/DocumentsUI/res/values/strings.xml
@@ -247,4 +247,9 @@
         <item quantity="one">Delete <xliff:g id="count" example="1">%1$d</xliff:g> item?</item>
         <item quantity="other">Delete <xliff:g id="count" example="3">%1$d</xliff:g> items?</item>
     </plurals>
+    <!-- Snackbar shown to users who wanted to select more than 1000 items (files or directories). -->
+    <string name="too_many_selected">Sorry, you can only select up to 1000 items at a time</string>
+    <!-- Snackbar shown to users who wanted to select all, but there were too many items (files or directories).
+         Only the first 1000 items are selected in such case. -->
+    <string name="too_many_in_select_all">Could only select 1000 items</string>
 </resources>
diff --git a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
index babde99..c78face 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -17,6 +17,7 @@
 package com.android.documentsui;
 
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.Shared.MAX_DOCS_IN_INTENT;
 import static com.android.documentsui.model.DocumentInfo.getCursorString;
 
 import android.content.ClipData;
@@ -46,7 +47,6 @@
 final class QuickViewIntentBuilder {
 
     private static final String TAG = "QuickViewIntentBuilder";
-    private static final int MAX_CLIP_ITEMS = 1000;
 
     private final DocumentInfo mDocument;
     private final Model mModel;
@@ -165,11 +165,11 @@
         int firstSibling;
         int lastSibling;
         if (documentLocation < uris.size() / 2) {
-            firstSibling = Math.max(0, documentLocation - MAX_CLIP_ITEMS / 2);
-            lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_CLIP_ITEMS - 1);
+            firstSibling = Math.max(0, documentLocation - MAX_DOCS_IN_INTENT / 2);
+            lastSibling = Math.min(uris.size() - 1, firstSibling + MAX_DOCS_IN_INTENT - 1);
         } else {
-            lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_CLIP_ITEMS / 2);
-            firstSibling = Math.max(0, lastSibling - MAX_CLIP_ITEMS + 1);
+            lastSibling = Math.min(uris.size() - 1, documentLocation + MAX_DOCS_IN_INTENT / 2);
+            firstSibling = Math.max(0, lastSibling - MAX_DOCS_IN_INTENT + 1);
         }
 
         if (DEBUG) Log.d(TAG, "Copmuted siblings from index: " + firstSibling
diff --git a/packages/DocumentsUI/src/com/android/documentsui/Shared.java b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
index 1ba836a..07c3cdbc 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java
@@ -104,6 +104,11 @@
      */
     public static final String EXTRA_BENCHMARK = "com.android.documentsui.benchmark";
 
+    /**
+     * Maximum number of items in a Binder transaction packet.
+     */
+    public static final int MAX_DOCS_IN_INTENT = 1000;
+
     private static final Collator sCollator;
 
     static {
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 8c073c9c..297fbc7 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -17,6 +17,7 @@
 package com.android.documentsui.dirlist;
 
 import static com.android.documentsui.Shared.DEBUG;
+import static com.android.documentsui.Shared.MAX_DOCS_IN_INTENT;
 import static com.android.documentsui.State.MODE_GRID;
 import static com.android.documentsui.State.MODE_LIST;
 import static com.android.documentsui.State.SORT_ORDER_UNKNOWN;
@@ -108,9 +109,11 @@
 import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * Display the documents inside a single directory.
@@ -475,8 +478,18 @@
 
                 final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                 final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+                if (!mTuner.canSelectType(docMimeType, docFlags)) {
+                    return false;
+                }
 
-                return mTuner.canSelectType(docMimeType, docFlags);
+                if (mSelected.size() >= MAX_DOCS_IN_INTENT) {
+                    Snackbars.makeSnackbar(
+                            getActivity(),
+                            R.string.too_many_selected,
+                            Snackbar.LENGTH_SHORT)
+                            .show();
+                    return false;
+                }
             }
             return true;
         }
@@ -1108,9 +1121,17 @@
     public void selectAllFiles() {
         Metrics.logUserAction(getContext(), Metrics.USER_ACTION_SELECT_ALL);
 
-        // Exclude disabled files
-        List<String> enabled = new ArrayList<String>();
-        for (String id : mAdapter.getModelIds()) {
+        // Exclude disabled files.
+        Set<String> enabled = new HashSet<String>();
+        List<String> modelIds = mAdapter.getModelIds();
+
+        // Get the current selection.
+        String[] alreadySelected = mSelectionManager.getSelection().getAll();
+        for (String id : alreadySelected) {
+           enabled.add(id);
+        }
+
+        for (String id : modelIds) {
             Cursor cursor = getModel().getItem(id);
             if (cursor == null) {
                 Log.w(TAG, "Skipping selection. Can't obtain cursor for modeId: " + id);
@@ -1118,7 +1139,15 @@
             }
             String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
             int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
-            if (isDocumentEnabled(docMimeType, docFlags)) {
+            if (mTuner.canSelectType(docMimeType, docFlags)) {
+                if (enabled.size() >= MAX_DOCS_IN_INTENT) {
+                    Snackbars.makeSnackbar(
+                        getActivity(),
+                        R.string.too_many_in_select_all,
+                        Snackbar.LENGTH_SHORT)
+                        .show();
+                    break;
+                }
                 enabled.add(id);
             }
         }
diff --git a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 35d8988..8852985 100644
--- a/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/packages/DocumentsUI/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -154,6 +154,10 @@
                         // Update the selection to remove any disappeared IDs.
                         mSelection.cancelProvisionalSelection();
                         mSelection.intersect(mModelIds);
+
+                        if (mBandManager != null && mBandManager.isActive()) {
+                            mBandManager.endBandSelect();
+                        }
                     }
 
                     @Override
@@ -940,6 +944,10 @@
          * Layout items are excluded from the GridModel.
          */
         boolean isLayoutItem(int adapterPosition);
+        /**
+         * Items may be in the adapter, but without an attached view.
+         */
+        boolean hasView(int adapterPosition);
     }
 
     /** Recycler view facade implementation backed by good ol' RecyclerView. */
@@ -1061,6 +1069,11 @@
                     return true;
             }
         }
+
+        @Override
+        public boolean hasView(int pos) {
+            return mView.findViewHolderForAdapterPosition(pos) != null;
+        }
     }
 
     public interface Callback {
@@ -1473,10 +1486,14 @@
          *     y-value.
          */
         void startSelection(Point relativeOrigin) {
+            recordVisibleChildren();
+            if (isEmpty()) {
+                // The selection band logic works only if there is at least one visible child.
+                return;
+            }
+
             mIsActive = true;
             mPointer = mHelper.createAbsolutePoint(relativeOrigin);
-
-            recordVisibleChildren();
             mRelativeOrigin = new RelativePoint(mPointer);
             mRelativePointer = new RelativePoint(mPointer);
             computeCurrentSelection();
@@ -1530,7 +1547,11 @@
         private void recordVisibleChildren() {
             for (int i = 0; i < mHelper.getVisibleChildCount(); i++) {
                 int adapterPosition = mHelper.getAdapterPositionAt(i);
-                if (!mHelper.isLayoutItem(adapterPosition) &&
+                // Sometimes the view is not attached, as we notify the multi selection manager
+                // synchronously, while views are attached asynchronously. As a result items which
+                // are in the adapter may not actually have a corresponding view (yet).
+                if (mHelper.hasView(adapterPosition) &&
+                        !mHelper.isLayoutItem(adapterPosition) &&
                         !mKnownPositions.get(adapterPosition)) {
                     mKnownPositions.put(adapterPosition, true);
                     recordItemData(mHelper.getAbsoluteRectForChildViewAt(i), adapterPosition);
@@ -1539,6 +1560,13 @@
         }
 
         /**
+         * Checks if there are any recorded children.
+         */
+        private boolean isEmpty() {
+            return mColumnBounds.size() == 0 || mRowBounds.size() == 0;
+        }
+
+        /**
          * Updates the limits lists and column map with the given item metadata.
          * @param absoluteChildRect The absolute rectangle for the child view being processed.
          * @param adapterPosition The position of the child view being processed.
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
index cc119fe..e401de1 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/MultiSelectManager_GridModelTest.java
@@ -448,6 +448,11 @@
             return false;
         }
 
+        @Override
+        public boolean hasView(int adapterPosition) {
+            return true;
+        }
+
         public static final class Item {
             public String name;
             public Rect rect;
diff --git a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
index 56e54a6..f564769 100644
--- a/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
+++ b/packages/DocumentsUI/tests/src/com/android/documentsui/dirlist/TestSelectionEnvironment.java
@@ -100,4 +100,9 @@
     public boolean isLayoutItem(int adapterPosition) {
         return false;
     }
+
+    @Override
+    public boolean hasView(int adapterPosition) {
+        return true;
+    }
 }