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

Commit e74fff48 authored by Steve McKay's avatar Steve McKay Committed by Android (Google) Code Review
Browse files

Merge "Clear selection when empty space is tapped."

parents e7dca534 b04b1648
Loading
Loading
Loading
Loading
+4 −2
Original line number Diff line number Diff line
@@ -1440,9 +1440,11 @@ public class DirectoryFragment extends Fragment {
    }

    void selectAllFiles() {
        mSelectionManager.selectItems(0, mAdapter.getItemCount());
        boolean changed = mSelectionManager.setItemsSelected(0, mAdapter.getItemCount(), true);
        if (changed) {
            updateDisplayState();
        }
    }

    private void setupDragAndDropOnDirectoryView(View view) {
        // Listen for drops on non-directory items and empty space.
+95 −39
Original line number Diff line number Diff line
@@ -16,6 +16,9 @@

package com.android.documentsui;

import static com.android.internal.util.Preconditions.checkNotNull;
import static com.android.internal.util.Preconditions.checkState;

import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.Adapter;
import android.support.v7.widget.RecyclerView.AdapterDataObserver;
@@ -94,13 +97,16 @@ public final class MultiSelectManager {
                });
    }

    /**
     * Constructs a new instance with {@code adapter} and {@code helper}.
     * @param adapter
     * @param helper
     * @hide
     */
    @VisibleForTesting
    MultiSelectManager(Adapter<?> adapter, RecyclerViewHelper helper) {
        if (adapter == null) {
            throw new IllegalArgumentException("Adapter cannot be null.");
        }
        if (helper == null) {
            throw new IllegalArgumentException("Helper cannot be null.");
        }
        checkNotNull(adapter, "'adapter' cannot be null.");
        checkNotNull(helper, "'helper' cannot be null.");

        mHelper = helper;
        mAdapter = adapter;
@@ -136,6 +142,12 @@ public final class MultiSelectManager {
                });
    }

    /**
     * Adds {@code callback} such that it will be notified when {@code MultiSelectManager}
     * events occur.
     *
     * @param callback
     */
    public void addCallback(MultiSelectManager.Callback callback) {
        mCallbacks.add(callback);
    }
