diff options
author | 2016-11-08 11:01:35 -0800 | |
---|---|---|
committer | 2016-11-08 11:08:15 -0800 | |
commit | 047182631669608af946480c2545a10acb2ef1bf (patch) | |
tree | 543c701b797d20b8bde895e71cea3b1464229e3c | |
parent | 30535bce902104c97bbf70783d684ee673cb7637 (diff) |
Shared input handling and injection.
Change-Id: I41ae072e55ecc60b708274b5c67bed3a486bf080
-rw-r--r-- | src/com/android/documentsui/BaseActivity.java | 126 | ||||
-rw-r--r-- | src/com/android/documentsui/FocusManager.java | 135 | ||||
-rw-r--r-- | src/com/android/documentsui/Injector.java | 46 | ||||
-rw-r--r-- | src/com/android/documentsui/SharedInputHandler.java | 51 | ||||
-rw-r--r-- | src/com/android/documentsui/base/Procedure.java | 26 | ||||
-rw-r--r-- | src/com/android/documentsui/dirlist/DirectoryFragment.java | 7 | ||||
-rw-r--r-- | src/com/android/documentsui/dirlist/FocusHandler.java | 16 | ||||
-rw-r--r-- | src/com/android/documentsui/files/FilesActivity.java | 22 | ||||
-rw-r--r-- | src/com/android/documentsui/picker/PickActivity.java | 27 | ||||
-rw-r--r-- | tests/common/com/android/documentsui/dirlist/TestFocusHandler.java | 7 | ||||
-rw-r--r-- | tests/unit/com/android/documentsui/FocusManagerTest.java | 16 |
11 files changed, 293 insertions, 186 deletions
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index ea029d981..717cf1615 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -37,40 +37,29 @@ import android.support.annotation.CallSuper; import android.support.annotation.LayoutRes; import android.support.annotation.Nullable; import android.support.annotation.VisibleForTesting; -import android.support.v7.widget.RecyclerView; import android.util.Log; -import android.view.KeyEvent; import android.view.Menu; 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.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.RootInfo; -import com.android.documentsui.base.ScopedPreferences; import com.android.documentsui.base.Shared; import com.android.documentsui.base.State; import com.android.documentsui.base.State.ViewMode; 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.queries.SearchViewManager; import com.android.documentsui.queries.SearchViewManager.SearchManagerListener; 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.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; @@ -79,7 +68,8 @@ import java.util.List; import java.util.concurrent.Executor; public abstract class BaseActivity<T extends ActionHandler> - extends Activity implements CommonAddons, NavigationViewManager.Environment { + extends Activity + implements CommonAddons, Injector, NavigationViewManager.Environment { private static final String BENCHMARK_TESTING_PACKAGE = "com.android.documentsui.appperftests"; @@ -104,7 +94,6 @@ public abstract class BaseActivity<T extends ActionHandler> private RootsMonitor<BaseActivity<?>> mRootsMonitor; - private boolean mNavDrawerHasFocus; private long mStartTime; public BaseActivity(@LayoutRes int layoutId, String tag) { @@ -118,54 +107,8 @@ public abstract class BaseActivity<T extends ActionHandler> protected abstract void includeState(State initialState); protected abstract void onDirectoryCreated(DocumentInfo doc); - /** - * Provides Activity a means of injection into and specialization of - * DirectoryFragment. - */ - public abstract ActivityConfig getActivityConfig(); - - /** - * Provides Activity a means of injection into and specialization of - * DirectoryFragment. - */ - public abstract ScopedPreferences getScopedPreferences(); - - /** - * Provides Activity a means of injection into and specialization of - * DirectoryFragment. - */ - public abstract SelectionManager getSelectionManager( - DocumentsAdapter adapter, SelectionPredicate canSetState); - - /** - * Provides Activity a means of injection into and specialization of - * DirectoryFragment hosted menus. - */ - public abstract MenuManager getMenuManager(); - - /** - * Provides Activity a means of injection into and specialization of - * DirectoryFragment. - */ - public abstract DialogController getDialogController(); - - /** - * Provides Activity a means of injection into and specialization of - * fragment actions. - * - * Args can be null when called from a context lacking fragment, such as RootsFragment. - */ - 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 abstract FocusManager getFocusManager(RecyclerView view, Model model); + // Get ref to to focus manager without reset. Presumes it has had scrope vars initialized. + protected abstract FocusManager getFocusManager(); public final MessageBuilder getMessages() { assert(mMessages != null); @@ -607,37 +550,6 @@ public abstract class BaseActivity<T extends ActionHandler> super.onBackPressed(); } - /** - * Declare a global key handler to route key events when there isn't a specific focus view. This - * covers the scenario where a user opens DocumentsUI and just starts typing. - * - * @param keyCode - * @param event - * @return - */ - @CallSuper - @Override - public boolean onKeyDown(int keyCode, KeyEvent event) { - if (Events.isNavigationKeyCode(keyCode)) { - // Forward all unclaimed navigation keystrokes to the DirectoryFragment. This causes any - // stray navigation keystrokes focus the content pane, which is probably what the user - // is trying to do. - DirectoryFragment df = DirectoryFragment.get(getFragmentManager()); - if (df != null) { - df.requestFocus(); - return true; - } - } else if (keyCode == KeyEvent.KEYCODE_TAB) { - // Tab toggles focus on the navigation drawer. - toggleNavDrawerFocus(); - return true; - } else if (keyCode == KeyEvent.KEYCODE_DEL) { - popDir(); - return true; - } - return super.onKeyDown(keyCode, event); - } - @VisibleForTesting public void addEventListener(EventListener listener) { mEventListeners.add(listener); @@ -664,34 +576,12 @@ public abstract class BaseActivity<T extends ActionHandler> } /** - * Toggles focus between the navigation drawer and the directory listing. If the drawer isn't - * locked, open/close it as appropriate. - */ - void toggleNavDrawerFocus() { - boolean toogleHappened = false; - if (mNavDrawerHasFocus) { - mDrawer.setOpen(false); - DirectoryFragment df = DirectoryFragment.get(getFragmentManager()); - assert (df != null); - toogleHappened = df.requestFocus(); - } else { - mDrawer.setOpen(true); - RootsFragment rf = RootsFragment.get(getFragmentManager()); - assert (rf != null); - toogleHappened = rf.requestFocus(); - } - if (toogleHappened) { - mNavDrawerHasFocus = !mNavDrawerHasFocus; - } - } - - /** * Pops the top entry off the directory stack, and returns the user to the previous directory. * If the directory stack only contains one item, this method does nothing. * * @return Whether the stack was popped. */ - private boolean popDir() { + protected boolean popDir() { if (mState.stack.size() > 1) { mState.stack.pop(); refreshCurrentRootAndDirectory(AnimationView.ANIM_LEAVE); @@ -700,6 +590,12 @@ public abstract class BaseActivity<T extends ActionHandler> return false; } + protected boolean focusRoots() { + RootsFragment rf = RootsFragment.get(getFragmentManager()); + assert (rf != null); + return rf.requestFocus(); + } + /** * Closes the activity when it's idle. */ diff --git a/src/com/android/documentsui/FocusManager.java b/src/com/android/documentsui/FocusManager.java index 965dc5577..f7c3e5296 100644 --- a/src/com/android/documentsui/FocusManager.java +++ b/src/com/android/documentsui/FocusManager.java @@ -41,6 +41,7 @@ import android.widget.TextView; import com.android.documentsui.base.EventListener; import com.android.documentsui.base.Events; +import com.android.documentsui.base.Procedure; import com.android.documentsui.dirlist.DocumentHolder; import com.android.documentsui.dirlist.DocumentsAdapter; import com.android.documentsui.dirlist.FocusHandler; @@ -53,22 +54,52 @@ import java.util.List; import java.util.Timer; import java.util.TimerTask; -/** - * A class that handles navigation and focus within the DirectoryFragment. - */ +/** A class that handles navigation and focus within the DirectoryFragment. */ public final class FocusManager implements FocusHandler { private static final String TAG = "FocusManager"; private final ContentScope mScope = new ContentScope(); - private final TitleSearchHelper mSearchHelper; + private final SelectionManager mSelectionMgr; + private final DrawerController mDrawer; + private final Procedure mRootsFocuser; + private final TitleSearchHelper mSearchHelper; + + private boolean mNavDrawerHasFocus; + + public FocusManager( + SelectionManager selectionMgr, + DrawerController drawer, + Procedure rootsFocuser, + @ColorRes int color) { - public FocusManager(@ColorRes int color, SelectionManager selectionMgr) { mSelectionMgr = selectionMgr; + mDrawer = drawer; + mRootsFocuser = rootsFocuser; + mSearchHelper = new TitleSearchHelper(color); } @Override + public boolean advanceFocusArea() { + boolean toogleHappened = false; + if (mNavDrawerHasFocus) { + mDrawer.setOpen(false); + focusDirectoryList(); + } else { + mDrawer.setOpen(true); + toogleHappened = mRootsFocuser.run(); + } + + if (toogleHappened) { + mNavDrawerHasFocus = !mNavDrawerHasFocus; + return true; + } + + return false; + } + + @Override public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) { // Search helper gets first crack, for doing type-to-focus. if (mSearchHelper.handleKey(doc, keyCode, event)) { @@ -98,22 +129,26 @@ public final class FocusManager implements FocusHandler { } @Override - public boolean requestFocus() { + public boolean focusDirectoryList() { if (mScope.adapter.getItemCount() == 0) { - if (DEBUG) Log.v(TAG, "Nothing to focus."); + if (DEBUG) + Log.v(TAG, "Nothing to focus."); return false; } // If there's a selection going on, we don't want to grant user the ability to focus - // on any individual item to prevent ambiguity in operations (Cut selection vs. Cut focused + // on any individfocusSomethingual item to prevent ambiguity in operations (Cut selection + // vs. Cut focused // item) if (mSelectionMgr.hasSelection()) { - if (DEBUG) Log.v(TAG, "Existing selection found. No focus will be done."); + if (DEBUG) + Log.v(TAG, "Existing selection found. No focus will be done."); return false; } final int focusPos = (mScope.lastFocusPosition != RecyclerView.NO_POSITION) - ? mScope.lastFocusPosition : mScope.layout.findFirstVisibleItemPosition(); + ? mScope.lastFocusPosition + : mScope.layout.findFirstVisibleItemPosition(); focusItem(focusPos); return true; } @@ -137,8 +172,8 @@ public final class FocusManager implements FocusHandler { /* * Attempts to put focus on the document associated with the given modelId. If item does not - * exist yet in the layout, this sets a pending modelId to be used when - * {@code #applyPendingFocus()} is called next time. + * exist yet in the layout, this sets a pending modelId to be used when {@code + * #applyPendingFocus()} is called next time. */ @Override public void focusDocument(String modelId) { @@ -244,13 +279,14 @@ public final class FocusManager implements FocusHandler { } /** - * Given a PgUp/PgDn event and the current view, find the position of the target view. - * This returns: - * <li>The position of the topmost (or bottom-most) visible item, if the current item is not - * the top- or bottom-most visible item. + * Given a PgUp/PgDn event and the current view, find the position of the target view. This + * returns: + * <li>The position of the topmost (or bottom-most) visible item, if the current item is not the + * top- or bottom-most visible item. * <li>The position of an item that is one page's worth of items up (or down) if the current - * item is the top- or bottom-most visible item. + * item is the top- or bottom-most visible item. * <li>The first (or last) item, if paging up (or down) would go past those limits. + * * @param view The view that received the key event. * @param keyCode Must be KEYCODE_PAGE_UP or KEYCODE_PAGE_DOWN. * @param event @@ -305,7 +341,8 @@ public final class FocusManager implements FocusHandler { * @param pos * @param callback A callback to call after the given item has been focused. */ - private void focusItem(final int pos, @Nullable final FocusCallback callback) { + private void focusItem(final int pos, @Nullable + final FocusCallback callback) { if (mScope.pendingFocusId != null) { Log.v(TAG, "clearing pending focus id: " + mScope.pendingFocusId); mScope.pendingFocusId = null; @@ -325,8 +362,8 @@ public final class FocusManager implements FocusHandler { public void onScrollStateChanged(RecyclerView view, int newState) { if (newState == RecyclerView.SCROLL_STATE_IDLE) { // When scrolling stops, find the item and focus it. - RecyclerView.ViewHolder vh = - view.findViewHolderForAdapterPosition(pos); + RecyclerView.ViewHolder vh = view + .findViewHolderForAdapterPosition(pos); if (vh != null) { if (vh.itemView.requestFocus() && callback != null) { callback.onFocus(vh.itemView); @@ -345,9 +382,7 @@ public final class FocusManager implements FocusHandler { } } - /** - * @return Whether the layout manager is currently in a grid-configuration. - */ + /** @return Whether the layout manager is currently in a grid-configuration. */ private boolean inGridMode() { return mScope.layout.getSpanCount() > 1; } @@ -364,7 +399,7 @@ public final class FocusManager implements FocusHandler { * highlights instances of the search term found in the view. */ private class TitleSearchHelper { - static private final int SEARCH_TIMEOUT = 500; // ms + private static final int SEARCH_TIMEOUT = 500; // ms private final KeyListener mTextListener = new TextKeyListener(Capitalize.NONE, false); private final Editable mSearchString = Editable.Factory.getInstance().newEditable(""); @@ -470,25 +505,28 @@ public final class FocusManager implements FocusHandler { for (int pos = 0; pos < mIndex.size(); pos++) { String title = mIndex.get(pos); if (title != null && title.startsWith(searchString)) { - focusItem(pos, new FocusCallback() { - @Override - public void onFocus(View view) { - mHighlighter.applyHighlight(view); - // Using a timer repeat period of SEARCH_TIMEOUT/2 means the amount of - // time between the last keystroke and a search expiring is actually - // between 500 and 750 ms. A smaller timer period results in less - // variability but does more polling. - mTimer.schedule(new TimeoutTask(), 0, SEARCH_TIMEOUT / 2); - } - }); + focusItem( + pos, + new FocusCallback() { + @Override + public void onFocus(View view) { + mHighlighter.applyHighlight(view); + // Using a timer repeat period of SEARCH_TIMEOUT/2 means the + // amount of + // time between the last keystroke and a search expiring is + // actually + // between 500 and 750 ms. A smaller timer period results in + // less + // variability but does more polling. + mTimer.schedule(new TimeoutTask(), 0, SEARCH_TIMEOUT / 2); + } + }); break; } } } - /** - * Ends the current search (see {@link #search()}. - */ + /** Ends the current search (see {@link #search()}. */ private void endSearch() { if (mActive) { mScope.model.removeUpdateListener(mModelListener); @@ -538,12 +576,13 @@ public final class FocusManager implements FocusHandler { long now = SystemClock.uptimeMillis(); if ((now - last) > SEARCH_TIMEOUT) { // endSearch must run on the main thread because it does UI work - mUiRunner.post(new Runnable() { - @Override - public void run() { - endSearch(); - } - }); + mUiRunner.post( + new Runnable() { + @Override + public void run() { + endSearch(); + } + }); } } }; @@ -552,8 +591,8 @@ public final class FocusManager implements FocusHandler { private Spannable mCurrentHighlight; /** - * Applies title highlights to the given view. The view must have a title field that is a - * spannable text field. If this condition is not met, this function does nothing. + * Applies title highlights to the given view. The view must have a title field that is + * a spannable text field. If this condition is not met, this function does nothing. * * @param view */ @@ -575,8 +614,8 @@ public final class FocusManager implements FocusHandler { } /** - * Removes title highlights from the given view. The view must have a title field that is a - * spannable text field. If this condition is not met, this function does nothing. + * Removes title highlights from the given view. The view must have a title field that + * is a spannable text field. If this condition is not met, this function does nothing. * * @param view */ diff --git a/src/com/android/documentsui/Injector.java b/src/com/android/documentsui/Injector.java new file mode 100644 index 000000000..ace3362ba --- /dev/null +++ b/src/com/android/documentsui/Injector.java @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2016 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; + +import android.support.annotation.Nullable; +import android.support.v7.widget.RecyclerView; +import android.view.MenuItem; +import android.view.View; + +import com.android.documentsui.MenuManager.SelectionDetails; +import com.android.documentsui.base.EventHandler; +import com.android.documentsui.base.ScopedPreferences; +import com.android.documentsui.dirlist.DocumentsAdapter; +import com.android.documentsui.dirlist.Model; +import com.android.documentsui.selection.SelectionManager; +import com.android.documentsui.selection.SelectionManager.SelectionPredicate; +import com.android.documentsui.ui.DialogController; + +/** + * Provides access to runtime dependencies. + */ +public interface Injector { + + ActivityConfig getActivityConfig(); + ScopedPreferences getScopedPreferences(); + SelectionManager getSelectionManager(DocumentsAdapter adapter, SelectionPredicate canSetState); + MenuManager getMenuManager(); + DialogController getDialogController(); + ActionHandler getActionHandler(@Nullable Model model, boolean searchMode); + ActionModeController getActionModeController( + SelectionDetails selectionDetails, EventHandler<MenuItem> menuItemClicker, View view); + FocusManager getFocusManager(RecyclerView view, Model model); +} diff --git a/src/com/android/documentsui/SharedInputHandler.java b/src/com/android/documentsui/SharedInputHandler.java new file mode 100644 index 000000000..e14084fb7 --- /dev/null +++ b/src/com/android/documentsui/SharedInputHandler.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2016 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; + +import android.view.KeyEvent; + +import com.android.documentsui.base.Events; +import com.android.documentsui.base.Procedure; + +public class SharedInputHandler { + + private final FocusManager mFocusManager; + private Procedure mDirPopper; + + public SharedInputHandler(FocusManager focusManager, Procedure dirPopper) { + mFocusManager = focusManager; + mDirPopper = dirPopper; + } + + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (Events.isNavigationKeyCode(keyCode)) { + // Forward all unclaimed navigation keystrokes to the directory list. + // This causes any stray navigation keystrokes to focus the content pane, + // which is probably what the user is trying to do. + mFocusManager.focusDirectoryList(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_TAB) { + // Tab toggles focus on the navigation drawer. + mFocusManager.advanceFocusArea(); + return true; + } else if (keyCode == KeyEvent.KEYCODE_DEL) { + mDirPopper.run(); + return true; + } + + return false; + } +} diff --git a/src/com/android/documentsui/base/Procedure.java b/src/com/android/documentsui/base/Procedure.java new file mode 100644 index 000000000..b28d16d05 --- /dev/null +++ b/src/com/android/documentsui/base/Procedure.java @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2016 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.base; + +/** + * Functional interface like a {@link Runnable}, but returning a boolean value + * indicating if the Procedure succeeded. + */ +@FunctionalInterface +public interface Procedure { + + boolean run(); +} diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index 03ed834de..12430eb23 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -883,13 +883,6 @@ public class DirectoryFragment extends Fragment } } - /** - * Attempts to restore focus on the directory listing. - */ - public boolean requestFocus() { - return mFocusManager.requestFocus(); - } - private void setupDragAndDropOnDocumentView(View view, Cursor cursor) { final String docMimeType = getCursorString(cursor, Document.COLUMN_MIME_TYPE); if (Document.MIME_TYPE_DIR.equals(docMimeType)) { diff --git a/src/com/android/documentsui/dirlist/FocusHandler.java b/src/com/android/documentsui/dirlist/FocusHandler.java index 8db8ffb79..ac9e2ea1e 100644 --- a/src/com/android/documentsui/dirlist/FocusHandler.java +++ b/src/com/android/documentsui/dirlist/FocusHandler.java @@ -44,10 +44,20 @@ public interface FocusHandler extends View.OnFocusChangeListener { void focusDocument(String modelId); /** - * Requests focus on the item that last had focus. Scrolls to that item if necessary. If focus - * is unsuccessful, return false. + * Requests focus on the the directory list. Will specifically + * attempt to focus the item in the directory list that last had focus. + * Scrolls to that item if necessary. + * + * <p>If focus is unsuccessful, return false. + */ + boolean focusDirectoryList(); + + /** + * Attempts to advance the focus to the next available focus area + * in the app. As of this writing, known focus areas are the sidebar + * and the directory list (specifically an item in the directory list). */ - boolean requestFocus(); + boolean advanceFocusArea(); /** * @return The adapter position of the last focused item. diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index 8c6ca5d0d..99a6bb379 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -46,6 +46,7 @@ import com.android.documentsui.OperationDialogFragment; import com.android.documentsui.OperationDialogFragment.DialogType; import com.android.documentsui.ProviderExecutor; import com.android.documentsui.R; +import com.android.documentsui.SharedInputHandler; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.DocumentStack; import com.android.documentsui.base.EventHandler; @@ -87,6 +88,7 @@ public class FilesActivity private DocumentClipper mClipper; private ActionModeController mActionModeController; private ActivityInputHandler mActivityInputHandler; + private SharedInputHandler mSharedInputHandler; private DragShadowBuilder mShadowBuilder; public FilesActivity() { @@ -104,7 +106,13 @@ public class FilesActivity mClipper = DocumentsApplication.getDocumentClipper(this); mSelectionMgr = new SelectionManager(SelectionManager.MODE_MULTIPLE); - mFocusManager = new FocusManager(getColor(R.color.accent_dark), mSelectionMgr); + + mFocusManager = new FocusManager( + mSelectionMgr, + mDrawer, + this::focusRoots, + getColor(R.color.accent_dark)); + mMenuManager = new MenuManager( mSearchManager, mState, @@ -139,6 +147,7 @@ public class FilesActivity DocumentsApplication.getClipStore(this)); mActivityInputHandler = new ActivityInputHandler(mActions::deleteSelectedDocuments); + mSharedInputHandler = new SharedInputHandler(mFocusManager, this::popDir); RootsFragment.show(getFragmentManager(), null); @@ -307,8 +316,9 @@ public class FilesActivity @CallSuper @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - return mActivityInputHandler.onKeyDown(keyCode, event) ? true - : super.onKeyDown(keyCode, event); + return mActivityInputHandler.onKeyDown(keyCode, event) + || mSharedInputHandler.onKeyDown(keyCode, event) + || super.onKeyDown(keyCode, event); } @Override @@ -391,6 +401,12 @@ public class FilesActivity } @Override + protected FocusManager getFocusManager() { + assert (mFocusManager != null); + return mFocusManager; + } + + @Override public FocusManager getFocusManager(RecyclerView view, Model model) { assert (mFocusManager != null); return mFocusManager.reset(view, model); diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java index 3e9d6aa1a..ee8efe17e 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -37,9 +37,11 @@ import android.net.Uri; import android.os.Bundle; import android.os.Parcelable; import android.provider.DocumentsContract; +import android.support.annotation.CallSuper; import android.support.design.widget.Snackbar; import android.support.v7.widget.RecyclerView; import android.util.Log; +import android.view.KeyEvent; import android.view.Menu; import android.view.MenuItem; import android.view.View; @@ -53,6 +55,7 @@ import com.android.documentsui.MenuManager.DirectoryDetails; import com.android.documentsui.MenuManager.SelectionDetails; import com.android.documentsui.ProviderExecutor; import com.android.documentsui.R; +import com.android.documentsui.SharedInputHandler; import com.android.documentsui.base.DocumentInfo; import com.android.documentsui.base.EventHandler; import com.android.documentsui.base.MimeTypes; @@ -89,6 +92,7 @@ public class PickActivity private MenuManager mMenuManager; private FocusManager mFocusManager; private ActionModeController mActionModeController; + private SharedInputHandler mSharedInputHandler; public PickActivity() { super(R.layout.documents_activity, TAG); @@ -107,7 +111,13 @@ public class PickActivity mState.allowMultiple ? SelectionManager.MODE_MULTIPLE : SelectionManager.MODE_SINGLE); - mFocusManager = new FocusManager(getColor(R.color.accent_dark), mSelectionMgr); + + mFocusManager = new FocusManager( + mSelectionMgr, + mDrawer, + this::focusRoots, + getColor(R.color.accent_dark)); + mMenuManager = new MenuManager(mSearchManager, mState, new DirectoryDetails(this)); mActions = new ActionHandler<>( this, @@ -128,6 +138,8 @@ public class PickActivity Intent intent = getIntent(); + mSharedInputHandler = new SharedInputHandler(mFocusManager, this::popDir); + setupLayout(intent); mActions.initLocation(intent); } @@ -419,6 +431,12 @@ public class PickActivity finish(); } + @CallSuper + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + return mSharedInputHandler.onKeyDown(keyCode, event) + || super.onKeyDown(keyCode, event); + } public static PickActivity get(Fragment fragment) { return (PickActivity) fragment.getActivity(); @@ -444,6 +462,13 @@ public class PickActivity return mMenuManager; } + + @Override + protected FocusManager getFocusManager() { + assert (mFocusManager != null); + return mFocusManager; + } + @Override public FocusManager getFocusManager(RecyclerView view, Model model) { assert (mFocusManager != null); diff --git a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java index 3cbe1f184..d196c332d 100644 --- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java +++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java @@ -38,7 +38,12 @@ public final class TestFocusHandler implements FocusHandler { } @Override - public boolean requestFocus() { + public boolean advanceFocusArea() { + return true; + } + + @Override + public boolean focusDirectoryList() { return true; } diff --git a/tests/unit/com/android/documentsui/FocusManagerTest.java b/tests/unit/com/android/documentsui/FocusManagerTest.java index 0f38353ea..d7fdedf39 100644 --- a/tests/unit/com/android/documentsui/FocusManagerTest.java +++ b/tests/unit/com/android/documentsui/FocusManagerTest.java @@ -43,8 +43,8 @@ public class FocusManagerTest extends AndroidTestCase { public void setUp() throws Exception { mView = TestRecyclerView.create(ITEMS); mSelectionMgr = SelectionManagers.createTestInstance(ITEMS); - mManager = new FocusManager(0, mSelectionMgr).reset(mView, - new TestModel(TEST_AUTHORITY)); + mManager = new FocusManager(mSelectionMgr, null, null, 0) + .reset(mView, new TestModel(TEST_AUTHORITY)); } public void testFocus() { @@ -61,15 +61,15 @@ public class FocusManagerTest extends AndroidTestCase { mView.assertItemViewFocused(10); } - public void testRequestFocus_noItemsToFocus() { + public void testFocusDirectoryList_noItemsToFocus() { mView = TestRecyclerView.create(new ArrayList<>()); - mManager = new FocusManager(0, SelectionManagers.createTestInstance()).reset(mView, - new TestModel(TEST_AUTHORITY)); - assertFalse(mManager.requestFocus()); + mManager = new FocusManager(SelectionManagers.createTestInstance(), null, null, 0) + .reset(mView, new TestModel(TEST_AUTHORITY)); + assertFalse(mManager.focusDirectoryList()); } - public void testRequestFocus_hasSelection() { + public void testFocusDirectoryList_hasSelection() { mSelectionMgr.toggleSelection("0"); - assertFalse(mManager.requestFocus()); + assertFalse(mManager.focusDirectoryList()); } } |