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

Commit 5b0a2c18 authored by Steve McKay's avatar Steve McKay
Browse files

Inject SelectionManager instead of passing on reset.

Move more delete functionality into ActionHandler.
Make ActionModeController Activity scoped.
Name most "Config" objects (relating to scope) to ContentScope.
Clear focus info when resetting FocusManager.

Bug: 31658228

Change-Id: I6dbdb07ded83c75b82aec5773a60ef0febb6f02c
parent 4f78ba64
Loading
Loading
Loading
Loading
+14 −1
Original line number Diff line number Diff line
@@ -40,6 +40,7 @@ import com.android.documentsui.files.OpenUriForViewTask;
import com.android.documentsui.roots.LoadRootTask;
import com.android.documentsui.roots.RootsAccess;
import com.android.documentsui.selection.Selection;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.sidebar.EjectRootTask;

import java.util.List;
@@ -55,6 +56,7 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
    protected final State mState;
    protected final RootsAccess mRoots;
    protected final DocumentsAccess mDocs;
    protected final SelectionManager mSelectionMgr;
    protected final Lookup<String, Executor> mExecutors;

    public AbstractActionHandler(
@@ -62,17 +64,20 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
            State state,
            RootsAccess roots,
            DocumentsAccess docs,
            SelectionManager selectionMgr,
            Lookup<String, Executor> executors) {

        assert(activity != null);
        assert(state != null);
        assert(roots != null);
        assert(selectionMgr != null);
        assert(docs != null);

        mActivity = activity;
        mState = state;
        mRoots = roots;
        mDocs = docs;
        mSelectionMgr = selectionMgr;
        mExecutors = executors;
    }

