1 /* 2 * Copyright (C) 2023 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.launcher3.util; 17 18 import static com.android.launcher3.LauncherState.ALL_APPS; 19 import static com.android.launcher3.LauncherState.NORMAL; 20 import static com.android.launcher3.LauncherState.OVERVIEW; 21 import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions; 22 23 import android.util.Log; 24 import android.view.KeyEvent; 25 import android.view.KeyboardShortcutGroup; 26 import android.view.KeyboardShortcutInfo; 27 import android.view.Menu; 28 29 import com.android.launcher3.AbstractFloatingView; 30 import com.android.launcher3.Launcher; 31 import com.android.launcher3.LauncherState; 32 import com.android.launcher3.R; 33 import com.android.launcher3.Utilities; 34 import com.android.launcher3.accessibility.BaseAccessibilityDelegate; 35 import com.android.launcher3.testing.shared.TestProtocol; 36 import com.android.launcher3.views.OptionsPopupView; 37 38 import java.util.ArrayList; 39 import java.util.List; 40 41 /** 42 * Delegate to define the keyboard shortcuts. 43 */ 44 public class KeyboardShortcutsDelegate { 45 46 Launcher mLauncher; 47 KeyboardShortcutsDelegate(Launcher launcher)48 public KeyboardShortcutsDelegate(Launcher launcher) { 49 mLauncher = launcher; 50 } 51 52 /** 53 * Populates the list of shortcuts. 54 */ onProvideKeyboardShortcuts( List<KeyboardShortcutGroup> data, Menu menu, int deviceId)55 public void onProvideKeyboardShortcuts( 56 List<KeyboardShortcutGroup> data, Menu menu, int deviceId) { 57 ArrayList<KeyboardShortcutInfo> shortcutInfos = new ArrayList<>(); 58 if (mLauncher.isInState(NORMAL)) { 59 shortcutInfos.add( 60 new KeyboardShortcutInfo(mLauncher.getString(R.string.all_apps_button_label), 61 KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)); 62 shortcutInfos.add( 63 new KeyboardShortcutInfo(mLauncher.getString(R.string.widget_button_text), 64 KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON)); 65 } 66 getSupportedActions(mLauncher, mLauncher.getCurrentFocus()).forEach(la -> 67 shortcutInfos.add(new KeyboardShortcutInfo( 68 la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON))); 69 if (!shortcutInfos.isEmpty()) { 70 data.add(new KeyboardShortcutGroup(mLauncher.getString(R.string.home_screen), 71 shortcutInfos)); 72 } 73 } 74 75 /** 76 * Handles combinations of keys like ctrl+s or ctrl+c and runs before onKeyDown. 77 * @param keyCode code of the key being pressed. 78 * @see android.view.KeyEvent 79 * @return weather the event is already handled and if it should be passed to other components. 80 */ onKeyShortcut(int keyCode, KeyEvent event)81 public Boolean onKeyShortcut(int keyCode, KeyEvent event) { 82 if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { 83 switch (keyCode) { 84 case KeyEvent.KEYCODE_A: 85 if (mLauncher.isInState(NORMAL)) { 86 mLauncher.getStateManager().goToState(ALL_APPS); 87 return true; 88 } 89 break; 90 case KeyEvent.KEYCODE_W: 91 if (mLauncher.isInState(NORMAL)) { 92 OptionsPopupView.openWidgets(mLauncher); 93 return true; 94 } 95 break; 96 default: 97 for (BaseAccessibilityDelegate.LauncherAction la : getSupportedActions( 98 mLauncher, mLauncher.getCurrentFocus())) { 99 if (la.keyCode == keyCode) { 100 return la.invokeFromKeyboard(mLauncher.getCurrentFocus()); 101 } 102 } 103 } 104 } 105 return null; 106 } 107 108 /** 109 * Handle key down event. 110 * @param keyCode code of the key being pressed. 111 * @see android.view.KeyEvent 112 */ onKeyDown(int keyCode, KeyEvent event)113 public Boolean onKeyDown(int keyCode, KeyEvent event) { 114 // Ignore escape if pressed in conjunction with any modifier keys. 115 if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) { 116 AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); 117 if (topView != null) { 118 // Close each floating view one at a time for each key press. 119 topView.close(/* animate= */ true); 120 return true; 121 } else if (mLauncher.getAppsView().isInAllApps()) { 122 // Close all apps if there are no open floating views. 123 mLauncher.getStateManager().goToState(NORMAL, true); 124 return true; 125 } else if (mLauncher.isInState(LauncherState.OVERVIEW) 126 || mLauncher.isInState(LauncherState.OVERVIEW_SPLIT_SELECT)) { 127 // Close Overview and return to home. 128 mLauncher.getStateManager().goToState(NORMAL, true); 129 return true; 130 } else if (mLauncher.isInState(LauncherState.OVERVIEW_MODAL_TASK)) { 131 // Return to the previous state (Overview) when the modal task is open. 132 mLauncher.getStateManager().goToState(OVERVIEW, true); 133 return true; 134 } 135 } 136 return null; 137 } 138 139 /** 140 * Handle key up event. 141 * @param keyCode code of the key being pressed. 142 * @see android.view.KeyEvent 143 */ onKeyUp(int keyCode, KeyEvent event)144 public Boolean onKeyUp(int keyCode, KeyEvent event) { 145 if (keyCode == KeyEvent.KEYCODE_MENU) { 146 // KEYCODE_MENU is sent by some tests, for example 147 // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling. 148 if (!mLauncher.getDragController().isDragging() 149 && !mLauncher.getWorkspace().isSwitchingState() 150 && mLauncher.isInState(NORMAL)) { 151 // Close any open floating views. 152 mLauncher.closeOpenViews(); 153 154 // Setting the touch point to (-1, -1) will show the options popup in the center of 155 // the screen. 156 if (Utilities.isRunningInTestHarness()) { 157 Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up"); 158 } 159 mLauncher.showDefaultOptions(-1, -1); 160 } 161 return true; 162 } 163 return null; 164 } 165 } 166