Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit f43ced0f authored by Steve McKay's avatar Steve McKay Committed by android-build-merger
Browse files

Preserve selection across device rotation.

am: e852d93e

* commit 'e852d93e':
  Preserve selection across device rotation.
parents 61fc472e e852d93e
Loading
Loading
Loading
Loading
+7 −1
Original line number Original line Diff line number Diff line
@@ -25,6 +25,7 @@ import android.os.Parcelable;
import android.util.Log;
import android.util.Log;
import android.util.SparseArray;
import android.util.SparseArray;


import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.DurableUtils;
import com.android.documentsui.model.DurableUtils;
@@ -99,8 +100,11 @@ public class State implements android.os.Parcelable {
    /** Instance state for every shown directory */
    /** Instance state for every shown directory */
    public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();
    public HashMap<String, SparseArray<Parcelable>> dirState = new HashMap<>();


    /** UI selection */
    public Selection selectedDocuments = new Selection();

    /** Currently copying file */
    /** Currently copying file */
    public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<DocumentInfo>();
    public List<DocumentInfo> selectedDocumentsForCopy = new ArrayList<>();


    /** Name of the package that started DocsUI */
    /** Name of the package that started DocsUI */
    public List<String> excludedAuthorities = new ArrayList<>();
    public List<String> excludedAuthorities = new ArrayList<>();
@@ -173,6 +177,7 @@ public class State implements android.os.Parcelable {
        DurableUtils.writeToParcel(out, stack);
        DurableUtils.writeToParcel(out, stack);
        out.writeString(currentSearch);
        out.writeString(currentSearch);
        out.writeMap(dirState);
        out.writeMap(dirState);
        out.writeParcelable(selectedDocuments, 0);
        out.writeList(selectedDocumentsForCopy);
        out.writeList(selectedDocumentsForCopy);
        out.writeList(excludedAuthorities);
        out.writeList(excludedAuthorities);
        out.writeInt(openableOnly ? 1 : 0);
        out.writeInt(openableOnly ? 1 : 0);
@@ -203,6 +208,7 @@ public class State implements android.os.Parcelable {
            DurableUtils.readFromParcel(in, state.stack);
            DurableUtils.readFromParcel(in, state.stack);
            state.currentSearch = in.readString();
            state.currentSearch = in.readString();
            in.readMap(state.dirState, loader);
            in.readMap(state.dirState, loader);
            state.selectedDocuments = in.readParcelable(loader);
            in.readList(state.selectedDocumentsForCopy, loader);
            in.readList(state.selectedDocumentsForCopy, loader);
            in.readList(state.excludedAuthorities, loader);
            in.readList(state.excludedAuthorities, loader);
            state.openableOnly = in.readInt() != 0;
            state.openableOnly = in.readInt() != 0;
+28 −6
Original line number Original line Diff line number Diff line
@@ -211,9 +211,6 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
            final View view = mRecView.getChildAt(i);
            final View view = mRecView.getChildAt(i);
            cancelThumbnailTask(view);
            cancelThumbnailTask(view);
        }
        }

        // Clear any outstanding selection
        mSelectionManager.clearSelection();
    }
    }


    @Override
    @Override
@@ -225,6 +222,7 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi


        final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
        final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
        final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
        final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);
        mStateKey = buildStateKey(root, doc);


        mIconHelper = new IconHelper(context, MODE_GRID);
        mIconHelper = new IconHelper(context, MODE_GRID);


@@ -244,6 +242,13 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi


        mRecView.addOnItemTouchListener(mGestureDetector);
        mRecView.addOnItemTouchListener(mGestureDetector);


        // final here because we'll manually bump the listener iwhen we had an initial selection,
        // but only after the model is fully loaded.
        final SelectionModeListener selectionListener = new SelectionModeListener();
        final Selection initialSelection = state.selectedDocuments.hasDirectoryKey(mStateKey)
            ? state.selectedDocuments
            : null;

        // TODO: instead of inserting the view into the constructor, extract listener-creation code
        // 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
        // and set the listener on the view after the fact.  Then the view doesn't need to be passed
        // into the selection manager.
        // into the selection manager.
