diff options
7 files changed, 90 insertions, 13 deletions
diff --git a/packages/DocumentsUI/res/values/strings.xml b/packages/DocumentsUI/res/values/strings.xml index be21b55c4066..e67cc8a06da2 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 babde992a90b..c78face9730a 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 @@ import java.util.List; 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 @@ final class QuickViewIntentBuilder { 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 1ba836a7fa4a..07c3cdbcbec0 100644 --- a/packages/DocumentsUI/src/com/android/documentsui/Shared.java +++ b/packages/DocumentsUI/src/com/android/documentsui/Shared.java @@ -104,6 +104,11 @@ public final class Shared { */ 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 8c073c9cc616..297fbc784acd 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.Retention; 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 @@ public class DirectoryFragment extends Fragment 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 class DirectoryFragment extends Fragment 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 @@ public class DirectoryFragment extends Fragment } 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 35d8988244f6..8852985da5d5 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 @@ public final class MultiSelectManager { // 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 @@ public final class MultiSelectManager { * 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 @@ public final class MultiSelectManager { return true; } } + + @Override + public boolean hasView(int pos) { + return mView.findViewHolderForAdapterPosition(pos) != null; + } } public interface Callback { @@ -1473,10 +1486,14 @@ public final class MultiSelectManager { * 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 @@ public final class MultiSelectManager { 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 @@ public final class MultiSelectManager { } /** + * 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 cc119fec8267..e401de160708 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 @@ public class MultiSelectManager_GridModelTest extends AndroidTestCase { 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 56e54a61cfc0..f56476978a4e 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 class TestSelectionEnvironment implements SelectionEnvironment { public boolean isLayoutItem(int adapterPosition) { return false; } + + @Override + public boolean hasView(int adapterPosition) { + return true; + } } |