Loading src/com/android/documentsui/BaseActivity.java +11 −115 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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); Loading @@ -663,35 +575,13 @@ 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); Loading @@ -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. */ Loading src/com/android/documentsui/FocusManager.java +87 −48 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -53,21 +54,51 @@ 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. Loading Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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. * <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 Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; } Loading @@ -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(""); Loading Loading @@ -470,13 +505,18 @@ 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() { 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 // 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); } Loading @@ -486,9 +526,7 @@ public final class FocusManager implements FocusHandler { } } /** * Ends the current search (see {@link #search()}. */ /** Ends the current search (see {@link #search()}. */ private void endSearch() { if (mActive) { mScope.model.removeUpdateListener(mModelListener); Loading Loading @@ -538,7 +576,8 @@ 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() { mUiRunner.post( new Runnable() { @Override public void run() { endSearch(); Loading @@ -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 */ Loading @@ -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 */ Loading src/com/android/documentsui/Injector.java 0 → 100644 +46 −0 Original line number Diff line number Diff line /* * 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); } src/com/android/documentsui/SharedInputHandler.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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; } } src/com/android/documentsui/base/Procedure.java 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * 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(); } Loading
src/com/android/documentsui/BaseActivity.java +11 −115 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -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"; Loading @@ -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) { Loading @@ -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); Loading Loading @@ -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); Loading @@ -663,35 +575,13 @@ 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); Loading @@ -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. */ Loading
src/com/android/documentsui/FocusManager.java +87 −48 Original line number Diff line number Diff line Loading @@ -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; Loading @@ -53,21 +54,51 @@ 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. Loading Loading @@ -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; } Loading @@ -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) { Loading Loading @@ -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. * <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 Loading Loading @@ -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; Loading @@ -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); Loading @@ -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; } Loading @@ -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(""); Loading Loading @@ -470,13 +505,18 @@ 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() { 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 // 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); } Loading @@ -486,9 +526,7 @@ public final class FocusManager implements FocusHandler { } } /** * Ends the current search (see {@link #search()}. */ /** Ends the current search (see {@link #search()}. */ private void endSearch() { if (mActive) { mScope.model.removeUpdateListener(mModelListener); Loading Loading @@ -538,7 +576,8 @@ 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() { mUiRunner.post( new Runnable() { @Override public void run() { endSearch(); Loading @@ -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 */ Loading @@ -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 */ Loading
src/com/android/documentsui/Injector.java 0 → 100644 +46 −0 Original line number Diff line number Diff line /* * 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); }
src/com/android/documentsui/SharedInputHandler.java 0 → 100644 +51 −0 Original line number Diff line number Diff line /* * 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; } }
src/com/android/documentsui/base/Procedure.java 0 → 100644 +26 −0 Original line number Diff line number Diff line /* * 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(); }