@@ -252,15 +257,16 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
                mAdapter,
                mAdapter,
                state.allowMultiple
                state.allowMultiple
                    ? MultiSelectManager.MODE_MULTIPLE
                    ? MultiSelectManager.MODE_MULTIPLE
                    : MultiSelectManager.MODE_SINGLE);
                    : MultiSelectManager.MODE_SINGLE,
        mSelectionManager.addCallback(new SelectionModeListener());
                initialSelection);

        mSelectionManager.addCallback(selectionListener);


        mModel = new Model();
        mModel = new Model();
        mModel.addUpdateListener(mAdapter);
        mModel.addUpdateListener(mAdapter);
        mModel.addUpdateListener(mModelUpdateListener);
        mModel.addUpdateListener(mModelUpdateListener);


        mType = getArguments().getInt(EXTRA_TYPE);
        mType = getArguments().getInt(EXTRA_TYPE);
        mStateKey = buildStateKey(root, doc);


        mTuner = FragmentTuner.pick(getContext(), state);
        mTuner = FragmentTuner.pick(getContext(), state);
        mClipper = new DocumentClipper(context);
        mClipper = new DocumentClipper(context);
@@ -320,6 +326,10 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi


                updateDisplayState();
                updateDisplayState();


                if (initialSelection != null) {
                    selectionListener.onSelectionChanged();
                }

                // Restore any previous instance state
                // Restore any previous instance state
                final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
                final SparseArray<Parcelable> container = state.dirState.remove(mStateKey);
                if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
                if (container != null && !getArguments().getBoolean(EXTRA_IGNORE_STATE, false)) {
@@ -346,6 +356,18 @@ public class DirectoryFragment extends Fragment implements DocumentsAdapter.Envi
        getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
        getLoaderManager().restartLoader(LOADER_ID, null, mCallbacks);
    }
    }


    @Override
    public void onSaveInstanceState(Bundle outState) {
        State state = getDisplayState();
        if (mSelectionManager.hasSelection()) {
            mSelectionManager.getSelection(state.selectedDocuments);
            state.selectedDocuments.setDirectoryKey(mStateKey);
            if (!state.selectedDocuments.isEmpty()) {
                if (DEBUG) Log.d(TAG, "Persisted selection: " + state.selectedDocuments);
            }
        }
    }

    @Override
    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        // There's only one request code right now. Replace this with a switch statement or
        // There's only one request code right now. Replace this with a switch statement or
+123 −54
Original line number Original line Diff line number Diff line
@@ -23,9 +23,12 @@ import static com.android.internal.util.Preconditions.checkArgument;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;
import static com.android.internal.util.Preconditions.checkState;


import android.annotation.IntDef;
import android.graphics.Point;
import android.graphics.Point;
import android.graphics.Rect;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.Drawable;
import android.os.Parcel;
import android.os.Parcelable;
import android.support.annotation.Nullable;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager;
@@ -41,12 +44,13 @@ import com.android.documentsui.Events.InputEvent;
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.Events.MotionInputEvent;
import com.android.documentsui.R;
import com.android.documentsui.R;


import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collection;
import java.util.Collections;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashMap;
import java.util.HashSet;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.List;
import java.util.Map;
import java.util.Map;
import java.util.Set;
import java.util.Set;
@@ -58,10 +62,13 @@ import java.util.Set;
 */
 */
public final class MultiSelectManager {
public final class MultiSelectManager {


    /** Selection mode for multiple select. **/
    @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_MULTIPLE = 0;

    /** Selection mode for multiple select. **/
    public static final int MODE_SINGLE = 1;
    public static final int MODE_SINGLE = 1;


