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