diff options
8 files changed, 315 insertions, 53 deletions
diff --git a/src/com/android/documentsui/BaseActivity.java b/src/com/android/documentsui/BaseActivity.java index c4dc81089..145bf1aa8 100644 --- a/src/com/android/documentsui/BaseActivity.java +++ b/src/com/android/documentsui/BaseActivity.java @@ -545,25 +545,6 @@ public abstract class BaseActivity return mState.stack.peek(); } - @Override - public void onBackPressed() { - // While action bar is expanded, the state stack UI is hidden. - if (mSearchManager.cancelSearch()) { - return; - } - - DirectoryFragment dir = getDirectoryFragment(); - if (dir != null && dir.onBackPressed()) { - return; - } - - if (popDir()) { - return; - } - - super.onBackPressed(); - } - @VisibleForTesting public void addEventListener(EventListener listener) { mEventListeners.add(listener); diff --git a/src/com/android/documentsui/SharedInputHandler.java b/src/com/android/documentsui/SharedInputHandler.java index 367805902..f3f26419d 100644 --- a/src/com/android/documentsui/SharedInputHandler.java +++ b/src/com/android/documentsui/SharedInputHandler.java @@ -15,43 +15,114 @@ */ package com.android.documentsui; +import static com.android.documentsui.base.Shared.DEBUG; + +import android.util.Log; import android.view.KeyEvent; import com.android.documentsui.base.Events; import com.android.documentsui.base.Features; import com.android.documentsui.base.Procedure; +import com.android.documentsui.dirlist.FocusHandler; +import com.android.documentsui.selection.SelectionManager; public class SharedInputHandler { - private final FocusManager mFocusManager; + private static final String TAG = "SharedInputHandler"; + + private final FocusHandler mFocusManager; + private final Procedure mSearchCanceler; private final Procedure mDirPopper; private final Features mFeatures; + private final SelectionManager mSelectionMgr; - public SharedInputHandler(FocusManager focusManager, Procedure dirPopper, Features features) { - mFocusManager = focusManager; + public SharedInputHandler( + FocusHandler focusHandler, + SelectionManager selectionMgr, + Procedure searchCanceler, + Procedure dirPopper, + Features features) { + mFocusManager = focusHandler; + mSearchCanceler = searchCanceler; + mSelectionMgr = selectionMgr; mDirPopper = dirPopper; mFeatures = features; } 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 - && !mFeatures.isSystemKeyboardNavigationEnabled()) { + switch (keyCode) { + // Unhandled ESC keys end up being rethrown back at us as BACK keys. So by returning + // true, we make sure it always does no-op. + case KeyEvent.KEYCODE_ESCAPE: + return onEscape(); + + case KeyEvent.KEYCODE_DEL: + return onDelete(); + + // This is the Android back button, not backspace. + case KeyEvent.KEYCODE_BACK: + return onBack(); + + case KeyEvent.KEYCODE_TAB: + return onTab(); + + default: + // Instead of duplicating the switch-case in #isNavigationKeyCode, best just to + // leave it here. + 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; + } + return false; + } + } + + private boolean onTab() { + if (!mFeatures.isSystemKeyboardNavigationEnabled()) { // Tab toggles focus on the navigation drawer. - // This should only be called in pre-O devices, since O has built-in keyboard navigation + // This should only be called in pre-O devices, since O has built-in keyboard + // navigation // support. mFocusManager.advanceFocusArea(); return true; - } else if (keyCode == KeyEvent.KEYCODE_DEL) { - mDirPopper.run(); - return true; } return false; } + + private boolean onDelete() { + mDirPopper.run(); + return true; + } + + private boolean onBack() { + if (mSearchCanceler.run()) { + return true; + } + + if (mSelectionMgr.hasSelection()) { + if (DEBUG) Log.d(TAG, "Back pressed. Clearing existing selection."); + mSelectionMgr.clearSelection(); + return true; + } + + return mDirPopper.run(); + } + + private boolean onEscape() { + if (mSearchCanceler.run()) { + return true; + } + + if (mSelectionMgr.hasSelection()) { + if (DEBUG) Log.d(TAG, "ESC pressed. Clearing existing selection."); + mSelectionMgr.clearSelection(); + return true; + } + + return true; + } } diff --git a/src/com/android/documentsui/dirlist/DirectoryFragment.java b/src/com/android/documentsui/dirlist/DirectoryFragment.java index 26e0c1d6e..d64fa93a5 100644 --- a/src/com/android/documentsui/dirlist/DirectoryFragment.java +++ b/src/com/android/documentsui/dirlist/DirectoryFragment.java @@ -692,15 +692,6 @@ public class DirectoryFragment extends Fragment } } - public final boolean onBackPressed() { - if (mSelectionMgr.hasSelection()) { - if (DEBUG) Log.d(TAG, "Clearing selection on selection manager."); - mSelectionMgr.clearSelection(); - return true; - } - return false; - } - private boolean onAccessibilityClick(View child) { DocumentDetails doc = getDocumentHolder(child); mActions.openDocument(doc); diff --git a/src/com/android/documentsui/files/ActivityInputHandler.java b/src/com/android/documentsui/files/ActivityInputHandler.java index 35653d2d7..3f0eb65b1 100644 --- a/src/com/android/documentsui/files/ActivityInputHandler.java +++ b/src/com/android/documentsui/files/ActivityInputHandler.java @@ -30,11 +30,18 @@ final class ActivityInputHandler { } boolean onKeyDown(int keyCode, KeyEvent event) { - if ((keyCode == KeyEvent.KEYCODE_DEL && event.isAltPressed()) - || keyCode == KeyEvent.KEYCODE_FORWARD_DEL) { - mDeleteHandler.run(); - return true; + switch (keyCode) { + case KeyEvent.KEYCODE_FORWARD_DEL: + mDeleteHandler.run(); + return true; + case KeyEvent.KEYCODE_DEL: + if (event.isAltPressed()) { + mDeleteHandler.run(); + return true; + } + return false; + default: + return false; } - return false; } -}
\ No newline at end of file +} diff --git a/src/com/android/documentsui/files/FilesActivity.java b/src/com/android/documentsui/files/FilesActivity.java index 103c272a5..835e82146 100644 --- a/src/com/android/documentsui/files/FilesActivity.java +++ b/src/com/android/documentsui/files/FilesActivity.java @@ -132,7 +132,12 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons mActivityInputHandler = new ActivityInputHandler(mInjector.actions::deleteSelectedDocuments); mSharedInputHandler = - new SharedInputHandler(mInjector.focusManager, this::popDir, mInjector.features); + new SharedInputHandler( + mInjector.focusManager, + mInjector.selectionMgr, + mInjector.searchManager::cancelSearch, + this::popDir, + mInjector.features); RootsFragment.show(getFragmentManager(), null); @@ -302,7 +307,9 @@ public class FilesActivity extends BaseActivity implements ActionHandler.Addons @Override public boolean onKeyDown(int keyCode, KeyEvent event) { return mActivityInputHandler.onKeyDown(keyCode, event) - || mSharedInputHandler.onKeyDown(keyCode, event) + || mSharedInputHandler.onKeyDown( + keyCode, + event) || super.onKeyDown(keyCode, event); } diff --git a/src/com/android/documentsui/picker/PickActivity.java b/src/com/android/documentsui/picker/PickActivity.java index a90510399..e3d245e53 100644 --- a/src/com/android/documentsui/picker/PickActivity.java +++ b/src/com/android/documentsui/picker/PickActivity.java @@ -124,7 +124,12 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { Intent intent = getIntent(); mSharedInputHandler = - new SharedInputHandler(mInjector.focusManager, this::popDir, mInjector.features); + new SharedInputHandler( + mInjector.focusManager, + mInjector.selectionMgr, + mInjector.searchManager::cancelSearch, + this::popDir, + mInjector.features); setupLayout(intent); mInjector.actions.initLocation(intent); } @@ -339,7 +344,9 @@ public class PickActivity extends BaseActivity implements ActionHandler.Addons { @CallSuper @Override public boolean onKeyDown(int keyCode, KeyEvent event) { - return mSharedInputHandler.onKeyDown(keyCode, event) + return mSharedInputHandler.onKeyDown( + keyCode, + event) || super.onKeyDown(keyCode, event); } diff --git a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java index d196c332d..73eb8e8db 100644 --- a/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java +++ b/tests/common/com/android/documentsui/dirlist/TestFocusHandler.java @@ -27,6 +27,8 @@ public final class TestFocusHandler implements FocusHandler { public boolean handleKey; public int focusPos = 0; public String focusModelId; + public boolean advanceFocusAreaCalled; + public boolean focusDirectoryCalled; @Override public boolean handleKey(DocumentHolder doc, int keyCode, KeyEvent event) { @@ -39,11 +41,13 @@ public final class TestFocusHandler implements FocusHandler { @Override public boolean advanceFocusArea() { + advanceFocusAreaCalled = true; return true; } @Override public boolean focusDirectoryList() { + focusDirectoryCalled = true; return true; } diff --git a/tests/unit/com/android/documentsui/SharedInputHandlerTest.java b/tests/unit/com/android/documentsui/SharedInputHandlerTest.java new file mode 100644 index 000000000..0f5c28064 --- /dev/null +++ b/tests/unit/com/android/documentsui/SharedInputHandlerTest.java @@ -0,0 +1,194 @@ +/* + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import android.support.test.filters.SmallTest; +import android.support.test.runner.AndroidJUnit4; +import android.view.KeyEvent; +import android.view.MotionEvent; + +import com.android.documentsui.base.Procedure; +import com.android.documentsui.dirlist.TestFocusHandler; +import com.android.documentsui.selection.SelectionManager; +import com.android.documentsui.testing.SelectionManagers; +import com.android.documentsui.testing.TestFeatures; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +@RunWith(AndroidJUnit4.class) +@SmallTest +public class SharedInputHandlerTest { + + private SharedInputHandler mSharedInputHandler; + private SelectionManager mSelectionMgr = SelectionManagers.createTestInstance(); + private TestFeatures mFeatures = new TestFeatures(); + private TestFocusHandler mFocusHandler = new TestFocusHandler(); + private boolean mDirPopHappened; + private boolean mCanceledSearch; + private Procedure mDirPopper = new Procedure() { + @Override + public boolean run() { + mDirPopHappened = true; + return true; + } + }; + + @Before + public void setUp() { + mDirPopHappened = false; + mSharedInputHandler = new SharedInputHandler( + mFocusHandler, + mSelectionMgr, + () -> { + return false; + }, + mDirPopper, + mFeatures); + } + + @Test + public void testUnrelatedButton_DoesNothing() { + KeyEvent event = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_A, 0, 0); + assertFalse(mSharedInputHandler.onKeyDown(event.getKeyCode(), event)); + } + + @Test + public void testBackButton_CancelsSearch() { + mSelectionMgr.toggleSelection("1"); + mSharedInputHandler = new SharedInputHandler( + new TestFocusHandler(), + SelectionManagers.createTestInstance(), + () -> { + mCanceledSearch = true; + return true; + }, + mDirPopper, + new TestFeatures()); + KeyEvent backEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(backEvent.getKeyCode(), backEvent)); + + assertTrue(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 1); + assertFalse(mDirPopHappened); + } + + @Test + public void testBackButton_ClearsSelection() { + mSelectionMgr.toggleSelection("1"); + assertEquals(mSelectionMgr.getSelection().size(), 1); + KeyEvent backEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(backEvent.getKeyCode(), backEvent)); + + assertFalse(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 0); + assertFalse(mDirPopHappened); + } + + @Test + public void testBackButton_PopsDirectory() { + KeyEvent backEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_BACK, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(backEvent.getKeyCode(), backEvent)); + + assertFalse(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 0); + assertTrue(mDirPopHappened); + } + + @Test + public void testEscButton_CancelsSearch() { + mSelectionMgr.toggleSelection("1"); + mSharedInputHandler = new SharedInputHandler( + new TestFocusHandler(), + SelectionManagers.createTestInstance(), + () -> { + mCanceledSearch = true; + return true; + }, + mDirPopper, + new TestFeatures()); + KeyEvent escapeEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ESCAPE, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(escapeEvent.getKeyCode(), escapeEvent)); + + assertTrue(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 1); + assertFalse(mDirPopHappened); + } + + @Test + public void testEscButton_ClearsSelection() { + mSelectionMgr.toggleSelection("1"); + assertEquals(mSelectionMgr.getSelection().size(), 1); + KeyEvent escapeEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ESCAPE, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(escapeEvent.getKeyCode(), escapeEvent)); + + assertFalse(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 0); + assertFalse(mDirPopHappened); + } + + @Test + public void testEscButton_DoesNotPopDirectory() { + KeyEvent escapeEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_ESCAPE, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(escapeEvent.getKeyCode(), escapeEvent)); + + assertFalse(mCanceledSearch); + assertEquals(mSelectionMgr.getSelection().size(), 0); + assertFalse(mDirPopHappened); + } + + @Test + public void testDeleteButton_PopsDirectory() { + KeyEvent delEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DEL, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(delEvent.getKeyCode(), delEvent)); + + assertTrue(mDirPopHappened); + } + + @Test + public void testTab_AdvancesFocusArea() { + mFeatures.systemKeyboardNavigation = false; + KeyEvent tabEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_TAB, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(tabEvent.getKeyCode(), tabEvent)); + + assertTrue(mFocusHandler.advanceFocusAreaCalled); + } + + @Test + public void testNavKey_FocusesDirectory() { + mFeatures.systemKeyboardNavigation = false; + KeyEvent navEvent = + new KeyEvent(0, 0, MotionEvent.ACTION_DOWN, KeyEvent.KEYCODE_DPAD_UP, 0, 0); + assertTrue(mSharedInputHandler.onKeyDown(navEvent.getKeyCode(), navEvent)); + + assertTrue(mFocusHandler.focusDirectoryCalled); + } +} |