Merge "Refactor SelectionModeListener." into nyc-andromeda-dev
diff --git a/app-perf-tests/src/com/android/documentsui/LauncherActivity.java b/app-perf-tests/src/com/android/documentsui/LauncherActivity.java
index 21fc52e..21ea8aa 100644
--- a/app-perf-tests/src/com/android/documentsui/LauncherActivity.java
+++ b/app-perf-tests/src/com/android/documentsui/LauncherActivity.java
@@ -20,10 +20,8 @@
import android.app.Activity;
import android.content.Intent;
-import android.content.pm.PackageManager;
import android.os.Bundle;
import android.os.Handler;
-import android.util.Log;
import java.util.concurrent.CountDownLatch;
diff --git a/src/com/android/documentsui/Events.java b/src/com/android/documentsui/Events.java
index c0e3d7d..0de4039 100644
--- a/src/com/android/documentsui/Events.java
+++ b/src/com/android/documentsui/Events.java
@@ -336,9 +336,4 @@
.toString();
}
}
-
- @FunctionalInterface
- public interface EventHandler {
- boolean apply(InputEvent event);
- }
}
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 8b2880d..19d4482 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -16,8 +16,6 @@
package com.android.documentsui;
-import android.annotation.Nullable;
-import android.support.v7.widget.RecyclerView;
import android.view.Menu;
import android.view.MenuItem;
diff --git a/src/com/android/documentsui/OpenExternalDirectoryActivity.java b/src/com/android/documentsui/OpenExternalDirectoryActivity.java
index 6588ee1..245f902 100644
--- a/src/com/android/documentsui/OpenExternalDirectoryActivity.java
+++ b/src/com/android/documentsui/OpenExternalDirectoryActivity.java
@@ -17,12 +17,10 @@
package com.android.documentsui;
import static android.os.Environment.isStandardDirectory;
-import static android.os.Environment.STANDARD_DIRECTORIES;
import static android.os.storage.StorageVolume.EXTRA_DIRECTORY_NAME;
import static android.os.storage.StorageVolume.EXTRA_STORAGE_VOLUME;
import static com.android.documentsui.LocalPreferences.getScopedAccessPermissionStatus;
-import static com.android.documentsui.LocalPreferences.PERMISSION_ASK;
import static com.android.documentsui.LocalPreferences.PERMISSION_ASK_AGAIN;
import static com.android.documentsui.LocalPreferences.PERMISSION_NEVER_ASK;
import static com.android.documentsui.LocalPreferences.setScopedAccessPermissionStatus;
diff --git a/src/com/android/documentsui/QuickViewIntentBuilder.java b/src/com/android/documentsui/QuickViewIntentBuilder.java
index 5e3bbbb..b8d2247 100644
--- a/src/com/android/documentsui/QuickViewIntentBuilder.java
+++ b/src/com/android/documentsui/QuickViewIntentBuilder.java
@@ -22,7 +22,6 @@
import android.content.ClipData;
import android.content.ClipDescription;
-import android.content.ComponentName;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.res.Resources;
diff --git a/src/com/android/documentsui/base/FunctionalInterfaces.java b/src/com/android/documentsui/base/FunctionalInterfaces.java
new file mode 100644
index 0000000..936883a
--- /dev/null
+++ b/src/com/android/documentsui/base/FunctionalInterfaces.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.documentsui.base;
+
+/**
+ * A container class that contains common functional interfaces used in DocumentsUI.
+ *
+ * This class should never be instantiated.
+ */
+public class FunctionalInterfaces {
+
+ private FunctionalInterfaces() {}
+
+ /**
+ * A functional interface that handles an event and returns a boolean to indicate if the event
+ * is consumed.
+ */
+ @FunctionalInterface
+ public interface EventHandler<T> {
+ boolean apply(T event);
+ }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/dirlist/ActionModeController.java b/src/com/android/documentsui/dirlist/ActionModeController.java
new file mode 100644
index 0000000..8038768
--- /dev/null
+++ b/src/com/android/documentsui/dirlist/ActionModeController.java
@@ -0,0 +1,237 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.Shared.DEBUG;
+
+import android.annotation.IdRes;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.content.Context;
+import android.text.TextUtils;
+import android.util.Log;
+import android.view.ActionMode;
+import android.view.HapticFeedbackConstants;
+import android.view.Menu;
+import android.view.MenuItem;
+import android.view.View;
+
+import com.android.documentsui.MenuManager;
+import com.android.documentsui.Menus;
+import com.android.documentsui.R;
+import com.android.documentsui.base.FunctionalInterfaces.EventHandler;
+import com.android.documentsui.Shared;
+import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.IntConsumer;
+
+/**
+ * A controller that listens to selection changes and manages life cycles of action modes.
+ */
+class ActionModeController implements MultiSelectManager.Callback, ActionMode.Callback {
+
+ private static final String TAG = "ActionModeController";
+
+ private final Context mContext;
+ private final MultiSelectManager mSelectionMgr;
+ private final MenuManager mMenuManager;
+ private final MenuManager.SelectionDetails mSelectionDetails;
+
+ private final Function<ActionMode.Callback, ActionMode> mActionModeFactory;
+ private final EventHandler<MenuItem> mMenuItemClicker;
+ private final IntConsumer mHapticPerformer;
+ private final Consumer<CharSequence> mAccessibilityAnnouncer;
+ private final AccessibilityImportanceSetter mAccessibilityImportanceSetter;
+
+ private final Selection mSelected = new Selection();
+
+ private @Nullable ActionMode mActionMode;
+ private @Nullable Menu mMenu;
+
+ private ActionModeController(
+ Context context,
+ MultiSelectManager selectionMgr,
+ MenuManager menuManager,
+ MenuManager.SelectionDetails selectionDetails,
+ Function<ActionMode.Callback, ActionMode> actionModeFactory,
+ EventHandler<MenuItem> menuItemClicker,
+ IntConsumer hapticPerformer,
+ Consumer<CharSequence> accessibilityAnnouncer,
+ AccessibilityImportanceSetter accessibilityImportanceSetter) {
+ mContext = context;
+ mSelectionMgr = selectionMgr;
+ mMenuManager = menuManager;
+ mSelectionDetails = selectionDetails;
+
+ mActionModeFactory = actionModeFactory;
+ mMenuItemClicker = menuItemClicker;
+ mHapticPerformer = hapticPerformer;
+ mAccessibilityAnnouncer = accessibilityAnnouncer;
+ mAccessibilityImportanceSetter = accessibilityImportanceSetter;
+ }
+
+ @Override
+ public void onSelectionChanged() {
+ mSelectionMgr.getSelection(mSelected);
+ if (mSelected.size() > 0) {
+ if (mActionMode == null) {
+ if (DEBUG) Log.d(TAG, "Starting action mode.");
+ mActionMode = mActionModeFactory.apply(this);
+ mHapticPerformer.accept(HapticFeedbackConstants.LONG_PRESS);
+ }
+ updateActionMenu();
+ } else {
+ if (mActionMode != null) {
+ if (DEBUG) Log.d(TAG, "Finishing action mode.");
+ mActionMode.finish();
+ }
+ }
+
+ if (mActionMode != null) {
+ assert(!mSelected.isEmpty());
+ final String title = Shared.getQuantityString(mContext,
+ R.plurals.elements_selected, mSelected.size());
+ mActionMode.setTitle(title);
+ mAccessibilityAnnouncer.accept(title);
+ }
+ }
+
+ @Override
+ public void onSelectionRestored() {
+ mSelectionMgr.getSelection(mSelected);
+ if (mSelected.size() > 0) {
+ if (mActionMode == null) {
+ if (DEBUG) Log.d(TAG, "Starting action mode.");
+ mActionMode = mActionModeFactory.apply(this);
+ }
+ updateActionMenu();
+ } else {
+ if (mActionMode != null) {
+ if (DEBUG) Log.d(TAG, "Finishing action mode.");
+ mActionMode.finish();
+ }
+ }
+
+ if (mActionMode != null) {
+ assert(!mSelected.isEmpty());
+ final String title = Shared.getQuantityString(mContext,
+ R.plurals.elements_selected, mSelected.size());
+ mActionMode.setTitle(title);
+ mAccessibilityAnnouncer.accept(title);
+ }
+ }
+
+ void finishActionMode() {
+ if (mActionMode != null) {
+ mActionMode.finish();
+ mActionMode = null;
+ } else {
+ Log.w(TAG, "Tried to finish a null action mode.");
+ }
+ }
+
+ // Called when the user exits the action mode
+ @Override
+ public void onDestroyActionMode(ActionMode mode) {
+ if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
+ mActionMode = null;
+ // clear selection
+ mSelectionMgr.clearSelection();
+ mSelected.clear();
+
+ // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
+ mAccessibilityImportanceSetter.setAccessibilityImportance(
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, R.id.toolbar, R.id.roots_toolbar);
+ }
+
+ @Override
+ public boolean onCreateActionMode(ActionMode mode, Menu menu) {
+ int size = mSelectionMgr.getSelection().size();
+ mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
+ mode.setTitle(TextUtils.formatSelectedCount(size));
+
+ if (size > 0) {
+
+ // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
+ // these controls when using linear navigation.
+ mAccessibilityImportanceSetter.setAccessibilityImportance(
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS,
+ R.id.toolbar,
+ R.id.roots_toolbar);
+ return true;
+ }
+
+ return false;
+ }
+
+ @Override
+ public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
+ mMenu = menu;
+ updateActionMenu();
+ return true;
+ }
+
+ private void updateActionMenu() {
+ assert(mMenu != null);
+ mMenuManager.updateActionMenu(mMenu, mSelectionDetails);
+ Menus.disableHiddenItems(mMenu);
+ }
+
+ @Override
+ public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
+ return mMenuItemClicker.apply(item);
+ }
+
+ static ActionModeController create(
+ Context context,
+ MultiSelectManager selectionMgr,
+ MenuManager menuManager,
+ MenuManager.SelectionDetails selectionDetails,
+ Activity activity,
+ View view,
+ EventHandler<MenuItem> menuItemClicker) {
+ return new ActionModeController(
+ context,
+ selectionMgr,
+ menuManager,
+ selectionDetails,
+ activity::startActionMode,
+ menuItemClicker,
+ view::performHapticFeedback,
+ view::announceForAccessibility,
+ (int accessibilityImportance, @IdRes int[] viewIds) -> {
+ setImportantForAccessibility(activity, accessibilityImportance, viewIds);
+ });
+ }
+
+ private static void setImportantForAccessibility(
+ Activity activity, int accessibilityImportance, @IdRes int[] viewIds) {
+ for (final int id : viewIds) {
+ final View v = activity.findViewById(id);
+ if (v != null) {
+ v.setImportantForAccessibility(accessibilityImportance);
+ }
+ }
+ }
+
+ @FunctionalInterface
+ private interface AccessibilityImportanceSetter {
+ void setAccessibilityImportance(int accessibilityImportance, @IdRes int... viewIds);
+ }
+}
diff --git a/src/com/android/documentsui/dirlist/BandController.java b/src/com/android/documentsui/dirlist/BandController.java
index 1257303..9190aae 100644
--- a/src/com/android/documentsui/dirlist/BandController.java
+++ b/src/com/android/documentsui/dirlist/BandController.java
@@ -33,12 +33,9 @@
import android.util.SparseArray;
import android.util.SparseBooleanArray;
import android.util.SparseIntArray;
-import android.view.MotionEvent;
import android.view.View;
-import com.android.documentsui.Events;
import com.android.documentsui.Events.InputEvent;
-import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.R;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
@@ -328,7 +325,7 @@
}
private boolean onBeforeItemStateChange(String id, boolean nextState) {
- return mSelectionManager.notifyBeforeItemStateChange(id, nextState);
+ return mSelectionManager.canSetState(id, nextState);
}
@Override
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 30ccb28..fe07958 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -50,13 +50,10 @@
import android.support.v7.widget.RecyclerView.RecyclerListener;
import android.support.v7.widget.RecyclerView.ViewHolder;
import android.text.BidiFormatter;
-import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;
-import android.view.ActionMode;
import android.view.ContextMenu;
import android.view.DragEvent;
-import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
@@ -66,7 +63,6 @@
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
-import android.widget.Toolbar;
import com.android.documentsui.BaseActivity;
import com.android.documentsui.DirectoryLoader;
@@ -77,10 +73,8 @@
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.ItemDragListener;
import com.android.documentsui.MenuManager;
-import com.android.documentsui.Menus;
import com.android.documentsui.MessageBar;
import com.android.documentsui.Metrics;
-import com.android.documentsui.MimePredicate;
import com.android.documentsui.R;
import com.android.documentsui.RecentsLoader;
import com.android.documentsui.RetainedState;
@@ -108,7 +102,6 @@
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;
-import java.util.Objects;
import javax.annotation.Nullable;
@@ -143,8 +136,9 @@
private final Model mModel = new Model();
private final Model.UpdateListener mModelUpdateListener = new ModelUpdateListener();
- private final SelectionModeListener mSelectionModeListener = new SelectionModeListener();
private MultiSelectManager mSelectionMgr;
+ private ActionModeController mActionModeController;
+ private SelectionMetadata mSelectionMetadata;
private UserInputHandler<InputEvent> mInputHandler;
private FocusManager mFocusManager;
@@ -183,7 +177,6 @@
private boolean mSearchMode = false;
private @Nullable BandController mBandController;
- private @Nullable ActionMode mActionMode;
private DragHoverListener mDragHoverListener;
private MenuManager mMenuManager;
@@ -301,7 +294,10 @@
mAdapter,
state.allowMultiple
? MultiSelectManager.MODE_MULTIPLE
- : MultiSelectManager.MODE_SINGLE);
+ : MultiSelectManager.MODE_SINGLE,
+ this::canSetSelectionState);
+ mSelectionMetadata = new SelectionMetadata(mSelectionMgr, mModel::getItem);
+ mSelectionMgr.addItemCallback(mSelectionMetadata);
mModel.addUpdateListener(mAdapter);
mModel.addUpdateListener(mModelUpdateListener);
@@ -355,7 +351,19 @@
mInputHandler,
mBandController);
- mSelectionMgr.addCallback(mSelectionModeListener);
+ final BaseActivity activity = getBaseActivity();
+ mTuner = activity.createFragmentTuner();
+ mMenuManager = activity.getMenuManager();
+
+ mActionModeController = ActionModeController.create(
+ getContext(),
+ mSelectionMgr,
+ mMenuManager,
+ mSelectionMetadata,
+ getActivity(),
+ mRecView,
+ this::handleMenuItemClick);
+ mSelectionMgr.addCallback(mActionModeController);
final ActivityManager am = (ActivityManager) context.getSystemService(
Context.ACTIVITY_SERVICE);
@@ -440,7 +448,9 @@
boolean mouseOverFile = !(v == mRecView || v == mEmptyView);
if (mouseOverFile) {
mMenuManager.updateContextMenuForFile(
- menu, mSelectionModeListener, getBaseActivity().getDirectoryDetails());
+ menu,
+ mSelectionMetadata,
+ getBaseActivity().getDirectoryDetails());
} else {
mMenuManager.updateContextMenuForContainer(
menu, getBaseActivity().getDirectoryDetails());
@@ -593,220 +603,19 @@
return (BaseActivity) getActivity();
}
- /**
- * Manages the integration between our ActionMode and MultiSelectManager, initiating
- * ActionMode when there is a selection, canceling it when there is no selection,
- * and clearing selection when action mode is explicitly exited by the user.
- */
- private final class SelectionModeListener implements MultiSelectManager.Callback,
- ActionMode.Callback, MenuManager.SelectionDetails {
-
- private Selection mSelected = new Selection();
-
- // Partial files are files that haven't been fully downloaded.
- private int mPartialCount = 0;
- private int mDirectoryCount = 0;
- private int mWritableDirectoryCount = 0;
- private int mNoDeleteCount = 0;
- private int mNoRenameCount = 0;
-
- private Menu mMenu;
-
- @Override
- public boolean onBeforeItemStateChange(String modelId, boolean selected) {
- if (selected) {
- final Cursor cursor = mModel.getItem(modelId);
- if (cursor == null) {
- Log.w(TAG, "Can't obtain cursor for modelId: " + modelId);
- return false;
- }
-
- 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);
- }
- return true;
- }
-
- @Override
- public void onItemStateChanged(String modelId, boolean selected) {
- final Cursor cursor = mModel.getItem(modelId);
- if (cursor == null) {
- Log.w(TAG, "Model returned null cursor for document: " + modelId
- + ". Ignoring state changed event.");
- return;
- }
-
- // TODO: Should this be happening in onSelectionChanged? Technically this callback is
- // triggered on "silent" selection updates (i.e. we might be reacting to unfinalized
- // selection changes here)
- final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- if (MimePredicate.isDirectoryType(mimeType)) {
- mDirectoryCount += selected ? 1 : -1;
- }
-
- final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- if ((docFlags & Document.FLAG_PARTIAL) != 0) {
- mPartialCount += selected ? 1 : -1;
- }
- if ((docFlags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
- mWritableDirectoryCount += selected ? 1 : -1;
- }
- if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
- mNoDeleteCount += selected ? 1 : -1;
- }
- if ((docFlags & Document.FLAG_SUPPORTS_RENAME) == 0) {
- mNoRenameCount += selected ? 1 : -1;
- }
- }
-
- @Override
- public void onSelectionChanged() {
- mSelectionMgr.getSelection(mSelected);
- if (mSelected.size() > 0) {
- if (mActionMode == null) {
- if (DEBUG) Log.d(TAG, "Starting action mode.");
- mActionMode = getActivity().startActionMode(this);
- }
- updateActionMenu();
- } else {
- if (mActionMode != null) {
- if (DEBUG) Log.d(TAG, "Finishing action mode.");
- mActionMode.finish();
- }
- }
-
- if (mActionMode != null) {
- assert(!mSelected.isEmpty());
- final String title = Shared.getQuantityString(getActivity(),
- R.plurals.elements_selected, mSelected.size());
- mActionMode.setTitle(title);
- mRecView.announceForAccessibility(title);
- }
- }
-
- // Called when the user exits the action mode
- @Override
- public void onDestroyActionMode(ActionMode mode) {
- if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
- mActionMode = null;
- // clear selection
- mSelectionMgr.clearSelection();
- mSelected.clear();
-
- mDirectoryCount = 0;
- mPartialCount = 0;
- mNoDeleteCount = 0;
- mNoRenameCount = 0;
-
- // Re-enable TalkBack for the toolbars, as they are no longer covered by action mode.
- final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
- toolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
-
- // This toolbar is not present in the fixed_layout
- final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(R.id.roots_toolbar);
- if (rootsToolbar != null) {
- rootsToolbar.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO);
- }
- }
-
- @Override
- public boolean onCreateActionMode(ActionMode mode, Menu menu) {
- if (mRestoredSelection != null) {
- // This is a careful little song and dance to avoid haptic feedback
- // when selection has been restored after rotation. We're
- // also responsible for cleaning up restored selection so the
- // object dones't unnecessarily hang around.
- mRestoredSelection = null;
- } else {
- mRecView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS);
- }
-
- int size = mSelectionMgr.getSelection().size();
- mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
- mode.setTitle(TextUtils.formatSelectedCount(size));
-
- if (size > 0) {
- // Hide the toolbars if action mode is enabled, so TalkBack doesn't navigate to
- // these controls when using linear navigation.
- final Toolbar toolbar = (Toolbar) getActivity().findViewById(R.id.toolbar);
- toolbar.setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
-
- // This toolbar is not present in the fixed_layout
- final Toolbar rootsToolbar = (Toolbar) getActivity().findViewById(
- R.id.roots_toolbar);
- if (rootsToolbar != null) {
- rootsToolbar.setImportantForAccessibility(
- View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS);
- }
- return true;
- }
-
- return false;
- }
-
- @Override
- public boolean onPrepareActionMode(ActionMode mode, Menu menu) {
- mMenu = menu;
- updateActionMenu();
- return true;
- }
-
- @Override
- public boolean containsDirectories() {
- return mDirectoryCount > 0;
- }
-
- @Override
- public boolean containsPartialFiles() {
- return mPartialCount > 0;
- }
-
- @Override
- public boolean canDelete() {
- return mNoDeleteCount == 0;
- }
-
- @Override
- public boolean canRename() {
- return mNoRenameCount == 0 && mSelectionMgr.getSelection().size() == 1;
- }
-
- @Override
- public boolean canPasteInto() {
- return mDirectoryCount == 1 && mWritableDirectoryCount == 1
- && mSelectionMgr.getSelection().size() == 1;
- }
-
- private void updateActionMenu() {
- assert(mMenu != null);
- mMenuManager.updateActionMenu(mMenu, this);
- Menus.disableHiddenItems(mMenu);
- }
-
- @Override
- public boolean onActionItemClicked(ActionMode mode, MenuItem item) {
- return handleMenuItemClick(item);
- }
- }
-
private boolean handleMenuItemClick(MenuItem item) {
Selection selection = mSelectionMgr.getSelection(new Selection());
switch (item.getItemId()) {
case R.id.menu_open:
openDocuments(selection);
- mActionMode.finish();
+ mActionModeController.finishActionMode();
return true;
case R.id.menu_share:
shareDocuments(selection);
// TODO: Only finish selection if share action is completed.
- mActionMode.finish();
+ mActionModeController.finishActionMode();
return true;
case R.id.menu_delete:
@@ -819,12 +628,12 @@
transferDocuments(selection, FileOperationService.OPERATION_COPY);
// TODO: Only finish selection mode if copy-to is not canceled.
// Need to plum down into handling the way we do with deleteDocuments.
- mActionMode.finish();
+ mActionModeController.finishActionMode();
return true;
case R.id.menu_move_to:
// Exit selection mode first, so we avoid deselecting deleted documents.
- mActionMode.finish();
+ mActionModeController.finishActionMode();
transferDocuments(selection, FileOperationService.OPERATION_MOVE);
return true;
@@ -851,7 +660,7 @@
case R.id.menu_rename:
// Exit selection mode first, so we avoid deselecting deleted
// (renamed) documents.
- mActionMode.finish();
+ mActionModeController.finishActionMode();
renameDocuments(selection);
return true;
@@ -1013,11 +822,7 @@
// This is done here, rather in the onActionItemClicked
// so we can avoid de-selecting items in the case where
// the user cancels the delete.
- if (mActionMode != null) {
- mActionMode.finish();
- } else {
- Log.w(TAG, "Action mode is null before deleting documents.");
- }
+ mActionModeController.finishActionMode();
UrisSupplier srcs;
try {
@@ -1312,9 +1117,7 @@
// When files are selected for dragging, ActionMode is started. This obscures the breadcrumb
// with an ActionBar. In order to make drag and drop to the breadcrumb possible, we first
// end ActionMode so the breadcrumb is visible to the user.
- if (mActionMode != null) {
- mActionMode.finish();
- }
+ mActionModeController.finishActionMode();
}
void dragStopped(boolean result) {
@@ -1475,22 +1278,25 @@
}
private boolean canSelect(DocumentDetails doc) {
- return canSelect(doc.getModelId());
+ return canSetSelectionState(doc.getModelId(), true);
}
- private boolean canSelect(String modelId) {
+ private boolean canSetSelectionState(String modelId, boolean nextState) {
+ if (nextState) {
+ // Check if an item can be selected
+ final Cursor cursor = mModel.getItem(modelId);
+ if (cursor == null) {
+ Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
+ return false;
+ }
- // TODO: Combine this method with onBeforeItemStateChange, as both of them are almost
- // the same, and responsible for the same thing (whether to select or not).
- final Cursor cursor = mModel.getItem(modelId);
- if (cursor == null) {
- Log.w(TAG, "Couldn't obtain cursor for modelId: " + modelId);
- return false;
+ final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+ return mTuner.canSelectType(docMimeType, docFlags);
+ } else {
+ // Right now all selected items can be deselected.
+ return true;
}
-
- final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
- final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
- return mTuner.canSelectType(docMimeType, docFlags);
}
public static void showDirectory(
diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index 4b35447..cb5afd7 100644
--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -35,7 +35,7 @@
* dummy layout objects was error prone when interspersed with the core mode / adapter code.
*
* @see ModelBackedDocumentsAdapter
- * @see SectionBreakDocumentsAdapter
+ * @see SectionBreakDocumentsAdapterWrapper
*/
abstract class DocumentsAdapter
extends RecyclerView.Adapter<DocumentHolder>
diff --git a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
index 4cf8455..514c030 100644
--- a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
+++ b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -26,9 +26,9 @@
import android.view.View.OnTouchListener;
import com.android.documentsui.Events;
-import com.android.documentsui.Events.EventHandler;
import com.android.documentsui.Events.InputEvent;
import com.android.documentsui.Events.MotionInputEvent;
+import com.android.documentsui.base.FunctionalInterfaces.EventHandler;
//Receives event meant for both directory and empty view, and either pass them to
//{@link UserInputHandler} for simple gestures (Single Tap, Long-Press), or intercept them for
@@ -37,7 +37,7 @@
implements OnItemTouchListener, OnTouchListener {
private final GestureSelector mGestureSelector;
- private final EventHandler mMouseDragListener;
+ private final EventHandler<InputEvent> mMouseDragListener;
private final BandController mBandController;
private final MouseDelegate mMouseDelegate = new MouseDelegate();
private final TouchDelegate mTouchDelegate = new TouchDelegate();
@@ -46,7 +46,7 @@
Context context,
RecyclerView recView,
View emptyView,
- EventHandler mouseDragListener,
+ EventHandler<InputEvent> mouseDragListener,
GestureSelector gestureSelector,
UserInputHandler<? extends InputEvent> handler,
@Nullable BandController bandController) {
diff --git a/src/com/android/documentsui/dirlist/MultiSelectManager.java b/src/com/android/documentsui/dirlist/MultiSelectManager.java
index 0a2cd42..780c564 100644
--- a/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ b/src/com/android/documentsui/dirlist/MultiSelectManager.java
@@ -68,17 +68,25 @@
private final Selection mSelection = new Selection();
private final DocumentsAdapter mAdapter;
- private final List<MultiSelectManager.Callback> mCallbacks = new ArrayList<>(1);
+ private final List<Callback> mCallbacks = new ArrayList<>(1);
+ private final List<ItemCallback> mItemCallbacks = new ArrayList<>(1);
private @Nullable Range mRanger;
private boolean mSingleSelect;
- public MultiSelectManager(DocumentsAdapter adapter, @SelectionMode int mode) {
+ private final SelectionPredicate mCanSetState;
+
+ public MultiSelectManager(
+ DocumentsAdapter adapter,
+ @SelectionMode int mode,
+ SelectionPredicate canSetState) {
assert(adapter != null);
mAdapter = adapter;
+ mCanSetState = canSetState;
+
mSingleSelect = mode == MODE_SINGLE;
mAdapter.registerAdapterDataObserver(
new RecyclerView.AdapterDataObserver() {
@@ -133,10 +141,16 @@
*
* @param callback
*/
- public void addCallback(MultiSelectManager.Callback callback) {
+ public void addCallback(Callback callback) {
+ assert(callback != null);
mCallbacks.add(callback);
}
+ public void addItemCallback(ItemCallback itemCallback) {
+ assert(itemCallback != null);
+ mItemCallbacks.add(itemCallback);
+ }
+
public boolean hasSelection() {
return !mSelection.isEmpty();
}
@@ -172,12 +186,13 @@
}
/**
- * Returns an unordered array of selected positions, including any
- * provisional selection currently in effect.
+ * Restores the selected state of specified items. Used in cases such as restore the selection
+ * after rotation etc.
*/
public void restoreSelection(Selection other) {
- setItemsSelected(other.mSelection, true);
+ setItemsSelectedQuietly(other.mSelection, true);
// NOTE: We intentionally don't restore provisional selection. It's provisional.
+ notifySelectionRestored();
}
/**
@@ -189,15 +204,20 @@
* @return
*/
public boolean setItemsSelected(Iterable<String> ids, boolean selected) {
+ final boolean changed = setItemsSelectedQuietly(ids, selected);
+ notifySelectionChanged();
+ return changed;
+ }
+
+ private boolean setItemsSelectedQuietly(Iterable<String> ids, boolean selected) {
boolean changed = false;
for (String id: ids) {
- boolean itemChanged = selected ? mSelection.add(id) : mSelection.remove(id);
+ final boolean itemChanged = selected ? mSelection.add(id) : mSelection.remove(id);
if (itemChanged) {
notifyItemStateChanged(id, selected);
}
changed |= itemChanged;
}
- notifySelectionChanged();
return changed;
}
@@ -239,12 +259,10 @@
public void toggleSelection(String modelId) {
assert(modelId != null);
- boolean changed = false;
- if (mSelection.contains(modelId)) {
- changed = attemptDeselect(modelId);
- } else {
- changed = attemptSelect(modelId);
- }
+ final boolean changed =
+ mSelection.contains(modelId)
+ ? attemptDeselect(modelId)
+ : attemptSelect(modelId);
if (changed) {
notifySelectionChanged();
@@ -345,7 +363,7 @@
*/
private boolean attemptDeselect(String id) {
assert(id != null);
- if (notifyBeforeItemStateChange(id, false)) {
+ if (canSetState(id, false)) {
mSelection.remove(id);
notifyItemStateChanged(id, false);
if (DEBUG) Log.d(TAG, "Selection after deselect: " + mSelection);
@@ -362,7 +380,7 @@
*/
private boolean attemptSelect(String id) {
assert(id != null);
- boolean canSelect = notifyBeforeItemStateChange(id, true);
+ boolean canSelect = canSetState(id, true);
if (!canSelect) {
return false;
}
@@ -374,14 +392,8 @@
return true;
}
- boolean notifyBeforeItemStateChange(String id, boolean nextState) {
- int lastListener = mCallbacks.size() - 1;
- for (int i = lastListener; i > -1; i--) {
- if (!mCallbacks.get(i).onBeforeItemStateChange(id, nextState)) {
- return false;
- }
- }
- return true;
+ boolean canSetState(String id, boolean nextState) {
+ return mCanSetState.test(id, nextState);
}
/**
@@ -390,9 +402,9 @@
*/
void notifyItemStateChanged(String id, boolean selected) {
assert(id != null);
- int lastListener = mCallbacks.size() - 1;
- for (int i = lastListener; i > -1; i--) {
- mCallbacks.get(i).onItemStateChanged(id, selected);
+ int lastListener = mItemCallbacks.size() - 1;
+ for (int i = lastListener; i >= 0; i--) {
+ mItemCallbacks.get(i).onItemStateChanged(id, selected);
}
mAdapter.onItemSelectionChanged(id);
}
@@ -410,6 +422,13 @@
}
}
+ private void notifySelectionRestored() {
+ int lastListener = mCallbacks.size() - 1;
+ for (int i = lastListener; i > -1; i--) {
+ mCallbacks.get(i).onSelectionRestored();
+ }
+ }
+
private void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
switch (type) {
case RANGE_REGULAR:
@@ -432,7 +451,7 @@
}
if (selected) {
- boolean canSelect = notifyBeforeItemStateChange(id, true);
+ boolean canSelect = canSetState(id, true);
if (canSelect) {
if (mSingleSelect && hasSelection()) {
clearSelectionQuietly();
@@ -453,7 +472,10 @@
continue;
}
if (selected) {
- mSelection.mProvisionalSelection.add(id);
+ boolean canSelect = canSetState(id, true);
+ if (canSelect) {
+ mSelection.mProvisionalSelection.add(id);
+ }
} else {
mSelection.mProvisionalSelection.remove(id);
}
@@ -834,29 +856,24 @@
};
}
+ public interface ItemCallback {
+ void onItemStateChanged(String id, boolean selected);
+ }
+
public interface Callback {
/**
- * Called when an item is selected or unselected while in selection mode.
- *
- * @param position Adapter position of the item that was checked or unchecked
- * @param selected <code>true</code> if the item is now selected, <code>false</code>
- * if the item is now unselected.
- */
- public void onItemStateChanged(String id, boolean selected);
-
- /**
- * Called prior to an item changing state. Callbacks can cancel
- * the change at {@code position} by returning {@code false}.
- *
- * @param id Adapter position of the item that was checked or unchecked
- * @param selected <code>true</code> if the item is to be selected, <code>false</code>
- * if the item is to be unselected.
- */
- public boolean onBeforeItemStateChange(String id, boolean selected);
-
- /**
* Called immediately after completion of any set of changes.
*/
- public void onSelectionChanged();
+ void onSelectionChanged();
+
+ /**
+ * Called immediately after selection is restored.
+ */
+ void onSelectionRestored();
+ }
+
+ @FunctionalInterface
+ public interface SelectionPredicate {
+ boolean test(String id, boolean nextState);
}
}
diff --git a/src/com/android/documentsui/dirlist/SelectionMetadata.java b/src/com/android/documentsui/dirlist/SelectionMetadata.java
new file mode 100644
index 0000000..6f91749
--- /dev/null
+++ b/src/com/android/documentsui/dirlist/SelectionMetadata.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.documentsui.dirlist;
+
+import static com.android.documentsui.model.DocumentInfo.getCursorInt;
+import static com.android.documentsui.model.DocumentInfo.getCursorString;
+
+import android.database.Cursor;
+import android.provider.DocumentsContract.Document;
+import android.util.Log;
+
+import com.android.documentsui.MenuManager;
+import com.android.documentsui.MimePredicate;
+
+import java.util.function.Function;
+
+/**
+ * A class that holds metadata
+ */
+class SelectionMetadata implements MenuManager.SelectionDetails, MultiSelectManager.ItemCallback {
+
+ private static final String TAG = "SelectionMetadata";
+
+ private final MultiSelectManager mSelectionMgr;
+ private final Function<String, Cursor> mDocFinder;
+
+ // Partial files are files that haven't been fully downloaded.
+ private int mPartialCount = 0;
+ private int mDirectoryCount = 0;
+ private int mWritableDirectoryCount = 0;
+ private int mNoDeleteCount = 0;
+ private int mNoRenameCount = 0;
+
+ SelectionMetadata(
+ MultiSelectManager selectionMgr, Function<String, Cursor> docFinder) {
+ mSelectionMgr = selectionMgr;
+ mDocFinder = docFinder;
+ }
+
+ @Override
+ public void onItemStateChanged(String modelId, boolean selected) {
+ final Cursor cursor = mDocFinder.apply(modelId);
+ if (cursor == null) {
+ Log.w(TAG, "Model returned null cursor for document: " + modelId
+ + ". Ignoring state changed event.");
+ return;
+ }
+
+ final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
+ if (MimePredicate.isDirectoryType(mimeType)) {
+ mDirectoryCount += selected ? 1 : -1;
+ }
+
+ final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
+ if ((docFlags & Document.FLAG_PARTIAL) != 0) {
+ mPartialCount += selected ? 1 : -1;
+ }
+ if ((docFlags & Document.FLAG_DIR_SUPPORTS_CREATE) != 0) {
+ mWritableDirectoryCount += selected ? 1 : -1;
+ }
+ if ((docFlags & Document.FLAG_SUPPORTS_DELETE) == 0) {
+ mNoDeleteCount += selected ? 1 : -1;
+ }
+ if ((docFlags & Document.FLAG_SUPPORTS_RENAME) == 0) {
+ mNoRenameCount += selected ? 1 : -1;
+ }
+ }
+
+ @Override
+ public boolean containsDirectories() {
+ return mDirectoryCount > 0;
+ }
+
+ @Override
+ public boolean containsPartialFiles() {
+ return mPartialCount > 0;
+ }
+
+ @Override
+ public boolean canDelete() {
+ return mNoDeleteCount == 0;
+ }
+
+ @Override
+ public boolean canRename() {
+ return mNoRenameCount == 0 && mSelectionMgr.getSelection().size() == 1;
+ }
+
+ @Override
+ public boolean canPasteInto() {
+ return mDirectoryCount == 1 && mWritableDirectoryCount == 1
+ && mSelectionMgr.getSelection().size() == 1;
+ }
+}
diff --git a/src/com/android/documentsui/dirlist/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index bf6ac3c..604ccd6 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -25,8 +25,8 @@
import android.view.MotionEvent;
import com.android.documentsui.Events;
-import com.android.documentsui.Events.EventHandler;
import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.base.FunctionalInterfaces.EventHandler;
import java.util.Collections;
import java.util.function.Function;
@@ -47,11 +47,11 @@
private final FocusHandler mFocusHandler;
private final Function<MotionEvent, T> mEventConverter;
private final Predicate<DocumentDetails> mSelectable;
- private final EventHandler mRightClickHandler;
+ private final EventHandler<InputEvent> mRightClickHandler;
private final DocumentHandler mActivateHandler;
private final DocumentHandler mDeleteHandler;
- private final EventHandler mTouchDragListener;
- private final EventHandler mGestureSelectHandler;
+ private final EventHandler<InputEvent> mTouchDragListener;
+ private final EventHandler<InputEvent> mGestureSelectHandler;
private final TouchInputDelegate mTouchDelegate;
private final MouseInputDelegate mMouseDelegate;
private final KeyInputHandler mKeyListener;
@@ -61,11 +61,11 @@
FocusHandler focusHandler,
Function<MotionEvent, T> eventConverter,
Predicate<DocumentDetails> selectable,
- EventHandler rightClickHandler,
+ EventHandler<InputEvent> rightClickHandler,
DocumentHandler activateHandler,
DocumentHandler deleteHandler,
- EventHandler touchDragListener,
- EventHandler gestureSelectHandler) {
+ EventHandler<InputEvent> touchDragListener,
+ EventHandler<InputEvent> gestureSelectHandler) {
mSelectionMgr = selectionMgr;
mFocusHandler = focusHandler;
diff --git a/tests/src/com/android/documentsui/dirlist/DragStartListenerTest.java b/tests/src/com/android/documentsui/dirlist/DragStartListenerTest.java
index d51ef1f..81612a9 100644
--- a/tests/src/com/android/documentsui/dirlist/DragStartListenerTest.java
+++ b/tests/src/com/android/documentsui/dirlist/DragStartListenerTest.java
@@ -26,6 +26,7 @@
import com.android.documentsui.State;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.testing.TestEvent;
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.Views;
import java.util.ArrayList;
@@ -41,10 +42,7 @@
@Override
public void setUp() throws Exception {
-
- mMultiSelectManager = new MultiSelectManager(
- new TestDocumentsAdapter(new ArrayList<String>()),
- MultiSelectManager.MODE_MULTIPLE);
+ mMultiSelectManager = MultiSelectManagers.createTestInstance();
mListener = new DragStartListener.ActiveListener(
new State(),
diff --git a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
index 237899b..1c7b863 100644
--- a/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/tests/src/com/android/documentsui/dirlist/MultiSelectManagerTest.java
@@ -16,38 +16,49 @@
package com.android.documentsui.dirlist;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import android.util.SparseBooleanArray;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.dirlist.SelectionProbe;
import com.android.documentsui.testing.dirlist.TestSelectionListener;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.HashSet;
import java.util.List;
import java.util.Set;
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class MultiSelectManagerTest extends AndroidTestCase {
+public class MultiSelectManagerTest {
private static final List<String> ITEMS = TestData.create(100);
+ private final Set<String> mIgnored = new HashSet<>();
private MultiSelectManager mManager;
private TestSelectionListener mCallback;
- private TestDocumentsAdapter mAdapter;
private SelectionProbe mSelection;
- @Override
+ @Before
public void setUp() throws Exception {
mCallback = new TestSelectionListener();
- mAdapter = new TestDocumentsAdapter(ITEMS);
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ mManager = MultiSelectManagers.createTestInstance(
+ ITEMS,
+ MultiSelectManager.MODE_MULTIPLE,
+ (String id, boolean nextState) -> (!nextState || !mIgnored.contains(id)));
mManager.addCallback(mCallback);
mSelection = new SelectionProbe(mManager);
+
+ mIgnored.clear();
}
+ @Test
public void testSelection() {
// Check selection.
mManager.toggleSelection(ITEMS.get(7));
@@ -57,6 +68,15 @@
mSelection.assertNoSelection();
}
+ @Test
+ public void testSelection_DoNothingOnUnselectableItem() {
+ mIgnored.add(ITEMS.get(7));
+
+ mManager.toggleSelection(ITEMS.get(7));
+ mSelection.assertNoSelection();
+ }
+
+ @Test
public void testSelection_NotifiesSelectionChanged() {
// Selection should notify.
mManager.toggleSelection(ITEMS.get(7));
@@ -66,12 +86,26 @@
mCallback.assertSelectionChanged();
}
+ @Test
public void testRangeSelection() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(19);
mSelection.assertRangeSelection(15, 19);
}
+ @Test
+ public void testRangeSelection_SkipUnselectableItem() {
+ mIgnored.add(ITEMS.get(17));
+
+ mManager.startRangeSelection(15);
+ mManager.snapRangeSelection(19);
+
+ mSelection.assertRangeSelected(15, 16);
+ mSelection.assertNotSelected(17);
+ mSelection.assertRangeSelected(18, 19);
+ }
+
+ @Test
public void testRangeSelection_snapExpand() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(19);
@@ -79,6 +113,7 @@
mSelection.assertRangeSelection(15, 27);
}
+ @Test
public void testRangeSelection_snapContract() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(27);
@@ -86,6 +121,7 @@
mSelection.assertRangeSelection(15, 19);
}
+ @Test
public void testRangeSelection_snapInvert() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(27);
@@ -93,6 +129,7 @@
mSelection.assertRangeSelection(3, 15);
}
+ @Test
public void testRangeSelection_multiple() {
mManager.startRangeSelection(15);
mManager.snapRangeSelection(27);
@@ -104,6 +141,7 @@
mSelection.assertRangeSelected(42, 57);
}
+ @Test
public void testProvisionalRangeSelection() {
mManager.startRangeSelection(13);
mManager.snapProvisionalRangeSelection(15);
@@ -113,6 +151,7 @@
mSelection.assertSelectionSize(3);
}
+ @Test
public void testProvisionalRangeSelection_endEarly() {
mManager.startRangeSelection(13);
mManager.snapProvisionalRangeSelection(15);
@@ -124,6 +163,7 @@
mSelection.assertSelectionSize(1);
}
+ @Test
public void testProvisionalRangeSelection_snapExpand() {
mManager.startRangeSelection(13);
mManager.snapProvisionalRangeSelection(15);
@@ -133,6 +173,7 @@
mSelection.assertRangeSelection(13, 18);
}
+ @Test
public void testCombinationRangeSelection_IntersectsOldSelection() {
mManager.startRangeSelection(13);
mManager.snapRangeSelection(15);
@@ -148,6 +189,7 @@
mSelection.assertSelectionSize(4);
}
+ @Test
public void testProvisionalSelection() {
Selection s = mManager.getSelection();
mSelection.assertNoSelection();
@@ -159,6 +201,7 @@
mSelection.assertSelection(1, 2);
}
+ @Test
public void testProvisionalSelection_Replace() {
Selection s = mManager.getSelection();
@@ -174,6 +217,7 @@
mSelection.assertSelection(3, 4);
}
+ @Test
public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
Selection s = mManager.getSelection();
@@ -188,6 +232,7 @@
mSelection.assertSelection(1);
}
+ @Test
public void testProvisionalSelection_Apply() {
Selection s = mManager.getSelection();
@@ -199,6 +244,7 @@
mSelection.assertSelection(1, 2);
}
+ @Test
public void testProvisionalSelection_Cancel() {
mManager.toggleSelection(ITEMS.get(1));
mManager.toggleSelection(ITEMS.get(2));
@@ -214,6 +260,7 @@
mSelection.assertSelection(1, 2);
}
+ @Test
public void testProvisionalSelection_IntersectsAppliedSelection() {
mManager.toggleSelection(ITEMS.get(1));
mManager.toggleSelection(ITEMS.get(2));
diff --git a/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
index 444b2dc..11818ac 100644
--- a/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
@@ -16,17 +16,27 @@
package com.android.documentsui.dirlist;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import static junit.framework.Assert.assertEquals;
+import static junit.framework.Assert.assertFalse;
+import static junit.framework.Assert.assertTrue;
+
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+
import com.google.common.collect.Sets;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.HashSet;
import java.util.Set;
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class MultiSelectManager_SelectionTest extends AndroidTestCase {
+public class MultiSelectManager_SelectionTest {
private Selection selection;
@@ -36,7 +46,7 @@
"auth|id=@53di*/f3#d"
};
- @Override
+ @Before
public void setUp() throws Exception {
selection = new Selection();
selection.add(ids[0]);
@@ -44,6 +54,7 @@
selection.add(ids[2]);
}
+ @Test
public void testAdd() {
// We added in setUp.
assertEquals(3, selection.size());
@@ -52,6 +63,7 @@
assertContains(ids[2]);
}
+ @Test
public void testRemove() {
selection.remove(ids[0]);
selection.remove(ids[2]);
@@ -59,11 +71,13 @@
assertContains(ids[1]);
}
+ @Test
public void testClear() {
selection.clear();
assertEquals(0, selection.size());
}
+ @Test
public void testIsEmpty() {
assertTrue(new Selection().isEmpty());
selection.clear();
diff --git a/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java b/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
index 62cb1b0..020f316 100644
--- a/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
+++ b/tests/src/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
@@ -16,16 +16,24 @@
package com.android.documentsui.dirlist;
-import android.test.AndroidTestCase;
-import android.test.suitebuilder.annotation.SmallTest;
+import static junit.framework.Assert.fail;
+import android.support.test.filters.SmallTest;
+import android.support.test.runner.AndroidJUnit4;
+
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.dirlist.SelectionProbe;
import com.android.documentsui.testing.dirlist.TestSelectionListener;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.List;
+@RunWith(AndroidJUnit4.class)
@SmallTest
-public class MultiSelectManager_SingleSelectTest extends AndroidTestCase {
+public class MultiSelectManager_SingleSelectTest {
private static final List<String> ITEMS = TestData.create(100);
@@ -34,16 +42,16 @@
private TestDocumentsAdapter mAdapter;
private SelectionProbe mSelection;
- @Override
+ @Before
public void setUp() throws Exception {
mCallback = new TestSelectionListener();
- mAdapter = new TestDocumentsAdapter(ITEMS);
- mManager = new MultiSelectManager(mAdapter, MultiSelectManager.MODE_SINGLE);
+ mManager = MultiSelectManagers.createTestInstance(ITEMS, MultiSelectManager.MODE_SINGLE);
mManager.addCallback(mCallback);
mSelection = new SelectionProbe(mManager);
}
+ @Test
public void testSimpleSelect() {
mManager.toggleSelection(ITEMS.get(3));
mManager.toggleSelection(ITEMS.get(4));
@@ -51,6 +59,7 @@
mSelection.assertSelection(4);
}
+ @Test
public void testRangeSelectionNotEstablished() {
mManager.toggleSelection(ITEMS.get(3));
mCallback.reset();
diff --git a/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index ef8fd9f..0d99bab 100644
--- a/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/tests/src/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -24,6 +24,7 @@
import android.view.MotionEvent;
import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.TestEvent;
import com.android.documentsui.testing.TestEvent.Builder;
import com.android.documentsui.testing.TestPredicate;
@@ -57,9 +58,7 @@
@Before
public void setUp() {
- mAdapter = new TestDocumentsAdapter(ITEMS);
- MultiSelectManager selectionMgr =
- new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
mSelection = new SelectionProbe(selectionMgr);
mCanSelect = new TestPredicate<>();
diff --git a/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java b/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
index 1223e3a..6500a56 100644
--- a/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
+++ b/tests/src/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
@@ -21,6 +21,7 @@
import android.view.MotionEvent;
import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.TestEvent;
import com.android.documentsui.testing.TestEvent.Builder;
import com.android.documentsui.testing.TestPredicate;
@@ -57,9 +58,7 @@
@Before
public void setUp() {
- mAdapter = new TestDocumentsAdapter(ITEMS);
- MultiSelectManager selectionMgr =
- new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
mSelection = new SelectionProbe(selectionMgr);
mCanSelect = new TestPredicate<>();
diff --git a/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java b/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
index 11c4222..3946fd6 100644
--- a/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
+++ b/tests/src/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
@@ -24,6 +24,7 @@
import android.view.MotionEvent;
import com.android.documentsui.Events.InputEvent;
+import com.android.documentsui.testing.MultiSelectManagers;
import com.android.documentsui.testing.TestEvent;
import com.android.documentsui.testing.TestEvent.Builder;
import com.android.documentsui.testing.TestPredicate;
@@ -56,10 +57,7 @@
@Before
public void setUp() {
-
- mAdapter = new TestDocumentsAdapter(ITEMS);
- MultiSelectManager selectionMgr =
- new MultiSelectManager(mAdapter, MultiSelectManager.MODE_MULTIPLE);
+ MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
mSelection = new SelectionProbe(selectionMgr);
mCanSelect = new TestPredicate<>();
diff --git a/tests/src/com/android/documentsui/testing/MultiSelectManagers.java b/tests/src/com/android/documentsui/testing/MultiSelectManagers.java
new file mode 100644
index 0000000..e3b4261
--- /dev/null
+++ b/tests/src/com/android/documentsui/testing/MultiSelectManagers.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2016 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.
+ */
+
+package com.android.documentsui.testing;
+
+import com.android.documentsui.dirlist.MultiSelectManager;
+import com.android.documentsui.dirlist.MultiSelectManager.SelectionMode;
+import com.android.documentsui.dirlist.MultiSelectManager.SelectionPredicate;
+import com.android.documentsui.dirlist.TestDocumentsAdapter;
+
+import java.util.Collections;
+import java.util.List;
+
+public class MultiSelectManagers {
+ private MultiSelectManagers() {}
+
+ public static MultiSelectManager createTestInstance() {
+ return createTestInstance(Collections.emptyList());
+ }
+
+ public static MultiSelectManager createTestInstance(List<String> docs) {
+ return createTestInstance(docs, MultiSelectManager.MODE_MULTIPLE);
+ }
+
+ public static MultiSelectManager createTestInstance(
+ List<String> docs, @SelectionMode int mode) {
+ return createTestInstance(
+ docs,
+ mode,
+ (String id, boolean nextState) -> true);
+ }
+
+ public static MultiSelectManager createTestInstance(
+ List<String> docs, @SelectionMode int mode, SelectionPredicate canSetState) {
+ TestDocumentsAdapter adapter = new TestDocumentsAdapter(docs);
+ MultiSelectManager manager = new MultiSelectManager(
+ adapter,
+ mode,
+ canSetState);
+
+ return manager;
+ }
+}
diff --git a/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java b/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java
index 08f29f0..06c2219 100644
--- a/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java
+++ b/tests/src/com/android/documentsui/testing/dirlist/TestSelectionListener.java
@@ -21,27 +21,18 @@
import com.android.documentsui.dirlist.MultiSelectManager;
-import java.util.HashSet;
-import java.util.Set;
-
public final class TestSelectionListener implements MultiSelectManager.Callback {
- Set<String> ignored = new HashSet<>();
private boolean mSelectionChanged = false;
@Override
- public void onItemStateChanged(String modelId, boolean selected) {}
-
- @Override
- public boolean onBeforeItemStateChange(String modelId, boolean selected) {
- return !ignored.contains(modelId);
- }
-
- @Override
public void onSelectionChanged() {
mSelectionChanged = true;
}
+ @Override
+ public void onSelectionRestored() {}
+
public void reset() {
mSelectionChanged = false;
}