@@ -165,18 +177,45 @@ public final class MultiSelectManager {
        return dest;
    }

    public void selectItem(int position) {
        selectItems(position, 1);
    /**
     * Causes item at {@code position} in adapter to be selected.
     *
     * @param position Adapter position
     * @param selected
     * @return True if the selection state of the item changed.
     */
    public boolean setItemSelected(int position, boolean selected) {
        boolean changed = (selected)
                ? mSelection.add(position)
                : mSelection.remove(position);

        if (changed) {
            notifyItemStateChanged(position, true);
        }
        return changed;
    }

    public void selectItems(int position, int length) {
    /**
     * @param position
     * @param length
     * @param selected
     * @return True if the selection state of any of the items changed.
     */
    public boolean setItemsSelected(int position, int length, boolean selected) {
        boolean changed = false;
        for (int i = position; i < position + length; i++) {
            mSelection.add(i);
            changed |= setItemSelected(i, selected);
        }
        return changed;
    }

    /**
     * Clears the selection.
     */
    public void clearSelection() {
        if (DEBUG) Log.d(TAG, "Clearing selection");
        if (mSelection.isEmpty()) {
            return;
        }
        if (mIntermediateSelection == null) {
            mIntermediateSelection = new Selection();
        }
@@ -185,14 +224,17 @@ public final class MultiSelectManager {

        for (int i = 0; i < mIntermediateSelection.size(); i++) {
            int position = mIntermediateSelection.get(i);
            mAdapter.notifyItemChanged(position);
            notifyItemStateChanged(position, false);
        }
    }

    public boolean onSingleTapUp(MotionEvent e) {
    /**
     * @param e
     * @return true if the event was consumed.
     */
    private boolean onSingleTapUp(MotionEvent e) {
        if (DEBUG) Log.d(TAG, "Handling tap event.");
        if (mSelection.size() == 0) {
        if (mSelection.isEmpty()) {
            return false;
        }

@@ -200,25 +242,30 @@ public final class MultiSelectManager {
    }

    /**
     * TODO: Roll this into {@link #onSingleTapUp(MotionEvent)} once MotionEvent
     * can be mocked.
     *
     * @param position
     * @return true if the event was consumed.
     * @hide
     */
    @VisibleForTesting
    boolean onSingleTapUp(int position) {
        if (mSelection.size() == 0) {
        if (mSelection.isEmpty()) {
            return false;
        }

        if (position == RecyclerView.NO_POSITION) {
            if (DEBUG) Log.i(TAG, "View is null. Cannot handle tap event.");
            return false;
            if (DEBUG) Log.d(TAG, "View is null. Canceling selection.");
            clearSelection();
            return true;
        }

        toggleSelection(position);
        return true;
    }

    public void onLongPress(MotionEvent e) {
    private void onLongPress(MotionEvent e) {
        if (DEBUG) Log.d(TAG, "Handling long press event.");

        int position = mHelper.findEventPosition(e);
@@ -230,6 +277,9 @@ public final class MultiSelectManager {
    }

    /**
     * TODO: Roll this back into {@link #onLongPress(MotionEvent)} once MotionEvent
     * can be mocked.
     *
     * @param position
     * @hide
     */
@@ -255,7 +305,6 @@ public final class MultiSelectManager {
        if (notifyBeforeItemStateChange(position, nextState)) {
            boolean selected = mSelection.flip(position);
            notifyItemStateChanged(position, selected);
            mAdapter.notifyItemChanged(position);
            if (DEBUG) Log.d(TAG, "Selection after long press: " + mSelection);
        } else {
            Log.i(TAG, "Selection change cancelled by listener.");
@@ -283,13 +332,13 @@ public final class MultiSelectManager {
        for (int i = lastListener; i > -1; i--) {
            mCallbacks.get(i).onItemStateChanged(position, selected);
        }
        mAdapter.notifyItemChanged(position);
    }

    /**
     * Object representing the current selection.
     * Object representing the current selection. Provides read only access
     * public access, and private write access.
     */
    // NOTE: Much of the code in this class was copious swiped from
    // ArrayUtils, GrowingArrayUtils, and SparseBooleanArray.
    public static final class Selection {

        private SparseBooleanArray mSelection;
@@ -327,6 +376,13 @@ public final class MultiSelectManager {
            return mSelection.size();
        }

        /**
         * @return true if the selection is empty.
         */
        public boolean isEmpty() {
            return mSelection.size() == 0;
        }

        private boolean flip(int position) {
            if (contains(position)) {
                remove(position);
@@ -339,14 +395,22 @@ public final class MultiSelectManager {

        /** @hide */
        @VisibleForTesting
        void add(int position) {
        boolean add(int position) {
            if (!mSelection.get(position)) {
                mSelection.put(position, true);
                return true;
            }
            return false;
        }

        /** @hide */
        @VisibleForTesting
        void remove(int position) {
        boolean remove(int position) {
            if (mSelection.get(position)) {
                mSelection.delete(position);
                return true;
            }
            return false;
        }

        /**
@@ -360,12 +424,8 @@ public final class MultiSelectManager {
         */
        @VisibleForTesting
        void expand(int startPosition, int count) {
            if (startPosition < 0) {
                throw new IllegalArgumentException("startPosition must be non-negative");
            }
            if (count < 1) {
                throw new IllegalArgumentException("countMust be greater than 0");
            }
            checkState(startPosition >= 0);
            checkState(count > 0);

            for (int i = 0; i < mSelection.size(); i++) {
                int itemPosition = mSelection.keyAt(i);
@@ -386,12 +446,8 @@ public final class MultiSelectManager {
         */
        @VisibleForTesting
        void collapse(int startPosition, int count) {
            if (startPosition < 0) {
                throw new IllegalArgumentException("startPosition must be non-negative");
            }
            if (count < 1) {
                throw new IllegalArgumentException("countMust be greater than 0");
            }
            checkState(startPosition >= 0);
            checkState(count > 0);

            int endPosition = startPosition + count - 1;

@@ -481,7 +537,7 @@ public final class MultiSelectManager {

    /**
     * A composite {@code OnGestureDetector} that allows us to delegate unhandled
     * events to other interested parties.
     * events to an outside party (presumably DirectoryFragment).
     */
    private static final class CompositeOnGestureListener implements OnGestureListener {

+18 −10
Original line number Diff line number Diff line
@@ -64,33 +64,41 @@ public class MultiSelectManagerTest {
    }

    @Test
    public void singleTapDoesNotSelectBeforeLongPress() {
        mManager.onSingleTapUp(99);
        assertSelection();
    }

    @Test
    public void longPressStartsSelectionMode() {
    public void longPress_StartsSelectionMode() {
        mManager.onLongPress(7);
        assertSelection(7);
    }

    @Test
    public void secondLongPressExtendsSelection() {
    public void longPress_SecondPressExtendsSelection() {
        mManager.onLongPress(7);
        mManager.onLongPress(99);
        assertSelection(7, 99);
    }

    @Test
    public void singleTapUnselectedLastItem() {
    public void singleTapUp_DoesNotSelectBeforeLongPress() {
        mManager.onSingleTapUp(99);
        assertSelection();
    }

    @Test
    public void singleTapUp_UnselectsSelectedItem() {
        mManager.onLongPress(7);
        mManager.onSingleTapUp(7);
        assertSelection();
    }

    @Test
    public void singleTapUpExtendsSelection() {
    public void singleTapUp_NoPositionClearsSelection() {
        mManager.onLongPress(7);
        mManager.onSingleTapUp(11);
        mManager.onSingleTapUp(RecyclerView.NO_POSITION);
        assertSelection();
    }

    @Test
    public void singleTapUp_ExtendsSelection() {
        mManager.onLongPress(99);
        mManager.onSingleTapUp(7);
        mManager.onSingleTapUp(13);
+7 −0
Original line number Diff line number Diff line
@@ -58,6 +58,13 @@ public class MultiSelectManager_SelectionTest {
        assertEquals(0, selection.size());
    }

    @Test
    public void isEmpty() {
        assertTrue(new Selection().isEmpty());
        selection.clear();
        assertTrue(selection.isEmpty());
    }

    @Test
    public void sizeAndGet() {
        Selection other = new Selection();