@@ -85,6 +90,11 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
                listener).executeOnExecutor(ProviderExecutor.forAuthority(root.authority));
    }

    @Override
    public void openSelectedInNewWindow() {
        throw new UnsupportedOperationException("Can't open in new window.");
    }

    @Override
    public void openInNewWindow(DocumentStack path) {
        Metrics.logUserAction(mActivity, Metrics.USER_ACTION_NEW_WINDOW);
@@ -143,7 +153,7 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
    }

    @Override
    public void deleteDocuments(Model model, Selection selection, ConfirmationCallback callback) {
    public void deleteSelectedDocuments(Model model, ConfirmationCallback callback) {
        throw new UnsupportedOperationException("Delete not supported!");
    }

@@ -163,6 +173,9 @@ public abstract class AbstractActionHandler<T extends Activity & CommonAddons>
        loadRoot(Shared.getDefaultRootUri(mActivity));
    }

    protected Selection getStableSelection() {
        return mSelectionMgr.getSelection(new Selection());
    }
    /**
     * A class primarily for the support of isolating our tests
     * from our concrete activity implementations.
+3 −2
Original line number Diff line number Diff line
@@ -28,7 +28,6 @@ import com.android.documentsui.base.DocumentStack;
import com.android.documentsui.base.RootInfo;
import com.android.documentsui.dirlist.DocumentDetails;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.selection.Selection;

public interface ActionHandler {

@@ -54,6 +53,8 @@ public interface ActionHandler {

    void loadDocument(Uri uri);

    void openSelectedInNewWindow();

    void openInNewWindow(DocumentStack path);

    void pasteIntoFolder(RootInfo root);
@@ -66,7 +67,7 @@ public interface ActionHandler {

    void showChooserForDoc(DocumentInfo doc);

    void deleteDocuments(Model model, Selection selection, ConfirmationCallback callback);
    void deleteSelectedDocuments(Model model, ConfirmationCallback callback);

    /**
     * Called when initial activity setup is complete. Implementations
+28 −11
Original line number Diff line number Diff line
@@ -52,9 +52,11 @@ import android.view.MenuItem;
import android.view.View;

import com.android.documentsui.AbstractActionHandler.CommonAddons;
import com.android.documentsui.MenuManager.SelectionDetails;
import com.android.documentsui.NavigationViewManager.Breadcrumb;
import com.android.documentsui.SearchViewManager.SearchManagerListener;
import com.android.documentsui.base.DocumentInfo;
import com.android.documentsui.base.EventHandler;
import com.android.documentsui.base.Events;
import com.android.documentsui.base.LocalPreferences;
import com.android.documentsui.base.PairedTask;
@@ -62,19 +64,21 @@ import com.android.documentsui.base.RootInfo;
import com.android.documentsui.base.Shared;
import com.android.documentsui.base.State;
import com.android.documentsui.base.State.ViewMode;
import com.android.documentsui.dirlist.ActionModeController;
import com.android.documentsui.dirlist.AnimationView;
import com.android.documentsui.dirlist.DirectoryFragment;
import com.android.documentsui.dirlist.DocumentsAdapter;
import com.android.documentsui.dirlist.Model;
import com.android.documentsui.roots.GetRootDocumentTask;
import com.android.documentsui.roots.RootsCache;
import com.android.documentsui.selection.Selection;
import com.android.documentsui.selection.SelectionManager;
import com.android.documentsui.selection.SelectionManager.SelectionPredicate;
import com.android.documentsui.selection.Selection;
import com.android.documentsui.sidebar.RootsFragment;
import com.android.documentsui.sorting.SortController;
import com.android.documentsui.sorting.SortModel;
import com.android.documentsui.ui.DialogController;
import com.android.documentsui.ui.MessageBuilder;

import java.util.ArrayList;
import java.util.Collection;
@@ -92,11 +96,13 @@ public abstract class BaseActivity

    protected @Nullable RetainedState mRetainedState;
    protected RootsCache mRoots;
    protected MessageBuilder mMessages;
    protected DrawerController mDrawer;
    protected NavigationViewManager mNavigator;
    List<EventListener> mEventListeners = new ArrayList<>();
    protected FocusManager mFocusManager;
    protected SortController mSortController;

    private final List<EventListener> mEventListeners = new ArrayList<>();
    private final String mTag;
    private final ContentObserver mRootsCacheObserver = new ContentObserver(
            new Handler()) {
@@ -131,12 +137,6 @@ public abstract class BaseActivity
    public abstract SelectionManager getSelectionManager(
            DocumentsAdapter adapter, SelectionPredicate canSetState);

    /**
     * Provides Activity a means of injection into and specialization of
     * DirectoryFragment.
     */
    public abstract FocusManager getFocusManager(RecyclerView view, Model model);

    /**
     * Provides Activity a means of injection into and specialization of
     * DirectoryFragment hosted menus.
@@ -155,8 +155,24 @@ public abstract class BaseActivity
     *
     * Args can be null when called from a context lacking fragment, such as RootsFragment.
     */
    public abstract ActionHandler getActionHandler(
            @Nullable Model model, @Nullable SelectionManager selectionMgr, boolean searchMode);
    public abstract ActionHandler getActionHandler(@Nullable Model model, boolean searchMode);

    /**
     * Provides Activity a means of injection into and specialization of
     * DirectoryFragment.
     */
    public abstract ActionModeController getActionModeController(
            SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view);

    public final FocusManager getFocusManager(RecyclerView view, Model model) {
        assert(mFocusManager != null);
        return mFocusManager.reset(view, model);
    }

    public final MessageBuilder getMessages() {
        assert(mMessages != null);
        return mMessages;
    }

    public BaseActivity(@LayoutRes int layoutId, String tag) {
        mLayoutId = layoutId;
@@ -178,6 +194,7 @@ public abstract class BaseActivity
        setContentView(mLayoutId);

        mState = getState(icicle);
        mFocusManager = new FocusManager(getColor(R.color.accent_dark));
        mDrawer = DrawerController.create(this, getActivityConfig());
        Metrics.logActivityLaunch(this, mState, intent);

@@ -186,7 +203,7 @@ public abstract class BaseActivity
        // support to that fragment.
        mRetainedState = (RetainedState) getLastNonConfigurationInstance();
        mRoots = DocumentsApplication.getRootsCache(this);

        mMessages = new MessageBuilder(this);
        getContentResolver().registerContentObserver(
                RootsCache.sNotificationUri, false, mRootsCacheObserver);

+55 −57
Original line number Diff line number Diff line
@@ -57,11 +57,8 @@ import java.util.TimerTask;
public final class FocusManager implements FocusHandler {
    private static final String TAG = "FocusManager";

    private final Config mConfig = new Config();
    @Nullable TitleSearchHelper mSearchHelper;
    private @Nullable String mPendingFocusId;

    private int mLastFocusPosition = RecyclerView.NO_POSITION;
    private final ContentScope mScope = new ContentScope();
    private final TitleSearchHelper mSearchHelper;

    public FocusManager(@ColorRes int color) {
        mSearchHelper = new TitleSearchHelper(color);
@@ -91,24 +88,24 @@ public final class FocusManager implements FocusHandler {
    @Override
    public void onFocusChange(View v, boolean hasFocus) {
        // Remember focus events on items.
        if (hasFocus && v.getParent() == mConfig.mView) {
            mLastFocusPosition = mConfig.mView.getChildAdapterPosition(v);
        if (hasFocus && v.getParent() == mScope.view) {
            mScope.lastFocusPosition = mScope.view.getChildAdapterPosition(v);
        }
    }

    @Override
    public void restoreLastFocus() {
        if (mConfig.mAdapter.getItemCount() == 0) {
        if (mScope.adapter.getItemCount() == 0) {
            // Nothing to focus.
            return;
        }

        if (mLastFocusPosition != RecyclerView.NO_POSITION) {
        if (mScope.lastFocusPosition != RecyclerView.NO_POSITION) {
            // The system takes care of situations when a view is no longer on screen, etc,
            focusItem(mLastFocusPosition);
            focusItem(mScope.lastFocusPosition);
        } else {
            // Focus the first visible item
            focusItem(mConfig.mLayout.findFirstVisibleItemPosition());
            focusItem(mScope.layout.findFirstVisibleItemPosition());
        }
    }

@@ -118,15 +115,15 @@ public final class FocusManager implements FocusHandler {
     */
    @Override
    public void onLayoutCompleted() {
        if (mPendingFocusId == null) {
        if (mScope.pendingFocusId == null) {
            return;
        }

        int pos = mConfig.mAdapter.getModelIds().indexOf(mPendingFocusId);
        int pos = mScope.adapter.getModelIds().indexOf(mScope.pendingFocusId);
        if (pos != -1) {
            focusItem(pos);
        }
        mPendingFocusId = null;
        mScope.pendingFocusId = null;
    }

    /*
@@ -136,17 +133,17 @@ public final class FocusManager implements FocusHandler {
     */
    @Override
    public void focusDocument(String modelId) {
        int pos = mConfig.mAdapter.getModelIds().indexOf(modelId);
        if (pos != -1 && mConfig.mView.findViewHolderForAdapterPosition(pos) != null) {
        int pos = mScope.adapter.getModelIds().indexOf(modelId);
        if (pos != -1 && mScope.view.findViewHolderForAdapterPosition(pos) != null) {
            focusItem(pos);
        } else {
            mPendingFocusId = modelId;
            mScope.pendingFocusId = modelId;
        }
    }

    @Override
    public int getFocusPosition() {
        return mLastFocusPosition;
        return mScope.lastFocusPosition;
    }

    /**
@@ -162,7 +159,7 @@ public final class FocusManager implements FocusHandler {
            case KeyEvent.KEYCODE_MOVE_HOME:
                return 0;
            case KeyEvent.KEYCODE_MOVE_END:
                return mConfig.mAdapter.getItemCount() - 1;
                return mScope.adapter.getItemCount() - 1;
            case KeyEvent.KEYCODE_PAGE_UP:
            case KeyEvent.KEYCODE_PAGE_DOWN:
                return findPagedTargetPosition(view, keyCode, event);
@@ -180,7 +177,7 @@ public final class FocusManager implements FocusHandler {
        }

        if (inGridMode()) {
            int currentPosition = mConfig.mView.getChildAdapterPosition(view);
            int currentPosition = mScope.view.getChildAdapterPosition(view);
            // Left and right arrow keys only work in grid mode.
            switch (keyCode) {
                case KeyEvent.KEYCODE_DPAD_LEFT:
@@ -191,7 +188,7 @@ public final class FocusManager implements FocusHandler {
                    }
                    break;
                case KeyEvent.KEYCODE_DPAD_RIGHT:
                    if (currentPosition < mConfig.mAdapter.getItemCount() - 1) {
                    if (currentPosition < mScope.adapter.getItemCount() - 1) {
                        // Stop forward focus search at the last item, otherwise focus will wrap
                        // around to the first visible item.
                        searchDir = View.FOCUS_FORWARD;
@@ -206,15 +203,15 @@ public final class FocusManager implements FocusHandler {
            // events that cause a UI rebuild (like rotating the device). Compromise: turn focusable
            // off while performing the focus search.
            // TODO: Revisit this when RV focus issues are resolved.
            mConfig.mView.setFocusable(false);
            mScope.view.setFocusable(false);
            View targetView = view.focusSearch(searchDir);
            mConfig.mView.setFocusable(true);
            mScope.view.setFocusable(true);
            // TargetView can be null, for example, if the user pressed <down> at the bottom
            // of the list.
            if (targetView != null) {
                // Ignore navigation targets that aren't items in the RecyclerView.
                if (targetView.getParent() == mConfig.mView) {
                    return mConfig.mView.getChildAdapterPosition(targetView);
                if (targetView.getParent() == mScope.view) {
                    return mScope.view.getChildAdapterPosition(targetView);
                }
            }
        }
@@ -236,9 +233,9 @@ public final class FocusManager implements FocusHandler {
     * @return The adapter position of the target item.
     */
    private int findPagedTargetPosition(View view, int keyCode, KeyEvent event) {
        int first = mConfig.mLayout.findFirstVisibleItemPosition();
        int last = mConfig.mLayout.findLastVisibleItemPosition();
        int current = mConfig.mView.getChildAdapterPosition(view);
        int first = mScope.layout.findFirstVisibleItemPosition();
        int last = mScope.layout.findLastVisibleItemPosition();
        int current = mScope.view.getChildAdapterPosition(view);
        int pageSize = last - first + 1;

        if (keyCode == KeyEvent.KEYCODE_PAGE_UP) {
@@ -259,7 +256,7 @@ public final class FocusManager implements FocusHandler {
            } else {
                // If the current item is the last item, target the item one page down.
                int target = current + pageSize;
                int max = mConfig.mAdapter.getItemCount() - 1;
                int max = mScope.adapter.getItemCount() - 1;
                return target < max ? target : max;
            }
        }
@@ -285,20 +282,20 @@ public final class FocusManager implements FocusHandler {
     * @param callback A callback to call after the given item has been focused.
     */
    private void focusItem(final int pos, @Nullable final FocusCallback callback) {
        if (mPendingFocusId != null) {
            Log.v(TAG, "clearing pending focus id: " + mPendingFocusId);
            mPendingFocusId = null;
        if (mScope.pendingFocusId != null) {
            Log.v(TAG, "clearing pending focus id: " + mScope.pendingFocusId);
            mScope.pendingFocusId = null;
        }

        // If the item is already in view, focus it; otherwise, scroll to it and focus it.
        RecyclerView.ViewHolder vh = mConfig.mView.findViewHolderForAdapterPosition(pos);
        RecyclerView.ViewHolder vh = mScope.view.findViewHolderForAdapterPosition(pos);
        if (vh != null) {
            if (vh.itemView.requestFocus() && callback != null) {
                callback.onFocus(vh.itemView);
            }
        } else {
            // Set a one-time listener to request focus when the scroll has completed.
            mConfig.mView.addOnScrollListener(
            mScope.view.addOnScrollListener(
                    new RecyclerView.OnScrollListener() {
                        @Override
                        public void onScrollStateChanged(RecyclerView view, int newState) {
@@ -320,7 +317,7 @@ public final class FocusManager implements FocusHandler {
                            }
                        }
                    });
            mConfig.mView.smoothScrollToPosition(pos);
            mScope.view.smoothScrollToPosition(pos);
        }
    }

@@ -328,7 +325,7 @@ public final class FocusManager implements FocusHandler {
     * @return Whether the layout manager is currently in a grid-configuration.
     */
    private boolean inGridMode() {
        return mConfig.mLayout.getSpanCount() > 1;
        return mScope.layout.getSpanCount() > 1;
    }

    private interface FocusCallback {
@@ -428,7 +425,7 @@ public final class FocusManager implements FocusHandler {
        private void search() {
            if (!mActive) {
                // The model listener invalidates the search index when the model changes.
                mConfig.mModel.addUpdateListener(mModelListener);
                mScope.model.addUpdateListener(mModelListener);

                // Used to keep the current search alive until the timeout expires. If the user
                // presses another key within that time, that keystroke is added to the current
@@ -470,7 +467,7 @@ public final class FocusManager implements FocusHandler {
         */
        private void endSearch() {
            if (mActive) {
                mConfig.mModel.removeUpdateListener(mModelListener);
                mScope.model.removeUpdateListener(mModelListener);
                mTimer.cancel();
            }

@@ -486,11 +483,11 @@ public final class FocusManager implements FocusHandler {
         * must be set up before calling this method.
         */
        private void buildIndex() {
            int itemCount = mConfig.mAdapter.getItemCount();
            int itemCount = mScope.adapter.getItemCount();
            List<String> index = new ArrayList<>(itemCount);
            for (int i = 0; i < itemCount; i++) {
                String modelId = mConfig.mAdapter.getModelId(i);
                Cursor cursor = mConfig.mModel.getItem(modelId);
                String modelId = mScope.adapter.getModelId(i);
                Cursor cursor = mScope.model.getItem(modelId);
                if (modelId != null && cursor != null) {
                    String title = getCursorString(cursor, Document.COLUMN_DISPLAY_NAME);
                    // Perform case-insensitive search.
@@ -568,25 +565,26 @@ public final class FocusManager implements FocusHandler {
    }

    public FocusManager reset(RecyclerView view, Model model) {
        mConfig.reset(view, model);
        return this;
    }
        assert (view != null);
        assert (model != null);
        mScope.view = view;
        mScope.adapter = (DocumentsAdapter) view.getAdapter();
        mScope.layout = (GridLayoutManager) view.getLayoutManager();
        mScope.model = model;

    private static final class Config {
        mScope.lastFocusPosition = RecyclerView.NO_POSITION;
        mScope.pendingFocusId = null;

        @Nullable RecyclerView mView;
        @Nullable DocumentsAdapter mAdapter;
        @Nullable GridLayoutManager mLayout;
        return this;
    }

        private Model mModel;
    private static final class ContentScope {
        private @Nullable RecyclerView view;
        private @Nullable DocumentsAdapter adapter;
        private @Nullable GridLayoutManager layout;
        private @Nullable Model model;

        public void reset(RecyclerView view, Model model) {
            assert (view != null);
            assert (model != null);
            mView = view;
            mAdapter = (DocumentsAdapter) view.getAdapter();
            mLayout = (GridLayoutManager) view.getLayoutManager();
            mModel = model;
        }
        private @Nullable String pendingFocusId;
        private int lastFocusPosition = RecyclerView.NO_POSITION;
    }
}
+4 −2
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.documentsui.base;

import android.annotation.PluralsRes;
import android.app.Activity;
import android.app.AlertDialog;
import android.content.Context;
@@ -131,9 +132,10 @@ public final class Shared {
    }

    /**
     * Generates a formatted quantity string.
     * @deprecated use {@ link MessageBuilder#getQuantityString}
     */
    public static final String getQuantityString(Context context, int resourceId, int quantity) {
    @Deprecated
    public static final String getQuantityString(Context context, @PluralsRes int resourceId, int quantity) {
        return context.getResources().getQuantityString(resourceId, quantity, quantity);
    }

Loading