1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.documentsui.dirlist; 17 18 import android.view.KeyEvent; 19 20 import com.android.documentsui.base.Events; 21 import com.android.documentsui.selection.ItemDetailsLookup; 22 import com.android.documentsui.selection.MotionInputHandler; 23 import com.android.documentsui.selection.SelectionHelper; 24 import com.android.documentsui.selection.ItemDetailsLookup.ItemDetails; 25 import com.android.documentsui.selection.MotionInputHandler.Callbacks; 26 import com.android.documentsui.selection.SelectionHelper.SelectionPredicate; 27 28 import javax.annotation.Nullable; 29 30 /** 31 * Class that handles keyboard events on RecyclerView items. The input handler 32 * must be attached directly to a RecyclerView item since, unlike DOM, events 33 * don't appear bubble up. 34 */ 35 public final class KeyInputHandler extends KeyboardEventListener { 36 37 private final SelectionHelper mSelectionHelper; 38 private final SelectionPredicate mSelectionPredicate; 39 private final Callbacks mCallbacks; 40 41 public KeyInputHandler( 42 SelectionHelper selectionHelper, 43 SelectionPredicate selectionPredicate, 44 Callbacks callbacks) { 45 46 mSelectionHelper = selectionHelper; 47 mSelectionPredicate = selectionPredicate; 48 mCallbacks = callbacks; 49 } 50 51 @Override 52 public boolean onKey(@Nullable ItemDetails details, int keyCode, KeyEvent event) { 53 // Only handle key-down events. This is simpler, consistent with most other UIs, and 54 // enables the handling of repeated key events from holding down a key. 55 if (event.getAction() != KeyEvent.ACTION_DOWN) { 56 return false; 57 } 58 59 // Ignore tab key events. Those should be handled by the top-level key handler. 60 if (keyCode == KeyEvent.KEYCODE_TAB) { 61 return false; 62 } 63 64 // Ignore events sent to Addon Holders. 65 if (details != null) { 66 int itemType = details.getItemViewType(); 67 if (itemType == DocumentsAdapter.ITEM_TYPE_HEADER_MESSAGE 68 || itemType == DocumentsAdapter.ITEM_TYPE_INFLATED_MESSAGE 69 || itemType == DocumentsAdapter.ITEM_TYPE_SECTION_BREAK) { 70 return false; 71 } 72 } 73 74 if (mCallbacks.onFocusItem(details, keyCode, event)) { 75 // Handle range selection adjustments. Extending the selection will adjust the 76 // bounds of the in-progress range selection. Each time an unshifted navigation 77 // event is received, the range selection is restarted. 78 if (shouldExtendSelection(details, event)) { 79 if (!mSelectionHelper.isRangeActive()) { 80 // Start a range selection if one isn't active 81 mSelectionHelper.startRange(details.getPosition()); 82 } 83 mSelectionHelper.extendRange(details.getPosition()); 84 } else { 85 mSelectionHelper.endRange(); 86 mSelectionHelper.clearSelection(); 87 } 88 return true; 89 } 90 91 // we don't yet have a mechanism to handle opening/previewing multiple documents at once 92 if (mSelectionHelper.getSelection().size() > 1) { 93 return false; 94 } 95 96 return mCallbacks.onItemActivated(details, event); 97 } 98 99 private boolean shouldExtendSelection(ItemDetails item, KeyEvent event) { 100 if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) { 101 return false; 102 } 103 104 return mSelectionPredicate.canSetStateForId(item.getStableId(), true); 105 } 106 107 public static abstract class Callbacks extends MotionInputHandler.Callbacks { 108 public abstract boolean isInteractiveItem(ItemDetails item, KeyEvent e); 109 public abstract boolean onItemActivated(ItemDetails item, KeyEvent e); 110 public boolean onFocusItem(ItemDetails details, int keyCode, KeyEvent event) { 111 return true; 112 } 113 } 114 }