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