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