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

Commit edd0575b authored by Steve McKay's avatar Steve McKay
Browse files

Don't try to delete files twice.

Also, apply delete in reverse order to placate RecyclerView.
TODO: Refactor this whole stack...needs some love.

Bug: 25091323, 24749296
Change-Id: I84e21c16423048bd50cd988eb1e2a36dc62b9d16
parent 9991e5f3
Loading
Loading
Loading
Loading
+87 −56
Original line number Diff line number Diff line
@@ -98,10 +98,12 @@ import com.android.documentsui.RecentsProvider.StateColumns;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.internal.annotations.GuardedBy;

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

@@ -1743,6 +1745,9 @@ public class DirectoryFragment extends Fragment {
        private Context mContext;
        private int mCursorCount;
        private boolean mIsLoading;
        @GuardedBy("mPendingDelete")
        private Boolean mPendingDelete = false;
        @GuardedBy("mPendingDelete")
        private SparseBooleanArray mMarkedForDeletion = new SparseBooleanArray();
        private UpdateListener mUpdateListener;
        @Nullable private Cursor mCursor;
@@ -1787,10 +1792,13 @@ public class DirectoryFragment extends Fragment {
        }

        int getItemCount() {
            synchronized(mPendingDelete) {
                return mCursorCount - mMarkedForDeletion.size();
            }
        }

        Cursor getItem(int position) {
            synchronized(mPendingDelete) {
                // Items marked for deletion are masked out of the UI.  To do this, for every marked
                // item whose position is less than the requested item position, advance the requested
                // position by 1.
@@ -1817,6 +1825,7 @@ public class DirectoryFragment extends Fragment {
                mCursor.moveToPosition(position);
                return mCursor;
            }
        }

        private boolean isEmpty() {
            return mCursorCount == 0;
@@ -1848,6 +1857,7 @@ public class DirectoryFragment extends Fragment {
        }

        List<DocumentInfo> getDocumentsMarkedForDeletion() {
            synchronized (mPendingDelete) {
                final int size = mMarkedForDeletion.size();
                List<DocumentInfo> docs =  new ArrayList<>(size);

@@ -1860,6 +1870,7 @@ public class DirectoryFragment extends Fragment {
                }
                return docs;
            }
        }

        /**
         * Marks the given files for deletion. This will remove them from the UI. Clients must then
@@ -1869,17 +1880,23 @@ public class DirectoryFragment extends Fragment {
         * @param selected A selection representing the files to delete.
         */
        void markForDeletion(Selection selected) {
            synchronized (mPendingDelete) {
                mPendingDelete = true;
                // Only one deletion operation at a time.
                checkState(mMarkedForDeletion.size() == 0);
                // There should never be more to delete than what exists.
                checkState(mCursorCount >= selected.size());

            final int size = selected.size();
            for (int i = 0; i < size; ++i) {
                int position = selected.get(i);
                if (DEBUG) Log.d(TAG, "Marked position " + position + " for deletion");
                mMarkedForDeletion.append(position, true);
                mViewAdapter.notifyItemRemoved(position);
                int[] positions = selected.getAll();
                Arrays.sort(positions);

                // Walk backwards through the set, since we're removing positions.
                // Otherwise, positions would change after the first modification.
                for (int p = positions.length - 1; p >= 0; p--) {
                    mMarkedForDeletion.append(positions[p], true);
                    mViewAdapter.notifyItemRemoved(positions[p]);
                    if (DEBUG) Log.d(TAG, "Scheduled " + positions[p] + " for delete.");
                }
            }
        }

@@ -1888,6 +1905,7 @@ public class DirectoryFragment extends Fragment {
         * unmarked, and restored in the UI.  See {@link #markForDeletion(Selection)}.
         */
        void undoDeletion() {
            synchronized (mPendingDelete) {
                // Iterate over deleted items, temporarily marking them false in the deletion list, and
                // re-adding them to the UI.
                final int size = mMarkedForDeletion.size();
@@ -1896,10 +1914,16 @@ public class DirectoryFragment extends Fragment {
                    mMarkedForDeletion.put(position, false);
                    mViewAdapter.notifyItemInserted(position);
                }
                resetDeleteData();
            }
        }

            // Then, clear the deletion list.
        private void resetDeleteData() {
            synchronized (mPendingDelete) {
                mPendingDelete = false;
                mMarkedForDeletion.clear();
            }
        }

        /**
         * Finalizes an ongoing deletion operation. All files currently marked for deletion will be
@@ -1909,10 +1933,17 @@ public class DirectoryFragment extends Fragment {
         * snackbars) for errors, info, etc.
         */
        void finalizeDeletion(DeletionListener listener) {
            synchronized (mPendingDelete) {
                if (mPendingDelete) {
                    // Necessary to avoid b/25072545. Even when that's resolved, this
                    // is a nice safe thing to day.
                    mPendingDelete = false;
                    final ContentResolver resolver = mContext.getContentResolver();
                    DeleteFilesTask task = new DeleteFilesTask(resolver, listener);
                    task.execute();
                }
            }
        }

        /**
         * A Task which collects the DocumentInfo for documents that have been marked for deletion,
@@ -1968,7 +1999,7 @@ public class DirectoryFragment extends Fragment {
                } else {
                    if (DEBUG) Log.d(TAG, "Deletion task completed successfully.");
                }
                mMarkedForDeletion.clear();
                resetDeleteData();

                mListener.onCompletion();
            }
+20 −0
Original line number Diff line number Diff line
@@ -601,6 +601,14 @@ public final class MultiSelectManager implements View.OnKeyListener {
            mTotalSelection = new SparseBooleanArray();
        }

        @VisibleForTesting
        public Selection(int... positions) {
            this();
            for (int i = 0; i < positions.length; i++) {
                add(positions[i]);
            }
        }

        /**
         * @param position
         * @return true if the position is currently selected.
@@ -623,6 +631,18 @@ public final class MultiSelectManager implements View.OnKeyListener {
            return mTotalSelection.keyAt(index);
        }

        /**
         * Returns an unordered array of selected positions.
         */
        public int[] getAll() {
            final int size = size();
            int[] positions = new int[size];
            for (int i = 0; i < size; i++) {
                positions[i] = get(i);
            }
            return positions;
        }

        /**
         * @return size of the selection.
         */
+4 −41
Original line number Diff line number Diff line
@@ -32,10 +32,6 @@ import com.android.documentsui.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;

import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;



public class DirectoryFragmentModelTest extends AndroidTestCase {

@@ -117,15 +113,6 @@ public class DirectoryFragmentModelTest extends AndroidTestCase {
        assertEquals("0", docs.get(0).documentId);
        assertEquals("1", docs.get(1).documentId);
        assertEquals("4", docs.get(2).documentId);

        TestDeletionListener testListener = new TestDeletionListener();
        model.finalizeDeletion(testListener);
        testListener.waitForDone();

        docs = getDocumentInfo(0, 1, 2);
        assertEquals("0", docs.get(0).documentId);
        assertEquals("1", docs.get(1).documentId);
        assertEquals("2", docs.get(2).documentId);
    }

    // Tests that Model.getItem returns the right items after a deletion is undone.
@@ -149,20 +136,12 @@ public class DirectoryFragmentModelTest extends AndroidTestCase {
        };
    }

    private void delete(int... items) {
        Selection sel = new Selection();
        for (int item: items) {
            sel.add(item);
        }
        model.markForDeletion(sel);
    private void delete(int... positions) {
        model.markForDeletion(new Selection(positions));
    }

    private List<DocumentInfo> getDocumentInfo(int... items) {
        Selection sel = new Selection();
        for (int item: items) {
            sel.add(item);
        }
        return model.getDocuments(sel);
    private List<DocumentInfo> getDocumentInfo(int... positions) {
        return model.getDocuments(new Selection(positions));
    }

    private static class DummyListener extends Model.UpdateListener {
@@ -177,20 +156,4 @@ public class DirectoryFragmentModelTest extends AndroidTestCase {
            return null;
        }
    }

    private static class TestDeletionListener extends Model.DeletionListener {
        final CountDownLatch mSignal = new CountDownLatch(1);

        @Override
        public void onCompletion() {
            mSignal.countDown();
        }

        public void waitForDone() {
            try {
                boolean timeout = mSignal.await(10, TimeUnit.SECONDS);
                assertTrue("Timed out waiting for deletion completion", timeout);
            } catch (InterruptedException e) {}
        }
    }
}