/* * Copyright (C) 2023 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.launcher3.util; import static com.android.launcher3.LauncherState.ALL_APPS; import static com.android.launcher3.LauncherState.NORMAL; import static com.android.launcher3.LauncherState.OVERVIEW; import static com.android.launcher3.accessibility.LauncherAccessibilityDelegate.getSupportedActions; import android.util.Log; import android.view.KeyEvent; import android.view.KeyboardShortcutGroup; import android.view.KeyboardShortcutInfo; import android.view.Menu; import com.android.launcher3.AbstractFloatingView; import com.android.launcher3.Launcher; import com.android.launcher3.LauncherState; import com.android.launcher3.R; import com.android.launcher3.Utilities; import com.android.launcher3.accessibility.BaseAccessibilityDelegate; import com.android.launcher3.testing.shared.TestProtocol; import com.android.launcher3.views.OptionsPopupView; import java.util.ArrayList; import java.util.List; /** * Delegate to define the keyboard shortcuts. */ public class KeyboardShortcutsDelegate { Launcher mLauncher; public KeyboardShortcutsDelegate(Launcher launcher) { mLauncher = launcher; } /** * Populates the list of shortcuts. */ public void onProvideKeyboardShortcuts( List data, Menu menu, int deviceId) { ArrayList shortcutInfos = new ArrayList<>(); if (mLauncher.isInState(NORMAL)) { shortcutInfos.add( new KeyboardShortcutInfo(mLauncher.getString(R.string.all_apps_button_label), KeyEvent.KEYCODE_A, KeyEvent.META_CTRL_ON)); shortcutInfos.add( new KeyboardShortcutInfo(mLauncher.getString(R.string.widget_button_text), KeyEvent.KEYCODE_W, KeyEvent.META_CTRL_ON)); } getSupportedActions(mLauncher, mLauncher.getCurrentFocus()).forEach(la -> shortcutInfos.add(new KeyboardShortcutInfo( la.accessibilityAction.getLabel(), la.keyCode, KeyEvent.META_CTRL_ON))); if (!shortcutInfos.isEmpty()) { data.add(new KeyboardShortcutGroup(mLauncher.getString(R.string.home_screen), shortcutInfos)); } } /** * Handles combinations of keys like ctrl+s or ctrl+c and runs before onKeyDown. * @param keyCode code of the key being pressed. * @see android.view.KeyEvent * @return weather the event is already handled and if it should be passed to other components. */ public Boolean onKeyShortcut(int keyCode, KeyEvent event) { if (event.hasModifiers(KeyEvent.META_CTRL_ON)) { switch (keyCode) { case KeyEvent.KEYCODE_A: if (mLauncher.isInState(NORMAL)) { mLauncher.getStateManager().goToState(ALL_APPS); return true; } break; case KeyEvent.KEYCODE_W: if (mLauncher.isInState(NORMAL)) { OptionsPopupView.openWidgets(mLauncher); return true; } break; default: for (BaseAccessibilityDelegate.LauncherAction la : getSupportedActions( mLauncher, mLauncher.getCurrentFocus())) { if (la.keyCode == keyCode) { return la.invokeFromKeyboard(mLauncher.getCurrentFocus()); } } } } return null; } /** * Handle key down event. * @param keyCode code of the key being pressed. * @see android.view.KeyEvent */ public Boolean onKeyDown(int keyCode, KeyEvent event) { // Ignore escape if pressed in conjunction with any modifier keys. if (keyCode == KeyEvent.KEYCODE_ESCAPE && event.hasNoModifiers()) { AbstractFloatingView topView = AbstractFloatingView.getTopOpenView(mLauncher); if (topView != null) { // Close each floating view one at a time for each key press. topView.close(/* animate= */ true); return true; } else if (mLauncher.getAppsView().isInAllApps()) { // Close all apps if there are no open floating views. mLauncher.getStateManager().goToState(NORMAL, true); return true; } else if (mLauncher.isInState(LauncherState.OVERVIEW) || mLauncher.isInState(LauncherState.OVERVIEW_SPLIT_SELECT)) { // Close Overview and return to home. mLauncher.getStateManager().goToState(NORMAL, true); return true; } else if (mLauncher.isInState(LauncherState.OVERVIEW_MODAL_TASK)) { // Return to the previous state (Overview) when the modal task is open. mLauncher.getStateManager().goToState(OVERVIEW, true); return true; } } return null; } /** * Handle key up event. * @param keyCode code of the key being pressed. * @see android.view.KeyEvent */ public Boolean onKeyUp(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { // KEYCODE_MENU is sent by some tests, for example // LauncherJankTests#testWidgetsContainerFling. Don't just remove its handling. if (!mLauncher.getDragController().isDragging() && !mLauncher.getWorkspace().isSwitchingState() && mLauncher.isInState(NORMAL)) { // Close any open floating views. mLauncher.closeOpenViews(); // Setting the touch point to (-1, -1) will show the options popup in the center of // the screen. if (Utilities.isRunningInTestHarness()) { Log.d(TestProtocol.PERMANENT_DIAG_TAG, "Opening options popup on key up"); } mLauncher.showDefaultOptions(-1, -1); } return true; } return null; } }