• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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