    private static final String TAG = "MultiSelectManager";
    private static final String TAG = "MultiSelectManager";
@@ -79,14 +86,19 @@ public final class MultiSelectManager {




    /**
    /**
     * @param recyclerView
     * @param mode Selection single or multiple selection mode.
     * @param mode Selection mode
     * @param initialSelection selection state probably preserved in external state.
     */
     */
    public MultiSelectManager(
    public MultiSelectManager(
            final RecyclerView recyclerView, DocumentsAdapter adapter, int mode) {
            final RecyclerView recyclerView,
        this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode);
            DocumentsAdapter adapter,
            @SelectionMode int mode,
            @Nullable Selection initialSelection) {

        this(new RuntimeSelectionEnvironment(recyclerView), adapter, mode, initialSelection);


        if (mode == MODE_MULTIPLE) {
        if (mode == MODE_MULTIPLE) {
            // TODO: Don't load this on low memory devices.
            mBandManager = new BandController();
            mBandManager = new BandController();
        }
        }


@@ -116,10 +128,18 @@ public final class MultiSelectManager {
     * @hide
     * @hide
     */
     */
    @VisibleForTesting
    @VisibleForTesting
    MultiSelectManager(SelectionEnvironment environment, DocumentsAdapter adapter, int mode) {
    MultiSelectManager(
            SelectionEnvironment environment,
            DocumentsAdapter adapter,
            @SelectionMode int mode,
            @Nullable Selection initialSelection) {

        mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
        mEnvironment = checkNotNull(environment, "'environment' cannot be null.");
        mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
        mAdapter = checkNotNull(adapter, "'adapter' cannot be null.");
        mSingleSelect = mode == MODE_SINGLE;
        mSingleSelect = mode == MODE_SINGLE;
        if (initialSelection != null) {
            mSelection.copyFrom(initialSelection);
        }


        mAdapter.registerAdapterDataObserver(
        mAdapter.registerAdapterDataObserver(
                new RecyclerView.AdapterDataObserver() {
                new RecyclerView.AdapterDataObserver() {
@@ -202,6 +222,13 @@ public final class MultiSelectManager {
        return dest;
        return dest;
    }
    }


    /**
     * Updates selection to include items in {@code selection}.
     */
    public void updateSelection(Selection selection) {
        setItemsSelected(selection.toList(), true);
    }

    /**
    /**
     * Sets the selected state of the specified items. Note that the callback will NOT
     * Sets the selected state of the specified items. Note that the callback will NOT
     * be consulted to see if an item can be selected.
     * be consulted to see if an item can be selected.
@@ -615,7 +642,7 @@ public final class MultiSelectManager {
     * Object representing the current selection. Provides read only access
     * Object representing the current selection. Provides read only access
     * public access, and private write access.
     * public access, and private write access.
     */
     */
    public static final class Selection {
    public static final class Selection implements Parcelable {


        // This class tracks selected items by managing two sets: the saved selection, and the total
        // 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
        // selection. Saved selections are those which have been completed by tapping an item or by
@@ -628,8 +655,9 @@ public final class MultiSelectManager {
        // item A is tapped (and selected), then an in-progress band select covers A then uncovers
        // 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
        // A, A should still be selected as it has been saved. To ensure this behavior, the saved
        // selection must be tracked separately.
        // selection must be tracked separately.
        private Set<String> mSavedSelection = new HashSet<>();
        private Set<String> mSelection = new HashSet<>();
        private Set<String> mTotalSelection = new HashSet<>();
        private Set<String> mProvisionalSelection = new HashSet<>();
        private String mDirectoryKey;


        @VisibleForTesting
        @VisibleForTesting
        public Selection(String... ids) {
        public Selection(String... ids) {
@@ -643,53 +671,70 @@ public final class MultiSelectManager {
         * @return true if the position is currently selected.
         * @return true if the position is currently selected.
         */
         */
        public boolean contains(@Nullable String id) {
        public boolean contains(@Nullable String id) {
            return mTotalSelection.contains(id);
            return mSelection.contains(id) || mProvisionalSelection.contains(id);
        }
        }


        /**
        /**
         * Returns an unordered array of selected positions.
         * Returns an unordered array of selected positions.
         */
         */
        public String[] getAll() {
        public String[] getAll() {
            return mTotalSelection.toArray(new String[0]);
            return toList().toArray(new String[0]);
        }

        /**
         * Returns an unordered array of selected positions (including any
         * provisional selections current in effect).
         */
        private List<String> toList() {
            ArrayList<String> selection = new ArrayList<String>(mSelection);
            selection.addAll(mProvisionalSelection);
            return selection;
        }
        }


        /**
        /**
         * @return size of the selection.
         * @return size of the selection.
         */
         */
        public int size() {
        public int size() {
            return mTotalSelection.size();
            return mSelection.size() + mProvisionalSelection.size();
        }
        }


        /**
        /**
         * @return true if the selection is empty.
         * @return true if the selection is empty.
         */
         */
        public boolean isEmpty() {
        public boolean isEmpty() {
            return mTotalSelection.isEmpty();
            return mSelection.isEmpty() && mProvisionalSelection.isEmpty();
        }
        }


        /**
        /**
         * Sets the provisional selection, which is a temporary selection that can be saved,
         * 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
         * canceled, or adjusted at a later time. When a new provision selection is applied, the old
         * one (if it exists) is abandoned.
         * one (if it exists) is abandoned.
         * @return Array with entry for each position added or removed. Entries which were added
         * @return Map of ids added or removed. Added ids have a value of true, removed are false.
         *     contain a value of true, and entries which were removed contain a value of false.
         */
         */
        @VisibleForTesting
        @VisibleForTesting
        protected Map<String, Boolean> setProvisionalSelection(Set<String> provisionalSelection) {
        protected Map<String, Boolean> setProvisionalSelection(Set<String> newSelection) {
            Map<String, Boolean> delta = new HashMap<>();
            Map<String, Boolean> delta = new HashMap<>();


            for (String id: mTotalSelection) {
            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
                // Mark each item that used to be in the selection but is unsaved and not in the new
                // provisional selection.
                // provisional selection.
                if (!provisionalSelection.contains(id) && !mSavedSelection.contains(id)) {
                if (!newSelection.contains(id)) {
                    delta.put(id, false);
                    delta.put(id, false);
                }
                }
            }
            }


            for (String id: provisionalSelection) {
            for (String id: newSelection) {
                // Mark each item that was not previously in the selection but is in the new
                // Mark each item that was not previously in the selection but is in the new
                // provisional selection.
                // provisional selection.
                if (!mTotalSelection.contains(id)) {
                if (!mSelection.contains(id) && !mProvisionalSelection.contains(id)) {
                    delta.put(id, true);
                    delta.put(id, true);
                }
                }
            }
            }
@@ -700,9 +745,9 @@ public final class MultiSelectManager {
            for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
            for (Map.Entry<String, Boolean> entry: delta.entrySet()) {
                String id = entry.getKey();
                String id = entry.getKey();
                if (entry.getValue()) {
                if (entry.getValue()) {
                    mTotalSelection.add(id);
                    mProvisionalSelection.add(id);
                } else {
                } else {
                    mTotalSelection.remove(id);
                    mProvisionalSelection.remove(id);
                }
                }
            }
            }


@@ -716,7 +761,8 @@ public final class MultiSelectManager {
         */
         */
        @VisibleForTesting
        @VisibleForTesting
        protected void applyProvisionalSelection() {
        protected void applyProvisionalSelection() {
            mSavedSelection = new HashSet<>(mTotalSelection);
            mSelection.addAll(mProvisionalSelection);
            mProvisionalSelection.clear();
        }
        }


        /**
        /**
@@ -725,15 +771,14 @@ public final class MultiSelectManager {
         */
         */
        @VisibleForTesting
        @VisibleForTesting
        void cancelProvisionalSelection() {
        void cancelProvisionalSelection() {
            mTotalSelection = new HashSet<>(mSavedSelection);
            mProvisionalSelection.clear();
        }
        }


        /** @hide */
        /** @hide */
        @VisibleForTesting
        @VisibleForTesting
        boolean add(String id) {
        boolean add(String id) {
            if (!mTotalSelection.contains(id)) {
            if (!mSelection.contains(id)) {
                mTotalSelection.add(id);
                mSelection.add(id);
                mSavedSelection.add(id);
                return true;
                return true;
            }
            }
            return false;
            return false;
@@ -742,31 +787,29 @@ public final class MultiSelectManager {
        /** @hide */
        /** @hide */
        @VisibleForTesting
        @VisibleForTesting
        boolean remove(String id) {
        boolean remove(String id) {
            if (mTotalSelection.contains(id)) {
            if (mSelection.contains(id)) {
                mTotalSelection.remove(id);
                mSelection.remove(id);
                mSavedSelection.remove(id);
                return true;
                return true;
            }
            }
            return false;
            return false;
        }
        }


        public void clear() {
        public void clear() {
            mSavedSelection.clear();
            mSelection.clear();
            mTotalSelection.clear();
        }
        }


        /**
        /**
         * Trims this selection to be the intersection of itself with the set of given IDs.
         * Trims this selection to be the intersection of itself with the set of given IDs.
         */
         */
        public void intersect(Collection<String> ids) {
        public void intersect(Collection<String> ids) {
            mSavedSelection.retainAll(ids);
            mSelection.retainAll(ids);
            mTotalSelection.retainAll(ids);
            mProvisionalSelection.retainAll(ids);
        }
        }


        @VisibleForTesting
        @VisibleForTesting
        void copyFrom(Selection source) {
        void copyFrom(Selection source) {
            mSavedSelection = new HashSet<>(source.mSavedSelection);
            mSelection = new HashSet<>(source.mSelection);
            mTotalSelection = new HashSet<>(source.mTotalSelection);
            mProvisionalSelection = new HashSet<>(source.mProvisionalSelection);
        }
        }


        @Override
        @Override
@@ -775,24 +818,19 @@ public final class MultiSelectManager {
                return "size=0, items=[]";
                return "size=0, items=[]";
            }
            }


            StringBuilder buffer = new StringBuilder(mTotalSelection.size() * 28);
            StringBuilder buffer = new StringBuilder(size() * 28);
            buffer.append("{size=")
            buffer.append("Selection{")
                    .append(mTotalSelection.size())
                .append("applied{size=" + mSelection.size())
                    .append(", ")
                .append(", entries=" + mSelection)
                    .append("items=[");
                .append("}, provisional{size=" + mProvisionalSelection.size())
            for (Iterator<String> i = mTotalSelection.iterator(); i.hasNext(); ) {
                .append(", entries=" + mProvisionalSelection)
                buffer.append(i.next());
                .append("}}");
                if (i.hasNext()) {
                    buffer.append(", ");
                }
            }
            buffer.append("]}");
            return buffer.toString();
            return buffer.toString();
        }
        }


        @Override
        @Override
        public int hashCode() {
        public int hashCode() {
            return mSavedSelection.hashCode() ^ mTotalSelection.hashCode();
            return mSelection.hashCode() ^ mProvisionalSelection.hashCode();
        }
        }


        @Override
        @Override
@@ -805,8 +843,39 @@ public final class MultiSelectManager {
              return false;
              return false;
          }
          }


          return mSavedSelection.equals(((Selection) that).mSavedSelection) &&
          return mSelection.equals(((Selection) that).mSelection) &&
                  mTotalSelection.equals(((Selection) that).mTotalSelection);
                  mProvisionalSelection.equals(((Selection) that).mProvisionalSelection);
        }

        /**
         * Sets the state key for this selection, which allows us to match selections
         * to particular states (of DirectoryFragment). Basically this lets us avoid
         * loading a persisted selection in the wrong directory.
         */
        public void setDirectoryKey(String key) {
            mDirectoryKey = key;
        }

        /**
         * Sets the state key for this selection, which allows us to match selections
         * to particular states (of DirectoryFragment). Basically this lets us avoid
         * loading a persisted selection in the wrong directory.
         */
        public boolean hasDirectoryKey(String key) {
            return key.equals(mDirectoryKey);
        }

        @Override
        public int describeContents() {
            return 0;
        }

        public void writeToParcel(Parcel dest, int flags) {
            checkState(mDirectoryKey != null);
            dest.writeString(mDirectoryKey);
            dest.writeList(new ArrayList<>(mSelection));
            // We don't include provisional selection since it is
            // typically coupled to some other runtime state (like a band).
        }
        }
    }
    }


+59 −10
Original line number Original line Diff line number Diff line
@@ -50,7 +50,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
        mCallback = new TestCallback();
        mCallback = new TestCallback();
        mEnv = new TestSelectionEnvironment(items);
        mEnv = new TestSelectionEnvironment(items);
        mAdapter = new TestDocumentsAdapter(items);
        mAdapter = new TestDocumentsAdapter(items);
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE);
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_MULTIPLE, null);
        mManager.addCallback(mCallback);
        mManager.addCallback(mCallback);
    }
    }


@@ -174,7 +174,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
    }
    }


    public void testSingleSelectMode() {
    public void testSingleSelectMode() {
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
        mManager.addCallback(mCallback);
        mManager.addCallback(mCallback);
        longPress(20);
        longPress(20);
        tap(13);
        tap(13);
@@ -182,7 +182,7 @@ public class MultiSelectManagerTest extends AndroidTestCase {
    }
    }


    public void testSingleSelectMode_ShiftTap() {
    public void testSingleSelectMode_ShiftTap() {
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE);
        mManager = new MultiSelectManager(mEnv, mAdapter, MultiSelectManager.MODE_SINGLE, null);
        mManager.addCallback(mCallback);
        mManager.addCallback(mCallback);
        longPress(13);
        longPress(13);
        shiftTap(20);
        shiftTap(20);
@@ -198,24 +198,73 @@ public class MultiSelectManagerTest extends AndroidTestCase {
        provisional.append(2, true);
        provisional.append(2, true);
        s.setProvisionalSelection(getItemIds(provisional));
        s.setProvisionalSelection(getItemIds(provisional));
        assertSelection(items.get(1), items.get(2));
        assertSelection(items.get(1), items.get(2));
    }

    public void testProvisionalSelection_Replace() {
        Selection s = mManager.getSelection();


        provisional.delete(1);
        SparseBooleanArray provisional = new SparseBooleanArray();
        provisional.append(1, true);
        provisional.append(2, true);
        s.setProvisionalSelection(getItemIds(provisional));

        provisional.clear();
        provisional.append(3, true);
        provisional.append(3, true);
        provisional.append(4, true);
        s.setProvisionalSelection(getItemIds(provisional));
        s.setProvisionalSelection(getItemIds(provisional));
        assertSelection(items.get(2), items.get(3));
        assertSelection(items.get(3), items.get(4));
    }


        s.applyProvisionalSelection();
    public void testProvisionalSelection_IntersectsExistingProvisionalSelection() {
        assertSelection(items.get(2), items.get(3));
        Selection s = mManager.getSelection();

        SparseBooleanArray provisional = new SparseBooleanArray();
        provisional.append(1, true);
        provisional.append(2, true);
        s.setProvisionalSelection(getItemIds(provisional));


        provisional.clear();
        provisional.clear();
        provisional.append(1, true);
        s.setProvisionalSelection(getItemIds(provisional));
        assertSelection(items.get(1));
    }

    public void testProvisionalSelection_Apply() {
        Selection s = mManager.getSelection();

        SparseBooleanArray provisional = new SparseBooleanArray();
        provisional.append(1, true);
        provisional.append(2, true);
        s.setProvisionalSelection(getItemIds(provisional));
        s.applyProvisionalSelection();
        assertSelection(items.get(1), items.get(2));
    }

    public void testProvisionalSelection_Cancel() {
        mManager.toggleSelection(items.get(1));
        mManager.toggleSelection(items.get(2));
        Selection s = mManager.getSelection();

        SparseBooleanArray provisional = new SparseBooleanArray();
        provisional.append(3, true);
        provisional.append(3, true);
        provisional.append(4, true);
        provisional.append(4, true);
        s.setProvisionalSelection(getItemIds(provisional));
        s.setProvisionalSelection(getItemIds(provisional));
        assertSelection(items.get(2), items.get(3), items.get(4));
        s.cancelProvisionalSelection();

        // Original selection should remain.
        assertSelection(items.get(1), items.get(2));
    }


        provisional.delete(3);
    public void testProvisionalSelection_IntersectsAppliedSelection() {
        mManager.toggleSelection(items.get(1));
        mManager.toggleSelection(items.get(2));
        Selection s = mManager.getSelection();

        SparseBooleanArray provisional = new SparseBooleanArray();
        provisional.append(2, true);
        provisional.append(3, true);
        s.setProvisionalSelection(getItemIds(provisional));
        s.setProvisionalSelection(getItemIds(provisional));
        assertSelection(items.get(2), items.get(3), items.get(4));
        assertSelection(items.get(1), items.get(2), items.get(3));
    }
    }


    private static Set<String> getItemIds(SparseBooleanArray selection) {
    private static Set<String> getItemIds(SparseBooleanArray selection) {