Move selection to selection pkg, make activity scoped.

Move all other selection related classes to selection pkg.

Change-Id: I57a3964fada55b0f4d073f05a7833455235221b9
diff --git a/src/com/android/documentsui/AbstractActionHandler.java b/src/com/android/documentsui/AbstractActionHandler.java
index 6a6d71c..9eb1359 100644
--- a/src/com/android/documentsui/AbstractActionHandler.java
+++ b/src/com/android/documentsui/AbstractActionHandler.java
@@ -35,11 +35,11 @@
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.files.LauncherActivity;
 import com.android.documentsui.files.OpenUriForViewTask;
 import com.android.documentsui.roots.LoadRootTask;
 import com.android.documentsui.roots.RootsAccess;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.sidebar.EjectRootTask;
 
 import java.util.List;
diff --git a/src/com/android/documentsui/ActionHandler.java b/src/com/android/documentsui/ActionHandler.java
index 00aaee0..5ee3977 100644
--- a/src/com/android/documentsui/ActionHandler.java
+++ b/src/com/android/documentsui/ActionHandler.java
@@ -28,7 +28,7 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 
 public interface ActionHandler {
 
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java
index e24794a..997a9bd 100644
--- a/src/com/android/documentsui/BaseActivity.java
+++ b/src/com/android/documentsui/BaseActivity.java
@@ -64,11 +64,13 @@
 import com.android.documentsui.base.State.ViewMode;
 import com.android.documentsui.dirlist.AnimationView;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.MultiSelectManager;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsCache;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.sorting.SortController;
 import com.android.documentsui.sorting.SortModel;
@@ -96,12 +98,13 @@
     protected SortController mSortController;
 
     private final String mTag;
-    private final ContentObserver mRootsCacheObserver = new ContentObserver(new Handler()) {
-        @Override
-        public void onChange(boolean selfChange) {
-            new HandleRootsChangedTask(BaseActivity.this).execute(getCurrentRoot());
-        }
-    };
+    private final ContentObserver mRootsCacheObserver = new ContentObserver(
+            new Handler()) {
+                @Override
+                public void onChange(boolean selfChange) {
+                    new HandleRootsChangedTask(BaseActivity.this).execute(getCurrentRoot());
+                }
+            };
 
     @LayoutRes
     private int mLayoutId;
@@ -125,6 +128,13 @@
      * Provides Activity a means of injection into and specialization of
      * DirectoryFragment.
      */
+    public abstract SelectionManager getSelectionManager(
+            DocumentsAdapter adapter, SelectionPredicate canSetState);
+
+    /**
+     * Provides Activity a means of injection into and specialization of
+     * DirectoryFragment.
+     */
     public abstract FocusManager getFocusManager(RecyclerView view, Model model);
 
     /**
@@ -146,7 +156,7 @@
      * Args can be null when called from a context lacking fragment, such as RootsFragment.
      */
     public abstract ActionHandler getActionHandler(
-            @Nullable Model model, @Nullable MultiSelectManager selectionMgr, boolean searchMode);
+            @Nullable Model model, @Nullable SelectionManager selectionMgr, boolean searchMode);
 
     public BaseActivity(@LayoutRes int layoutId, String tag) {
         mLayoutId = layoutId;
diff --git a/src/com/android/documentsui/MenuManager.java b/src/com/android/documentsui/MenuManager.java
index 12c6217..5bf360f 100644
--- a/src/com/android/documentsui/MenuManager.java
+++ b/src/com/android/documentsui/MenuManager.java
@@ -17,7 +17,6 @@
 package com.android.documentsui;
 
 import android.app.Fragment;
-import android.view.KeyboardShortcutGroup;
 import android.view.Menu;
 import android.view.MenuInflater;
 import android.view.MenuItem;
@@ -33,9 +32,6 @@
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.internal.annotations.VisibleForTesting;
 
-import java.util.List;
-import java.util.function.IntFunction;
-
 public abstract class MenuManager {
 
     final protected SearchViewManager mSearchManager;
diff --git a/src/com/android/documentsui/clipping/DocumentClipper.java b/src/com/android/documentsui/clipping/DocumentClipper.java
index fb453bc..f864fd1 100644
--- a/src/com/android/documentsui/clipping/DocumentClipper.java
+++ b/src/com/android/documentsui/clipping/DocumentClipper.java
@@ -31,7 +31,7 @@
 import com.android.documentsui.base.DocumentStack;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.services.FileOperation;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
diff --git a/src/com/android/documentsui/clipping/UrisSupplier.java b/src/com/android/documentsui/clipping/UrisSupplier.java
index f499653..98759b2 100644
--- a/src/com/android/documentsui/clipping/UrisSupplier.java
+++ b/src/com/android/documentsui/clipping/UrisSupplier.java
@@ -30,7 +30,7 @@
 
 import com.android.documentsui.DocumentsApplication;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.services.FileOperation;
 
 import java.io.File;
diff --git a/src/com/android/documentsui/dirlist/ActionModeController.java b/src/com/android/documentsui/dirlist/ActionModeController.java
index 4e89cb1..6952d47 100644
--- a/src/com/android/documentsui/dirlist/ActionModeController.java
+++ b/src/com/android/documentsui/dirlist/ActionModeController.java
@@ -37,7 +37,8 @@
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.Menus;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
+import com.android.documentsui.selection.SelectionManager;
 
 import java.util.function.Consumer;
 import java.util.function.Function;
@@ -46,12 +47,12 @@
 /**
  * A controller that listens to selection changes and manages life cycles of action modes.
  */
-public class ActionModeController implements MultiSelectManager.Callback, ActionMode.Callback {
+public class ActionModeController implements SelectionManager.Callback, ActionMode.Callback {
 
     private static final String TAG = "ActionModeController";
 
     private final Context mContext;
-    private final MultiSelectManager mSelectionMgr;
+    private final SelectionManager mSelectionMgr;
     private final MenuManager mMenuManager;
     private final MenuManager.SelectionDetails mSelectionDetails;
 
@@ -68,7 +69,7 @@
 
     private ActionModeController(
             Context context,
-            MultiSelectManager selectionMgr,
+            SelectionManager selectionMgr,
             MenuManager menuManager,
             MenuManager.SelectionDetails selectionDetails,
             Function<ActionMode.Callback, ActionMode> actionModeFactory,
@@ -151,8 +152,16 @@
     // Called when the user exits the action mode
     @Override
     public void onDestroyActionMode(ActionMode mode) {
+        if (mActionMode == null) {
+            if (DEBUG) Log.w(TAG, "Received call to destroy action mode on alien mode object.");
+        }
+
+        assert(mActionMode.equals(mode));
+
         if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
         mActionMode = null;
+        mMenu = null;
+
         // clear selection
         mSelectionMgr.clearSelection();
         mSelected.clear();
@@ -202,7 +211,7 @@
 
     static ActionModeController create(
             Context context,
-            MultiSelectManager selectionMgr,
+            SelectionManager selectionMgr,
             MenuManager menuManager,
             MenuManager.SelectionDetails selectionDetails,
             Activity activity,
diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java
index 8ba2efb..e7ab4b1 100644
--- a/src/com/android/documentsui/dirlist/DirectoryFragment.java
+++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java
@@ -89,9 +89,13 @@
 import com.android.documentsui.clipping.DocumentClipper;
 import com.android.documentsui.clipping.UrisSupplier;
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.picker.PickActivity;
 import com.android.documentsui.roots.RootsAccess;
+import com.android.documentsui.selection.BandController;
+import com.android.documentsui.selection.GestureSelector;
+import com.android.documentsui.selection.Selection;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionMetadata;
 import com.android.documentsui.services.FileOperation;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
@@ -146,6 +150,9 @@
     private ActivityConfig mActivityConfig;
 
     // This dependency is informally "injected" from the owning Activity in our onCreate method.
+    private SelectionManager mSelectionMgr;
+
+    // This dependency is informally "injected" from the owning Activity in our onCreate method.
     private FocusManager mFocusManager;
 
     // This dependency is informally "injected" from the owning Activity in our onCreate method.
@@ -157,7 +164,6 @@
     // This dependency is informally "injected" from the owning Activity in our onCreate method.
     private DialogController mDialogs;
 
-    private MultiSelectManager mSelectionMgr;
     private ActionModeController mActionModeController;
     private SelectionMetadata mSelectionMetadata;
     private UserInputHandler<InputEvent> mInputHandler;
@@ -286,31 +292,33 @@
         }
         mRecView.setLayoutManager(mLayout);
 
-        // TODO: instead of inserting the view into the constructor, extract listener-creation code
-        // and set the listener on the view after the fact.  Then the view doesn't need to be passed
-        // into the selection manager.
-        mSelectionMgr = new MultiSelectManager(
-                mAdapter,
-                state.allowMultiple
-                    ? MultiSelectManager.MODE_MULTIPLE
-                    : MultiSelectManager.MODE_SINGLE,
-                this::canSetSelectionState);
-        mSelectionMetadata = new SelectionMetadata(mModel::getItem);
-        mSelectionMgr.addItemCallback(mSelectionMetadata);
-
         mModel.addUpdateListener(mAdapter.getModelUpdateListener());
         mModel.addUpdateListener(mModelUpdateListener);
 
-        GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView);
 
         final BaseActivity activity = getBaseActivity();
+        mSelectionMgr = activity.getSelectionManager(mAdapter, this::canSetSelectionState);
         mFocusManager = activity.getFocusManager(mRecView, mModel);
         mActions = activity.getActionHandler(mModel, mSelectionMgr, mConfig.mSearchMode);
         mMenuManager = activity.getMenuManager();
         mDialogs = activity.getDialogController();
 
+        mSelectionMetadata = new SelectionMetadata(mModel::getItem);
+        mSelectionMgr.addItemCallback(mSelectionMetadata);
+
+        GestureSelector gestureSel = GestureSelector.create(mSelectionMgr, mRecView);
+
         if (state.allowMultiple) {
-            mBandController = new BandController(mRecView, mAdapter, mSelectionMgr);
+            mBandController = new BandController(
+                    mRecView,
+                    mAdapter,
+                    mSelectionMgr,
+                    (int pos) -> {
+                        // The band selection model only operates on documents and directories.
+                        // Exclude other types of adapter items like whitespace and dividers.
+                        RecyclerView.ViewHolder vh = mRecView.findViewHolderForAdapterPosition(pos);
+                        return ModelBackedDocumentsAdapter.isContentType(vh.getItemViewType());
+                    });
         }
 
         DragStartListener mDragStartListener = mActivityConfig.dragAndDropEnabled()
@@ -1171,10 +1179,12 @@
         }
     }
 
+    // TODO: Move to activities when Model becomes activity level object.
     private boolean canSelect(DocumentDetails doc) {
         return canSetSelectionState(doc.getModelId(), true);
     }
 
+    // TODO: Move to activities when Model becomes activity level object.
     private boolean canSetSelectionState(String modelId, boolean nextState) {
         if (nextState) {
             // Check if an item can be selected
diff --git a/src/com/android/documentsui/dirlist/DocumentsAdapter.java b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
index a48c66a..04baa50 100644
--- a/src/com/android/documentsui/dirlist/DocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/DocumentsAdapter.java
@@ -56,7 +56,7 @@
      * Triggers item-change notifications by stable ID (as opposed to position).
      * Passing an unrecognized ID will result in a warning in logcat, but no other error.
      */
-    abstract void onItemSelectionChanged(String id);
+    public abstract void onItemSelectionChanged(String id);
 
     /**
      * @return The model ID of the item at the given adapter position.
diff --git a/src/com/android/documentsui/dirlist/DragHoverListener.java b/src/com/android/documentsui/dirlist/DragHoverListener.java
index 6b694e1..63d93ec 100644
--- a/src/com/android/documentsui/dirlist/DragHoverListener.java
+++ b/src/com/android/documentsui/dirlist/DragHoverListener.java
@@ -24,8 +24,9 @@
 
 import com.android.documentsui.ItemDragListener;
 import com.android.documentsui.ItemDragListener.DragHost;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate;
+import com.android.documentsui.ui.ViewAutoScroller;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollDistanceDelegate;
 
 import java.util.function.BooleanSupplier;
 import java.util.function.IntSupplier;
diff --git a/src/com/android/documentsui/dirlist/DragShadowBuilder.java b/src/com/android/documentsui/dirlist/DragShadowBuilder.java
index d32ede8..0092f6c 100644
--- a/src/com/android/documentsui/dirlist/DragShadowBuilder.java
+++ b/src/com/android/documentsui/dirlist/DragShadowBuilder.java
@@ -29,7 +29,7 @@
 import com.android.documentsui.R;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 
 import java.util.List;
 import java.util.function.Function;
diff --git a/src/com/android/documentsui/dirlist/DragStartListener.java b/src/com/android/documentsui/dirlist/DragStartListener.java
index c22df34..2bca5b2 100644
--- a/src/com/android/documentsui/dirlist/DragStartListener.java
+++ b/src/com/android/documentsui/dirlist/DragStartListener.java
@@ -30,7 +30,8 @@
 import com.android.documentsui.base.State;
 import com.android.documentsui.base.Events.InputEvent;
 import com.android.documentsui.clipping.DocumentClipper;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperationService.OpType;
 
@@ -66,7 +67,7 @@
         private static String TAG = "DragStartListener";
 
         private final State mState;
-        private final MultiSelectManager mSelectionMgr;
+        private final SelectionManager mSelectionMgr;
         private final ViewFinder mViewFinder;
         private final Function<View, String> mIdFinder;
         private final ClipDataFactory mClipFactory;
@@ -77,7 +78,7 @@
         @VisibleForTesting
         public ActiveListener(
                 State state,
-                MultiSelectManager selectionMgr,
+                SelectionManager selectionMgr,
                 ViewFinder viewFinder,
                 Function<View, String> idFinder,
                 Function<Selection, List<DocumentInfo>> docsConverter,
@@ -176,7 +177,7 @@
             IconHelper iconHelper,
             Context context,
             Model model,
-            MultiSelectManager selectionMgr,
+            SelectionManager selectionMgr,
             DocumentClipper clipper,
             State state,
             Function<View, String> idFinder,
diff --git a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
index a810244..296fa70 100644
--- a/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
+++ b/src/com/android/documentsui/dirlist/ListeningGestureDetector.java
@@ -29,6 +29,8 @@
 import com.android.documentsui.base.Events;
 import com.android.documentsui.base.Events.InputEvent;
 import com.android.documentsui.base.Events.MotionInputEvent;
+import com.android.documentsui.selection.BandController;
+import com.android.documentsui.selection.GestureSelector;
 
 //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
diff --git a/src/com/android/documentsui/dirlist/Model.java b/src/com/android/documentsui/dirlist/Model.java
index 6c08859..016346a 100644
--- a/src/com/android/documentsui/dirlist/Model.java
+++ b/src/com/android/documentsui/dirlist/Model.java
@@ -33,8 +33,8 @@
 import com.android.documentsui.DirectoryResult;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.EventListener;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
 import com.android.documentsui.roots.RootCursorWrapper;
+import com.android.documentsui.selection.Selection;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
index c1077c5..adc4b04 100644
--- a/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
+++ b/src/com/android/documentsui/dirlist/ModelBackedDocumentsAdapter.java
@@ -192,6 +192,19 @@
                 : ITEM_TYPE_DOCUMENT;
     }
 
+    /**
+     * @return true if the item type is either document or directory, false for all other
+     * possible types.
+     */
+    public static boolean isContentType(int type) {
+        switch (type) {
+            case ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT:
+            case ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY:
+                return true;
+        }
+        return false;
+    }
+
     @Override
     public void onItemSelectionChanged(String id) {
         int position = mModelIds.indexOf(id);
diff --git a/src/com/android/documentsui/dirlist/MultiSelectManager.java b/src/com/android/documentsui/dirlist/MultiSelectManager.java
deleted file mode 100644
index 168134a..0000000
--- a/src/com/android/documentsui/dirlist/MultiSelectManager.java
+++ /dev/null
@@ -1,882 +0,0 @@
-/*
- * Copyright (C) 2015 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.base.Shared.DEBUG;
-
-import android.annotation.IntDef;
-import android.os.Parcel;
-import android.os.Parcelable;
-import android.support.annotation.VisibleForTesting;
-import android.support.v7.widget.RecyclerView;
-import android.util.Log;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-
-import javax.annotation.Nullable;
-
-/**
- * MultiSelectManager provides support traditional multi-item selection support to RecyclerView.
- * Additionally it can be configured to restrict selection to a single element, @see
- * #setSelectMode.
- */
-public final class MultiSelectManager {
-
-    @IntDef(flag = true, value = {
-            MODE_MULTIPLE,
-            MODE_SINGLE
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface SelectionMode {}
-    public static final int MODE_MULTIPLE = 0;
-    public static final int MODE_SINGLE = 1;
-
-    @IntDef({
-            RANGE_REGULAR,
-            RANGE_PROVISIONAL
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface RangeType {}
-    public static final int RANGE_REGULAR = 0;
-    public static final int RANGE_PROVISIONAL = 1;
-
-    private static final String TAG = "MultiSelectManager";
-
-    private final Selection mSelection = new Selection();
-
-    private final DocumentsAdapter mAdapter;
-    private final List<Callback> mCallbacks = new ArrayList<>(1);
-    private final List<ItemCallback> mItemCallbacks = new ArrayList<>(1);
-
-    private @Nullable Range mRanger;
-    private boolean mSingleSelect;
-
-    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() {
-
-                    private List<String> mModelIds;
-
-                    @Override
-                    public void onChanged() {
-                        mModelIds = mAdapter.getModelIds();
-
-                        // Update the selection to remove any disappeared IDs.
-                        mSelection.cancelProvisionalSelection();
-                        mSelection.intersect(mModelIds);
-                    }
-
-                    @Override
-                    public void onItemRangeChanged(
-                            int startPosition, int itemCount, Object payload) {
-                        // No change in position. Ignoring.
-                    }
-
-                    @Override
-                    public void onItemRangeInserted(int startPosition, int itemCount) {
-                        mSelection.cancelProvisionalSelection();
-                    }
-
-                    @Override
-                    public void onItemRangeRemoved(int startPosition, int itemCount) {
-                        assert(startPosition >= 0);
-                        assert(itemCount > 0);
-
-                        mSelection.cancelProvisionalSelection();
-                        // Remove any disappeared IDs from the selection.
-                        mSelection.intersect(mModelIds);
-                    }
-
-                    @Override
-                    public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
-                        throw new UnsupportedOperationException();
-                    }
-                });
-    }
-
-    void bindContoller(BandController controller) {
-        // Provides BandController with access to private mSelection state.
-        controller.bindSelection(mSelection);
-    }
-
-    /**
-     * Adds {@code callback} such that it will be notified when {@code MultiSelectManager}
-     * events occur.
-     *
-     * @param 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();
-    }
-
-    /**
-     * Returns a Selection object that provides a live view
-     * on the current selection.
-     *
-     * @see #getSelection(Selection) on how to get a snapshot
-     *     of the selection that will not reflect future changes
-     *     to selection.
-     *
-     * @return The current selection.
-     */
-    public Selection getSelection() {
-        return mSelection;
-    }
-
-    /**
-     * Updates {@code dest} to reflect the current selection.
-     * @param dest
-     *
-     * @return The Selection instance passed in, for convenience.
-     */
-    public Selection getSelection(Selection dest) {
-        dest.copyFrom(mSelection);
-        return dest;
-    }
-
-    public void replaceSelection(Iterable<String> ids) {
-        clearSelection();
-        setItemsSelected(ids, true);
-    }
-
-    /**
-     * Restores the selected state of specified items. Used in cases such as restore the selection
-     * after rotation etc.
-     */
-    public void restoreSelection(Selection other) {
-        setItemsSelectedQuietly(other.mSelection, true);
-        // NOTE: We intentionally don't restore provisional selection. It's provisional.
-        notifySelectionRestored();
-    }
-
-    /**
-     * Sets the selected state of the specified items. Note that the callback will NOT
-     * be consulted to see if an item can be selected.
-     *
-     * @param ids
-     * @param selected
-     * @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) {
-            final boolean itemChanged =
-                    selected
-                    ? canSetState(id, true) && mSelection.add(id)
-                    : canSetState(id, false) && mSelection.remove(id);
-            if (itemChanged) {
-                notifyItemStateChanged(id, selected);
-            }
-            changed |= itemChanged;
-        }
-        return changed;
-    }
-
-    /**
-     * Clears the selection and notifies (even if nothing changes).
-     */
-    public void clearSelection() {
-        clearSelectionQuietly();
-        notifySelectionChanged();
-    }
-
-    /**
-     * Clears the selection, without notifying selection listeners. UI elements still need to be
-     * notified about state changes so that they can update their appearance.
-     */
-    private void clearSelectionQuietly() {
-        mRanger = null;
-
-        if (!hasSelection()) {
-            return;
-        }
-
-        Selection oldSelection = getSelection(new Selection());
-        mSelection.clear();
-
-        for (String id: oldSelection.mSelection) {
-            notifyItemStateChanged(id, false);
-        }
-        for (String id: oldSelection.mProvisionalSelection) {
-            notifyItemStateChanged(id, false);
-        }
-    }
-
-    /**
-     * Toggles selection on the item with the given model ID.
-     *
-     * @param modelId
-     */
-    public void toggleSelection(String modelId) {
-        assert(modelId != null);
-
-        final boolean changed =
-                mSelection.contains(modelId)
-                ? attemptDeselect(modelId)
-                : attemptSelect(modelId);
-
-        if (changed) {
-            notifySelectionChanged();
-        }
-    }
-
-    /**
-     * Starts a range selection. If a range selection is already active, this will start a new range
-     * selection (which will reset the range anchor).
-     *
-     * @param pos The anchor position for the selection range.
-     */
-    void startRangeSelection(int pos) {
-        attemptSelect(mAdapter.getModelId(pos));
-        setSelectionRangeBegin(pos);
-    }
-
-    void snapRangeSelection(int pos) {
-        snapRangeSelection(pos, RANGE_REGULAR);
-    }
-
-    void snapProvisionalRangeSelection(int pos) {
-        snapRangeSelection(pos, RANGE_PROVISIONAL);
-    }
-
-    /**
-     * Sets the end point for the current range selection, started by a call to
-     * {@link #startRangeSelection(int)}. This function should only be called when a range selection
-     * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be
-     * selected or in provisional select, depending on the type supplied. Note that if the type is
-     * provisional select, one should do {@link Selection#applyProvisionalSelection()} at some point
-     * before calling on {@link #endRangeSelection()}.
-     *
-     * @param pos The new end position for the selection range.
-     * @param type The type of selection the range should utilize.
-     */
-    private void snapRangeSelection(int pos, @RangeType int type) {
-        if (!isRangeSelectionActive()) {
-            throw new IllegalStateException("Range start point not set.");
-        }
-
-        mRanger.snapSelection(pos, type);
-
-        // We're being lazy here notifying even when something might not have changed.
-        // To make this more correct, we'd need to update the Ranger class to return
-        // information about what has changed.
-        notifySelectionChanged();
-    }
-
-    /**
-     * Stops an in-progress range selection. All selection done with
-     * {@link #snapRangeSelection(int, int)} with type RANGE_PROVISIONAL will be lost if
-     * {@link Selection#applyProvisionalSelection()} is not called beforehand.
-     */
-    void endRangeSelection() {
-        mRanger = null;
-        // Clean up in case there was any leftover provisional selection
-        mSelection.cancelProvisionalSelection();
-    }
-
-    /**
-     * @return Whether or not there is a current range selection active.
-     */
-    boolean isRangeSelectionActive() {
-        return mRanger != null;
-    }
-
-    /**
-     * Sets the magic location at which a selection range begins (the selection anchor). This value
-     * is consulted when determining how to extend, and modify selection ranges. Calling this when a
-     * range selection is active will reset the range selection.
-     */
-    void setSelectionRangeBegin(int position) {
-        if (position == RecyclerView.NO_POSITION) {
-            return;
-        }
-
-        if (mSelection.contains(mAdapter.getModelId(position))) {
-            mRanger = new Range(this::updateForRange, position);
-        }
-    }
-
-    /**
-     * @param modelId
-     * @return True if the update was applied.
-     */
-    private boolean selectAndNotify(String modelId) {
-        boolean changed = mSelection.add(modelId);
-        if (changed) {
-            notifyItemStateChanged(modelId, true);
-        }
-        return changed;
-    }
-
-    /**
-     * @param id
-     * @return True if the update was applied.
-     */
-    private boolean attemptDeselect(String id) {
-        assert(id != null);
-        if (canSetState(id, false)) {
-            mSelection.remove(id);
-            notifyItemStateChanged(id, false);
-            if (DEBUG) Log.d(TAG, "Selection after deselect: " + mSelection);
-            return true;
-        } else {
-            if (DEBUG) Log.d(TAG, "Select cancelled by listener.");
-            return false;
-        }
-    }
-
-    /**
-     * @param id
-     * @return True if the update was applied.
-     */
-    private boolean attemptSelect(String id) {
-        assert(id != null);
-        boolean canSelect = canSetState(id, true);
-        if (!canSelect) {
-            return false;
-        }
-        if (mSingleSelect && hasSelection()) {
-            clearSelectionQuietly();
-        }
-
-        selectAndNotify(id);
-        return true;
-    }
-
-    boolean canSetState(String id, boolean nextState) {
-        return mCanSetState.test(id, nextState);
-    }
-
-    /**
-     * Notifies registered listeners when the selection status of a single item
-     * (identified by {@code position}) changes.
-     */
-    void notifyItemStateChanged(String id, boolean selected) {
-        assert(id != null);
-        int lastListener = mItemCallbacks.size() - 1;
-        for (int i = lastListener; i >= 0; i--) {
-            mItemCallbacks.get(i).onItemStateChanged(id, selected);
-        }
-        mAdapter.onItemSelectionChanged(id);
-    }
-
-    /**
-     * Notifies registered listeners when the selection has changed. This
-     * notification should be sent only once a full series of changes
-     * is complete, e.g. clearingSelection, or updating the single
-     * selection from one item to another.
-     */
-    void notifySelectionChanged() {
-        int lastListener = mCallbacks.size() - 1;
-        for (int i = lastListener; i > -1; i--) {
-            mCallbacks.get(i).onSelectionChanged();
-        }
-    }
-
-    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:
-                updateForRegularRange(begin, end, selected);
-                break;
-            case RANGE_PROVISIONAL:
-                updateForProvisionalRange(begin, end, selected);
-                break;
-            default:
-                throw new IllegalArgumentException("Invalid range type: " + type);
-        }
-    }
-
-    private void updateForRegularRange(int begin, int end, boolean selected) {
-        assert(end >= begin);
-        for (int i = begin; i <= end; i++) {
-            String id = mAdapter.getModelId(i);
-            if (id == null) {
-                continue;
-            }
-
-            if (selected) {
-                boolean canSelect = canSetState(id, true);
-                if (canSelect) {
-                    if (mSingleSelect && hasSelection()) {
-                        clearSelectionQuietly();
-                    }
-                    selectAndNotify(id);
-                }
-            } else {
-                attemptDeselect(id);
-            }
-        }
-    }
-
-    private void updateForProvisionalRange(int begin, int end, boolean selected) {
-        assert (end >= begin);
-        for (int i = begin; i <= end; i++) {
-            String id = mAdapter.getModelId(i);
-            if (id == null) {
-                continue;
-            }
-            if (selected) {
-                boolean canSelect = canSetState(id, true);
-                if (canSelect) {
-                    mSelection.mProvisionalSelection.add(id);
-                }
-            } else {
-                mSelection.mProvisionalSelection.remove(id);
-            }
-            notifyItemStateChanged(id, selected);
-        }
-        notifySelectionChanged();
-    }
-
-    /**
-     * Class providing support for managing range selections.
-     */
-    private static final class Range {
-        private static final int UNDEFINED = -1;
-
-        private final RangeUpdater mUpdater;
-        private final int mBegin;
-        private int mEnd = UNDEFINED;
-
-        public Range(RangeUpdater updater, int begin) {
-            if (DEBUG) Log.d(TAG, "New Ranger created beginning @ " + begin);
-            mUpdater = updater;
-            mBegin = begin;
-        }
-
-        private void snapSelection(int position, @RangeType int type) {
-            assert(position != RecyclerView.NO_POSITION);
-
-            if (mEnd == UNDEFINED || mEnd == mBegin) {
-                // Reset mEnd so it can be established in establishRange.
-                mEnd = UNDEFINED;
-                establishRange(position, type);
-            } else {
-                reviseRange(position, type);
-            }
-        }
-
-        private void establishRange(int position, @RangeType int type) {
-            assert(mEnd == UNDEFINED);
-
-            if (position == mBegin) {
-                mEnd = position;
-            }
-
-            if (position > mBegin) {
-                updateRange(mBegin + 1, position, true, type);
-            } else if (position < mBegin) {
-                updateRange(position, mBegin - 1, true, type);
-            }
-
-            mEnd = position;
-        }
-
-        private void reviseRange(int position, @RangeType int type) {
-            assert(mEnd != UNDEFINED);
-            assert(mBegin != mEnd);
-
-            if (position == mEnd) {
-                if (DEBUG) Log.v(TAG, "Ignoring no-op revision for range: " + this);
-            }
-
-            if (mEnd > mBegin) {
-                reviseAscendingRange(position, type);
-            } else if (mEnd < mBegin) {
-                reviseDescendingRange(position, type);
-            }
-            // the "else" case is covered by checkState at beginning of method.
-
-            mEnd = position;
-        }
-
-        /**
-         * Updates an existing ascending seleciton.
-         * @param position
-         */
-        private void reviseAscendingRange(int position, @RangeType int type) {
-            // Reducing or reversing the range....
-            if (position < mEnd) {
-                if (position < mBegin) {
-                    updateRange(mBegin + 1, mEnd, false, type);
-                    updateRange(position, mBegin -1, true, type);
-                } else {
-                    updateRange(position + 1, mEnd, false, type);
-                }
-            }
-
-            // Extending the range...
-            else if (position > mEnd) {
-                updateRange(mEnd + 1, position, true, type);
-            }
-        }
-
-        private void reviseDescendingRange(int position, @RangeType int type) {
-            // Reducing or reversing the range....
-            if (position > mEnd) {
-                if (position > mBegin) {
-                    updateRange(mEnd, mBegin - 1, false, type);
-                    updateRange(mBegin + 1, position, true, type);
-                } else {
-                    updateRange(mEnd, position - 1, false, type);
-                }
-            }
-
-            // Extending the range...
-            else if (position < mEnd) {
-                updateRange(position, mEnd - 1, true, type);
-            }
-        }
-
-        /**
-         * Try to set selection state for all elements in range. Not that callbacks can cancel
-         * selection of specific items, so some or even all items may not reflect the desired state
-         * after the update is complete.
-         *
-         * @param begin Adapter position for range start (inclusive).
-         * @param end Adapter position for range end (inclusive).
-         * @param selected New selection state.
-         */
-        private void updateRange(int begin, int end, boolean selected, @RangeType int type) {
-            mUpdater.updateForRange(begin, end, selected, type);
-        }
-
-        @Override
-        public String toString() {
-            return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
-        }
-        /*
-         * @see {@link MultiSelectManager#updateForRegularRange(int, int , boolean)} and {@link
-         * MultiSelectManager#updateForProvisionalRange(int, int, boolean)}
-         */
-        @FunctionalInterface
-        private interface RangeUpdater {
-            void updateForRange(int begin, int end, boolean selected, @RangeType int type);
-        }
-    }
-
-    /**
-     * Object representing the current selection. Provides read only access
-     * public access, and private write access.
-     */
-    public static final class Selection implements Iterable<String>, Parcelable {
-
-        // This class tracks selected items by managing two sets: the saved selection, and the total
-        // selection. Saved selections are those which have been completed by tapping an item or by
-        // completing a band select operation. Provisional selections are selections which have been
-        // temporarily created by an in-progress band select operation (once the user releases the
-        // mouse button during a band select operation, the selected items become saved). The total
-        // selection is the combination of both the saved selection and the provisional
-        // selection. Tracking both separately is necessary to ensure that saved selections do not
-        // become deselected when they are removed from the provisional selection; for example, if
-        // item A is tapped (and selected), then an in-progress band select covers A then uncovers
-        // A, A should still be selected as it has been saved. To ensure this behavior, the saved
-        // selection must be tracked separately.
-        private final Set<String> mSelection;
-        private final Set<String> mProvisionalSelection;
-
-        public Selection() {
-            mSelection = new HashSet<>();
-            mProvisionalSelection = new HashSet<>();
-        }
-
-        /**
-         * Used by CREATOR.
-         */
-        private Selection(Set<String> selection) {
-            mSelection = selection;
-            mProvisionalSelection = new HashSet<>();
-        }
-
-        /**
-         * @param id
-         * @return true if the position is currently selected.
-         */
-        public boolean contains(@Nullable String id) {
-            return mSelection.contains(id) || mProvisionalSelection.contains(id);
-        }
-
-        /**
-         * Returns an {@link Iterator} that iterators over the selection, *excluding*
-         * any provisional selection.
-         *
-         * {@inheritDoc}
-         */
-        @Override
-        public Iterator<String> iterator() {
-            return mSelection.iterator();
-        }
-
-        /**
-         * @return size of the selection including both final and provisional selected items.
-         */
-        public int size() {
-            return mSelection.size() + mProvisionalSelection.size();
-        }
-
-        /**
-         * @return true if the selection is empty.
-         */
-        public boolean isEmpty() {
-            return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
-        }
-
-        /**
-         * Sets the provisional selection, which is a temporary selection that can be saved,
-         * canceled, or adjusted at a later time. When a new provision selection is applied, the old
-         * one (if it exists) is abandoned.
-         * @return Map of ids added or removed. Added ids have a value of true, removed are false.
-         */
-        @VisibleForTesting
-        protected Map<String, Boolean> setProvisionalSelection(Set<String> newSelection) {
-            Map<String, Boolean> delta = new HashMap<>();
-
-            for (String id: mProvisionalSelection) {
-                // Mark each item that used to be in the selection but is unsaved and not in the new
-                // provisional selection.
-                if (!newSelection.contains(id) && !mSelection.contains(id)) {
-                    delta.put(id, false);
-                }
-            }
-
-            for (String id: mSelection) {
-                // Mark each item that used to be in the selection but is unsaved and not in the new
-                // provisional selection.
-                if (!newSelection.contains(id)) {
-                    delta.put(id, false);
-                }
-            }
-
-            for (String id: newSelection) {
-                // Mark each item that was not previously in the selection but is in the new
-                // provisional selection.
-                if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) {
-                    delta.put(id, true);
-                }
-            }
-
-            // Now, iterate through the changes and actually add/remove them to/from the current
-            // selection. This could not be done in the previous loops because changing the size of
-            // the selection mid-iteration changes iteration order erroneously.
-            for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
-                String id = entry.getKey();
-                if (entry.getValue()) {
-                    mProvisionalSelection.add(id);
-                } else {
-                    mProvisionalSelection.remove(id);
-                }
-            }
-
-            return delta;
-        }
-
-        /**
-         * Saves the existing provisional selection. Once the provisional selection is saved,
-         * subsequent provisional selections which are different from this existing one cannot
-         * cause items in this existing provisional selection to become deselected.
-         */
-        @VisibleForTesting
-        protected void applyProvisionalSelection() {
-            mSelection.addAll(mProvisionalSelection);
-            mProvisionalSelection.clear();
-        }
-
-        /**
-         * Abandons the existing provisional selection so that all items provisionally selected are
-         * now deselected.
-         */
-        @VisibleForTesting
-        void cancelProvisionalSelection() {
-            mProvisionalSelection.clear();
-        }
-
-        /** @hide */
-        @VisibleForTesting
-        public boolean add(String id) {
-            if (!mSelection.contains(id)) {
-                mSelection.add(id);
-                return true;
-            }
-            return false;
-        }
-
-        /** @hide */
-        @VisibleForTesting
-        boolean remove(String id) {
-            if (mSelection.contains(id)) {
-                mSelection.remove(id);
-                return true;
-            }
-            return false;
-        }
-
-        public void clear() {
-            mSelection.clear();
-        }
-
-        /**
-         * Trims this selection to be the intersection of itself with the set of given IDs.
-         */
-        public void intersect(Collection<String> ids) {
-            mSelection.retainAll(ids);
-            mProvisionalSelection.retainAll(ids);
-        }
-
-        @VisibleForTesting
-        void copyFrom(Selection source) {
-            mSelection.clear();
-            mSelection.addAll(source.mSelection);
-
-            mProvisionalSelection.clear();
-            mProvisionalSelection.addAll(source.mProvisionalSelection);
-        }
-
-        @Override
-        public String toString() {
-            if (size() <= 0) {
-                return "size=0, items=[]";
-            }
-
-            StringBuilder buffer = new StringBuilder(size() * 28);
-            buffer.append("Selection{")
-                .append("applied{size=" + mSelection.size())
-                .append(", entries=" + mSelection)
-                .append("}, provisional{size=" + mProvisionalSelection.size())
-                .append(", entries=" + mProvisionalSelection)
-                .append("}}");
-            return buffer.toString();
-        }
-
-        @Override
-        public int hashCode() {
-            return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
-        }
-
-        @Override
-        public boolean equals(Object that) {
-          if (this == that) {
-              return true;
-          }
-
-          if (!(that instanceof Selection)) {
-              return false;
-          }
-
-          return mSelection.equals(((Selection) that).mSelection) &&
-                  mProvisionalSelection.equals(((Selection) that).mProvisionalSelection);
-        }
-
-        @Override
-        public int describeContents() {
-            return 0;
-        }
-
-        @Override
-        public void writeToParcel(Parcel dest, int flags) {
-            dest.writeStringList(new ArrayList<>(mSelection));
-            // We don't include provisional selection since it is
-            // typically coupled to some other runtime state (like a band).
-        }
-
-        public static final ClassLoaderCreator<Selection> CREATOR =
-                new ClassLoaderCreator<Selection>() {
-            @Override
-            public Selection createFromParcel(Parcel in) {
-                return createFromParcel(in, null);
-            }
-
-            @Override
-            public Selection createFromParcel(Parcel in, ClassLoader loader) {
-                ArrayList<String> selected = new ArrayList<>();
-                in.readStringList(selected);
-
-                return new Selection(new HashSet<>(selected));
-            }
-
-            @Override
-            public Selection[] newArray(int size) {
-                return new Selection[size];
-            }
-        };
-    }
-
-    public interface ItemCallback {
-        void onItemStateChanged(String id, boolean selected);
-    }
-
-    public interface Callback {
-        /**
-         * Called immediately after completion of any set of changes.
-         */
-        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/UserInputHandler.java b/src/com/android/documentsui/dirlist/UserInputHandler.java
index cc9a81f..4af1fcd 100644
--- a/src/com/android/documentsui/dirlist/UserInputHandler.java
+++ b/src/com/android/documentsui/dirlist/UserInputHandler.java
@@ -28,6 +28,7 @@
 import com.android.documentsui.base.EventHandler;
 import com.android.documentsui.base.Events;
 import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.selection.SelectionManager;
 
 import java.util.Collections;
 import java.util.function.Function;
@@ -46,7 +47,7 @@
 
     private ActionHandler mActionHandler;
     private final FocusHandler mFocusHandler;
-    private final MultiSelectManager mSelectionMgr;
+    private final SelectionManager mSelectionMgr;
     private final Function<MotionEvent, T> mEventConverter;
     private final Predicate<DocumentDetails> mSelectable;
 
@@ -63,7 +64,7 @@
     public UserInputHandler(
             ActionHandler actionHandler,
             FocusHandler focusHandler,
-            MultiSelectManager selectionMgr,
+            SelectionManager selectionMgr,
             Function<MotionEvent, T> eventConverter,
             Predicate<DocumentDetails> selectable,
             EventHandler<InputEvent> rightClickHandler,
diff --git a/src/com/android/documentsui/files/ActionHandler.java b/src/com/android/documentsui/files/ActionHandler.java
index 6a1f3ad..7e94a29 100644
--- a/src/com/android/documentsui/files/ActionHandler.java
+++ b/src/com/android/documentsui/files/ActionHandler.java
@@ -46,11 +46,13 @@
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.Model.Update;
-import com.android.documentsui.dirlist.MultiSelectManager;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.files.ActionHandler.Addons;
 import com.android.documentsui.roots.GetRootDocumentTask;
 import com.android.documentsui.roots.RootsAccess;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.services.FileOperation;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.services.FileOperations;
@@ -466,7 +468,7 @@
         mConfig.modelLoadObserved = true;
     }
 
-    ActionHandler<T> reset(Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+    ActionHandler<T> reset(Model model, SelectionManager selectionMgr, boolean searchMode) {
         mConfig.reset(model, selectionMgr, searchMode);
         return this;
     }
@@ -474,7 +476,7 @@
     private static final class Config {
 
         @Nullable Model model;
-        @Nullable MultiSelectManager selectionMgr;
+        @Nullable SelectionManager selectionMgr;
         boolean searchMode;
 
         private final EventListener<Update> mModelUpdateListener;
@@ -487,7 +489,7 @@
             mModelUpdateListener = modelUpdateListener;
         }
 
-        public void reset(Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+        public void reset(Model model, SelectionManager selectionMgr, boolean searchMode) {
             assert(model != null);
 
             this.model = model;
diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java
index 4feb5f1..0de0e26 100644
--- a/src/com/android/documentsui/files/FilesActivity.java
+++ b/src/com/android/documentsui/files/FilesActivity.java
@@ -50,8 +50,10 @@
 import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.dirlist.AnimationView.AnimationType;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.MultiSelectManager;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.ui.DialogController;
@@ -68,6 +70,7 @@
     public static final String TAG = "FilesActivity";
 
     private final Config mConfig = new Config();
+    private SelectionManager mSelectionMgr;
     private MenuManager mMenuManager;
     private FocusManager mFocusManager;
     private ActionHandler<FilesActivity> mActions;
@@ -83,6 +86,7 @@
         super.onCreate(icicle);
 
         mClipper = DocumentsApplication.getDocumentClipper(this);
+        mSelectionMgr = new SelectionManager(SelectionManager.MODE_MULTIPLE);
         mMenuManager = new MenuManager(
                 mSearchManager,
                 mState,
@@ -344,6 +348,11 @@
         return mConfig;
     }
 
+    public SelectionManager getSelectionManager(
+            DocumentsAdapter adapter, SelectionPredicate canSetState) {
+        return mSelectionMgr.reset(adapter, canSetState);
+    }
+
     @Override
     public FocusManager getFocusManager(RecyclerView view, Model model) {
         return mFocusManager.reset(view, model);
@@ -356,7 +365,7 @@
 
     @Override
     public ActionHandler<FilesActivity> getActionHandler(
-            Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+            Model model, SelectionManager selectionMgr, boolean searchMode) {
 
         // provide our friend, RootsFragment, early access to this special feature!
         if (model == null || selectionMgr == null) {
diff --git a/src/com/android/documentsui/picker/ActionHandler.java b/src/com/android/documentsui/picker/ActionHandler.java
index 1117363..fbf4a43 100644
--- a/src/com/android/documentsui/picker/ActionHandler.java
+++ b/src/com/android/documentsui/picker/ActionHandler.java
@@ -28,6 +28,7 @@
 import android.util.Log;
 
 import com.android.documentsui.AbstractActionHandler;
+import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.DocumentsAccess;
 import com.android.documentsui.Metrics;
 import com.android.documentsui.base.DocumentInfo;
@@ -38,13 +39,12 @@
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
 import com.android.documentsui.base.State;
-import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.dirlist.DocumentDetails;
 import com.android.documentsui.dirlist.Model;
 import com.android.documentsui.dirlist.Model.Update;
-import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.picker.ActionHandler.Addons;
 import com.android.documentsui.roots.RootsAccess;
+import com.android.documentsui.selection.SelectionManager;
 
 import java.util.concurrent.Executor;
 
@@ -175,7 +175,7 @@
         mConfig.modelLoadObserved = true;
     }
 
-    ActionHandler<T> reset(Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+    ActionHandler<T> reset(Model model, SelectionManager selectionMgr, boolean searchMode) {
         mConfig.reset(model, selectionMgr, searchMode);
         return this;
     }
@@ -183,7 +183,7 @@
     private static final class Config {
 
         @Nullable Model model;
-        @Nullable MultiSelectManager selectionMgr;
+        @Nullable SelectionManager selectionMgr;
         boolean searchMode;
 
         // We use this to keep track of whether a model has been previously loaded or not so we can
@@ -196,7 +196,8 @@
             mModelUpdateListener = modelUpdateListener;
         }
 
-        public void reset(Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+
+        public void reset(Model model, SelectionManager selectionMgr, boolean searchMode) {
             assert(model != null);
 
             this.model = model;
diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java
index da8b97b..2196ba9 100644
--- a/src/com/android/documentsui/picker/PickActivity.java
+++ b/src/com/android/documentsui/picker/PickActivity.java
@@ -57,9 +57,11 @@
 import com.android.documentsui.base.State;
 import com.android.documentsui.ActivityConfig;
 import com.android.documentsui.dirlist.DirectoryFragment;
+import com.android.documentsui.dirlist.DocumentsAdapter;
 import com.android.documentsui.dirlist.Model;
-import com.android.documentsui.dirlist.MultiSelectManager;
 import com.android.documentsui.picker.LastAccessedProvider.Columns;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 import com.android.documentsui.services.FileOperationService;
 import com.android.documentsui.sidebar.RootsFragment;
 import com.android.documentsui.ui.DialogController;
@@ -73,6 +75,8 @@
     private static final int CODE_FORWARD = 42;
     private static final String TAG = "PickActivity";
     private final Config mConfig = new Config();
+
+    private SelectionManager mSelectionMgr;
     private FocusManager mFocusManager;
     private MenuManager mMenuManager;
     private ActionHandler<PickActivity> mActionHandler;
@@ -85,6 +89,10 @@
     public void onCreate(Bundle icicle) {
         super.onCreate(icicle);
 
+        mSelectionMgr = new SelectionManager(
+                mState.allowMultiple
+                        ? SelectionManager.MODE_MULTIPLE
+                        : SelectionManager.MODE_SINGLE);
         mFocusManager = new FocusManager(getColor(R.color.accent_dark));
         mMenuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this));
         mActionHandler = new ActionHandler<>(
@@ -398,6 +406,11 @@
         return mConfig;
     }
 
+    public SelectionManager getSelectionManager(
+            DocumentsAdapter adapter, SelectionPredicate canSetState) {
+        return mSelectionMgr.reset(adapter, canSetState);
+    }
+
     @Override
     public FocusManager getFocusManager(RecyclerView view, Model model) {
         return mFocusManager.reset(view, model);
@@ -410,7 +423,7 @@
 
     @Override
     public ActionHandler<PickActivity> getActionHandler(
-            Model model, MultiSelectManager selectionMgr, boolean searchMode) {
+            Model model, SelectionManager selectionMgr, boolean searchMode) {
 
         // provide our friend, RootsFragment, early access to this special feature!
         if (model == null || selectionMgr == null) {
diff --git a/src/com/android/documentsui/dirlist/BandController.java b/src/com/android/documentsui/selection/BandController.java
similarity index 95%
rename from src/com/android/documentsui/dirlist/BandController.java
rename to src/com/android/documentsui/selection/BandController.java
index 429b326..a255e52 100644
--- a/src/com/android/documentsui/dirlist/BandController.java
+++ b/src/com/android/documentsui/selection/BandController.java
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import static com.android.documentsui.base.Shared.DEBUG;
-import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DIRECTORY;
-import static com.android.documentsui.dirlist.ModelBackedDocumentsAdapter.ITEM_TYPE_DOCUMENT;
-import static com.android.documentsui.dirlist.ViewAutoScroller.NOT_SET;
+import static com.android.documentsui.ui.ViewAutoScroller.NOT_SET;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -37,9 +35,10 @@
 
 import com.android.documentsui.R;
 import com.android.documentsui.base.Events.InputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate;
+import com.android.documentsui.dirlist.DocumentsAdapter;
+import com.android.documentsui.ui.ViewAutoScroller;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollDistanceDelegate;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -47,11 +46,12 @@
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
+import java.util.function.IntPredicate;
 
 /**
  * Provides mouse driven band-select support when used in conjunction with {@link RecyclerView}
- * and {@link MultiSelectManager}. This class is responsible for rendering the band select
- * overlay and selecting overlaid items via MultiSelectManager.
+ * and {@link SelectionManager}. This class is responsible for rendering the band select
+ * overlay and selecting overlaid items via SelectionManager.
  */
 public class BandController extends OnScrollListener {
 
@@ -60,7 +60,7 @@
     private final Runnable mModelBuilder;
     private final SelectionEnvironment mEnvironment;
     private final DocumentsAdapter mAdapter;
-    private final MultiSelectManager mSelectionManager;
+    private final SelectionManager mSelectionManager;
     private final Runnable mViewScroller;
     private final GridModel.OnSelectionChangedListener mGridListener;
 
@@ -74,14 +74,16 @@
     public BandController(
             final RecyclerView view,
             DocumentsAdapter adapter,
-            MultiSelectManager selectionManager) {
-        this(new RuntimeSelectionEnvironment(view), adapter, selectionManager);
+            SelectionManager selectionManager,
+            IntPredicate gridItemTester) {
+        this(new RuntimeSelectionEnvironment(view), adapter, selectionManager, gridItemTester);
     }
 
     private BandController(
             SelectionEnvironment env,
             DocumentsAdapter adapter,
-            MultiSelectManager selectionManager) {
+            SelectionManager selectionManager,
+            IntPredicate gridItemTester) {
 
         selectionManager.bindContoller(this);
 
@@ -161,7 +163,7 @@
         mModelBuilder = new Runnable() {
             @Override
             public void run() {
-                mModel = new GridModel(mEnvironment, mAdapter);
+                mModel = new GridModel(mEnvironment, gridItemTester, mAdapter);
                 mModel.addOnSelectionChangedListener(mGridListener);
             }
         };
@@ -175,7 +177,7 @@
         mSelection = selection;
     }
 
-    boolean onInterceptTouchEvent(InputEvent e) {
+    public boolean onInterceptTouchEvent(InputEvent e) {
         if (shouldStart(e)) {
             if (!e.isCtrlKeyDown()) {
                 mSelectionManager.clearSelection();
@@ -235,7 +237,7 @@
      * Processes a MotionEvent by starting, ending, or resizing the band select overlay.
      * @param input
      */
-    void onTouchEvent(InputEvent input) {
+    public void onTouchEvent(InputEvent input) {
         assert(input.isMouseEvent());
 
         if (shouldStop(input)) {
@@ -359,6 +361,7 @@
         private static final int LOWER_RIGHT = LOWER | RIGHT;
 
         private final SelectionEnvironment mHelper;
+        private final IntPredicate mGridItemTester;
         private final DocumentsAdapter mAdapter;
 
         private final List<GridModel.OnSelectionChangedListener> mOnSelectionChangedListeners =
@@ -398,9 +401,10 @@
         // should expand from when Shift+click is used.
         private int mPositionNearestOrigin = NOT_SET;
 
-        GridModel(SelectionEnvironment helper, DocumentsAdapter adapter) {
+        GridModel(SelectionEnvironment helper, IntPredicate gridItemTester, DocumentsAdapter adapter) {
             mHelper = helper;
             mAdapter = adapter;
+            mGridItemTester = gridItemTester;
             mHelper.addOnScrollListener(this);
         }
 
@@ -485,7 +489,7 @@
                 // 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) &&
+                        mGridItemTester.test(adapterPosition) &&
                         !mKnownPositions.get(adapterPosition)) {
                     mKnownPositions.put(adapterPosition, true);
                     recordItemData(mHelper.getAbsoluteRectForChildViewAt(i), adapterPosition);
@@ -1008,10 +1012,6 @@
         int getChildCount();
         int getVisibleChildCount();
         /**
-         * 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);
@@ -1124,22 +1124,8 @@
         }
 
         @Override
-        public boolean isLayoutItem(int pos) {
-            // The band selection model only operates on documents and directories. Exclude other
-            // types of adapter items (e.g. whitespace items like dividers).
-            RecyclerView.ViewHolder vh = mView.findViewHolderForAdapterPosition(pos);
-            switch (vh.getItemViewType()) {
-                case ITEM_TYPE_DOCUMENT:
-                case ITEM_TYPE_DIRECTORY:
-                    return false;
-                default:
-                    return true;
-            }
-        }
-
-        @Override
         public boolean hasView(int pos) {
             return mView.findViewHolderForAdapterPosition(pos) != null;
         }
     }
-}
\ No newline at end of file
+}
diff --git a/src/com/android/documentsui/dirlist/GestureSelector.java b/src/com/android/documentsui/selection/GestureSelector.java
similarity index 93%
rename from src/com/android/documentsui/dirlist/GestureSelector.java
rename to src/com/android/documentsui/selection/GestureSelector.java
index d0b68ae..08afac5 100644
--- a/src/com/android/documentsui/dirlist/GestureSelector.java
+++ b/src/com/android/documentsui/selection/GestureSelector.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import android.graphics.Point;
 import android.support.annotation.VisibleForTesting;
@@ -22,8 +22,9 @@
 import android.view.View;
 
 import com.android.documentsui.base.Events.InputEvent;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollDistanceDelegate;
+import com.android.documentsui.ui.ViewAutoScroller;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollDistanceDelegate;
 
 import java.util.function.IntSupplier;
 
@@ -33,9 +34,9 @@
  * Helper class used to intercept events that could cause a gesture multi-select, and keeps
  * the interception going if necessary.
  */
-final class GestureSelector {
+public final class GestureSelector {
 
-    private final MultiSelectManager mSelectionMgr;
+    private final SelectionManager mSelectionMgr;
     private final Runnable mDragScroller;
     private final IntSupplier mHeight;
     private final ViewFinder mViewFinder;
@@ -44,7 +45,7 @@
     private Point mLastInterceptedPoint;
 
     GestureSelector(
-            MultiSelectManager selectionMgr,
+            SelectionManager selectionMgr,
             IntSupplier heightSupplier,
             ViewFinder viewFinder,
             ScrollActionDelegate actionDelegate) {
@@ -72,8 +73,8 @@
         mDragScroller = new ViewAutoScroller(distanceDelegate, actionDelegate);
     }
 
-    static GestureSelector create(
-            MultiSelectManager selectionMgr,
+    public static GestureSelector create(
+            SelectionManager selectionMgr,
             RecyclerView scrollView) {
         ScrollActionDelegate actionDelegate = new ScrollActionDelegate() {
             @Override
@@ -102,7 +103,7 @@
     }
 
     // Explicitly kick off a gesture multi-select.
-    boolean start(InputEvent event) {
+    public boolean start(InputEvent event) {
         if (mStarted) {
             return false;
         }
diff --git a/src/com/android/documentsui/selection/Range.java b/src/com/android/documentsui/selection/Range.java
new file mode 100644
index 0000000..cd04f90
--- /dev/null
+++ b/src/com/android/documentsui/selection/Range.java
@@ -0,0 +1,151 @@
+/*
+ * 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.selection;
+
+import static com.android.documentsui.base.Shared.DEBUG;
+
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import com.android.documentsui.selection.SelectionManager.RangeType;
+
+/**
+ * Class providing support for managing range selections.
+ */
+final class Range {
+    private static final int UNDEFINED = -1;
+
+    private final Range.RangeUpdater mUpdater;
+    private final int mBegin;
+    private int mEnd = UNDEFINED;
+
+    public Range(Range.RangeUpdater updater, int begin) {
+        if (DEBUG) Log.d(SelectionManager.TAG, "New Ranger created beginning @ " + begin);
+        mUpdater = updater;
+        mBegin = begin;
+    }
+
+    void snapSelection(int position, @RangeType int type) {
+        assert(position != RecyclerView.NO_POSITION);
+
+        if (mEnd == UNDEFINED || mEnd == mBegin) {
+            // Reset mEnd so it can be established in establishRange.
+            mEnd = UNDEFINED;
+            establishRange(position, type);
+        } else {
+            reviseRange(position, type);
+        }
+    }
+
+    private void establishRange(int position, @RangeType int type) {
+        assert(mEnd == UNDEFINED);
+
+        if (position == mBegin) {
+            mEnd = position;
+        }
+
+        if (position > mBegin) {
+            updateRange(mBegin + 1, position, true, type);
+        } else if (position < mBegin) {
+            updateRange(position, mBegin - 1, true, type);
+        }
+
+        mEnd = position;
+    }
+
+    private void reviseRange(int position, @RangeType int type) {
+        assert(mEnd != UNDEFINED);
+        assert(mBegin != mEnd);
+
+        if (position == mEnd) {
+            if (DEBUG) Log.v(SelectionManager.TAG, "Ignoring no-op revision for range: " + this);
+        }
+
+        if (mEnd > mBegin) {
+            reviseAscendingRange(position, type);
+        } else if (mEnd < mBegin) {
+            reviseDescendingRange(position, type);
+        }
+        // the "else" case is covered by checkState at beginning of method.
+
+        mEnd = position;
+    }
+
+    /**
+     * Updates an existing ascending seleciton.
+     * @param position
+     */
+    private void reviseAscendingRange(int position, @RangeType int type) {
+        // Reducing or reversing the range....
+        if (position < mEnd) {
+            if (position < mBegin) {
+                updateRange(mBegin + 1, mEnd, false, type);
+                updateRange(position, mBegin -1, true, type);
+            } else {
+                updateRange(position + 1, mEnd, false, type);
+            }
+        }
+
+        // Extending the range...
+        else if (position > mEnd) {
+            updateRange(mEnd + 1, position, true, type);
+        }
+    }
+
+    private void reviseDescendingRange(int position, @RangeType int type) {
+        // Reducing or reversing the range....
+        if (position > mEnd) {
+            if (position > mBegin) {
+                updateRange(mEnd, mBegin - 1, false, type);
+                updateRange(mBegin + 1, position, true, type);
+            } else {
+                updateRange(mEnd, position - 1, false, type);
+            }
+        }
+
+        // Extending the range...
+        else if (position < mEnd) {
+            updateRange(position, mEnd - 1, true, type);
+        }
+    }
+
+    /**
+     * Try to set selection state for all elements in range. Not that callbacks can cancel
+     * selection of specific items, so some or even all items may not reflect the desired state
+     * after the update is complete.
+     *
+     * @param begin Adapter position for range start (inclusive).
+     * @param end Adapter position for range end (inclusive).
+     * @param selected New selection state.
+     */
+    private void updateRange(int begin, int end, boolean selected, @RangeType int type) {
+        mUpdater.updateForRange(begin, end, selected, type);
+    }
+
+    @Override
+    public String toString() {
+        return "Range{begin=" + mBegin + ", end=" + mEnd + "}";
+    }
+
+    /*
+     * @see {@link MultiSelectManager#updateForRegularRange(int, int , boolean)} and {@link
+     * MultiSelectManager#updateForProvisionalRange(int, int, boolean)}
+     */
+    @FunctionalInterface
+    interface RangeUpdater {
+        void updateForRange(int begin, int end, boolean selected, @RangeType int type);
+    }
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/selection/Selection.java b/src/com/android/documentsui/selection/Selection.java
new file mode 100644
index 0000000..8775c58
--- /dev/null
+++ b/src/com/android/documentsui/selection/Selection.java
@@ -0,0 +1,275 @@
+/*
+ * 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.selection;
+
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.support.annotation.VisibleForTesting;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import javax.annotation.Nullable;
+
+/**
+ * Object representing the current selection. Provides read only access
+ * public access, and private write access.
+ */
+public final class Selection implements Iterable<String>, Parcelable {
+
+    // This class tracks selected items by managing two sets: the saved selection, and the total
+    // selection. Saved selections are those which have been completed by tapping an item or by
+    // completing a band select operation. Provisional selections are selections which have been
+    // temporarily created by an in-progress band select operation (once the user releases the
+    // mouse button during a band select operation, the selected items become saved). The total
+    // selection is the combination of both the saved selection and the provisional
+    // selection. Tracking both separately is necessary to ensure that saved selections do not
+    // become deselected when they are removed from the provisional selection; for example, if
+    // item A is tapped (and selected), then an in-progress band select covers A then uncovers
+    // A, A should still be selected as it has been saved. To ensure this behavior, the saved
+    // selection must be tracked separately.
+    final Set<String> mSelection;
+    final Set<String> mProvisionalSelection;
+
+    public Selection() {
+        mSelection = new HashSet<>();
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * Used by CREATOR.
+     */
+    private Selection(Set<String> selection) {
+        mSelection = selection;
+        mProvisionalSelection = new HashSet<>();
+    }
+
+    /**
+     * @param id
+     * @return true if the position is currently selected.
+     */
+    public boolean contains(@Nullable String id) {
+        return mSelection.contains(id) || mProvisionalSelection.contains(id);
+    }
+
+    /**
+     * Returns an {@link Iterator} that iterators over the selection, *excluding*
+     * any provisional selection.
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public Iterator<String> iterator() {
+        return mSelection.iterator();
+    }
+
+    /**
+     * @return size of the selection including both final and provisional selected items.
+     */
+    public int size() {
+        return mSelection.size() + mProvisionalSelection.size();
+    }
+
+    /**
+     * @return true if the selection is empty.
+     */
+    public boolean isEmpty() {
+        return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
+    }
+
+    /**
+     * Sets the provisional selection, which is a temporary selection that can be saved,
+     * canceled, or adjusted at a later time. When a new provision selection is applied, the old
+     * one (if it exists) is abandoned.
+     * @return Map of ids added or removed. Added ids have a value of true, removed are false.
+     */
+    @VisibleForTesting
+    protected Map<String, Boolean> setProvisionalSelection(Set<String> newSelection) {
+        Map<String, Boolean> delta = new HashMap<>();
+
+        for (String id: mProvisionalSelection) {
+            // Mark each item that used to be in the selection but is unsaved and not in the new
+            // provisional selection.
+            if (!newSelection.contains(id) && !mSelection.contains(id)) {
+                delta.put(id, false);
+            }
+        }
+
+        for (String id: mSelection) {
+            // Mark each item that used to be in the selection but is unsaved and not in the new
+            // provisional selection.
+            if (!newSelection.contains(id)) {
+                delta.put(id, false);
+            }
+        }
+
+        for (String id: newSelection) {
+            // Mark each item that was not previously in the selection but is in the new
+            // provisional selection.
+            if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) {
+                delta.put(id, true);
+            }
+        }
+
+        // Now, iterate through the changes and actually add/remove them to/from the current
+        // selection. This could not be done in the previous loops because changing the size of
+        // the selection mid-iteration changes iteration order erroneously.
+        for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
+            String id = entry.getKey();
+            if (entry.getValue()) {
+                mProvisionalSelection.add(id);
+            } else {
+                mProvisionalSelection.remove(id);
+            }
+        }
+
+        return delta;
+    }
+
+    /**
+     * Saves the existing provisional selection. Once the provisional selection is saved,
+     * subsequent provisional selections which are different from this existing one cannot
+     * cause items in this existing provisional selection to become deselected.
+     */
+    @VisibleForTesting
+    protected void applyProvisionalSelection() {
+        mSelection.addAll(mProvisionalSelection);
+        mProvisionalSelection.clear();
+    }
+
+    /**
+     * Abandons the existing provisional selection so that all items provisionally selected are
+     * now deselected.
+     */
+    @VisibleForTesting
+    void cancelProvisionalSelection() {
+        mProvisionalSelection.clear();
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public boolean add(String id) {
+        if (!mSelection.contains(id)) {
+            mSelection.add(id);
+            return true;
+        }
+        return false;
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    boolean remove(String id) {
+        if (mSelection.contains(id)) {
+            mSelection.remove(id);
+            return true;
+        }
+        return false;
+    }
+
+    public void clear() {
+        mSelection.clear();
+    }
+
+    /**
+     * Trims this selection to be the intersection of itself with the set of given IDs.
+     */
+    public void intersect(Collection<String> ids) {
+        mSelection.retainAll(ids);
+        mProvisionalSelection.retainAll(ids);
+    }
+
+    @VisibleForTesting
+    void copyFrom(Selection source) {
+        mSelection.clear();
+        mSelection.addAll(source.mSelection);
+
+        mProvisionalSelection.clear();
+        mProvisionalSelection.addAll(source.mProvisionalSelection);
+    }
+
+    @Override
+    public String toString() {
+        if (size() <= 0) {
+            return "size=0, items=[]";
+        }
+
+        StringBuilder buffer = new StringBuilder(size() * 28);
+        buffer.append("Selection{")
+            .append("applied{size=" + mSelection.size())
+            .append(", entries=" + mSelection)
+            .append("}, provisional{size=" + mProvisionalSelection.size())
+            .append(", entries=" + mProvisionalSelection)
+            .append("}}");
+        return buffer.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
+    }
+
+    @Override
+    public boolean equals(Object that) {
+      if (this == that) {
+          return true;
+      }
+
+      if (!(that instanceof Selection)) {
+          return false;
+      }
+
+      return mSelection.equals(((Selection) that).mSelection) &&
+              mProvisionalSelection.equals(((Selection) that).mProvisionalSelection);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeStringList(new ArrayList<>(mSelection));
+        // We don't include provisional selection since it is
+        // typically coupled to some other runtime state (like a band).
+    }
+
+    public static final ClassLoaderCreator<Selection> CREATOR =
+            new ClassLoaderCreator<Selection>() {
+        @Override
+        public Selection createFromParcel(Parcel in) {
+            return createFromParcel(in, null);
+        }
+
+        @Override
+        public Selection createFromParcel(Parcel in, ClassLoader loader) {
+            ArrayList<String> selected = new ArrayList<>();
+            in.readStringList(selected);
+
+            return new Selection(new HashSet<>(selected));
+        }
+
+        @Override
+        public Selection[] newArray(int size) {
+            return new Selection[size];
+        }
+    };
+}
\ No newline at end of file
diff --git a/src/com/android/documentsui/selection/SelectionManager.java b/src/com/android/documentsui/selection/SelectionManager.java
new file mode 100644
index 0000000..98d1cda
--- /dev/null
+++ b/src/com/android/documentsui/selection/SelectionManager.java
@@ -0,0 +1,517 @@
+/*
+ * Copyright (C) 2015 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.selection;
+
+import static com.android.documentsui.base.Shared.DEBUG;
+
+import android.annotation.IntDef;
+import android.support.v7.widget.RecyclerView;
+import android.util.Log;
+
+import com.android.documentsui.dirlist.DocumentsAdapter;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.annotation.Nullable;
+
+/**
+ * MultiSelectManager provides support traditional multi-item selection support to RecyclerView.
+ * Additionally it can be configured to restrict selection to a single element, @see
+ * #setSelectMode.
+ */
+public final class SelectionManager {
+
+    @IntDef(flag = true, value = {
+            MODE_MULTIPLE,
+            MODE_SINGLE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SelectionMode {}
+    public static final int MODE_MULTIPLE = 0;
+    public static final int MODE_SINGLE = 1;
+
+    @IntDef({
+            RANGE_REGULAR,
+            RANGE_PROVISIONAL
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface RangeType {}
+    public static final int RANGE_REGULAR = 0;
+    public static final int RANGE_PROVISIONAL = 1;
+
+    static final String TAG = "SelectionManager";
+
+    private final Selection mSelection = new Selection();
+
+    private final List<Callback> mCallbacks = new ArrayList<>(1);
+    private final List<ItemCallback> mItemCallbacks = new ArrayList<>(1);
+
+    private @Nullable DocumentsAdapter mAdapter;
+    private @Nullable Range mRanger;
+    private boolean mSingleSelect;
+
+    private RecyclerView.AdapterDataObserver mAdapterObserver;
+    private SelectionPredicate mCanSetState;
+
+    public SelectionManager(@SelectionMode int mode) {
+        mSingleSelect = mode == MODE_SINGLE;
+    }
+
+    public SelectionManager reset(DocumentsAdapter adapter, SelectionPredicate canSetState) {
+
+        mCallbacks.clear();
+        mItemCallbacks.clear();
+        if (mAdapter != null && mAdapterObserver != null) {
+            mAdapter.unregisterAdapterDataObserver(mAdapterObserver);
+        }
+
+        clearSelectionQuietly();
+
+        assert(adapter != null);
+        assert(canSetState != null);
+
+        mAdapter = adapter;
+        mCanSetState = canSetState;
+
+        mAdapterObserver = new RecyclerView.AdapterDataObserver() {
+
+            private List<String> mModelIds;
+
+            @Override
+            public void onChanged() {
+                mModelIds = mAdapter.getModelIds();
+
+                // Update the selection to remove any disappeared IDs.
+                mSelection.cancelProvisionalSelection();
+                mSelection.intersect(mModelIds);
+            }
+
+            @Override
+            public void onItemRangeChanged(
+                    int startPosition, int itemCount, Object payload) {
+                // No change in position. Ignoring.
+            }
+
+            @Override
+            public void onItemRangeInserted(int startPosition, int itemCount) {
+                mSelection.cancelProvisionalSelection();
+            }
+
+            @Override
+            public void onItemRangeRemoved(int startPosition, int itemCount) {
+                assert(startPosition >= 0);
+                assert(itemCount > 0);
+
+                mSelection.cancelProvisionalSelection();
+                // Remove any disappeared IDs from the selection.
+                mSelection.intersect(mModelIds);
+            }
+
+            @Override
+            public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
+                throw new UnsupportedOperationException();
+            }
+        };
+
+        mAdapter.registerAdapterDataObserver(mAdapterObserver);
+        return this;
+    }
+
+    void bindContoller(BandController controller) {
+        // Provides BandController with access to private mSelection state.
+        controller.bindSelection(mSelection);
+    }
+
+    /**
+     * Adds {@code callback} such that it will be notified when {@code MultiSelectManager}
+     * events occur.
+     *
+     * @param 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();
+    }
+
+    /**
+     * Returns a Selection object that provides a live view
+     * on the current selection.
+     *
+     * @see #getSelection(Selection) on how to get a snapshot
+     *     of the selection that will not reflect future changes
+     *     to selection.
+     *
+     * @return The current selection.
+     */
+    public Selection getSelection() {
+        return mSelection;
+    }
+
+    /**
+     * Updates {@code dest} to reflect the current selection.
+     * @param dest
+     *
+     * @return The Selection instance passed in, for convenience.
+     */
+    public Selection getSelection(Selection dest) {
+        dest.copyFrom(mSelection);
+        return dest;
+    }
+
+    public void replaceSelection(Iterable<String> ids) {
+        clearSelection();
+        setItemsSelected(ids, true);
+    }
+
+    /**
+     * Restores the selected state of specified items. Used in cases such as restore the selection
+     * after rotation etc.
+     */
+    public void restoreSelection(Selection other) {
+        setItemsSelectedQuietly(other.mSelection, true);
+        // NOTE: We intentionally don't restore provisional selection. It's provisional.
+        notifySelectionRestored();
+    }
+
+    /**
+     * Sets the selected state of the specified items. Note that the callback will NOT
+     * be consulted to see if an item can be selected.
+     *
+     * @param ids
+     * @param selected
+     * @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) {
+            final boolean itemChanged =
+                    selected
+                    ? canSetState(id, true) && mSelection.add(id)
+                    : canSetState(id, false) && mSelection.remove(id);
+            if (itemChanged) {
+                notifyItemStateChanged(id, selected);
+            }
+            changed |= itemChanged;
+        }
+        return changed;
+    }
+
+    /**
+     * Clears the selection and notifies (if something changes).
+     */
+    public void clearSelection() {
+        if (!hasSelection()) {
+            return;
+        }
+
+        clearSelectionQuietly();
+        notifySelectionChanged();
+    }
+
+    /**
+     * Clears the selection, without notifying selection listeners. UI elements still need to be
+     * notified about state changes so that they can update their appearance.
+     */
+    private void clearSelectionQuietly() {
+        mRanger = null;
+
+        if (!hasSelection()) {
+            return;
+        }
+
+        Selection oldSelection = getSelection(new Selection());
+        mSelection.clear();
+
+        for (String id: oldSelection.mSelection) {
+            notifyItemStateChanged(id, false);
+        }
+        for (String id: oldSelection.mProvisionalSelection) {
+            notifyItemStateChanged(id, false);
+        }
+    }
+
+    /**
+     * Toggles selection on the item with the given model ID.
+     *
+     * @param modelId
+     */
+    public void toggleSelection(String modelId) {
+        assert(modelId != null);
+
+        final boolean changed = mSelection.contains(modelId)
+                ? attemptDeselect(modelId)
+                : attemptSelect(modelId);
+
+        if (changed) {
+            notifySelectionChanged();
+        }
+    }
+
+    /**
+     * Starts a range selection. If a range selection is already active, this will start a new range
+     * selection (which will reset the range anchor).
+     *
+     * @param pos The anchor position for the selection range.
+     */
+    public void startRangeSelection(int pos) {
+        attemptSelect(mAdapter.getModelId(pos));
+        setSelectionRangeBegin(pos);
+    }
+
+    public void snapRangeSelection(int pos) {
+        snapRangeSelection(pos, RANGE_REGULAR);
+    }
+
+    void snapProvisionalRangeSelection(int pos) {
+        snapRangeSelection(pos, RANGE_PROVISIONAL);
+    }
+
+    /**
+     * Sets the end point for the current range selection, started by a call to
+     * {@link #startRangeSelection(int)}. This function should only be called when a range selection
+     * is active (see {@link #isRangeSelectionActive()}. Items in the range [anchor, end] will be
+     * selected or in provisional select, depending on the type supplied. Note that if the type is
+     * provisional select, one should do {@link Selection#applyProvisionalSelection()} at some point
+     * before calling on {@link #endRangeSelection()}.
+     *
+     * @param pos The new end position for the selection range.
+     * @param type The type of selection the range should utilize.
+     */
+    private void snapRangeSelection(int pos, @RangeType int type) {
+        if (!isRangeSelectionActive()) {
+            throw new IllegalStateException("Range start point not set.");
+        }
+
+        mRanger.snapSelection(pos, type);
+
+        // We're being lazy here notifying even when something might not have changed.
+        // To make this more correct, we'd need to update the Ranger class to return
+        // information about what has changed.
+        notifySelectionChanged();
+    }
+
+    /**
+     * Stops an in-progress range selection. All selection done with
+     * {@link #snapRangeSelection(int, int)} with type RANGE_PROVISIONAL will be lost if
+     * {@link Selection#applyProvisionalSelection()} is not called beforehand.
+     */
+    public void endRangeSelection() {
+        mRanger = null;
+        // Clean up in case there was any leftover provisional selection
+        mSelection.cancelProvisionalSelection();
+    }
+
+    /**
+     * @return Whether or not there is a current range selection active.
+     */
+    public boolean isRangeSelectionActive() {
+        return mRanger != null;
+    }
+
+    /**
+     * Sets the magic location at which a selection range begins (the selection anchor). This value
+     * is consulted when determining how to extend, and modify selection ranges. Calling this when a
+     * range selection is active will reset the range selection.
+     */
+    public void setSelectionRangeBegin(int position) {
+        if (position == RecyclerView.NO_POSITION) {
+            return;
+        }
+
+        if (mSelection.contains(mAdapter.getModelId(position))) {
+            mRanger = new Range(this::updateForRange, position);
+        }
+    }
+
+    /**
+     * @param modelId
+     * @return True if the update was applied.
+     */
+    private boolean selectAndNotify(String modelId) {
+        boolean changed = mSelection.add(modelId);
+        if (changed) {
+            notifyItemStateChanged(modelId, true);
+        }
+        return changed;
+    }
+
+    /**
+     * @param id
+     * @return True if the update was applied.
+     */
+    private boolean attemptDeselect(String id) {
+        assert(id != null);
+        if (canSetState(id, false)) {
+            mSelection.remove(id);
+            notifyItemStateChanged(id, false);
+            if (DEBUG) Log.d(TAG, "Selection after deselect: " + mSelection);
+            return true;
+        } else {
+            if (DEBUG) Log.d(TAG, "Select cancelled by listener.");
+            return false;
+        }
+    }
+
+    /**
+     * @param id
+     * @return True if the update was applied.
+     */
+    private boolean attemptSelect(String id) {
+        assert(id != null);
+        boolean canSelect = canSetState(id, true);
+        if (!canSelect) {
+            return false;
+        }
+        if (mSingleSelect && hasSelection()) {
+            clearSelectionQuietly();
+        }
+
+        selectAndNotify(id);
+        return true;
+    }
+
+    boolean canSetState(String id, boolean nextState) {
+        return mCanSetState.test(id, nextState);
+    }
+
+    /**
+     * Notifies registered listeners when the selection status of a single item
+     * (identified by {@code position}) changes.
+     */
+    void notifyItemStateChanged(String id, boolean selected) {
+        assert(id != null);
+        int lastListener = mItemCallbacks.size() - 1;
+        for (int i = lastListener; i >= 0; i--) {
+            mItemCallbacks.get(i).onItemStateChanged(id, selected);
+        }
+        mAdapter.onItemSelectionChanged(id);
+    }
+
+    /**
+     * Notifies registered listeners when the selection has changed. This
+     * notification should be sent only once a full series of changes
+     * is complete, e.g. clearingSelection, or updating the single
+     * selection from one item to another.
+     */
+    void notifySelectionChanged() {
+        int lastListener = mCallbacks.size() - 1;
+        for (int i = lastListener; i > -1; i--) {
+            mCallbacks.get(i).onSelectionChanged();
+        }
+    }
+
+    private void notifySelectionRestored() {
+        int lastListener = mCallbacks.size() - 1;
+        for (int i = lastListener; i > -1; i--) {
+            mCallbacks.get(i).onSelectionRestored();
+        }
+    }
+
+    void updateForRange(int begin, int end, boolean selected, @RangeType int type) {
+        switch (type) {
+            case RANGE_REGULAR:
+                updateForRegularRange(begin, end, selected);
+                break;
+            case RANGE_PROVISIONAL:
+                updateForProvisionalRange(begin, end, selected);
+                break;
+            default:
+                throw new IllegalArgumentException("Invalid range type: " + type);
+        }
+    }
+
+    private void updateForRegularRange(int begin, int end, boolean selected) {
+        assert(end >= begin);
+        for (int i = begin; i <= end; i++) {
+            String id = mAdapter.getModelId(i);
+            if (id == null) {
+                continue;
+            }
+
+            if (selected) {
+                boolean canSelect = canSetState(id, true);
+                if (canSelect) {
+                    if (mSingleSelect && hasSelection()) {
+                        clearSelectionQuietly();
+                    }
+                    selectAndNotify(id);
+                }
+            } else {
+                attemptDeselect(id);
+            }
+        }
+    }
+
+    private void updateForProvisionalRange(int begin, int end, boolean selected) {
+        assert (end >= begin);
+        for (int i = begin; i <= end; i++) {
+            String id = mAdapter.getModelId(i);
+            if (id == null) {
+                continue;
+            }
+            if (selected) {
+                boolean canSelect = canSetState(id, true);
+                if (canSelect) {
+                    mSelection.mProvisionalSelection.add(id);
+                }
+            } else {
+                mSelection.mProvisionalSelection.remove(id);
+            }
+            notifyItemStateChanged(id, selected);
+        }
+        notifySelectionChanged();
+    }
+
+    public interface ItemCallback {
+        void onItemStateChanged(String id, boolean selected);
+    }
+
+    public interface Callback {
+        /**
+         * Called immediately after completion of any set of changes.
+         */
+        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/selection/SelectionMetadata.java
similarity index 93%
rename from src/com/android/documentsui/dirlist/SelectionMetadata.java
rename to src/com/android/documentsui/selection/SelectionMetadata.java
index 4c5a321..e5f64d9 100644
--- a/src/com/android/documentsui/dirlist/SelectionMetadata.java
+++ b/src/com/android/documentsui/selection/SelectionMetadata.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import static com.android.documentsui.base.DocumentInfo.getCursorInt;
 import static com.android.documentsui.base.DocumentInfo.getCursorString;
@@ -31,7 +31,8 @@
 /**
  * A class that holds metadata
  */
-class SelectionMetadata implements MenuManager.SelectionDetails, MultiSelectManager.ItemCallback {
+public class SelectionMetadata
+        implements MenuManager.SelectionDetails, SelectionManager.ItemCallback {
 
     private static final String TAG = "SelectionMetadata";
 
@@ -46,7 +47,7 @@
     private int mNoDeleteCount = 0;
     private int mNoRenameCount = 0;
 
-    SelectionMetadata(Function<String, Cursor> docFinder) {
+    public SelectionMetadata(Function<String, Cursor> docFinder) {
         mDocFinder = docFinder;
     }
 
diff --git a/src/com/android/documentsui/dirlist/ViewAutoScroller.java b/src/com/android/documentsui/ui/ViewAutoScroller.java
similarity index 95%
rename from src/com/android/documentsui/dirlist/ViewAutoScroller.java
rename to src/com/android/documentsui/ui/ViewAutoScroller.java
index b6e5f00..2add5ae 100644
--- a/src/com/android/documentsui/dirlist/ViewAutoScroller.java
+++ b/src/com/android/documentsui/ui/ViewAutoScroller.java
@@ -15,14 +15,14 @@
  */
 
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.ui;
 
 import android.graphics.Point;
 
 /**
  * Provides auto-scrolling upon request when user's interaction with the application
- * introduces a natural intent to scroll. Used by {@link BandController}, {@link GestureSelector}
- * and {@link DragHoverListener} to allow auto scrolling when user either does band selection,
+ * introduces a natural intent to scroll. Used by BandController, GestureSelector,
+ * and DragHoverListener to allow auto scrolling when user either does band selection,
  * attempting to drag and drop files to somewhere off the current screen, or trying to motion select
  * past top/bottom of the screen.
  */
@@ -131,7 +131,7 @@
      * Used by {@link run} to properly calculate the proper amount of pixels to scroll given time
      * passed since scroll started, and to properly scroll / proper listener clean up if necessary.
      */
-    interface ScrollDistanceDelegate {
+    public interface ScrollDistanceDelegate {
         public Point getCurrentPosition();
         public int getViewHeight();
         public boolean isActive();
@@ -140,7 +140,7 @@
     /**
      * Used by {@link run} to do UI tasks, such as scrolling and rerunning at next UI cycle.
      */
-    interface ScrollActionDelegate {
+    public interface ScrollActionDelegate {
         public void scrollBy(int dy);
         public void runAtNextFrame(Runnable r);
         public void removeCallback(Runnable r);
diff --git a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
index 37235d9..c702c9a 100644
--- a/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
+++ b/tests/common/com/android/documentsui/dirlist/TestDocumentsAdapter.java
@@ -48,7 +48,7 @@
     }
 
     @Override
-    void onItemSelectionChanged(String id) {
+    public void onItemSelectionChanged(String id) {
     }
 
     @Override
diff --git a/tests/common/com/android/documentsui/dirlist/TestSelectionEnvironment.java b/tests/common/com/android/documentsui/dirlist/TestSelectionEnvironment.java
deleted file mode 100644
index b69787c..0000000
--- a/tests/common/com/android/documentsui/dirlist/TestSelectionEnvironment.java
+++ /dev/null
@@ -1,108 +0,0 @@
-/*
- * Copyright (C) 2015 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 android.graphics.Point;
-import android.graphics.Rect;
-import android.support.v7.widget.RecyclerView.OnScrollListener;
-
-import com.android.documentsui.dirlist.BandController.SelectionEnvironment;
-
-import java.util.List;
-
-public class TestSelectionEnvironment implements SelectionEnvironment {
-
-    public TestSelectionEnvironment(List<String> items) {
-    }
-
-    @Override
-    public void showBand(Rect rect) {
-    }
-
-    @Override
-    public void hideBand() {
-    }
-
-    @Override
-    public void addOnScrollListener(OnScrollListener listener) {
-    }
-
-    @Override
-    public void removeOnScrollListener(OnScrollListener listener) {
-    }
-
-    @Override
-    public void scrollBy(int dy) {
-    }
-
-    @Override
-    public int getHeight() {
-        return 0;
-    }
-
-    @Override
-    public void invalidateView() {
-    }
-
-    @Override
-    public void runAtNextFrame(Runnable r) {
-    }
-
-    @Override
-    public void removeCallback(Runnable r) {
-    }
-
-    @Override
-    public Point createAbsolutePoint(Point relativePoint) {
-        return null;
-    }
-
-    @Override
-    public Rect getAbsoluteRectForChildViewAt(int index) {
-        return null;
-    }
-
-    @Override
-    public int getAdapterPositionAt(int index) {
-        return 0;
-    }
-
-    @Override
-    public int getColumnCount() {
-        return 0;
-    }
-
-    @Override
-    public int getChildCount() {
-        return 0;
-    }
-
-    @Override
-    public int getVisibleChildCount() {
-        return 0;
-    }
-
-    @Override
-    public boolean isLayoutItem(int adapterPosition) {
-        return false;
-    }
-
-    @Override
-    public boolean hasView(int adapterPosition) {
-        return true;
-    }
-}
diff --git a/tests/common/com/android/documentsui/testing/MultiSelectManagers.java b/tests/common/com/android/documentsui/testing/MultiSelectManagers.java
index e3b4261..3d0f253 100644
--- a/tests/common/com/android/documentsui/testing/MultiSelectManagers.java
+++ b/tests/common/com/android/documentsui/testing/MultiSelectManagers.java
@@ -16,10 +16,10 @@
 
 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 com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.SelectionManager.SelectionMode;
+import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
 
 import java.util.Collections;
 import java.util.List;
@@ -27,15 +27,15 @@
 public class MultiSelectManagers {
     private MultiSelectManagers() {}
 
-    public static MultiSelectManager createTestInstance() {
+    public static SelectionManager createTestInstance() {
         return createTestInstance(Collections.emptyList());
     }
 
-    public static MultiSelectManager createTestInstance(List<String> docs) {
-        return createTestInstance(docs, MultiSelectManager.MODE_MULTIPLE);
+    public static SelectionManager createTestInstance(List<String> docs) {
+        return createTestInstance(docs, SelectionManager.MODE_MULTIPLE);
     }
 
-    public static MultiSelectManager createTestInstance(
+    public static SelectionManager createTestInstance(
             List<String> docs, @SelectionMode int mode) {
         return createTestInstance(
                 docs,
@@ -43,13 +43,11 @@
                 (String id, boolean nextState) -> true);
     }
 
-    public static MultiSelectManager createTestInstance(
+    public static SelectionManager createTestInstance(
             List<String> docs, @SelectionMode int mode, SelectionPredicate canSetState) {
         TestDocumentsAdapter adapter = new TestDocumentsAdapter(docs);
-        MultiSelectManager manager = new MultiSelectManager(
-                adapter,
-                mode,
-                canSetState);
+        SelectionManager manager = new SelectionManager(mode);
+        manager.reset(adapter, canSetState);
 
         return manager;
     }
diff --git a/tests/common/com/android/documentsui/testing/dirlist/SelectionProbe.java b/tests/common/com/android/documentsui/testing/dirlist/SelectionProbe.java
index 12f4642..9cadf71 100644
--- a/tests/common/com/android/documentsui/testing/dirlist/SelectionProbe.java
+++ b/tests/common/com/android/documentsui/testing/dirlist/SelectionProbe.java
@@ -20,17 +20,17 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.documentsui.dirlist.MultiSelectManager;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.Selection;
 
 /**
  * Helper class for making assertions against the state of a MultiSelectManager instance.
  */
 public final class SelectionProbe {
 
-    private final MultiSelectManager mMgr;
+    private final SelectionManager mMgr;
 
-    public SelectionProbe(MultiSelectManager mgr) {
+    public SelectionProbe(SelectionManager mgr) {
         mMgr = mgr;
     }
 
diff --git a/tests/common/com/android/documentsui/testing/dirlist/TestSelectionListener.java b/tests/common/com/android/documentsui/testing/dirlist/TestSelectionListener.java
index 06c2219..14eac8f 100644
--- a/tests/common/com/android/documentsui/testing/dirlist/TestSelectionListener.java
+++ b/tests/common/com/android/documentsui/testing/dirlist/TestSelectionListener.java
@@ -19,9 +19,9 @@
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 
-import com.android.documentsui.dirlist.MultiSelectManager;
+import com.android.documentsui.selection.SelectionManager;
 
-public final class TestSelectionListener implements MultiSelectManager.Callback {
+public final class TestSelectionListener implements SelectionManager.Callback {
 
     private boolean mSelectionChanged = false;
 
diff --git a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
index 27479ed..daae554 100644
--- a/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DragScrollListenerTest.java
@@ -24,10 +24,10 @@
 import android.view.View;
 
 import com.android.documentsui.ItemDragListener;
-import com.android.documentsui.dirlist.ViewAutoScroller.ScrollActionDelegate;
 import com.android.documentsui.testing.DragEvents;
 import com.android.documentsui.testing.TestTimer;
 import com.android.documentsui.testing.Views;
+import com.android.documentsui.ui.ViewAutoScroller.ScrollActionDelegate;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
index cec5ba8..6ad40bb 100644
--- a/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/DragStartListenerTest.java
@@ -24,7 +24,8 @@
 
 import com.android.documentsui.base.State;
 import com.android.documentsui.base.Events.InputEvent;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.SelectionManager;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.testing.TestEvent;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.Views;
@@ -36,7 +37,7 @@
 
     private DragStartListener mListener;
     private TestEvent.Builder mEvent;
-    private MultiSelectManager mMultiSelectManager;
+    private SelectionManager mMultiSelectManager;
     private String mViewModelId;
     private boolean mDragStarted;
 
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_KeyboardTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_KeyboardTest.java
index b475a21..e8b1d4a 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_KeyboardTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_KeyboardTest.java
@@ -22,6 +22,7 @@
 import android.view.MotionEvent;
 
 import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.TestActionHandler;
 import com.android.documentsui.testing.TestEvent;
@@ -57,7 +58,7 @@
 
     @Before
     public void setUp() {
-        MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
+        SelectionManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
 
         mActionHandler = new TestActionHandler();
         mSelection = new SelectionProbe(selectionMgr);
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
index 5b79c87..a77b2d8 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_MouseTest.java
@@ -24,6 +24,7 @@
 import android.view.MotionEvent;
 
 import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.TestActionHandler;
 import com.android.documentsui.testing.TestEvent;
@@ -58,7 +59,7 @@
     @Before
     public void setUp() {
 
-        MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
+        SelectionManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
         mActionHandler = new TestActionHandler();
 
         mSelection = new SelectionProbe(selectionMgr);
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
index 3087ef1..28aae70 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_RangeTest.java
@@ -21,6 +21,7 @@
 import android.view.MotionEvent;
 
 import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.TestActionHandler;
 import com.android.documentsui.testing.TestEvent;
@@ -59,7 +60,7 @@
     @Before
     public void setUp() {
 
-        MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
+        SelectionManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
         mActionHandler = new TestActionHandler();
 
         mSelection = new SelectionProbe(selectionMgr);
diff --git a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
index 1f11cde..2eebc8c 100644
--- a/tests/unit/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
+++ b/tests/unit/com/android/documentsui/dirlist/UserInputHandler_TouchTest.java
@@ -24,6 +24,7 @@
 import android.view.MotionEvent;
 
 import com.android.documentsui.base.Events.InputEvent;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.TestActionHandler;
 import com.android.documentsui.testing.TestEvent;
@@ -58,7 +59,7 @@
 
     @Before
     public void setUp() {
-        MultiSelectManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
+        SelectionManager selectionMgr = MultiSelectManagers.createTestInstance(ITEMS);
 
         mActionHandler = new TestActionHandler();
 
diff --git a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
index 496fb51..5ec0e35 100644
--- a/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/files/ActionHandlerTest.java
@@ -28,7 +28,7 @@
 import com.android.documentsui.R;
 import com.android.documentsui.base.DocumentInfo;
 import com.android.documentsui.base.RootInfo;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.testing.TestConfirmationCallback;
 import com.android.documentsui.testing.TestEnv;
 import com.android.documentsui.testing.TestRootsAccess;
diff --git a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
index 8a11b08..5f1d567 100644
--- a/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
+++ b/tests/unit/com/android/documentsui/picker/ActionHandlerTest.java
@@ -30,7 +30,7 @@
 import com.android.documentsui.R;
 import com.android.documentsui.base.RootInfo;
 import com.android.documentsui.base.Shared;
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.testing.TestEnv;
 import com.android.documentsui.testing.TestRootsAccess;
 import com.android.documentsui.ui.TestDialogController;
diff --git a/tests/unit/com/android/documentsui/dirlist/BandController_GridModelTest.java b/tests/unit/com/android/documentsui/selection/BandController_GridModelTest.java
similarity index 97%
rename from tests/unit/com/android/documentsui/dirlist/BandController_GridModelTest.java
rename to tests/unit/com/android/documentsui/selection/BandController_GridModelTest.java
index 59547ad..4ce161d 100644
--- a/tests/unit/com/android/documentsui/dirlist/BandController_GridModelTest.java
+++ b/tests/unit/com/android/documentsui/selection/BandController_GridModelTest.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
-import static com.android.documentsui.dirlist.BandController.GridModel.NOT_SET;
+import static com.android.documentsui.selection.BandController.GridModel.NOT_SET;
 
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -24,7 +24,8 @@
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 
-import com.android.documentsui.dirlist.BandController.GridModel;
+import com.android.documentsui.dirlist.TestDocumentsAdapter;
+import com.android.documentsui.selection.BandController.GridModel;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -69,7 +70,7 @@
         };
 
         viewWidth = VIEW_PADDING_PX + numColumns * (VIEW_PADDING_PX + CHILD_VIEW_EDGE_PX);
-        model = new GridModel(env, adapter);
+        model = new GridModel(env, (int pos) -> true, adapter);
         model.addOnSelectionChangedListener(
                 new GridModel.OnSelectionChangedListener() {
                     @Override
@@ -444,11 +445,6 @@
         }
 
         @Override
-        public boolean isLayoutItem(int adapterPosition) {
-            return false;
-        }
-
-        @Override
         public boolean hasView(int adapterPosition) {
             return true;
         }
diff --git a/tests/unit/com/android/documentsui/dirlist/GestureSelectorTest.java b/tests/unit/com/android/documentsui/selection/GestureSelectorTest.java
similarity index 95%
rename from tests/unit/com/android/documentsui/dirlist/GestureSelectorTest.java
rename to tests/unit/com/android/documentsui/selection/GestureSelectorTest.java
index 266854a..85a857a 100644
--- a/tests/unit/com/android/documentsui/dirlist/GestureSelectorTest.java
+++ b/tests/unit/com/android/documentsui/selection/GestureSelectorTest.java
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import android.test.AndroidTestCase;
 import android.test.suitebuilder.annotation.SmallTest;
 import android.view.View;
 
+import com.android.documentsui.selection.GestureSelector;
 import com.android.documentsui.testing.TestEvent;
 
 @SmallTest
diff --git a/tests/unit/com/android/documentsui/dirlist/MultiSelectManagerTest.java b/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
similarity index 96%
rename from tests/unit/com/android/documentsui/dirlist/MultiSelectManagerTest.java
rename to tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
index fef5672..6b54577 100644
--- a/tests/unit/com/android/documentsui/dirlist/MultiSelectManagerTest.java
+++ b/tests/unit/com/android/documentsui/selection/SelectionManagerTest.java
@@ -14,13 +14,15 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 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.selection.SelectionManager;
+import com.android.documentsui.dirlist.TestData;
+import com.android.documentsui.selection.Selection;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.dirlist.SelectionProbe;
 import com.android.documentsui.testing.dirlist.TestSelectionListener;
@@ -36,12 +38,12 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class MultiSelectManagerTest {
+public class SelectionManagerTest {
 
     private static final List<String> ITEMS = TestData.create(100);
 
     private final Set<String> mIgnored = new HashSet<>();
-    private MultiSelectManager mManager;
+    private SelectionManager mManager;
     private TestSelectionListener mCallback;
     private SelectionProbe mSelection;
 
@@ -50,7 +52,7 @@
         mCallback = new TestSelectionListener();
         mManager = MultiSelectManagers.createTestInstance(
                 ITEMS,
-                MultiSelectManager.MODE_MULTIPLE,
+                SelectionManager.MODE_MULTIPLE,
                 (String id, boolean nextState) -> (!nextState || !mIgnored.contains(id)));
         mManager.addCallback(mCallback);
 
diff --git a/tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java b/tests/unit/com/android/documentsui/selection/SelectionManager_SingleSelectTest.java
similarity index 86%
rename from tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
rename to tests/unit/com/android/documentsui/selection/SelectionManager_SingleSelectTest.java
index 020f316..e659d49 100644
--- a/tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SingleSelectTest.java
+++ b/tests/unit/com/android/documentsui/selection/SelectionManager_SingleSelectTest.java
@@ -14,13 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import static junit.framework.Assert.fail;
 
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.documentsui.dirlist.TestData;
+import com.android.documentsui.dirlist.TestDocumentsAdapter;
+import com.android.documentsui.selection.SelectionManager;
 import com.android.documentsui.testing.MultiSelectManagers;
 import com.android.documentsui.testing.dirlist.SelectionProbe;
 import com.android.documentsui.testing.dirlist.TestSelectionListener;
@@ -33,11 +36,11 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class MultiSelectManager_SingleSelectTest {
+public class SelectionManager_SingleSelectTest {
 
     private static final List<String> ITEMS = TestData.create(100);
 
-    private MultiSelectManager mManager;
+    private SelectionManager mManager;
     private TestSelectionListener mCallback;
     private TestDocumentsAdapter mAdapter;
     private SelectionProbe mSelection;
@@ -45,7 +48,7 @@
     @Before
     public void setUp() throws Exception {
         mCallback = new TestSelectionListener();
-        mManager = MultiSelectManagers.createTestInstance(ITEMS, MultiSelectManager.MODE_SINGLE);
+        mManager = MultiSelectManagers.createTestInstance(ITEMS, SelectionManager.MODE_SINGLE);
         mManager.addCallback(mCallback);
 
         mSelection = new SelectionProbe(mManager);
diff --git a/tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java b/tests/unit/com/android/documentsui/selection/SelectionTest.java
similarity index 96%
rename from tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
rename to tests/unit/com/android/documentsui/selection/SelectionTest.java
index dcdc842..ad1422a 100644
--- a/tests/unit/com/android/documentsui/dirlist/MultiSelectManager_SelectionTest.java
+++ b/tests/unit/com/android/documentsui/selection/SelectionTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.selection;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
@@ -23,7 +23,7 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
-import com.android.documentsui.dirlist.MultiSelectManager.Selection;
+import com.android.documentsui.selection.Selection;
 
 import com.google.common.collect.Sets;
 
@@ -36,7 +36,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class MultiSelectManager_SelectionTest {
+public class SelectionTest {
 
     private Selection selection;
 
diff --git a/tests/unit/com/android/documentsui/dirlist/ViewAutoScrollerTest.java b/tests/unit/com/android/documentsui/ui/ViewAutoScrollerTest.java
similarity index 97%
rename from tests/unit/com/android/documentsui/dirlist/ViewAutoScrollerTest.java
rename to tests/unit/com/android/documentsui/ui/ViewAutoScrollerTest.java
index f7a148a..5ad5e3c 100644
--- a/tests/unit/com/android/documentsui/dirlist/ViewAutoScrollerTest.java
+++ b/tests/unit/com/android/documentsui/ui/ViewAutoScrollerTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.documentsui.dirlist;
+package com.android.documentsui.ui;
 
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
@@ -23,6 +23,8 @@
 import android.support.test.filters.SmallTest;
 import android.support.test.runner.AndroidJUnit4;
 
+import com.android.documentsui.ui.ViewAutoScroller;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;