summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Steve McKay <smckay@google.com> 2016-11-08 11:01:35 -0800
committer Steve McKay <smckay@google.com> 2016-11-08 11:08:15 -0800
commit047182631669608af946480c2545a10acb2ef1bf (patch)
tree543c701b797d20b8bde895e71cea3b1464229e3c
parent30535bce902104c97bbf70783d684ee673cb7637 (diff)
Shared input handling and injection.
Change-Id: I41ae072e55ecc60b708274b5c67bed3a486bf080
-rw-r--r--src/com/android/documentsui/BaseActivity.java126
-rw-r--r--src/com/android/documentsui/FocusManager.java135
-rw-r--r--src/com/android/documentsui/Injector.java46
-rw-r--r--src/com/android/documentsui/SharedInputHandler.java51
-rw-r--r--src/com/android/documentsui/base/Procedure.java26
-rw-r--r--src/com/android/documentsui/dirlist/DirectoryFragment.java7
-rw-r--r--src/com/android/documentsui/dirlist/FocusHandler.java16
-rw-r--r--src/com/android/documentsui/files/FilesActivity.java22
-rw-r--r--src/com/android/documentsui/picker/PickActivity.java27
-rw-r--r--tests/common/com/android/documentsui/dirlist/TestFocusHandler.java7
-rw-r--r--tests/unit/com/android/documentsui/FocusManagerTest.java16
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());
}
}