1 /* 2 * Copyright 2021 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 17 package com.android.launcher3.taskbar; 18 19 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; 20 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; 21 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS; 22 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_A11Y_BUTTON_TAP; 23 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS; 24 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_BACK_BUTTON_TAP; 25 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS; 26 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_HOME_BUTTON_TAP; 27 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP; 28 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS; 29 import static com.android.launcher3.logging.StatsLogManager.LauncherEvent.LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP; 30 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY; 31 import static com.android.systemui.shared.system.ActivityManagerWrapper.CLOSE_SYSTEM_WINDOWS_REASON_RECENTS; 32 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 33 34 import android.os.Bundle; 35 import android.os.Handler; 36 import android.util.Log; 37 import android.view.HapticFeedbackConstants; 38 import android.view.View; 39 40 import androidx.annotation.IntDef; 41 import androidx.annotation.Nullable; 42 import androidx.annotation.StringRes; 43 44 import com.android.launcher3.R; 45 import com.android.launcher3.logging.StatsLogManager; 46 import com.android.launcher3.testing.TestLogging; 47 import com.android.launcher3.testing.shared.TestProtocol; 48 import com.android.quickstep.OverviewCommandHelper; 49 import com.android.quickstep.SystemUiProxy; 50 import com.android.quickstep.TaskUtils; 51 import com.android.quickstep.TouchInteractionService; 52 53 import java.io.PrintWriter; 54 import java.lang.annotation.Retention; 55 import java.lang.annotation.RetentionPolicy; 56 57 /** 58 * Controller for 3 button mode in the taskbar. 59 * Handles all the functionality of the various buttons, making/routing the right calls into 60 * launcher or sysui/system. 61 */ 62 public class TaskbarNavButtonController implements TaskbarControllers.LoggableTaskbarController { 63 64 /** Allow some time in between the long press for back and recents. */ 65 static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200; 66 static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100; 67 private static final String TAG = TaskbarNavButtonController.class.getSimpleName(); 68 69 private long mLastScreenPinLongPress; 70 private boolean mScreenPinned; 71 private boolean mAssistantLongPressEnabled; 72 73 @Override dumpLogs(String prefix, PrintWriter pw)74 public void dumpLogs(String prefix, PrintWriter pw) { 75 pw.println(prefix + "TaskbarNavButtonController:"); 76 77 pw.println(prefix + "\tmLastScreenPinLongPress=" + mLastScreenPinLongPress); 78 pw.println(prefix + "\tmScreenPinned=" + mScreenPinned); 79 } 80 81 @Retention(RetentionPolicy.SOURCE) 82 @IntDef(value = { 83 BUTTON_BACK, 84 BUTTON_HOME, 85 BUTTON_RECENTS, 86 BUTTON_IME_SWITCH, 87 BUTTON_A11Y, 88 BUTTON_QUICK_SETTINGS, 89 BUTTON_NOTIFICATIONS, 90 }) 91 92 public @interface TaskbarButton {} 93 94 static final int BUTTON_BACK = 1; 95 static final int BUTTON_HOME = BUTTON_BACK << 1; 96 static final int BUTTON_RECENTS = BUTTON_HOME << 1; 97 static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; 98 static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1; 99 static final int BUTTON_QUICK_SETTINGS = BUTTON_A11Y << 1; 100 static final int BUTTON_NOTIFICATIONS = BUTTON_QUICK_SETTINGS << 1; 101 102 private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS; 103 private int mLongPressedButtons = 0; 104 105 private final TouchInteractionService mService; 106 private final SystemUiProxy mSystemUiProxy; 107 private final Handler mHandler; 108 @Nullable private StatsLogManager mStatsLogManager; 109 110 private final Runnable mResetLongPress = this::resetScreenUnpin; 111 TaskbarNavButtonController(TouchInteractionService service, SystemUiProxy systemUiProxy, Handler handler)112 public TaskbarNavButtonController(TouchInteractionService service, 113 SystemUiProxy systemUiProxy, Handler handler) { 114 mService = service; 115 mSystemUiProxy = systemUiProxy; 116 mHandler = handler; 117 } 118 onButtonClick(@askbarButton int buttonType, View view)119 public void onButtonClick(@TaskbarButton int buttonType, View view) { 120 // Provide the same haptic feedback that the system offers for virtual keys. 121 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 122 switch (buttonType) { 123 case BUTTON_BACK: 124 logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_TAP); 125 executeBack(); 126 break; 127 case BUTTON_HOME: 128 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_TAP); 129 navigateHome(); 130 break; 131 case BUTTON_RECENTS: 132 logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_TAP); 133 navigateToOverview(); 134 break; 135 case BUTTON_IME_SWITCH: 136 logEvent(LAUNCHER_TASKBAR_IME_SWITCHER_BUTTON_TAP); 137 showIMESwitcher(); 138 break; 139 case BUTTON_A11Y: 140 logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_TAP); 141 notifyA11yClick(false /* longClick */); 142 break; 143 case BUTTON_QUICK_SETTINGS: 144 showQuickSettings(); 145 break; 146 case BUTTON_NOTIFICATIONS: 147 showNotifications(); 148 break; 149 } 150 } 151 onButtonLongClick(@askbarButton int buttonType, View view)152 public boolean onButtonLongClick(@TaskbarButton int buttonType, View view) { 153 // Provide the same haptic feedback that the system offers for virtual keys. 154 view.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); 155 switch (buttonType) { 156 case BUTTON_HOME: 157 logEvent(LAUNCHER_TASKBAR_HOME_BUTTON_LONGPRESS); 158 startAssistant(); 159 return true; 160 case BUTTON_A11Y: 161 logEvent(LAUNCHER_TASKBAR_A11Y_BUTTON_LONGPRESS); 162 notifyA11yClick(true /* longClick */); 163 return true; 164 case BUTTON_BACK: 165 logEvent(LAUNCHER_TASKBAR_BACK_BUTTON_LONGPRESS); 166 return backRecentsLongpress(buttonType); 167 case BUTTON_RECENTS: 168 logEvent(LAUNCHER_TASKBAR_OVERVIEW_BUTTON_LONGPRESS); 169 return backRecentsLongpress(buttonType); 170 case BUTTON_IME_SWITCH: 171 default: 172 return false; 173 } 174 } 175 getButtonContentDescription(@askbarButton int buttonType)176 public @StringRes int getButtonContentDescription(@TaskbarButton int buttonType) { 177 switch (buttonType) { 178 case BUTTON_HOME: 179 return R.string.taskbar_button_home; 180 case BUTTON_A11Y: 181 return R.string.taskbar_button_a11y; 182 case BUTTON_BACK: 183 return R.string.taskbar_button_back; 184 case BUTTON_IME_SWITCH: 185 return R.string.taskbar_button_ime_switcher; 186 case BUTTON_RECENTS: 187 return R.string.taskbar_button_recents; 188 case BUTTON_NOTIFICATIONS: 189 return R.string.taskbar_button_notifications; 190 case BUTTON_QUICK_SETTINGS: 191 return R.string.taskbar_button_quick_settings; 192 default: 193 return 0; 194 } 195 } 196 backRecentsLongpress(@askbarButton int buttonType)197 private boolean backRecentsLongpress(@TaskbarButton int buttonType) { 198 mLongPressedButtons |= buttonType; 199 return determineScreenUnpin(); 200 } 201 202 /** 203 * Checks if the user has long pressed back and recents buttons 204 * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms 205 * If so, then requests the system to turn off screen pinning. 206 * 207 * @return true if the long press is a valid user action in attempting to unpin an app 208 * Will always return {@code false} when screen pinning is not active. 209 * NOTE: Returning true does not mean that screen pinning has stopped 210 */ determineScreenUnpin()211 private boolean determineScreenUnpin() { 212 long timeNow = System.currentTimeMillis(); 213 if (!mScreenPinned) { 214 return false; 215 } 216 217 if (mLastScreenPinLongPress == 0) { 218 // First button long press registered, just mark time and wait for second button press 219 mLastScreenPinLongPress = System.currentTimeMillis(); 220 mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET); 221 return true; 222 } 223 224 if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) { 225 // Too long in-between presses, reset the clock 226 resetScreenUnpin(); 227 return false; 228 } 229 230 if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) { 231 // Hooray! They did it (finally...) 232 mSystemUiProxy.stopScreenPinning(); 233 mHandler.removeCallbacks(mResetLongPress); 234 resetScreenUnpin(); 235 } 236 return true; 237 } 238 resetScreenUnpin()239 private void resetScreenUnpin() { 240 mLongPressedButtons = 0; 241 mLastScreenPinLongPress = 0; 242 } 243 updateSysuiFlags(int sysuiFlags)244 public void updateSysuiFlags(int sysuiFlags) { 245 mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0; 246 } 247 init(TaskbarControllers taskbarControllers)248 public void init(TaskbarControllers taskbarControllers) { 249 mStatsLogManager = taskbarControllers.getTaskbarActivityContext().getStatsLogManager(); 250 } 251 onDestroy()252 public void onDestroy() { 253 mStatsLogManager = null; 254 } 255 setAssistantLongPressEnabled(boolean assistantLongPressEnabled)256 public void setAssistantLongPressEnabled(boolean assistantLongPressEnabled) { 257 mAssistantLongPressEnabled = assistantLongPressEnabled; 258 } 259 logEvent(StatsLogManager.LauncherEvent event)260 private void logEvent(StatsLogManager.LauncherEvent event) { 261 if (mStatsLogManager == null) { 262 Log.w(TAG, "No stats log manager to log taskbar button event"); 263 return; 264 } 265 mStatsLogManager.logger().log(event); 266 } 267 navigateHome()268 private void navigateHome() { 269 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_HOME_KEY); 270 mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME); 271 } 272 navigateToOverview()273 private void navigateToOverview() { 274 if (mScreenPinned) { 275 return; 276 } 277 TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); 278 TaskUtils.closeSystemWindowsAsync(CLOSE_SYSTEM_WINDOWS_REASON_RECENTS); 279 mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE); 280 } 281 executeBack()282 private void executeBack() { 283 mSystemUiProxy.onBackPressed(); 284 } 285 showIMESwitcher()286 private void showIMESwitcher() { 287 mSystemUiProxy.onImeSwitcherPressed(); 288 } 289 notifyA11yClick(boolean longClick)290 private void notifyA11yClick(boolean longClick) { 291 if (longClick) { 292 mSystemUiProxy.notifyAccessibilityButtonLongClicked(); 293 } else { 294 mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId()); 295 } 296 } 297 startAssistant()298 private void startAssistant() { 299 if (mScreenPinned || !mAssistantLongPressEnabled) { 300 return; 301 } 302 Bundle args = new Bundle(); 303 args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); 304 mSystemUiProxy.startAssistant(args); 305 } 306 showQuickSettings()307 private void showQuickSettings() { 308 mSystemUiProxy.toggleNotificationPanel(); 309 } 310 showNotifications()311 private void showNotifications() { 312 mSystemUiProxy.toggleNotificationPanel(); 313 } 314 } 315