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

Commit c64cb252 authored by Ben Kwa's avatar Ben Kwa
Browse files

Refactor DirectoryFragment.

Consolidate data logic in a DirectoryFragment.Model class, instead of
having it spread out between the DirectoryFragment and the DocumentsAdapter.

BUG=23518578

Change-Id: I7c2b6c7aa388f5872ac18c320b86445f3c354792
parent a747d2e2
Loading
Loading
Loading
Loading
+185 −107
Original line number Diff line number Diff line
@@ -61,6 +61,7 @@ import android.os.Parcelable;
import android.os.SystemProperties;
import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
@@ -96,7 +97,6 @@ import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;
import com.android.internal.util.Preconditions;

import com.google.common.collect.Lists;

import java.util.ArrayList;
@@ -129,6 +129,8 @@ public class DirectoryFragment extends Fragment {
    private static final String EXTRA_QUERY = "query";
    private static final String EXTRA_IGNORE_STATE = "ignoreState";

    private final Model mModel = new Model();

    private final Handler mHandler = new Handler(Looper.getMainLooper());

    private View mEmptyView;
@@ -147,7 +149,6 @@ public class DirectoryFragment extends Fragment {
    private LoaderCallbacks<DirectoryResult> mCallbacks;
    private FragmentTuner mFragmentTuner;
    private DocumentClipper mClipper;
    private MultiSelectManager mSelectionManager;
    // These are lazily initialized.
    private LinearLayoutManager mListLayout;
    private GridLayoutManager mGridLayout;
@@ -261,7 +262,7 @@ public class DirectoryFragment extends Fragment {
        }

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

    @Override
@@ -290,14 +291,17 @@ public class DirectoryFragment extends Fragment {
                    }
                };

        mSelectionManager = new MultiSelectManager(
        // 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 which is passed into the model.
        MultiSelectManager selMgr= new MultiSelectManager(
                mRecView,
                listener,
                state.allowMultiple
                    ? MultiSelectManager.MODE_MULTIPLE
                    : MultiSelectManager.MODE_SINGLE);

        mSelectionManager.addCallback(new SelectionModeListener());
        selMgr.addCallback(new SelectionModeListener());
        mModel.setSelectionManager(selMgr);

        mType = getArguments().getInt(EXTRA_TYPE);
        mStateKey = buildStateKey(root, doc);
@@ -367,7 +371,9 @@ public class DirectoryFragment extends Fragment {

                if (!isAdded()) return;

                mAdapter.replaceResult(result);
                // TODO: make the adapter listen to the model
                mModel.update(result);
                mAdapter.update();

                // Push latest state up to UI
                // TODO: if mode change was racing with us, don't overwrite it
@@ -380,7 +386,7 @@ public class DirectoryFragment extends Fragment {
                updateDisplayState();

                // When launched into empty recents, show drawer
                if (mType == TYPE_RECENT_OPEN && mAdapter.isEmpty() && !state.stackTouched &&
                if (mType == TYPE_RECENT_OPEN && mModel.isEmpty() && !state.stackTouched &&
                        context instanceof DocumentsActivity) {
                    ((DocumentsActivity) context).setRootsDrawerOpen(true);
                }
@@ -398,7 +404,9 @@ public class DirectoryFragment extends Fragment {

            @Override
            public void onLoaderReset(Loader<DirectoryResult> loader) {
                mAdapter.replaceResult(null);
                // TODO: make the adapter listen to the model.
                mModel.update(null);
                mAdapter.update();
            }
        };

@@ -433,7 +441,7 @@ public class DirectoryFragment extends Fragment {
    }

    private boolean onSingleTapUp(MotionEvent e) {
        if (Events.isTouchEvent(e) && mSelectionManager.getSelection().isEmpty()) {
        if (Events.isTouchEvent(e) && mModel.getSelection().isEmpty()) {
            int position = getEventAdapterPosition(e);
            if (position != RecyclerView.NO_POSITION) {
                return handleViewItem(position);
@@ -454,14 +462,14 @@ public class DirectoryFragment extends Fragment {
    }

    private boolean handleViewItem(int position) {
        final Cursor cursor = mAdapter.getItem(position);
        final Cursor cursor = mModel.getItem(position);
        checkNotNull(cursor, "Cursor cannot be null.");
        final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
        final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
        if (isDocumentEnabled(docMimeType, docFlags)) {
            final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
            ((BaseActivity) getActivity()).onDocumentPicked(doc, mAdapter);
            mSelectionManager.clearSelection();
            ((BaseActivity) getActivity()).onDocumentPicked(doc, mModel);
            mModel.clearSelection();
            return true;
        }
        return false;
@@ -598,7 +606,7 @@ public class DirectoryFragment extends Fragment {
        public boolean onBeforeItemStateChange(int position, boolean selected) {
            // Directories and footer items cannot be checked
            if (selected) {
                final Cursor cursor = mAdapter.getItem(position);
                final Cursor cursor = mModel.getItem(position);
                checkNotNull(cursor, "Cursor cannot be null.");
                final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
                final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
@@ -610,7 +618,7 @@ public class DirectoryFragment extends Fragment {
        @Override
        public void onItemStateChanged(int position, boolean selected) {

            final Cursor cursor = mAdapter.getItem(position);
            final Cursor cursor = mModel.getItem(position);
            checkNotNull(cursor, "Cursor cannot be null.");

            final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);
@@ -621,7 +629,7 @@ public class DirectoryFragment extends Fragment {

        @Override
        public void onSelectionChanged() {
            mSelectionManager.getSelection(mSelected);
            mModel.getSelection(mSelected);
            if (mSelected.size() > 0) {
                if (DEBUG) Log.d(TAG, "Maybe starting action mode.");
                if (mActionMode == null) {
@@ -651,7 +659,7 @@ public class DirectoryFragment extends Fragment {
            if (DEBUG) Log.d(TAG, "Handling action mode destroyed.");
            mActionMode = null;
            // clear selection
            mSelectionManager.clearSelection();
            mModel.clearSelection();
            mSelected.clear();
            mNoDeleteCount = 0;
        }
@@ -659,8 +667,8 @@ public class DirectoryFragment extends Fragment {
        @Override
        public boolean onCreateActionMode(ActionMode mode, Menu menu) {
            mode.getMenuInflater().inflate(R.menu.mode_directory, menu);
            mode.setTitle(TextUtils.formatSelectedCount(mSelectionManager.getSelection().size()));
            return mSelectionManager.getSelection().size() > 0;
            mode.setTitle(TextUtils.formatSelectedCount(mModel.getSelection().size()));
            return mModel.getSelection().size() > 0;
        }

        @Override
@@ -679,8 +687,7 @@ public class DirectoryFragment extends Fragment {
        @Override
        public boolean onActionItemClicked(ActionMode mode, MenuItem item) {

            Selection selection = new Selection();
            mSelectionManager.getSelection(selection);
            Selection selection = mModel.getSelection(new Selection());

            final int id = item.getItemId();
            if (id == R.id.menu_open) {
@@ -948,50 +955,31 @@ public class DirectoryFragment extends Fragment {
        }
    }

    private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder>
            implements DocumentContext {
    private final class DocumentsAdapter extends RecyclerView.Adapter<DocumentHolder> {

        private final Context mContext;
        private final LayoutInflater mInflater;
        // TODO: Bring back support for footers.
        private final List<Footer> mFooters = new ArrayList<>();

        private Cursor mCursor;
        private int mCursorCount;

        public DocumentsAdapter(Context context) {
            mContext = context;
            mInflater = LayoutInflater.from(context);
        }

        public void replaceResult(DirectoryResult result) {
            if (DEBUG) Log.i(TAG, "Updating adapter with new result set.");
            mCursor = result != null ? result.cursor : null;
            mCursorCount = mCursor != null ? mCursor.getCount() : 0;

        public void update() {
            mFooters.clear();

            final Bundle extras = mCursor != null ? mCursor.getExtras() : null;
            if (extras != null) {
                final String info = extras.getString(DocumentsContract.EXTRA_INFO);
                if (info != null) {
                    mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, info));
            if (mModel.info != null) {
                mFooters.add(new MessageFooter(2, R.drawable.ic_dialog_info, mModel.info));
            }
                final String error = extras.getString(DocumentsContract.EXTRA_ERROR);
                if (error != null) {
                    mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, error));
            if (mModel.error != null) {
                mFooters.add(new MessageFooter(3, R.drawable.ic_dialog_alert, mModel.error));
            }
                if (extras.getBoolean(DocumentsContract.EXTRA_LOADING, false)) {
            if (mModel.isLoading()) {
                mFooters.add(new LoadingFooter());
            }
            }

            if (result != null && result.exception != null) {
                mFooters.add(new MessageFooter(
                        3, R.drawable.ic_dialog_alert, getString(R.string.query_error)));
            }

            if (isEmpty()) {
            if (mModel.isEmpty()) {
                mEmptyView.setVisibility(View.VISIBLE);
            } else {
                mEmptyView.setVisibility(View.GONE);
@@ -1025,7 +1013,7 @@ public class DirectoryFragment extends Fragment {
            final ThumbnailCache thumbs = DocumentsApplication.getThumbnailsCache(
                    context, mThumbSize);

            final Cursor cursor = getItem(position);
            final Cursor cursor = mModel.getItem(position);
            checkNotNull(cursor, "Cursor cannot be null.");

            final String docAuthority = getCursorString(cursor, RootCursorWrapper.COLUMN_AUTHORITY);
@@ -1041,7 +1029,7 @@ public class DirectoryFragment extends Fragment {

            holder.docId = docId;
            final View itemView = holder.view;
            itemView.setActivated(mSelectionManager.getSelection().contains(position));
            itemView.setActivated(mModel.isSelected(position));

            final View line1 = itemView.findViewById(R.id.line1);
            final View line2 = itemView.findViewById(R.id.line2);
@@ -1213,45 +1201,22 @@ public class DirectoryFragment extends Fragment {
            }
        }

        @Override
        public Cursor getCursor() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("Can't call getCursor from non-main thread.");
            }
            return mCursor;
        }

        private Cursor getItem(int position) {
            if (position < mCursorCount) {
                mCursor.moveToPosition(position);
                return mCursor;
            }

            Log.w(TAG, "Returning null cursor for position: " + position);
            if (DEBUG) Log.d(TAG, "...Adapter size: " + mCursorCount);
            if (DEBUG) Log.d(TAG, "...Footer size: " + mFooters.size());
            return null;
        }

        @Override
        public int getItemCount() {
            return mCursorCount;
            return mModel.getItemCount();
            // return mCursorCount + mFooters.size();
        }

        @Override
        public int getItemViewType(int position) {
            if (position < mCursorCount) {
            final int itemCount = mModel.getItemCount();
            if (position < itemCount) {
                return 0;
            } else {
                position -= mCursorCount;
                position -= itemCount;
                return mFooters.get(position).getItemViewType();
            }
        }

        private boolean isEmpty() {
            return getItemCount() > 0;
        }
    }

    private static String formatTime(Context context, long when) {
@@ -1328,27 +1293,6 @@ public class DirectoryFragment extends Fragment {
        return MimePredicate.mimeMatches(state.acceptMimes, docMimeType);
    }

    private List<DocumentInfo> getSelectedDocuments() {
        Selection sel = mSelectionManager.getSelection(new Selection());
        return getItemsAsDocuments(sel);
    }

    private List<DocumentInfo> getItemsAsDocuments(Selection items) {
        if (items == null || items.size() == 0) {
            return new ArrayList<>(0);
        }

        final List<DocumentInfo> docs =  new ArrayList<>(items.size());
        final int size = items.size();
        for (int i = 0; i < size; i++) {
            final Cursor cursor = mAdapter.getItem(items.get(i));
            checkNotNull(cursor, "Cursor cannot be null.");
            final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
            docs.add(doc);
        }
        return docs;
    }

    private void copyFromClipboard() {
        new AsyncTask<Void, Void, List<DocumentInfo>>() {

@@ -1426,7 +1370,7 @@ public class DirectoryFragment extends Fragment {
    }

    void copySelectedToClipboard() {
        Selection sel = mSelectionManager.getSelection(new Selection());
        Selection sel = mModel.getSelection(new Selection());
        copySelectionToClipboard(sel);
    }

@@ -1475,7 +1419,7 @@ public class DirectoryFragment extends Fragment {
    }

    void selectAllFiles() {
        boolean changed = mSelectionManager.setItemsSelected(0, mAdapter.getItemCount(), true);
        boolean changed = mModel.selectAll();
        if (changed) {
            updateDisplayState();
        }
@@ -1521,7 +1465,7 @@ public class DirectoryFragment extends Fragment {
                    int dstPosition = mRecView.getChildAdapterPosition(v);
                    DocumentInfo dstDir = null;
                    if (dstPosition != android.widget.AdapterView.INVALID_POSITION) {
                        Cursor dstCursor = mAdapter.getItem(dstPosition);
                        Cursor dstCursor = mModel.getItem(dstPosition);
                        checkNotNull(dstCursor, "Cursor cannot be null.");
                        dstDir = DocumentInfo.fromDirectoryCursor(dstCursor);
                        // TODO: Do not drop into the directory where the documents came from.
@@ -1556,16 +1500,16 @@ public class DirectoryFragment extends Fragment {
            return Collections.EMPTY_LIST;
        }

        final List<DocumentInfo> selectedDocs = getSelectedDocuments();
        final List<DocumentInfo> selectedDocs = mModel.getSelectedDocuments();
        if (!selectedDocs.isEmpty()) {
            if (!mSelectionManager.getSelection().contains(position)) {
            if (!mModel.isSelected(position)) {
                // There is a selection that does not include the current item, drag nothing.
                return Collections.EMPTY_LIST;
            }
            return selectedDocs;
        }

        final Cursor cursor = mAdapter.getItem(position);
        final Cursor cursor = mModel.getItem(position);
        checkNotNull(cursor, "Cursor cannot be null.");
        final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);

@@ -1671,12 +1615,14 @@ public class DirectoryFragment extends Fragment {
            mShadow.setBounds(0, 0, mShadowDimension, mShadowDimension);
        }

        @Override
        public void onProvideShadowMetrics(
                Point shadowSize, Point shadowTouchPoint) {
            shadowSize.set(mShadowDimension, mShadowDimension);
            shadowTouchPoint.set(mShadowDimension / 2, mShadowDimension / 2);
        }

        @Override
        public void onDrawShadow(Canvas canvas) {
            mShadow.draw(canvas);
        }
@@ -1706,7 +1652,7 @@ public class DirectoryFragment extends Fragment {
            extends AsyncTask<Selection, Void, List<DocumentInfo>> {
        @Override
        protected final List<DocumentInfo> doInBackground(Selection... selected) {
            return getItemsAsDocuments(selected[0]);
            return mModel.getDocuments(selected[0]);
        }

        @Override
@@ -1777,4 +1723,136 @@ public class DirectoryFragment extends Fragment {
            new BandSelectManager(fragment.mRecView, fragment.mSelectionManager);
        }
    }

    /**
     * The data model for the current loaded directory.
     */
    private final class Model implements DocumentContext {
        private MultiSelectManager mSelectionManager;
        private int mCursorCount;
        private boolean mIsLoading;
        @Nullable private Cursor mCursor;
        @Nullable private String info;
        @Nullable private String error;

        /**
         * Sets the selection manager used by the model.
         * TODO: the model should instantiate the selection manager.  See onActivityCreated.
         */
        void setSelectionManager(MultiSelectManager mgr) {
            mSelectionManager = mgr;
        }

        /**
         * Selects all files in the current directory.
         * @return true if the selection state changed for any files.
         */
        boolean selectAll() {
            return mSelectionManager.setItemsSelected(0, mCursorCount, true);
        }

        /**
         * Clones the current selection into the given Selection object.
         * @param selection
         * @return The selection that was passed in, for convenience.
         */
        Selection getSelection(Selection selection) {
            return mSelectionManager.getSelection(selection);
        }

        /**
         * @return The current selection (the live instance, not a copy).
         */
        Selection getSelection() {
            return mSelectionManager.getSelection();
        }

        boolean isSelected(int position) {
            return mSelectionManager.getSelection().contains(position);
        }

        void clearSelection() {
            mSelectionManager.clearSelection();
        }

        void update(DirectoryResult result) {
            if (DEBUG) Log.i(TAG, "Updating model with new result set.");

            if (result == null) {
                mCursor = null;
                mCursorCount = 0;
                info = null;
                error = null;
                mIsLoading = false;
                return;
            }

            if (result.exception != null) {
                Log.e(TAG, "Error while loading directory contents", result.exception);
                error = getString(R.string.query_error);
                return;
            }

            mCursor = result.cursor;
            mCursorCount = mCursor.getCount();

            final Bundle extras = mCursor.getExtras();
            if (extras != null) {
                info = extras.getString(DocumentsContract.EXTRA_INFO);
                error = extras.getString(DocumentsContract.EXTRA_ERROR);
                mIsLoading = extras.getBoolean(DocumentsContract.EXTRA_LOADING, false);
            }
        }

        private int getItemCount() {
            return mCursorCount;
        }

        private Cursor getItem(int position) {
            if (position >= mCursorCount) {
                throw new IndexOutOfBoundsException("Attempt to retrieve " + position + " of " +
                        mCursorCount + " items");
            }

            mCursor.moveToPosition(position);
            return mCursor;
        }

        private boolean isEmpty() {
            return mCursorCount == 0;
        }

        private boolean isLoading() {
            return mIsLoading;
        }

        private List<DocumentInfo> getSelectedDocuments() {
            Selection sel = getSelection(new Selection());
            return getDocuments(sel);
        }

        private List<DocumentInfo> getDocuments(Selection items) {
            if (items == null || items.size() == 0) {
                return new ArrayList<>(0);
            }

            final List<DocumentInfo> docs =  new ArrayList<>(items.size());
            final int size = items.size();
            for (int i = 0; i < size; i++) {
                final Cursor cursor = getItem(items.get(i));
                checkNotNull(cursor, "Cursor cannot be null.");
                final DocumentInfo doc = DocumentInfo.fromDirectoryCursor(cursor);
                docs.add(doc);
            }
            return docs;
        }

        @Override
        public Cursor getCursor() {
            if (Looper.myLooper() != Looper.getMainLooper()) {
                throw new IllegalStateException("Can't call getCursor from non-main thread.");
            }
            return mCursor;
        }
    }
}
+3 −3
Original line number Diff line number Diff line
@@ -177,13 +177,13 @@ public final class MultiSelectManager {

    /**
     * Returns a Selection object that provides a live view
     * on the current selection. Callers wishing to get
     * on the current selection.
     *
     * @see #getSelectionSnapshot() on how to get a snapshot
     * @see #getSelection(Selection) on how to get a snapshot
     *     of the selection that will not reflect future changes
     *     to selection.
     *
     * @return The current seleciton.
     * @return The current selection.
     */
    public Selection getSelection() {
        return mSelection;