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 20 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS; 21 import static com.android.internal.app.AssistUtils.INVOCATION_TYPE_KEY; 22 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING; 23 24 import android.os.Bundle; 25 import android.os.Handler; 26 27 import androidx.annotation.IntDef; 28 29 import com.android.launcher3.testing.TestLogging; 30 import com.android.launcher3.testing.TestProtocol; 31 import com.android.quickstep.OverviewCommandHelper; 32 import com.android.quickstep.SystemUiProxy; 33 import com.android.quickstep.TouchInteractionService; 34 35 import java.lang.annotation.Retention; 36 import java.lang.annotation.RetentionPolicy; 37 38 /** 39 * Controller for 3 button mode in the taskbar. 40 * Handles all the functionality of the various buttons, making/routing the right calls into 41 * launcher or sysui/system. 42 */ 43 public class TaskbarNavButtonController { 44 45 /** Allow some time in between the long press for back and recents. */ 46 static final int SCREEN_PIN_LONG_PRESS_THRESHOLD = 200; 47 static final int SCREEN_PIN_LONG_PRESS_RESET = SCREEN_PIN_LONG_PRESS_THRESHOLD + 100; 48 49 private long mLastScreenPinLongPress; 50 private boolean mScreenPinned; 51 52 @Retention(RetentionPolicy.SOURCE) 53 @IntDef(value = { 54 BUTTON_BACK, 55 BUTTON_HOME, 56 BUTTON_RECENTS, 57 BUTTON_IME_SWITCH, 58 BUTTON_A11Y, 59 }) 60 61 public @interface TaskbarButton {} 62 63 static final int BUTTON_BACK = 1; 64 static final int BUTTON_HOME = BUTTON_BACK << 1; 65 static final int BUTTON_RECENTS = BUTTON_HOME << 1; 66 static final int BUTTON_IME_SWITCH = BUTTON_RECENTS << 1; 67 static final int BUTTON_A11Y = BUTTON_IME_SWITCH << 1; 68 69 private static final int SCREEN_UNPIN_COMBO = BUTTON_BACK | BUTTON_RECENTS; 70 private int mLongPressedButtons = 0; 71 72 private final TouchInteractionService mService; 73 private final SystemUiProxy mSystemUiProxy; 74 private final Handler mHandler; 75 76 private final Runnable mResetLongPress = this::resetScreenUnpin; 77 TaskbarNavButtonController(TouchInteractionService service, SystemUiProxy systemUiProxy, Handler handler)78 public TaskbarNavButtonController(TouchInteractionService service, 79 SystemUiProxy systemUiProxy, Handler handler) { 80 mService = service; 81 mSystemUiProxy = systemUiProxy; 82 mHandler = handler; 83 } 84 onButtonClick(@askbarButton int buttonType)85 public void onButtonClick(@TaskbarButton int buttonType) { 86 switch (buttonType) { 87 case BUTTON_BACK: 88 executeBack(); 89 break; 90 case BUTTON_HOME: 91 navigateHome(); 92 break; 93 case BUTTON_RECENTS: 94 navigateToOverview(); 95 break; 96 case BUTTON_IME_SWITCH: 97 showIMESwitcher(); 98 break; 99 case BUTTON_A11Y: 100 notifyA11yClick(false /* longClick */); 101 break; 102 } 103 } 104 onButtonLongClick(@askbarButton int buttonType)105 public boolean onButtonLongClick(@TaskbarButton int buttonType) { 106 switch (buttonType) { 107 case BUTTON_HOME: 108 startAssistant(); 109 return true; 110 case BUTTON_A11Y: 111 notifyA11yClick(true /* longClick */); 112 return true; 113 case BUTTON_BACK: 114 case BUTTON_RECENTS: 115 mLongPressedButtons |= buttonType; 116 return determineScreenUnpin(); 117 case BUTTON_IME_SWITCH: 118 default: 119 return false; 120 } 121 } 122 123 /** 124 * Checks if the user has long pressed back and recents buttons 125 * "together" (within {@link #SCREEN_PIN_LONG_PRESS_THRESHOLD})ms 126 * If so, then requests the system to turn off screen pinning. 127 * 128 * @return true if the long press is a valid user action in attempting to unpin an app 129 * Will always return {@code false} when screen pinning is not active. 130 * NOTE: Returning true does not mean that screen pinning has stopped 131 */ determineScreenUnpin()132 private boolean determineScreenUnpin() { 133 long timeNow = System.currentTimeMillis(); 134 if (!mScreenPinned) { 135 return false; 136 } 137 138 if (mLastScreenPinLongPress == 0) { 139 // First button long press registered, just mark time and wait for second button press 140 mLastScreenPinLongPress = System.currentTimeMillis(); 141 mHandler.postDelayed(mResetLongPress, SCREEN_PIN_LONG_PRESS_RESET); 142 return true; 143 } 144 145 if ((timeNow - mLastScreenPinLongPress) > SCREEN_PIN_LONG_PRESS_THRESHOLD) { 146 // Too long in-between presses, reset the clock 147 resetScreenUnpin(); 148 return false; 149 } 150 151 if ((mLongPressedButtons & SCREEN_UNPIN_COMBO) == SCREEN_UNPIN_COMBO) { 152 // Hooray! They did it (finally...) 153 mSystemUiProxy.stopScreenPinning(); 154 mHandler.removeCallbacks(mResetLongPress); 155 resetScreenUnpin(); 156 } 157 return true; 158 } 159 resetScreenUnpin()160 private void resetScreenUnpin() { 161 mLongPressedButtons = 0; 162 mLastScreenPinLongPress = 0; 163 } 164 updateSysuiFlags(int sysuiFlags)165 public void updateSysuiFlags(int sysuiFlags) { 166 mScreenPinned = (sysuiFlags & SYSUI_STATE_SCREEN_PINNING) != 0; 167 } 168 navigateHome()169 private void navigateHome() { 170 mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_HOME); 171 } 172 navigateToOverview()173 private void navigateToOverview() { 174 if (mScreenPinned) { 175 return; 176 } 177 TestLogging.recordEvent(TestProtocol.SEQUENCE_MAIN, "onOverviewToggle"); 178 mService.getOverviewCommandHelper().addCommand(OverviewCommandHelper.TYPE_TOGGLE); 179 } 180 executeBack()181 private void executeBack() { 182 mSystemUiProxy.onBackPressed(); 183 } 184 showIMESwitcher()185 private void showIMESwitcher() { 186 mSystemUiProxy.onImeSwitcherPressed(); 187 } 188 notifyA11yClick(boolean longClick)189 private void notifyA11yClick(boolean longClick) { 190 if (longClick) { 191 mSystemUiProxy.notifyAccessibilityButtonLongClicked(); 192 } else { 193 mSystemUiProxy.notifyAccessibilityButtonClicked(mService.getDisplayId()); 194 } 195 } 196 startAssistant()197 private void startAssistant() { 198 if (mScreenPinned) { 199 return; 200 } 201 Bundle args = new Bundle(); 202 args.putInt(INVOCATION_TYPE_KEY, INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS); 203 mSystemUiProxy.startAssistant(args); 204 } 205 } 206