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 androidx.recyclerview.selection.SelectionTracker; 21 import androidx.recyclerview.selection.ItemDetailsLookup.ItemDetails; 22 import androidx.recyclerview.selection.SelectionTracker.SelectionPredicate; 23 24 import com.android.documentsui.base.Events; 25 26 import javax.annotation.Nullable; 27 28 // TODO(b/69058726): Migrate to RecyclerView-Selection 29 /** 30 * Class that handles keyboard events on RecyclerView items. The input handler 31 * must be attached directly to a RecyclerView item since, unlike DOM, events 32 * don't appear bubble up. 33 */ 34 public final class KeyInputHandler extends KeyboardEventListener<DocumentItemDetails> { 35 36 private final SelectionTracker<String> mSelectionHelper; 37 private final SelectionPredicate<String> mSelectionPredicate; 38 private final Callbacks<DocumentItemDetails> mCallbacks; 39 KeyInputHandler( SelectionTracker<String> selectionHelper, SelectionPredicate<String> selectionPredicate, Callbacks<DocumentItemDetails> callbacks)40 public KeyInputHandler( 41 SelectionTracker<String> selectionHelper, 42 SelectionPredicate<String> selectionPredicate, 43 Callbacks<DocumentItemDetails> callbacks) { 44 45 mSelectionHelper = selectionHelper; 46 mSelectionPredicate = selectionPredicate; 47 mCallbacks = callbacks; 48 } 49 50 @Override onKey(@ullable DocumentItemDetails details, int keyCode, KeyEvent event)51 public boolean onKey(@Nullable DocumentItemDetails details, int keyCode, KeyEvent event) { 52 // Only handle key-down events. This is simpler, consistent with most other UIs, and 53 // enables the handling of repeated key events from holding down a key. 54 if (event.getAction() != KeyEvent.ACTION_DOWN) { 55 return false; 56 } 57 58 // Ignore tab key events. Those should be handled by the top-level key handler. 59 if (keyCode == KeyEvent.KEYCODE_TAB) { 60 return false; 61 } 62 63 // Ignore events sent to Addon Holders. 64 if (details != null) { 65 int itemType = details.getItemViewType(); 66 if (itemType == DocumentsAdapter.ITEM_TYPE_HEADER_MESSAGE 67 || itemType == DocumentsAdapter.ITEM_TYPE_INFLATED_MESSAGE 68 || itemType == DocumentsAdapter.ITEM_TYPE_SECTION_BREAK) { 69 return false; 70 } 71 } 72 73 if (mCallbacks.onFocusItem(details, keyCode, event)) { 74 // Handle range selection adjustments. Extending the selection will adjust the 75 // bounds of the in-progress range selection. Each time an unshifted navigation 76 // event is received, the range selection is restarted. 77 if (shouldExtendSelection(details, event)) { 78 if (!mSelectionHelper.isRangeActive()) { 79 // Start a range selection if one isn't active 80 mSelectionHelper.startRange(details.getPosition()); 81 } 82 mSelectionHelper.extendRange(details.getPosition()); 83 } else { 84 mSelectionHelper.endRange(); 85 mSelectionHelper.clearSelection(); 86 } 87 return true; 88 } 89 90 // we don't yet have a mechanism to handle opening/previewing multiple documents at once 91 if (mSelectionHelper.getSelection().size() > 1) { 92 return false; 93 } 94 95 return mCallbacks.onItemActivated(details, event); 96 } 97 shouldExtendSelection(DocumentItemDetails item, KeyEvent event)98 private boolean shouldExtendSelection(DocumentItemDetails item, KeyEvent event) { 99 if (!Events.isNavigationKeyCode(event.getKeyCode()) || !event.isShiftPressed()) { 100 return false; 101 } 102 103 return mSelectionPredicate.canSetStateForKey(item.getSelectionKey(), true); 104 } 105 106 public static abstract class Callbacks<T extends ItemDetails<?>> { isInteractiveItem(T item, KeyEvent e)107 public abstract boolean isInteractiveItem(T item, KeyEvent e); onItemActivated(T item, KeyEvent e)108 public abstract boolean onItemActivated(T item, KeyEvent e); onFocusItem(T details, int keyCode, KeyEvent event)109 public abstract boolean onFocusItem(T details, int keyCode, KeyEvent event); 110 } 111 } 112