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

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

Merge "Move Adapters to their own classes."

parents c9a0d952 c5ecf890
Loading
Loading
Loading
Loading
+47 −246
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.GridLayoutManager.SpanSizeLookup;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.RecyclerView.LayoutManager;
@@ -99,18 +100,17 @@ import com.android.documentsui.dirlist.MultiSelectManager.Selection;
import com.android.documentsui.model.DocumentInfo;
import com.android.documentsui.model.DocumentStack;
import com.android.documentsui.model.RootInfo;

import com.google.common.collect.Lists;
import com.google.common.collect.Sets;

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

/**
 * Display the documents inside a single directory.
 */
public class DirectoryFragment extends Fragment {
public class DirectoryFragment extends Fragment implements DocumentsAdapter.Environment {

    public static final int TYPE_NORMAL = 1;
    public static final int TYPE_SEARCH = 2;
@@ -126,7 +126,7 @@ public class DirectoryFragment extends Fragment {
    private static final String TAG = "DirectoryFragment";

    private static final int LOADER_ID = 42;
    private static final boolean DEBUG_ENABLE_DND = true;
    static final boolean DEBUG_ENABLE_DND = true;

    private static final String EXTRA_TYPE = "type";
    private static final String EXTRA_ROOT = "root";
@@ -289,7 +289,11 @@ public class DirectoryFragment extends Fragment {
        final RootInfo root = getArguments().getParcelable(EXTRA_ROOT);
        final DocumentInfo doc = getArguments().getParcelable(EXTRA_DOC);

        mAdapter = new DocumentsAdapter();
        mIconHelper = new IconHelper(context, state.derivedMode);

        mAdapter = new SectionBreakDocumentsAdapterWrapper(
                this, new ModelBackedDocumentsAdapter(this, mIconHelper));

        mRecView.setAdapter(mAdapter);

        GestureDetector.SimpleOnGestureListener listener =
@@ -333,7 +337,7 @@ public class DirectoryFragment extends Fragment {
                    : MultiSelectManager.MODE_SINGLE);
        mSelectionManager.addCallback(new SelectionModeListener());

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

@@ -343,8 +347,6 @@ public class DirectoryFragment extends Fragment {
        mTuner = FragmentTuner.pick(state);
        mClipper = new DocumentClipper(context);

        mIconHelper = new IconHelper(context, state.derivedMode);

        boolean hideGridTitles;
        if (mType == TYPE_RECENT_OPEN) {
            // Hide titles when showing recents for picking images/videos
@@ -574,7 +576,10 @@ public class DirectoryFragment extends Fragment {
            case MODE_GRID:
                if (mGridLayout == null) {
                    mGridLayout = new GridLayoutManager(getContext(), mColumnCount);
                    mGridLayout.setSpanSizeLookup(mAdapter.createSpanSizeLookup());
                    SpanSizeLookup lookup = mAdapter.createSpanSizeLookup();
                    if (lookup != null) {
                        mGridLayout.setSpanSizeLookup(lookup);
                    }
                }
                layout = mGridLayout;
                break;
@@ -609,6 +614,11 @@ public class DirectoryFragment extends Fragment {
        return columnCount;
    }

    @Override
    public int getColumnCount() {
        return mColumnCount;
    }

    /**
     * Manages the integration between our ActionMode and MultiSelectManager, initiating
     * ActionMode when there is a selection, canceling it when there is no selection,
@@ -893,265 +903,55 @@ public class DirectoryFragment extends Fragment {
        }.execute(selected);
    }

    private State getDisplayState() {
        return ((BaseActivity) getActivity()).getDisplayState();
    }

    void showEmptyView() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRecView.setVisibility(View.GONE);
        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
        msg.setText(R.string.empty);
        // No retry button for the empty view.
        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
    }

    void showErrorView() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRecView.setVisibility(View.GONE);
        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
        msg.setText(R.string.query_error);
        // TODO: Enable this once the retry button does something.
        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
    }

    void showRecyclerView() {
        mEmptyView.setVisibility(View.GONE);
        mRecView.setVisibility(View.VISIBLE);
    }

    final class DocumentsAdapter
            extends RecyclerView.Adapter<DocumentHolder>
            implements Model.UpdateListener {

        private static final String TAG = "DocumentsAdapter";
        public static final int ITEM_TYPE_LAYOUT_DIVIDER = 0;
        public static final int ITEM_TYPE_DOCUMENT = 1;
        public static final int ITEM_TYPE_DIRECTORY = 2;

        /**
         * An ordered list of model IDs. This is the data structure that determines what shows up in
         * the UI, and where.
         */
        private List<String> mModelIds = new ArrayList<>();

        // The list is divided into two segments - directories, and everything else. Record the
        // position where the transition happens.
        private int mDividerPosition;

        public GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
            return new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    // Make layout whitespace span the grid. This has the effect of breaking
                    // grid rows whenever layout whitespace is encountered.
                    if (getItemViewType(position) == ITEM_TYPE_LAYOUT_DIVIDER) {
                        return mColumnCount;
                    } else {
                        return 1;
                    }
                }
            };
        }

    @Override
        public DocumentHolder onCreateViewHolder(ViewGroup parent, int viewType) {
            if (viewType == ITEM_TYPE_LAYOUT_DIVIDER) {
                return new EmptyDocumentHolder(getContext());
            };

            DocumentHolder holder = null;
            final State state = getDisplayState();
            switch (state.derivedMode) {
                case MODE_GRID:
                    switch (viewType) {
                        case ITEM_TYPE_DIRECTORY:
                            holder = new GridDirectoryHolder(getContext(), parent);
                            break;
                        case ITEM_TYPE_DOCUMENT:
                            holder = new GridDocumentHolder(getContext(), parent, mIconHelper);
                            break;
                        default:
                            throw new IllegalStateException("Unsupported layout type.");
                    }
                    break;
                case MODE_LIST:
                    holder = new ListDocumentHolder(getContext(), parent, mIconHelper);
                    break;
                case MODE_UNKNOWN:
                default:
                    throw new IllegalStateException("Unsupported layout mode.");
            }

    public void initDocumentHolder(DocumentHolder holder) {
        holder.addClickListener(mItemClickListener);
        holder.addOnKeyListener(mSelectionManager);
            return holder;
        }

        /**
         * Deal with selection changed events by using a custom ItemAnimator that just changes the
         * background color.  This works around focus issues (otherwise items lose focus when their
         * selection state changes) but also optimizes change animations for selection.
         */
        @Override
        public void onBindViewHolder(DocumentHolder holder, int position, List<Object> payload) {
            if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) {
                // Whitespace items are hidden elements with no data to bind.
                return;
            }

            final View itemView = holder.itemView;

            if (payload.contains(MultiSelectManager.SELECTION_CHANGED_MARKER)) {
                final boolean selected = isSelected(mModelIds.get(position));
                itemView.setActivated(selected);
                return;
            } else {
                onBindViewHolder(holder, position);
            }
    }

    @Override
        public void onBindViewHolder(DocumentHolder holder, int position) {
            if (holder.getItemViewType() == ITEM_TYPE_LAYOUT_DIVIDER) {
                // Whitespace items are hidden elements with no data to bind.
                return;
            }

            String modelId = mModelIds.get(position);
            Cursor cursor = mModel.getItem(modelId);
            holder.bind(cursor, modelId, getDisplayState());

            final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
            final int docFlags = getCursorInt(cursor, Document.COLUMN_FLAGS);

            holder.setSelected(isSelected(modelId));
            holder.setEnabled(mTuner.isDocumentEnabled(docMimeType, docFlags));
    public void onBindDocumentHolder(DocumentHolder holder, Cursor cursor) {
        if (DEBUG_ENABLE_DND) {
            setupDragAndDropOnDocumentView(holder.itemView, cursor);
        }
    }

    @Override
        public int getItemCount() {
            return mModelIds.size();
    public State getDisplayState() {
        return ((BaseActivity) getActivity()).getDisplayState();
    }

    @Override
        public void onModelUpdate(Model model) {
            mModelIds = Lists.newArrayList(model.getModelIds());
            // Start the divider at the end. That way if the code below encounters no documents
            // (i.e. in a directory containing only directories), the divider is placed at the end
            // of the list, as expected.
            mDividerPosition = mModelIds.size();

            // Walk down the list of IDs till we encounter something that's not a directory, and
            // insert a whitespace element - this introduces a visual break in the grid between
            // folders and documents.
            // TODO: This code makes assumptions about the model, namely, that it performs a
            // bucketed sort where directories will always be ordered before other files.  CBB.
            for (int i = 0; i < mModelIds.size(); ++i) {
                final String mimeType = getCursorString(
                        model.getItem(mModelIds.get(i)), Document.COLUMN_MIME_TYPE);
                if (!Document.MIME_TYPE_DIR.equals(mimeType)) {
                    mDividerPosition = i;
                    break;
                }
            }

            mModelIds.add(mDividerPosition, null);
    public Model getModel() {
        return mModel;
    }

    @Override
        public void onModelUpdateFailed(Exception e) {
            if (DEBUG) Log.d(TAG, "onModelUpdateFailed called ");
            mModelIds.clear();
        }

        /**
         * @return The model ID of the item at the given adapter position.
         */
        public String getModelId(int adapterPosition) {
            return mModelIds.get(adapterPosition);
        }

        /**
         * Hides a set of items from the associated RecyclerView.
         *
         * @param ids The Model IDs of the items to hide.
         * @return A SparseArray that maps the hidden IDs to their old positions. This can be used
         *         to {@link #unhide} the items if necessary.
         */
        public SparseArray<String> hide(String... ids) {
            Set<String> toHide = Sets.newHashSet(ids);

            // Proceed backwards through the list of items, because each removal causes the
            // positions of all subsequent items to change.
            SparseArray<String> hiddenItems = new SparseArray<>();
            for (int i = mModelIds.size() - 1; i >= 0; --i) {
                String id = mModelIds.get(i);
                if (toHide.contains(id)) {
                    hiddenItems.put(i, mModelIds.remove(i));
                    notifyItemRemoved(i);
                }
    public boolean isDocumentEnabled(String docMimeType, int docFlags) {
        return mTuner.isDocumentEnabled(docMimeType, docFlags);
    }

            return hiddenItems;
        }

        /**
         * Unhides a set of previously hidden items.
         *
         * @param ids A sparse array of IDs from a previous call to {@link #hide}.
         */
        public void unhide(SparseArray<String> ids) {
            // Proceed backwards through the list of items, because each addition causes the
            // positions of all subsequent items to change.
            for (int i = ids.size() - 1; i >= 0; --i) {
                int pos = ids.keyAt(i);
                String id = ids.get(pos);
                mModelIds.add(pos, id);
                notifyItemInserted(pos);
            }
        }

        /**
         * Returns a list of model IDs of items currently in the adapter. Excludes items that are
         * currently hidden (see {@link #hide(String...)}).
         *
         * @return A list of Model IDs.
         */
        public List<String> getModelIds() {
            return mModelIds;
    void showEmptyView() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRecView.setVisibility(View.GONE);
        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
        msg.setText(R.string.empty);
        // No retry button for the empty view.
        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
    }

        @Override
        public int getItemViewType(int position) {
            if (position < mDividerPosition) {
                return ITEM_TYPE_DIRECTORY;
            } else if (position == mDividerPosition) {
                return ITEM_TYPE_LAYOUT_DIVIDER;
            } else {
                return ITEM_TYPE_DOCUMENT;
            }
    void showErrorView() {
        mEmptyView.setVisibility(View.VISIBLE);
        mRecView.setVisibility(View.GONE);
        TextView msg = (TextView) mEmptyView.findViewById(R.id.message);
        msg.setText(R.string.query_error);
        // TODO: Enable this once the retry button does something.
        mEmptyView.findViewById(R.id.button_retry).setVisibility(View.GONE);
    }

        /**
         * Triggers item-change notifications by stable ID. Passing an unrecognized ID will result
         * in a warning in logcat, but no other error.
         *
         * @param id
         * @param selectionChangedMarker
         */
        public void notifyItemChanged(String id, String selectionChangedMarker) {
            int position = mModelIds.indexOf(id);

            if (position >= 0) {
                notifyItemChanged(position, selectionChangedMarker);
            } else {
                Log.w(TAG, "Item change notification received for unknown item: " + id);
            }
        }
    void showRecyclerView() {
        mEmptyView.setVisibility(View.GONE);
        mRecView.setVisibility(View.VISIBLE);
    }

    private String findCommonMimeType(List<String> mimeTypes) {
@@ -1504,7 +1304,8 @@ public class DirectoryFragment extends Fragment {
        abstract void onDocumentsReady(List<DocumentInfo> docs);
    }

    boolean isSelected(String modelId) {
    @Override
    public boolean isSelected(String modelId) {
        return mSelectionManager.getSelection().contains(modelId);
    }

@@ -1520,7 +1321,7 @@ public class DirectoryFragment extends Fragment {
        }
    }

    private class ModelUpdateListener implements Model.UpdateListener {
    private final class ModelUpdateListener implements Model.UpdateListener {
        @Override
        public void onModelUpdate(Model model) {
            if (model.info != null || model.error != null) {
+117 −0
Original line number Diff line number Diff line
/*
 * 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.model.DocumentInfo.getCursorString;

import android.content.Context;
import android.database.Cursor;
import android.provider.DocumentsContract.Document;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.SparseArray;

import com.android.documentsui.State;

import java.nio.channels.UnsupportedAddressTypeException;
import java.util.List;

/**
 * DocumentsAdapter provides glue between a directory Model, and RecylcerView. We've
 * abstracted this a bit in order to decompose some specialized support
 * for adding dummy layout objects (@see SectionBreakDocumentsAdapter). Handling of the
 * dummy layout objects was error prone when interspersed with the core mode / adapter code.
 *
 * @see ModelBackedDocumentsAdapter
 * @see SectionBreakDocumentsAdapter
 */
abstract class DocumentsAdapter
        extends RecyclerView.Adapter<DocumentHolder>
        implements Model.UpdateListener {

    // Payloads for notifyItemChange to distinguish between selection and other events.
    static final String SELECTION_CHANGED_MARKER = "Selection-Changed";

    /**
     * Returns a list of model IDs of items currently in the adapter. Excludes items that are
     * currently hidden (see {@link #hide(String...)}).
     *
     * @return A list of Model IDs.
     */
    abstract List<String> getModelIds();

    /**
     * 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 notifyItemSelectionChanged(String id);

    /**
     * @return The model ID of the item at the given adapter position.
     */
    abstract String getModelId(int position);

    /**
     * Hides a set of items from the associated RecyclerView.
     *
     * @param ids The Model IDs of the items to hide.
     * @return A SparseArray that maps the hidden IDs to their old positions. This can be used
     *         to {@link #unhide} the items if necessary.
     */
    abstract public SparseArray<String> hide(String... ids);

    /**
     * Unhides a set of previously hidden items.
     *
     * @param ids A sparse array of IDs from a previous call to {@link #hide}.
     */
    abstract void unhide(SparseArray<String> ids);

    /**
     * Returns a class that yields the span size for a particular element. This is
     * primarily useful in {@link SectionBreakDocumentsAdapterWrapper} where
     * we adjust sizes.
     */
    GridLayoutManager.SpanSizeLookup createSpanSizeLookup() {
        throw new UnsupportedAddressTypeException();
    }

    static boolean isDirectory(Cursor cursor) {
        final String mimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE);
        return Document.MIME_TYPE_DIR.equals(mimeType);
    }

    boolean isDirectory(Model model, int position) {
        String modelId = getModelIds().get(position);
        Cursor cursor = model.getItem(modelId);
        return isDirectory(cursor);
    }

    /**
     * Environmental access for View adapter implementations.
     */
    interface Environment {
        Context getContext();
        int getColumnCount();
        State getDisplayState();
        boolean isSelected(String id);
        Model getModel();
        boolean isDocumentEnabled(String mimeType, int flags);
        void initDocumentHolder(DocumentHolder holder);
        void onBindDocumentHolder(DocumentHolder holder, Cursor cursor);
    }
}
+1 −2
Original line number Diff line number Diff line
@@ -35,7 +35,6 @@ import android.provider.DocumentsContract;
import android.provider.DocumentsContract.Document;
import android.support.annotation.Nullable;
import android.support.annotation.VisibleForTesting;
import android.support.v7.widget.RecyclerView;
import android.util.Log;

import com.android.documentsui.BaseActivity.SiblingProvider;
@@ -74,7 +73,7 @@ public class Model implements SiblingProvider {
    @Nullable String info;
    @Nullable String error;

    Model(Context context, RecyclerView.Adapter<?> viewAdapter) {
    Model(Context context) {
        mContext = context;
    }

+223 −0

File added.

Preview size limit exceeded, changes collapsed.

+22 −10
Original line number Diff line number Diff line
@@ -75,9 +75,6 @@ public final class MultiSelectManager implements View.OnKeyListener {

    private boolean mSingleSelect;

    // Payloads for notifyItemChange to distinguish between selection and other events.
    public static final String SELECTION_CHANGED_MARKER = "Selection-Changed";

    @Nullable private BandController mBandManager;

    /**
@@ -339,7 +336,10 @@ public final class MultiSelectManager implements View.OnKeyListener {
            if (DEBUG) Log.d(TAG, "Ignoring toggle for element with no position.");
            return;
        }
        toggleSelection(mEnvironment.getModelIdFromAdapterPosition(position));
        String id = mEnvironment.getModelIdFromAdapterPosition(position);
        if (id != null) {
            toggleSelection(id);
        }
    }

    /**
@@ -348,6 +348,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
     * @param modelId
     */
    public void toggleSelection(String modelId) {
        checkNotNull(modelId);
        boolean changed = false;
        if (mSelection.contains(modelId)) {
            changed = attemptDeselect(modelId);
@@ -405,6 +406,10 @@ public final class MultiSelectManager implements View.OnKeyListener {
        checkState(end >= begin);
        for (int i = begin; i <= end; i++) {
            String id = mEnvironment.getModelIdFromAdapterPosition(i);
            if (id == null) {
                continue;
            }

            if (selected) {
                boolean canSelect = notifyBeforeItemStateChange(id, true);
                if (canSelect) {
@@ -436,6 +441,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
     * @return True if the update was applied.
     */
    private boolean attemptDeselect(String id) {
        checkArgument(id != null);
        if (notifyBeforeItemStateChange(id, false)) {
            mSelection.remove(id);
            notifyItemStateChanged(id, false);
@@ -462,6 +468,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
     * (identified by {@code position}) changes.
     */
    private void notifyItemStateChanged(String id, boolean selected) {
        checkArgument(id != null);
        int lastListener = mCallbacks.size() - 1;
        for (int i = lastListener; i > -1; i--) {
            mCallbacks.get(i).onItemStateChanged(id, selected);
@@ -613,7 +620,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
         * @param id
         * @return true if the position is currently selected.
         */
        public boolean contains(String id) {
        public boolean contains(@Nullable String id) {
            return mTotalSelection.contains(id);
        }

@@ -804,7 +811,12 @@ public final class MultiSelectManager implements View.OnKeyListener {
        int getChildCount();
        int getVisibleChildCount();
        void focusItem(int position);
        String getModelIdFromAdapterPosition(int position);
        /**
         * Returns null if non-useful item.
         * @param position
         * @return
         */
        @Nullable String getModelIdFromAdapterPosition(int position);
        int getItemCount();
        List<String> getModelIds();
        void notifyItemChanged(String id);
@@ -818,11 +830,11 @@ public final class MultiSelectManager implements View.OnKeyListener {
        private final Drawable mBand;

        private boolean mIsOverlayShown = false;
        private DirectoryFragment.DocumentsAdapter mAdapter;
        private DocumentsAdapter mAdapter;

        RuntimeSelectionEnvironment(RecyclerView rv) {
            mView = rv;
            mAdapter = (DirectoryFragment.DocumentsAdapter) rv.getAdapter();
            mAdapter = (DocumentsAdapter) rv.getAdapter();
            mBand = mView.getContext().getTheme().getDrawable(R.drawable.band_select_overlay);
        }

@@ -841,7 +853,7 @@ public final class MultiSelectManager implements View.OnKeyListener {
        }

        @Override
        public String getModelIdFromAdapterPosition(int position) {
        public @Nullable String getModelIdFromAdapterPosition(int position) {
            return mAdapter.getModelId(position);
        }

@@ -964,7 +976,7 @@ public final class MultiSelectManager implements View.OnKeyListener {

        @Override
        public void notifyItemChanged(String id) {
            mAdapter.notifyItemChanged(id, SELECTION_CHANGED_MARKER);
            mAdapter.notifyItemSelectionChanged(id);
        }

        @Override
Loading