1 /*
2  * Copyright (C) 2020 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.systemui.navigationbar;
18 
19 import static android.app.ActivityManager.LOCK_TASK_MODE_PINNED;
20 import static android.app.StatusBarManager.NAVIGATION_HINT_BACK_ALT;
21 import static android.app.StatusBarManager.NAVIGATION_HINT_IME_SWITCHER_SHOWN;
22 import static android.app.StatusBarManager.WINDOW_STATE_HIDDEN;
23 import static android.app.StatusBarManager.WINDOW_STATE_SHOWING;
24 import static android.app.StatusBarManager.WindowType;
25 import static android.app.StatusBarManager.WindowVisibleState;
26 import static android.app.StatusBarManager.windowStateToString;
27 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
28 import static android.inputmethodservice.InputMethodService.ENABLE_HIDE_IME_CAPTION_BAR;
29 import static android.view.InsetsSource.FLAG_SUPPRESS_SCRIM;
30 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
31 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
32 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
33 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
34 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_3BUTTON;
35 import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
36 
37 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
38 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.HOME_BUTTON_LONG_PRESS_DURATION_MS;
39 import static com.android.systemui.navigationbar.NavBarHelper.transitionMode;
40 import static com.android.systemui.recents.OverviewProxyService.OverviewProxyListener;
41 import static com.android.systemui.shared.recents.utilities.Utilities.isLargeScreen;
42 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_CLICKABLE;
43 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE;
44 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY;
45 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SHOWING;
46 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_IME_SWITCHER_SHOWING;
47 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NAV_BAR_HIDDEN;
48 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
49 import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
50 import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
51 import static com.android.systemui.statusbar.phone.BarTransitions.TransitionMode;
52 import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_WINDOW_STATE;
53 import static com.android.systemui.statusbar.phone.CentralSurfaces.dumpBarTransitions;
54 import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
55 
56 import android.annotation.IdRes;
57 import android.annotation.NonNull;
58 import android.app.ActivityTaskManager;
59 import android.app.IActivityTaskManager;
60 import android.app.StatusBarManager;
61 import android.content.Context;
62 import android.content.Intent;
63 import android.content.res.Configuration;
64 import android.graphics.Insets;
65 import android.graphics.PixelFormat;
66 import android.graphics.Point;
67 import android.graphics.Rect;
68 import android.graphics.RectF;
69 import android.graphics.Region;
70 import android.os.Binder;
71 import android.os.Bundle;
72 import android.os.Handler;
73 import android.os.IBinder;
74 import android.os.RemoteException;
75 import android.provider.DeviceConfig;
76 import android.telecom.TelecomManager;
77 import android.text.TextUtils;
78 import android.util.Log;
79 import android.view.Display;
80 import android.view.Gravity;
81 import android.view.HapticFeedbackConstants;
82 import android.view.InsetsFrameProvider;
83 import android.view.KeyEvent;
84 import android.view.MotionEvent;
85 import android.view.Surface;
86 import android.view.SurfaceControl;
87 import android.view.SurfaceControl.Transaction;
88 import android.view.View;
89 import android.view.ViewRootImpl;
90 import android.view.ViewRootImpl.SurfaceChangedCallback;
91 import android.view.ViewTreeObserver;
92 import android.view.ViewTreeObserver.InternalInsetsInfo;
93 import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
94 import android.view.WindowInsets;
95 import android.view.WindowInsets.Type.InsetsType;
96 import android.view.WindowInsetsController.Appearance;
97 import android.view.WindowInsetsController.Behavior;
98 import android.view.WindowManager;
99 import android.view.accessibility.AccessibilityEvent;
100 import android.view.accessibility.AccessibilityManager;
101 import android.view.inputmethod.InputMethodManager;
102 
103 import androidx.annotation.Nullable;
104 import androidx.annotation.VisibleForTesting;
105 
106 import com.android.internal.accessibility.dialog.AccessibilityButtonChooserActivity;
107 import com.android.internal.logging.MetricsLogger;
108 import com.android.internal.logging.UiEvent;
109 import com.android.internal.logging.UiEventLogger;
110 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
111 import com.android.internal.statusbar.LetterboxDetails;
112 import com.android.internal.util.LatencyTracker;
113 import com.android.internal.view.AppearanceRegion;
114 import com.android.systemui.Gefingerpoken;
115 import com.android.systemui.R;
116 import com.android.systemui.assist.AssistManager;
117 import com.android.systemui.dagger.qualifiers.Background;
118 import com.android.systemui.dagger.qualifiers.DisplayId;
119 import com.android.systemui.dagger.qualifiers.Main;
120 import com.android.systemui.keyguard.WakefulnessLifecycle;
121 import com.android.systemui.model.SysUiState;
122 import com.android.systemui.navigationbar.NavigationBarComponent.NavigationBarScope;
123 import com.android.systemui.navigationbar.NavigationModeController.ModeChangedListener;
124 import com.android.systemui.navigationbar.buttons.ButtonDispatcher;
125 import com.android.systemui.navigationbar.buttons.DeadZone;
126 import com.android.systemui.navigationbar.buttons.KeyButtonView;
127 import com.android.systemui.navigationbar.buttons.RotationContextButton;
128 import com.android.systemui.navigationbar.gestural.EdgeBackGestureHandler;
129 import com.android.systemui.navigationbar.gestural.QuickswitchOrientedNavHandle;
130 import com.android.systemui.plugins.statusbar.StatusBarStateController;
131 import com.android.systemui.recents.OverviewProxyService;
132 import com.android.systemui.recents.Recents;
133 import com.android.systemui.settings.DisplayTracker;
134 import com.android.systemui.settings.UserContextProvider;
135 import com.android.systemui.settings.UserTracker;
136 import com.android.systemui.shade.ShadeController;
137 import com.android.systemui.shade.ShadeViewController;
138 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
139 import com.android.systemui.shared.recents.utilities.Utilities;
140 import com.android.systemui.shared.rotation.RotationButton;
141 import com.android.systemui.shared.rotation.RotationButtonController;
142 import com.android.systemui.shared.system.QuickStepContract;
143 import com.android.systemui.shared.system.SysUiStatsLog;
144 import com.android.systemui.shared.system.TaskStackChangeListener;
145 import com.android.systemui.shared.system.TaskStackChangeListeners;
146 import com.android.systemui.statusbar.AutoHideUiElement;
147 import com.android.systemui.statusbar.CommandQueue;
148 import com.android.systemui.statusbar.CommandQueue.Callbacks;
149 import com.android.systemui.statusbar.NotificationRemoteInputManager;
150 import com.android.systemui.statusbar.NotificationShadeDepthController;
151 import com.android.systemui.statusbar.StatusBarState;
152 import com.android.systemui.statusbar.notification.stack.StackStateAnimator;
153 import com.android.systemui.statusbar.phone.AutoHideController;
154 import com.android.systemui.statusbar.phone.BarTransitions;
155 import com.android.systemui.statusbar.phone.CentralSurfaces;
156 import com.android.systemui.statusbar.phone.LightBarController;
157 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
158 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
159 import com.android.systemui.util.DeviceConfigProxy;
160 import com.android.systemui.util.ViewController;
161 import com.android.wm.shell.back.BackAnimation;
162 import com.android.wm.shell.pip.Pip;
163 
164 import dagger.Lazy;
165 
166 import java.io.PrintWriter;
167 import java.util.Locale;
168 import java.util.Map;
169 import java.util.Optional;
170 import java.util.concurrent.Executor;
171 
172 import javax.inject.Inject;
173 
174 /**
175  * Contains logic for a navigation bar view.
176  */
177 @NavigationBarScope
178 public class NavigationBar extends ViewController<NavigationBarView> implements Callbacks {
179 
180     public static final String TAG = "NavigationBar";
181     private static final boolean DEBUG = false;
182     private static final String EXTRA_DISABLE_STATE = "disabled_state";
183     private static final String EXTRA_DISABLE2_STATE = "disabled2_state";
184     private static final String EXTRA_APPEARANCE = "appearance";
185     private static final String EXTRA_BEHAVIOR = "behavior";
186     private static final String EXTRA_TRANSIENT_STATE = "transient_state";
187 
188     /** Allow some time inbetween the long press for back and recents. */
189     private static final int LOCK_TO_APP_GESTURE_TOLERANCE = 200;
190     private static final long AUTODIM_TIMEOUT_MS = 2250;
191 
192     private final Context mContext;
193     private final Bundle mSavedState;
194     private final WindowManager mWindowManager;
195     private final AccessibilityManager mAccessibilityManager;
196     private final DeviceProvisionedController mDeviceProvisionedController;
197     private final StatusBarStateController mStatusBarStateController;
198     private final MetricsLogger mMetricsLogger;
199     private final Lazy<AssistManager> mAssistManagerLazy;
200     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
201     private final SysUiState mSysUiFlagsContainer;
202     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
203     private final ShadeController mShadeController;
204     private final ShadeViewController mShadeViewController;
205     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
206     private final OverviewProxyService mOverviewProxyService;
207     private final NavigationModeController mNavigationModeController;
208     private final UserTracker mUserTracker;
209     private final CommandQueue mCommandQueue;
210     private final Optional<Pip> mPipOptional;
211     private final Optional<Recents> mRecentsOptional;
212     private final DeviceConfigProxy mDeviceConfigProxy;
213     private final NavigationBarTransitions mNavigationBarTransitions;
214     private final Optional<BackAnimation> mBackAnimation;
215     private final Handler mHandler;
216     private final UiEventLogger mUiEventLogger;
217     private final NavBarHelper mNavBarHelper;
218     private final NotificationShadeDepthController mNotificationShadeDepthController;
219     private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener;
220     private final UserContextProvider mUserContextProvider;
221     private final WakefulnessLifecycle mWakefulnessLifecycle;
222     private final DisplayTracker mDisplayTracker;
223     private final RegionSamplingHelper mRegionSamplingHelper;
224     private final int mNavColorSampleMargin;
225     private EdgeBackGestureHandler mEdgeBackGestureHandler;
226     private NavigationBarFrame mFrame;
227 
228     private @WindowVisibleState int mNavigationBarWindowState = WINDOW_STATE_SHOWING;
229 
230     private int mNavigationIconHints = 0;
231     private @TransitionMode int mTransitionMode;
232     private boolean mLongPressHomeEnabled;
233 
234     private int mDisabledFlags1;
235     private int mDisabledFlags2;
236     private long mLastLockToAppLongPress;
237 
238     private Locale mLocale;
239     private int mLayoutDirection;
240 
241     private Optional<Long> mHomeButtonLongPressDurationMs;
242 
243     /** @see android.view.WindowInsetsController#setSystemBarsAppearance(int, int) */
244     private @Appearance int mAppearance;
245 
246     /** @see android.view.WindowInsetsController#setSystemBarsBehavior(int) */
247     private @Behavior int mBehavior;
248 
249     private boolean mTransientShown;
250     private boolean mTransientShownFromGestureOnSystemBar;
251     private int mNavBarMode = NAV_BAR_MODE_3BUTTON;
252     private LightBarController mLightBarController;
253     private final LightBarController mMainLightBarController;
254     private final LightBarController.Factory mLightBarControllerFactory;
255     private AutoHideController mAutoHideController;
256     private final AutoHideController mMainAutoHideController;
257     private final AutoHideController.Factory mAutoHideControllerFactory;
258     private final Optional<TelecomManager> mTelecomManagerOptional;
259     private final InputMethodManager mInputMethodManager;
260     private final TaskStackChangeListeners mTaskStackChangeListeners;
261 
262     @VisibleForTesting
263     public int mDisplayId;
264     private boolean mIsOnDefaultDisplay;
265     public boolean mHomeBlockedThisTouch;
266 
267     /**
268      * When user is QuickSwitching between apps of different orientations, we'll draw a fake
269      * home handle on the orientation they originally touched down to start their swipe
270      * gesture to indicate to them that they can continue in that orientation without having to
271      * rotate the phone
272      * The secondary handle will show when we get
273      * {@link OverviewProxyListener#notifyPrioritizedRotation(int)} callback with the
274      * original handle hidden and we'll flip the visibilities once the
275      * {@link #mTasksFrozenListener} fires
276      */
277     private QuickswitchOrientedNavHandle mOrientationHandle;
278     private WindowManager.LayoutParams mOrientationParams;
279     private int mStartingQuickSwitchRotation = -1;
280     private int mCurrentRotation;
281     private ViewTreeObserver.OnGlobalLayoutListener mOrientationHandleGlobalLayoutListener;
282     private boolean mShowOrientedHandleForImmersiveMode;
283     private final DeadZone mDeadZone;
284     private boolean mImeVisible;
285     private final Rect mSamplingBounds = new Rect();
286     private final Binder mInsetsSourceOwner = new Binder();
287 
288     /**
289      * When quickswitching between apps of different orientations, we draw a secondary home handle
290      * in the position of the first app's orientation. This rect represents the region of that
291      * home handle so we can apply the correct light/dark luma on that.
292      * @see {@link NavigationBar#mOrientationHandle}
293      */
294     @android.annotation.Nullable
295     private Rect mOrientedHandleSamplingRegion;
296 
297     @com.android.internal.annotations.VisibleForTesting
298     public enum NavBarActionEvent implements UiEventLogger.UiEventEnum {
299 
300         @UiEvent(doc = "Assistant invoked via home button long press.")
301         NAVBAR_ASSIST_LONGPRESS(550);
302 
303         private final int mId;
304 
NavBarActionEvent(int id)305         NavBarActionEvent(int id) {
306             mId = id;
307         }
308 
309         @Override
getId()310         public int getId() {
311             return mId;
312         }
313     }
314 
315     private final AutoHideUiElement mAutoHideUiElement = new AutoHideUiElement() {
316         @Override
317         public void synchronizeState() {
318             checkNavBarModes();
319         }
320 
321         @Override
322         public boolean shouldHideOnTouch() {
323             return !mNotificationRemoteInputManager.isRemoteInputActive();
324         }
325 
326         @Override
327         public boolean isVisible() {
328             return isTransientShown();
329         }
330 
331         @Override
332         public void hide() {
333             clearTransient();
334         }
335     };
336 
337     private final NavBarHelper.NavbarTaskbarStateUpdater mNavbarTaskbarStateUpdater =
338             new NavBarHelper.NavbarTaskbarStateUpdater() {
339                 @Override
340                 public void updateAccessibilityServicesState() {
341                     updateAccessibilityStateFlags();
342                 }
343 
344                 @Override
345                 public void updateAssistantAvailable(boolean available,
346                         boolean longPressHomeEnabled) {
347                     // TODO(b/198002034): Content observers currently can still be called back after
348                     //  being unregistered, and in this case we can ignore the change if the nav bar
349                     //  has been destroyed already
350                     if (mView == null) {
351                         return;
352                     }
353                     mLongPressHomeEnabled = longPressHomeEnabled;
354                     updateAssistantEntrypoints(available, longPressHomeEnabled);
355                 }
356 
357                 @Override
358                 public void updateWallpaperVisibility(boolean visible, int displayId) {
359                     mNavigationBarTransitions.setWallpaperVisibility(visible);
360                 }
361 
362                 @Override
363                 public void updateRotationWatcherState(int rotation) {
364                     if (mIsOnDefaultDisplay && mView != null) {
365                         mView.getRotationButtonController().onRotationWatcherChanged(rotation);
366                         if (mView.needsReorient(rotation)) {
367                             repositionNavigationBar(rotation);
368                         }
369                     }
370                 }
371             };
372 
373     private final OverviewProxyListener mOverviewProxyListener = new OverviewProxyListener() {
374         @Override
375         public void onConnectionChanged(boolean isConnected) {
376             mView.onOverviewProxyConnectionChange(
377                     mOverviewProxyService.isEnabled());
378             mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
379             updateScreenPinningGestures();
380         }
381 
382         @Override
383         public void onPrioritizedRotation(@Surface.Rotation int rotation) {
384             mStartingQuickSwitchRotation = rotation;
385             if (rotation == -1) {
386                 mShowOrientedHandleForImmersiveMode = false;
387             }
388             orientSecondaryHomeHandle();
389         }
390 
391         @Override
392         public void startAssistant(Bundle bundle) {
393             mAssistManagerLazy.get().startAssist(bundle);
394         }
395 
396         @Override
397         public void setAssistantOverridesRequested(int[] invocationTypes) {
398             mAssistManagerLazy.get().setAssistantOverridesRequested(invocationTypes);
399         }
400 
401         @Override
402         public void onHomeRotationEnabled(boolean enabled) {
403             mView.getRotationButtonController().setHomeRotationEnabled(enabled);
404         }
405 
406         @Override
407         public void onOverviewShown(boolean fromHome) {
408             // If the overview has fixed orientation that may change display to natural rotation,
409             // we don't want the user rotation to be reset. So after user returns to application,
410             // it can keep in the original rotation.
411             mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
412         }
413 
414         @Override
415         public void onTaskbarStatusUpdated(boolean visible, boolean stashed) {
416             mView.getFloatingRotationButton().onTaskbarStateChanged(visible, stashed);
417         }
418 
419         @Override
420         public void onToggleRecentApps() {
421             // The same case as onOverviewShown but only for 3-button navigation.
422             mView.getRotationButtonController().setSkipOverrideUserLockPrefsOnce();
423         }
424     };
425 
426     private NavigationBarTransitions.DarkIntensityListener mOrientationHandleIntensityListener =
427             new NavigationBarTransitions.DarkIntensityListener() {
428                 @Override
429                 public void onDarkIntensity(float darkIntensity) {
430                     mOrientationHandle.setDarkIntensity(darkIntensity);
431                 }
432             };
433 
434     private final Runnable mAutoDim = () -> getBarTransitions().setAutoDim(true);
435     private final Runnable mEnableLayoutTransitions = () -> mView.setLayoutTransitionsEnabled(true);
436     private final Runnable mOnVariableDurationHomeLongClick = () -> {
437         if (onHomeLongClick(mView.getHomeButton().getCurrentView())) {
438             mView.getHomeButton().getCurrentView().performHapticFeedback(
439                     HapticFeedbackConstants.LONG_PRESS,
440                     HapticFeedbackConstants.FLAG_IGNORE_VIEW_SETTING);
441         }
442     };
443 
444     private final DeviceConfig.OnPropertiesChangedListener mOnPropertiesChangedListener =
445             new DeviceConfig.OnPropertiesChangedListener() {
446                 @Override
447                 public void onPropertiesChanged(DeviceConfig.Properties properties) {
448                     if (properties.getKeyset().contains(HOME_BUTTON_LONG_PRESS_DURATION_MS)) {
449                         mHomeButtonLongPressDurationMs = Optional.of(
450                             properties.getLong(HOME_BUTTON_LONG_PRESS_DURATION_MS, 0))
451                                 .filter(duration -> duration != 0);
452                         if (mView != null) {
453                             reconfigureHomeLongClick();
454                         }
455                     }
456                 }
457             };
458 
459     private final NotificationShadeDepthController.DepthListener mDepthListener =
460             new NotificationShadeDepthController.DepthListener() {
461                 boolean mHasBlurs;
462 
463                 @Override
464                 public void onWallpaperZoomOutChanged(float zoomOut) {
465                 }
466 
467                 @Override
468                 public void onBlurRadiusChanged(int radius) {
469                     boolean hasBlurs = radius != 0;
470                     if (hasBlurs == mHasBlurs) {
471                         return;
472                     }
473                     mHasBlurs = hasBlurs;
474                     mRegionSamplingHelper.setWindowHasBlurs(hasBlurs);
475                 }
476             };
477 
478     private final WakefulnessLifecycle.Observer mWakefulnessObserver =
479             new WakefulnessLifecycle.Observer() {
480                 private void notifyScreenStateChanged(boolean isScreenOn) {
481                     notifyNavigationBarScreenOn();
482                     mView.onScreenStateChanged(isScreenOn);
483                 }
484 
485                 @Override
486                 public void onStartedWakingUp() {
487                     notifyScreenStateChanged(true);
488                     if (isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker,
489                             mNavBarMode)) {
490                         mRegionSamplingHelper.start(mSamplingBounds);
491                     }
492                 }
493 
494                 @Override
495                 public void onFinishedGoingToSleep() {
496                     notifyScreenStateChanged(false);
497                     mRegionSamplingHelper.stop();
498                 }
499             };
500 
501     private final ViewRootImpl.SurfaceChangedCallback mSurfaceChangedCallback =
502             new SurfaceChangedCallback() {
503             @Override
504             public void surfaceCreated(Transaction t) {
505                 notifyNavigationBarSurface();
506             }
507 
508             @Override
509             public void surfaceDestroyed() {
510                 notifyNavigationBarSurface();
511             }
512 
513             @Override
514             public void surfaceReplaced(Transaction t) {
515                 notifyNavigationBarSurface();
516             }
517     };
518 
519     private boolean mScreenPinningActive = false;
520     private final TaskStackChangeListener mTaskStackListener = new TaskStackChangeListener() {
521         @Override
522         public void onLockTaskModeChanged(int mode) {
523             mScreenPinningActive = (mode == LOCK_TASK_MODE_PINNED);
524             mSysUiFlagsContainer.setFlag(SYSUI_STATE_SCREEN_PINNING, mScreenPinningActive)
525                     .commitUpdate(mDisplayId);
526             mView.setInScreenPinning(mScreenPinningActive);
527             updateScreenPinningGestures();
528         }
529     };
530 
531     @Inject
NavigationBar( NavigationBarView navigationBarView, ShadeController shadeController, NavigationBarFrame navigationBarFrame, @Nullable Bundle savedState, @DisplayId Context context, @DisplayId WindowManager windowManager, Lazy<AssistManager> assistManagerLazy, AccessibilityManager accessibilityManager, DeviceProvisionedController deviceProvisionedController, MetricsLogger metricsLogger, OverviewProxyService overviewProxyService, NavigationModeController navigationModeController, StatusBarStateController statusBarStateController, StatusBarKeyguardViewManager statusBarKeyguardViewManager, SysUiState sysUiFlagsContainer, UserTracker userTracker, CommandQueue commandQueue, Optional<Pip> pipOptional, Optional<Recents> recentsOptional, Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy, ShadeViewController shadeViewController, NotificationRemoteInputManager notificationRemoteInputManager, NotificationShadeDepthController notificationShadeDepthController, @Main Handler mainHandler, @Main Executor mainExecutor, @Background Executor bgExecutor, UiEventLogger uiEventLogger, NavBarHelper navBarHelper, LightBarController mainLightBarController, LightBarController.Factory lightBarControllerFactory, AutoHideController mainAutoHideController, AutoHideController.Factory autoHideControllerFactory, Optional<TelecomManager> telecomManagerOptional, InputMethodManager inputMethodManager, DeadZone deadZone, DeviceConfigProxy deviceConfigProxy, NavigationBarTransitions navigationBarTransitions, Optional<BackAnimation> backAnimation, UserContextProvider userContextProvider, WakefulnessLifecycle wakefulnessLifecycle, TaskStackChangeListeners taskStackChangeListeners, DisplayTracker displayTracker)532     NavigationBar(
533             NavigationBarView navigationBarView,
534             ShadeController shadeController,
535             NavigationBarFrame navigationBarFrame,
536             @Nullable Bundle savedState,
537             @DisplayId Context context,
538             @DisplayId WindowManager windowManager,
539             Lazy<AssistManager> assistManagerLazy,
540             AccessibilityManager accessibilityManager,
541             DeviceProvisionedController deviceProvisionedController,
542             MetricsLogger metricsLogger,
543             OverviewProxyService overviewProxyService,
544             NavigationModeController navigationModeController,
545             StatusBarStateController statusBarStateController,
546             StatusBarKeyguardViewManager statusBarKeyguardViewManager,
547             SysUiState sysUiFlagsContainer,
548             UserTracker userTracker,
549             CommandQueue commandQueue,
550             Optional<Pip> pipOptional,
551             Optional<Recents> recentsOptional,
552             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
553             ShadeViewController shadeViewController,
554             NotificationRemoteInputManager notificationRemoteInputManager,
555             NotificationShadeDepthController notificationShadeDepthController,
556             @Main Handler mainHandler,
557             @Main Executor mainExecutor,
558             @Background Executor bgExecutor,
559             UiEventLogger uiEventLogger,
560             NavBarHelper navBarHelper,
561             LightBarController mainLightBarController,
562             LightBarController.Factory lightBarControllerFactory,
563             AutoHideController mainAutoHideController,
564             AutoHideController.Factory autoHideControllerFactory,
565             Optional<TelecomManager> telecomManagerOptional,
566             InputMethodManager inputMethodManager,
567             DeadZone deadZone,
568             DeviceConfigProxy deviceConfigProxy,
569             NavigationBarTransitions navigationBarTransitions,
570             Optional<BackAnimation> backAnimation,
571             UserContextProvider userContextProvider,
572             WakefulnessLifecycle wakefulnessLifecycle,
573             TaskStackChangeListeners taskStackChangeListeners,
574             DisplayTracker displayTracker) {
575         super(navigationBarView);
576         mFrame = navigationBarFrame;
577         mContext = context;
578         mSavedState = savedState;
579         mWindowManager = windowManager;
580         mAccessibilityManager = accessibilityManager;
581         mDeviceProvisionedController = deviceProvisionedController;
582         mStatusBarStateController = statusBarStateController;
583         mMetricsLogger = metricsLogger;
584         mAssistManagerLazy = assistManagerLazy;
585         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
586         mSysUiFlagsContainer = sysUiFlagsContainer;
587         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
588         mShadeController = shadeController;
589         mShadeViewController = shadeViewController;
590         mNotificationRemoteInputManager = notificationRemoteInputManager;
591         mOverviewProxyService = overviewProxyService;
592         mNavigationModeController = navigationModeController;
593         mUserTracker = userTracker;
594         mCommandQueue = commandQueue;
595         mPipOptional = pipOptional;
596         mRecentsOptional = recentsOptional;
597         mDeadZone = deadZone;
598         mDeviceConfigProxy = deviceConfigProxy;
599         mNavigationBarTransitions = navigationBarTransitions;
600         mBackAnimation = backAnimation;
601         mHandler = mainHandler;
602         mUiEventLogger = uiEventLogger;
603         mNavBarHelper = navBarHelper;
604         mNotificationShadeDepthController = notificationShadeDepthController;
605         mMainLightBarController = mainLightBarController;
606         mLightBarControllerFactory = lightBarControllerFactory;
607         mMainAutoHideController = mainAutoHideController;
608         mAutoHideControllerFactory = autoHideControllerFactory;
609         mTelecomManagerOptional = telecomManagerOptional;
610         mInputMethodManager = inputMethodManager;
611         mUserContextProvider = userContextProvider;
612         mWakefulnessLifecycle = wakefulnessLifecycle;
613         mTaskStackChangeListeners = taskStackChangeListeners;
614         mDisplayTracker = displayTracker;
615         mEdgeBackGestureHandler = navBarHelper.getEdgeBackGestureHandler();
616 
617         mNavColorSampleMargin = getResources()
618                 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
619 
620         mOnComputeInternalInsetsListener = info -> {
621             // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
622             // gestural mode, the entire nav bar should be touchable.
623             if (!mEdgeBackGestureHandler.isHandlingGestures()) {
624                 // We're in 2/3 button mode OR back button force-shown in SUW
625                 if (!mImeVisible) {
626                     // IME not showing, take all touches
627                     info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
628                     return;
629                 }
630                 if (!mView.isImeRenderingNavButtons()) {
631                     // IME showing but not drawing any buttons, take all touches
632                     info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
633                     return;
634                 }
635             }
636 
637             // When in gestural and the IME is showing, don't use the nearest region since it will
638             // take gesture space away from the IME
639             info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
640             info.touchableRegion.set(getButtonLocations(false /* includeFloatingButtons */,
641                     false /* inScreen */, false /* useNearestRegion */));
642         };
643 
644         mRegionSamplingHelper = new RegionSamplingHelper(mView,
645                 new RegionSamplingHelper.SamplingCallback() {
646                     @Override
647                     public void onRegionDarknessChanged(boolean isRegionDark) {
648                         getBarTransitions().getLightTransitionsController().setIconsDark(
649                                 !isRegionDark, true /* animate */);
650                     }
651 
652                     @Override
653                     public Rect getSampledRegion(View sampledView) {
654                         if (mOrientedHandleSamplingRegion != null) {
655                             return mOrientedHandleSamplingRegion;
656                         }
657 
658                         return calculateSamplingRect();
659                     }
660 
661                     @Override
662                     public boolean isSamplingEnabled() {
663                         return isGesturalModeOnDefaultDisplay(getContext(), mDisplayTracker,
664                                 mNavBarMode);
665                     }
666                 }, mainExecutor, bgExecutor);
667 
668         mView.setBackgroundExecutor(bgExecutor);
669         mView.setEdgeBackGestureHandler(mEdgeBackGestureHandler);
670         mView.setDisplayTracker(mDisplayTracker);
671         mNavBarMode = mNavigationModeController.addListener(mModeChangedListener);
672     }
673 
getView()674     public NavigationBarView getView() {
675         return mView;
676     }
677 
678     @Override
onInit()679     public void onInit() {
680         // TODO: A great deal of this code should probably live in onViewAttached.
681         // It should also has corresponding cleanup in onViewDetached.
682         mView.setBarTransitions(mNavigationBarTransitions);
683         mView.setTouchHandler(mTouchHandler);
684         setNavBarMode(mNavBarMode);
685         mEdgeBackGestureHandler.setStateChangeCallback(mView::updateStates);
686         mEdgeBackGestureHandler.setButtonForcedVisibleChangeCallback((forceVisible) -> {
687             repositionNavigationBar(mCurrentRotation);
688         });
689         mNavigationBarTransitions.addListener(this::onBarTransition);
690         mView.updateRotationButton();
691 
692         mView.setVisibility(
693                 mStatusBarKeyguardViewManager.isNavBarVisible() ? View.VISIBLE : View.INVISIBLE);
694 
695         if (DEBUG) Log.v(TAG, "addNavigationBar: about to add " + mView);
696 
697         mWindowManager.addView(mFrame,
698                 getBarLayoutParams(mContext.getResources().getConfiguration().windowConfiguration
699                         .getRotation()));
700         mDisplayId = mContext.getDisplayId();
701         mIsOnDefaultDisplay = mDisplayId == mDisplayTracker.getDefaultDisplayId();
702 
703         // Ensure we try to get currentSysuiState from navBarHelper before command queue callbacks
704         // start firing, since the latter is source of truth
705         parseCurrentSysuiState();
706         mCommandQueue.addCallback(this);
707         mHomeButtonLongPressDurationMs = Optional.of(mDeviceConfigProxy.getLong(
708                 DeviceConfig.NAMESPACE_SYSTEMUI,
709                 HOME_BUTTON_LONG_PRESS_DURATION_MS,
710                 /* defaultValue = */ 0
711         )).filter(duration -> duration != 0);
712         // This currently MUST be called after mHomeButtonLongPressDurationMs is initialized since
713         // the registration callbacks will trigger code that uses it
714         mNavBarHelper.registerNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
715         mDeviceConfigProxy.addOnPropertiesChangedListener(
716                 DeviceConfig.NAMESPACE_SYSTEMUI, mHandler::post, mOnPropertiesChangedListener);
717 
718         if (mSavedState != null) {
719             mDisabledFlags1 = mSavedState.getInt(EXTRA_DISABLE_STATE, 0);
720             mDisabledFlags2 = mSavedState.getInt(EXTRA_DISABLE2_STATE, 0);
721             mAppearance = mSavedState.getInt(EXTRA_APPEARANCE, 0);
722             mBehavior = mSavedState.getInt(EXTRA_BEHAVIOR, 0);
723             mTransientShown = mSavedState.getBoolean(EXTRA_TRANSIENT_STATE, false);
724         }
725 
726         // Respect the latest disabled-flags.
727         mCommandQueue.recomputeDisableFlags(mDisplayId, false);
728 
729         mNotificationShadeDepthController.addListener(mDepthListener);
730         mTaskStackChangeListeners.registerTaskStackListener(mTaskStackListener);
731     }
732 
destroyView()733     public void destroyView() {
734         setAutoHideController(/* autoHideController */ null);
735         mCommandQueue.removeCallback(this);
736         mWindowManager.removeViewImmediate(mView.getRootView());
737         mNavigationModeController.removeListener(mModeChangedListener);
738         mEdgeBackGestureHandler.setStateChangeCallback(null);
739 
740         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
741         mNotificationShadeDepthController.removeListener(mDepthListener);
742 
743         mDeviceConfigProxy.removeOnPropertiesChangedListener(mOnPropertiesChangedListener);
744         mTaskStackChangeListeners.unregisterTaskStackListener(mTaskStackListener);
745     }
746 
747     @Override
onViewAttached()748     public void onViewAttached() {
749         final Display display = mView.getDisplay();
750         mView.setComponents(mRecentsOptional);
751         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
752             mView.setComponents(mShadeViewController);
753         }
754         mView.setDisabledFlags(mDisabledFlags1, mSysUiFlagsContainer);
755         mView.setOnVerticalChangedListener(this::onVerticalChanged);
756         mView.setOnTouchListener(this::onNavigationTouch);
757         if (mSavedState != null) {
758             getBarTransitions().getLightTransitionsController().restoreState(mSavedState);
759         }
760         setNavigationIconHints(mNavigationIconHints);
761         setWindowVisible(isNavBarWindowVisible());
762         mView.setBehavior(mBehavior);
763         setNavBarMode(mNavBarMode);
764         repositionNavigationBar(mCurrentRotation);
765         mView.setUpdateActiveTouchRegionsCallback(
766                 () -> mOverviewProxyService.onActiveNavBarRegionChanges(
767                         getButtonLocations(
768                                 true /* includeFloatingButtons */,
769                                 true /* inScreen */,
770                                 true /* useNearestRegion */)));
771 
772         mView.getViewTreeObserver().addOnComputeInternalInsetsListener(
773                 mOnComputeInternalInsetsListener);
774         mView.getViewRootImpl().addSurfaceChangedCallback(mSurfaceChangedCallback);
775         notifyNavigationBarSurface();
776 
777         mPipOptional.ifPresent(mView::addPipExclusionBoundsChangeListener);
778         mBackAnimation.ifPresent(mView::registerBackAnimation);
779 
780         prepareNavigationBarView();
781         checkNavBarModes();
782 
783         mUserTracker.addCallback(mUserChangedCallback, mContext.getMainExecutor());
784         mWakefulnessLifecycle.addObserver(mWakefulnessObserver);
785         notifyNavigationBarScreenOn();
786 
787         mOverviewProxyService.addCallback(mOverviewProxyListener);
788         updateSystemUiStateFlags();
789 
790         // Currently there is no accelerometer sensor on non-default display.
791         if (mIsOnDefaultDisplay) {
792             final RotationButtonController rotationButtonController =
793                     mView.getRotationButtonController();
794 
795             // Reset user rotation pref to match that of the WindowManager if starting in locked
796             // mode. This will automatically happen when switching from auto-rotate to locked mode.
797             if (display != null && rotationButtonController.isRotationLocked()) {
798                 rotationButtonController.setRotationLockedAtAngle(display.getRotation());
799             }
800         } else {
801             mDisabledFlags2 |= StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS;
802         }
803         setDisabled2Flags(mDisabledFlags2);
804 
805         initSecondaryHomeHandleForRotation();
806 
807         // Unfortunately, we still need it because status bar needs LightBarController
808         // before notifications creation. We cannot directly use getLightBarController()
809         // from NavigationBarFragment directly.
810         LightBarController lightBarController = mIsOnDefaultDisplay
811                 ? mMainLightBarController : mLightBarControllerFactory.create(mContext);
812         setLightBarController(lightBarController);
813 
814         // TODO(b/118592525): to support multi-display, we start to add something which is
815         //                    per-display, while others may be global. I think it's time to
816         //                    add a new class maybe named DisplayDependency to solve
817         //                    per-display Dependency problem.
818         // Alternative: this is a good case for a Dagger subcomponent. Same with LightBarController.
819         AutoHideController autoHideController = mIsOnDefaultDisplay
820                 ? mMainAutoHideController : mAutoHideControllerFactory.create(mContext);
821         setAutoHideController(autoHideController);
822         restoreAppearanceAndTransientState();
823     }
824 
825     @Override
onViewDetached()826     public void onViewDetached() {
827         mView.setUpdateActiveTouchRegionsCallback(null);
828         getBarTransitions().destroy();
829         mOverviewProxyService.removeCallback(mOverviewProxyListener);
830         mUserTracker.removeCallback(mUserChangedCallback);
831         mWakefulnessLifecycle.removeObserver(mWakefulnessObserver);
832         if (mOrientationHandle != null) {
833             resetSecondaryHandle();
834             getBarTransitions().removeDarkIntensityListener(mOrientationHandleIntensityListener);
835             mWindowManager.removeView(mOrientationHandle);
836             mOrientationHandle.getViewTreeObserver().removeOnGlobalLayoutListener(
837                     mOrientationHandleGlobalLayoutListener);
838         }
839         mView.getViewTreeObserver().removeOnComputeInternalInsetsListener(
840                 mOnComputeInternalInsetsListener);
841         mHandler.removeCallbacks(mAutoDim);
842         mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
843         mHandler.removeCallbacks(mEnableLayoutTransitions);
844         mNavBarHelper.removeNavTaskStateUpdater(mNavbarTaskbarStateUpdater);
845         mPipOptional.ifPresent(mView::removePipExclusionBoundsChangeListener);
846         ViewRootImpl viewRoot = mView.getViewRootImpl();
847         if (viewRoot != null) {
848             viewRoot.removeSurfaceChangedCallback(mSurfaceChangedCallback);
849         }
850         mFrame = null;
851         mOrientationHandle = null;
852         notifyNavigationBarSurface();
853     }
854 
855     // TODO: Remove this when we update nav bar recreation
onSaveInstanceState(Bundle outState)856     public void onSaveInstanceState(Bundle outState) {
857         outState.putInt(EXTRA_DISABLE_STATE, mDisabledFlags1);
858         outState.putInt(EXTRA_DISABLE2_STATE, mDisabledFlags2);
859         outState.putInt(EXTRA_APPEARANCE, mAppearance);
860         outState.putInt(EXTRA_BEHAVIOR, mBehavior);
861         outState.putBoolean(EXTRA_TRANSIENT_STATE, mTransientShown);
862         getBarTransitions().getLightTransitionsController().saveState(outState);
863     }
864 
865     /**
866      * Called when a non-reloading configuration change happens and we need to update.
867      */
onConfigurationChanged(Configuration newConfig)868     public void onConfigurationChanged(Configuration newConfig) {
869         final int rotation = newConfig.windowConfiguration.getRotation();
870         final Locale locale = mContext.getResources().getConfiguration().locale;
871         final int ld = TextUtils.getLayoutDirectionFromLocale(locale);
872         if (!locale.equals(mLocale) || ld != mLayoutDirection) {
873             if (DEBUG) {
874                 Log.v(TAG, String.format(
875                         "config changed locale/LD: %s (%d) -> %s (%d)", mLocale, mLayoutDirection,
876                         locale, ld));
877             }
878             mLocale = locale;
879             mLayoutDirection = ld;
880             refreshLayout(ld);
881         }
882         repositionNavigationBar(rotation);
883         // NOTE(b/260220098): In some cases, the recreated nav bar will already have the right
884         // configuration, which means that NavBarView will not receive a configuration change to
885         // propagate to EdgeBackGestureHandler (which is injected into this and NBV). As such, we
886         // should also force-update the gesture handler to ensure it updates to the right bounds
887         mEdgeBackGestureHandler.onConfigurationChanged(newConfig);
888         if (canShowSecondaryHandle()) {
889             if (rotation != mCurrentRotation) {
890                 mCurrentRotation = rotation;
891                 orientSecondaryHomeHandle();
892             }
893         }
894     }
895 
initSecondaryHomeHandleForRotation()896     private void initSecondaryHomeHandleForRotation() {
897         if (mNavBarMode != NAV_BAR_MODE_GESTURAL) {
898             return;
899         }
900 
901         mOrientationHandle = new QuickswitchOrientedNavHandle(mContext);
902         mOrientationHandle.setId(R.id.secondary_home_handle);
903 
904         getBarTransitions().addDarkIntensityListener(mOrientationHandleIntensityListener);
905         mOrientationParams = new WindowManager.LayoutParams(0, 0,
906                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
907                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
908                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
909                         | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
910                         | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
911                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
912                 PixelFormat.TRANSLUCENT);
913         mOrientationParams.setTitle("SecondaryHomeHandle" + mContext.getDisplayId());
914         mOrientationParams.privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION
915                 | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
916         mWindowManager.addView(mOrientationHandle, mOrientationParams);
917         mOrientationHandle.setVisibility(View.GONE);
918         mOrientationParams.setFitInsetsTypes(0 /* types*/);
919         mOrientationHandleGlobalLayoutListener =
920                 () -> {
921                     if (mStartingQuickSwitchRotation == -1) {
922                         return;
923                     }
924 
925                     RectF boundsOnScreen = mOrientationHandle.computeHomeHandleBounds();
926                     mOrientationHandle.mapRectFromViewToScreenCoords(boundsOnScreen, true);
927                     Rect boundsRounded = new Rect();
928                     boundsOnScreen.roundOut(boundsRounded);
929                     setOrientedHandleSamplingRegion(boundsRounded);
930                 };
931         mOrientationHandle.getViewTreeObserver().addOnGlobalLayoutListener(
932                 mOrientationHandleGlobalLayoutListener);
933     }
934 
orientSecondaryHomeHandle()935     private void orientSecondaryHomeHandle() {
936         if (!canShowSecondaryHandle()) {
937             return;
938         }
939 
940         if (mStartingQuickSwitchRotation == -1) {
941             resetSecondaryHandle();
942         } else {
943             int deltaRotation = deltaRotation(mCurrentRotation, mStartingQuickSwitchRotation);
944             if (mStartingQuickSwitchRotation == -1 || deltaRotation == -1) {
945                 // Curious if starting quickswitch can change between the if check and our delta
946                 Log.d(TAG, "secondary nav delta rotation: " + deltaRotation
947                         + " current: " + mCurrentRotation
948                         + " starting: " + mStartingQuickSwitchRotation);
949             }
950             int height = 0;
951             int width = 0;
952             Rect dispSize = mWindowManager.getCurrentWindowMetrics().getBounds();
953             mOrientationHandle.setDeltaRotation(deltaRotation);
954             switch (deltaRotation) {
955                 case Surface.ROTATION_90:
956                 case Surface.ROTATION_270:
957                     height = dispSize.height();
958                     width = mView.getHeight();
959                     break;
960                 case Surface.ROTATION_180:
961                 case Surface.ROTATION_0:
962                     // TODO(b/152683657): Need to determine best UX for this
963                     if (!mShowOrientedHandleForImmersiveMode) {
964                         resetSecondaryHandle();
965                         return;
966                     }
967                     width = dispSize.width();
968                     height = mView.getHeight();
969                     break;
970             }
971 
972             mOrientationParams.gravity =
973                     deltaRotation == Surface.ROTATION_0 ? Gravity.BOTTOM :
974                             (deltaRotation == Surface.ROTATION_90 ? Gravity.LEFT : Gravity.RIGHT);
975             mOrientationParams.height = height;
976             mOrientationParams.width = width;
977             mWindowManager.updateViewLayout(mOrientationHandle, mOrientationParams);
978             mView.setVisibility(View.GONE);
979             mOrientationHandle.setVisibility(View.VISIBLE);
980         }
981     }
982 
resetSecondaryHandle()983     private void resetSecondaryHandle() {
984         if (mOrientationHandle != null) {
985             // Case where nav mode is changed w/o ever invoking a quickstep
986             // mOrientedHandle is initialized lazily
987             mOrientationHandle.setVisibility(View.GONE);
988         }
989         mView.setVisibility(View.VISIBLE);
990         setOrientedHandleSamplingRegion(null);
991     }
992 
parseCurrentSysuiState()993     private void parseCurrentSysuiState() {
994         NavBarHelper.CurrentSysuiState state = mNavBarHelper.getCurrentSysuiState();
995         if (state.mWindowStateDisplayId == mDisplayId) {
996             mNavigationBarWindowState = state.mWindowState;
997         }
998     }
999 
reconfigureHomeLongClick()1000     private void reconfigureHomeLongClick() {
1001         if (mView.getHomeButton().getCurrentView() == null) {
1002             return;
1003         }
1004         if (mHomeButtonLongPressDurationMs.isPresent() || !mLongPressHomeEnabled) {
1005             mView.getHomeButton().getCurrentView().setLongClickable(false);
1006             mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(false);
1007             mView.getHomeButton().setOnLongClickListener(null);
1008         } else {
1009             mView.getHomeButton().getCurrentView().setLongClickable(true);
1010             mView.getHomeButton().getCurrentView().setHapticFeedbackEnabled(true);
1011             mView.getHomeButton().setOnLongClickListener(this::onHomeLongClick);
1012         }
1013     }
1014 
notifyNavigationBarSurface()1015     private void notifyNavigationBarSurface() {
1016         ViewRootImpl viewRoot = mView.getViewRootImpl();
1017         SurfaceControl surface = mView.getParent() != null
1018                 && viewRoot != null
1019                 && viewRoot.getSurfaceControl() != null
1020                 && viewRoot.getSurfaceControl().isValid()
1021                         ? viewRoot.getSurfaceControl()
1022                         : null;
1023         mOverviewProxyService.onNavigationBarSurfaceChanged(surface);
1024     }
1025 
deltaRotation(int oldRotation, int newRotation)1026     private int deltaRotation(int oldRotation, int newRotation) {
1027         int delta = newRotation - oldRotation;
1028         if (delta < 0) delta += 4;
1029         return delta;
1030     }
1031 
dump(PrintWriter pw)1032     public void dump(PrintWriter pw) {
1033         pw.println("NavigationBar (displayId=" + mDisplayId + "):");
1034         pw.println("  mStartingQuickSwitchRotation=" + mStartingQuickSwitchRotation);
1035         pw.println("  mCurrentRotation=" + mCurrentRotation);
1036         pw.println("  mHomeButtonLongPressDurationMs=" + mHomeButtonLongPressDurationMs);
1037         pw.println("  mLongPressHomeEnabled=" + mLongPressHomeEnabled);
1038         pw.println("  mNavigationBarWindowState="
1039                 + windowStateToString(mNavigationBarWindowState));
1040         pw.println("  mTransitionMode="
1041                 + BarTransitions.modeToString(mTransitionMode));
1042         pw.println("  mTransientShown=" + mTransientShown);
1043         pw.println("  mTransientShownFromGestureOnSystemBar="
1044                 + mTransientShownFromGestureOnSystemBar);
1045         pw.println("  mScreenPinningActive=" + mScreenPinningActive);
1046         dumpBarTransitions(pw, "mNavigationBarView", getBarTransitions());
1047 
1048         pw.println("  mOrientedHandleSamplingRegion: " + mOrientedHandleSamplingRegion);
1049         mView.dump(pw);
1050         mRegionSamplingHelper.dump(pw);
1051         if (mAutoHideController != null) {
1052             mAutoHideController.dump(pw);
1053         }
1054     }
1055 
1056     // ----- CommandQueue Callbacks -----
1057 
1058     @Override
setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition, boolean showImeSwitcher)1059     public void setImeWindowStatus(int displayId, IBinder token, int vis, int backDisposition,
1060             boolean showImeSwitcher) {
1061         if (displayId != mDisplayId) {
1062             return;
1063         }
1064         boolean imeShown = mNavBarHelper.isImeShown(vis);
1065         showImeSwitcher = imeShown && showImeSwitcher;
1066         int hints = Utilities.calculateBackDispositionHints(mNavigationIconHints, backDisposition,
1067                 imeShown, showImeSwitcher);
1068         if (hints == mNavigationIconHints) return;
1069 
1070         setNavigationIconHints(hints);
1071         checkBarModes();
1072         updateSystemUiStateFlags();
1073     }
1074 
1075     @Override
setWindowState( int displayId, @WindowType int window, @WindowVisibleState int state)1076     public void setWindowState(
1077             int displayId, @WindowType int window, @WindowVisibleState int state) {
1078         if (displayId == mDisplayId
1079                 && window == StatusBarManager.WINDOW_NAVIGATION_BAR
1080                 && mNavigationBarWindowState != state) {
1081             mNavigationBarWindowState = state;
1082             updateSystemUiStateFlags();
1083             mShowOrientedHandleForImmersiveMode = state == WINDOW_STATE_HIDDEN;
1084             if (mOrientationHandle != null
1085                     && mStartingQuickSwitchRotation != -1) {
1086                 orientSecondaryHomeHandle();
1087             }
1088             if (DEBUG_WINDOW_STATE) Log.d(TAG, "Navigation bar " + windowStateToString(state));
1089             setWindowVisible(isNavBarWindowVisible());
1090         }
1091     }
1092 
1093     @Override
onRotationProposal(final int rotation, boolean isValid)1094     public void onRotationProposal(final int rotation, boolean isValid) {
1095         // The CommandQueue callbacks are added when the view is created to ensure we track other
1096         // states, but until the view is attached (at the next traversal), the view's display is
1097         // not valid.  Just ignore the rotation in this case.
1098         if (!mView.isAttachedToWindow()) return;
1099 
1100         final boolean rotateSuggestionsDisabled = RotationButtonController
1101                 .hasDisable2RotateSuggestionFlag(mDisabledFlags2);
1102         final RotationButtonController rotationButtonController =
1103                 mView.getRotationButtonController();
1104         final RotationButton rotationButton = rotationButtonController.getRotationButton();
1105 
1106         if (RotationContextButton.DEBUG_ROTATION) {
1107             Log.v(TAG, "onRotationProposal proposedRotation=" + Surface.rotationToString(rotation)
1108                     + ", isValid=" + isValid + ", mNavBarWindowState="
1109                     + StatusBarManager.windowStateToString(mNavigationBarWindowState)
1110                     + ", rotateSuggestionsDisabled=" + rotateSuggestionsDisabled
1111                     + ", isRotateButtonVisible=" + rotationButton.isVisible());
1112         }
1113 
1114         // Respect the disabled flag, no need for action as flag change callback will handle hiding
1115         if (rotateSuggestionsDisabled) return;
1116 
1117         rotationButtonController.onRotationProposal(rotation, isValid);
1118     }
1119 
1120     @Override
onRecentsAnimationStateChanged(boolean running)1121     public void onRecentsAnimationStateChanged(boolean running) {
1122         mView.getRotationButtonController().setRecentsAnimationRunning(running);
1123     }
1124 
1125     /** Restores the appearance and the transient saved state to {@link NavigationBar}. */
restoreAppearanceAndTransientState()1126     public void restoreAppearanceAndTransientState() {
1127         final int transitionMode = transitionMode(mTransientShown, mAppearance);
1128         mTransitionMode = transitionMode;
1129         checkNavBarModes();
1130         if (mAutoHideController != null) {
1131             mAutoHideController.touchAutoHide();
1132         }
1133         if (mLightBarController != null) {
1134             mLightBarController.onNavigationBarAppearanceChanged(mAppearance,
1135                     true /* nbModeChanged */, transitionMode, false /* navbarColorManagedByIme */);
1136         }
1137     }
1138 
1139     @Override
onSystemBarAttributesChanged(int displayId, @Appearance int appearance, AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme, @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName, LetterboxDetails[] letterboxDetails)1140     public void onSystemBarAttributesChanged(int displayId, @Appearance int appearance,
1141             AppearanceRegion[] appearanceRegions, boolean navbarColorManagedByIme,
1142             @Behavior int behavior, @InsetsType int requestedVisibleTypes, String packageName,
1143             LetterboxDetails[] letterboxDetails) {
1144         if (displayId != mDisplayId) {
1145             return;
1146         }
1147         boolean nbModeChanged = false;
1148         if (mAppearance != appearance) {
1149             mAppearance = appearance;
1150             nbModeChanged = updateTransitionMode(transitionMode(mTransientShown, appearance));
1151         }
1152         if (mLightBarController != null) {
1153             mLightBarController.onNavigationBarAppearanceChanged(appearance, nbModeChanged,
1154                     mTransitionMode, navbarColorManagedByIme);
1155         }
1156         if (mBehavior != behavior) {
1157             mBehavior = behavior;
1158             mView.setBehavior(behavior);
1159             updateSystemUiStateFlags();
1160         }
1161     }
1162 
1163     @Override
showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar)1164     public void showTransient(int displayId, @InsetsType int types, boolean isGestureOnSystemBar) {
1165         if (displayId != mDisplayId) {
1166             return;
1167         }
1168         if ((types & WindowInsets.Type.navigationBars()) == 0) {
1169             return;
1170         }
1171         if (!mTransientShown) {
1172             mTransientShown = true;
1173             mTransientShownFromGestureOnSystemBar = isGestureOnSystemBar;
1174             handleTransientChanged();
1175         }
1176     }
1177 
1178     @Override
abortTransient(int displayId, @InsetsType int types)1179     public void abortTransient(int displayId, @InsetsType int types) {
1180         if (displayId != mDisplayId) {
1181             return;
1182         }
1183         if ((types & WindowInsets.Type.navigationBars()) == 0) {
1184             return;
1185         }
1186         clearTransient();
1187     }
1188 
clearTransient()1189     private void clearTransient() {
1190         if (mTransientShown) {
1191             mTransientShown = false;
1192             mTransientShownFromGestureOnSystemBar = false;
1193             handleTransientChanged();
1194         }
1195     }
1196 
handleTransientChanged()1197     private void handleTransientChanged() {
1198         mEdgeBackGestureHandler.onNavBarTransientStateChanged(mTransientShown);
1199 
1200         final int transitionMode = transitionMode(mTransientShown, mAppearance);
1201         if (updateTransitionMode(transitionMode) && mLightBarController != null) {
1202             mLightBarController.onNavigationBarModeChanged(transitionMode);
1203         }
1204     }
1205 
1206     // Returns true if the bar mode is changed.
updateTransitionMode(int barMode)1207     private boolean updateTransitionMode(int barMode) {
1208         if (mTransitionMode != barMode) {
1209             mTransitionMode = barMode;
1210             checkNavBarModes();
1211             if (mAutoHideController != null) {
1212                 mAutoHideController.touchAutoHide();
1213             }
1214             return true;
1215         }
1216         return false;
1217     }
1218 
1219     @Override
disable(int displayId, int state1, int state2, boolean animate)1220     public void disable(int displayId, int state1, int state2, boolean animate) {
1221         if (displayId != mDisplayId) {
1222             return;
1223         }
1224         // Navigation bar flags are in both state1 and state2.
1225         final int masked = state1 & (StatusBarManager.DISABLE_HOME
1226                 | StatusBarManager.DISABLE_RECENT
1227                 | StatusBarManager.DISABLE_BACK
1228                 | StatusBarManager.DISABLE_SEARCH);
1229         if (masked != mDisabledFlags1) {
1230             mDisabledFlags1 = masked;
1231             mView.setDisabledFlags(state1, mSysUiFlagsContainer);
1232             updateScreenPinningGestures();
1233         }
1234 
1235         // Only default display supports rotation suggestions.
1236         if (mIsOnDefaultDisplay) {
1237             final int masked2 = state2 & (StatusBarManager.DISABLE2_ROTATE_SUGGESTIONS);
1238             if (masked2 != mDisabledFlags2) {
1239                 mDisabledFlags2 = masked2;
1240                 setDisabled2Flags(masked2);
1241             }
1242         }
1243     }
1244 
setDisabled2Flags(int state2)1245     private void setDisabled2Flags(int state2) {
1246         // Method only called on change of disable2 flags
1247         mView.getRotationButtonController().onDisable2FlagChanged(state2);
1248     }
1249 
1250     // ----- Internal stuff -----
1251 
refreshLayout(int layoutDirection)1252     private void refreshLayout(int layoutDirection) {
1253         mView.setLayoutDirection(layoutDirection);
1254     }
1255 
shouldDisableNavbarGestures()1256     private boolean shouldDisableNavbarGestures() {
1257         return !mDeviceProvisionedController.isDeviceProvisioned()
1258                 || (mDisabledFlags1 & StatusBarManager.DISABLE_SEARCH) != 0;
1259     }
1260 
repositionNavigationBar(int rotation)1261     private void repositionNavigationBar(int rotation) {
1262         if (mView == null || !mView.isAttachedToWindow()) return;
1263 
1264         prepareNavigationBarView();
1265 
1266         mWindowManager.updateViewLayout(mFrame, getBarLayoutParams(rotation));
1267     }
1268 
updateScreenPinningGestures()1269     private void updateScreenPinningGestures() {
1270         // Change the cancel pin gesture to home and back if recents button is invisible
1271         ButtonDispatcher backButton = mView.getBackButton();
1272         ButtonDispatcher recentsButton = mView.getRecentsButton();
1273         if (mScreenPinningActive) {
1274             boolean recentsVisible = mView.isRecentsButtonVisible();
1275             backButton.setOnLongClickListener(recentsVisible
1276                     ? this::onLongPressBackRecents
1277                     : this::onLongPressBackHome);
1278             recentsButton.setOnLongClickListener(this::onLongPressBackRecents);
1279         } else {
1280             backButton.setOnLongClickListener(null);
1281             recentsButton.setOnLongClickListener(null);
1282         }
1283         // Note, this needs to be set after even if we're setting the listener to null
1284         backButton.setLongClickable(mScreenPinningActive);
1285         recentsButton.setLongClickable(mScreenPinningActive);
1286     }
1287 
notifyNavigationBarScreenOn()1288     private void notifyNavigationBarScreenOn() {
1289         mView.updateNavButtonIcons();
1290     }
1291 
prepareNavigationBarView()1292     private void prepareNavigationBarView() {
1293         mView.reorient();
1294 
1295         ButtonDispatcher recentsButton = mView.getRecentsButton();
1296         recentsButton.setOnClickListener(this::onRecentsClick);
1297         recentsButton.setOnTouchListener(this::onRecentsTouch);
1298 
1299         ButtonDispatcher homeButton = mView.getHomeButton();
1300         homeButton.setOnTouchListener(this::onHomeTouch);
1301 
1302         reconfigureHomeLongClick();
1303 
1304         ButtonDispatcher accessibilityButton = mView.getAccessibilityButton();
1305         accessibilityButton.setOnClickListener(this::onAccessibilityClick);
1306         accessibilityButton.setOnLongClickListener(this::onAccessibilityLongClick);
1307         updateAccessibilityStateFlags();
1308 
1309         ButtonDispatcher imeSwitcherButton = mView.getImeSwitchButton();
1310         imeSwitcherButton.setOnClickListener(this::onImeSwitcherClick);
1311 
1312         updateScreenPinningGestures();
1313     }
1314 
1315     @VisibleForTesting
onHomeTouch(View v, MotionEvent event)1316     boolean onHomeTouch(View v, MotionEvent event) {
1317         if (mHomeBlockedThisTouch && event.getActionMasked() != MotionEvent.ACTION_DOWN) {
1318             return true;
1319         }
1320         // If an incoming call is ringing, HOME is totally disabled.
1321         // (The user is already on the InCallUI at this point,
1322         // and their ONLY options are to answer or reject the call.)
1323         final Optional<CentralSurfaces> centralSurfacesOptional = mCentralSurfacesOptionalLazy.get();
1324         switch (event.getAction()) {
1325             case MotionEvent.ACTION_DOWN:
1326                 mHomeBlockedThisTouch = false;
1327                 if (mTelecomManagerOptional.isPresent()
1328                         && mTelecomManagerOptional.get().isRinging()) {
1329                     if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing)
1330                             .orElse(false)) {
1331                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
1332                                 "No heads up");
1333                         mHomeBlockedThisTouch = true;
1334                         return true;
1335                     }
1336                 }
1337                 if (mLongPressHomeEnabled) {
1338                     mHomeButtonLongPressDurationMs.ifPresent(longPressDuration -> {
1339                         mHandler.postDelayed(mOnVariableDurationHomeLongClick, longPressDuration);
1340                     });
1341                 }
1342                 break;
1343             case MotionEvent.ACTION_UP:
1344             case MotionEvent.ACTION_CANCEL:
1345                 mHandler.removeCallbacks(mOnVariableDurationHomeLongClick);
1346                 centralSurfacesOptional.ifPresent(CentralSurfaces::awakenDreams);
1347                 break;
1348         }
1349         return false;
1350     }
1351 
onVerticalChanged(boolean isVertical)1352     private void onVerticalChanged(boolean isVertical) {
1353         // This check can probably be safely removed. It only remained to reduce regression
1354         // risk for a broad change that removed the CentralSurfaces reference in the if block
1355         if (mCentralSurfacesOptionalLazy.get().isPresent()) {
1356             mShadeViewController.setQsScrimEnabled(!isVertical);
1357         }
1358     }
1359 
onNavigationTouch(View v, MotionEvent event)1360     private boolean onNavigationTouch(View v, MotionEvent event) {
1361         if (mAutoHideController != null) {
1362             mAutoHideController.checkUserAutoHide(event);
1363         }
1364         return false;
1365     }
1366 
1367     @VisibleForTesting
onHomeLongClick(View v)1368     boolean onHomeLongClick(View v) {
1369         if (!mView.isRecentsButtonVisible() && mScreenPinningActive) {
1370             return onLongPressBackHome(v);
1371         }
1372         if (shouldDisableNavbarGestures()) {
1373             return false;
1374         }
1375         mMetricsLogger.action(MetricsEvent.ACTION_ASSIST_LONG_PRESS);
1376         mUiEventLogger.log(NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS);
1377         Bundle args = new Bundle();
1378         args.putInt(
1379                 AssistManager.INVOCATION_TYPE_KEY,
1380                 AssistManager.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
1381         mAssistManagerLazy.get().startAssist(args);
1382         mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::awakenDreams);
1383         mView.abortCurrentGesture();
1384         return true;
1385     }
1386 
1387     // additional optimization when we have software system buttons - start loading the recent
1388     // tasks on touch down
onRecentsTouch(View v, MotionEvent event)1389     private boolean onRecentsTouch(View v, MotionEvent event) {
1390         int action = event.getAction() & MotionEvent.ACTION_MASK;
1391         if (action == MotionEvent.ACTION_DOWN) {
1392             mCommandQueue.preloadRecentApps();
1393         } else if (action == MotionEvent.ACTION_CANCEL) {
1394             mCommandQueue.cancelPreloadRecentApps();
1395         } else if (action == MotionEvent.ACTION_UP) {
1396             if (!v.isPressed()) {
1397                 mCommandQueue.cancelPreloadRecentApps();
1398             }
1399         }
1400         return false;
1401     }
1402 
onRecentsClick(View v)1403     private void onRecentsClick(View v) {
1404         if (LatencyTracker.isEnabled(mContext)) {
1405             LatencyTracker.getInstance(mContext).onActionStart(
1406                     LatencyTracker.ACTION_TOGGLE_RECENTS);
1407         }
1408         mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::awakenDreams);
1409         mCommandQueue.toggleRecentApps();
1410     }
1411 
onImeSwitcherClick(View v)1412     private void onImeSwitcherClick(View v) {
1413         mInputMethodManager.showInputMethodPickerFromSystem(
1414                 true /* showAuxiliarySubtypes */, mDisplayId);
1415         mUiEventLogger.log(KeyButtonView.NavBarButtonEvent.NAVBAR_IME_SWITCHER_BUTTON_TAP);
1416     };
1417 
onLongPressBackHome(View v)1418     private boolean onLongPressBackHome(View v) {
1419         return onLongPressNavigationButtons(v, R.id.back, R.id.home);
1420     }
1421 
onLongPressBackRecents(View v)1422     private boolean onLongPressBackRecents(View v) {
1423         return onLongPressNavigationButtons(v, R.id.back, R.id.recent_apps);
1424     }
1425 
1426     /**
1427      * This handles long-press of both back and recents/home. Back is the common button with
1428      * combination of recents if it is visible or home if recents is invisible.
1429      * They are handled together to capture them both being long-pressed
1430      * at the same time to exit screen pinning (lock task).
1431      *
1432      * When accessibility mode is on, only a long-press from recents/home
1433      * is required to exit.
1434      *
1435      * In all other circumstances we try to pass through long-press events
1436      * for Back, so that apps can still use it.  Which can be from two things.
1437      * 1) Not currently in screen pinning (lock task).
1438      * 2) Back is long-pressed without recents/home.
1439      */
onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2)1440     private boolean onLongPressNavigationButtons(View v, @IdRes int btnId1, @IdRes int btnId2) {
1441         try {
1442             boolean sendBackLongPress = false;
1443             IActivityTaskManager activityManager = ActivityTaskManager.getService();
1444             boolean touchExplorationEnabled = mAccessibilityManager.isTouchExplorationEnabled();
1445             boolean inLockTaskMode = activityManager.isInLockTaskMode();
1446             boolean stopLockTaskMode = false;
1447             try {
1448                 if (inLockTaskMode && !touchExplorationEnabled) {
1449                     long time = System.currentTimeMillis();
1450 
1451                     // If we recently long-pressed the other button then they were
1452                     // long-pressed 'together'
1453                     if ((time - mLastLockToAppLongPress) < LOCK_TO_APP_GESTURE_TOLERANCE) {
1454                         stopLockTaskMode = true;
1455                         return true;
1456                     } else if (v.getId() == btnId1) {
1457                         ButtonDispatcher button = btnId2 == R.id.recent_apps
1458                                 ? mView.getRecentsButton() : mView.getHomeButton();
1459                         if (!button.getCurrentView().isPressed()) {
1460                             // If we aren't pressing recents/home right now then they presses
1461                             // won't be together, so send the standard long-press action.
1462                             sendBackLongPress = true;
1463                         }
1464                     }
1465                     mLastLockToAppLongPress = time;
1466                 } else {
1467                     // If this is back still need to handle sending the long-press event.
1468                     if (v.getId() == btnId1) {
1469                         sendBackLongPress = true;
1470                     } else if (touchExplorationEnabled && inLockTaskMode) {
1471                         // When in accessibility mode a long press that is recents/home (not back)
1472                         // should stop lock task.
1473                         stopLockTaskMode = true;
1474                         return true;
1475                     } else if (v.getId() == btnId2) {
1476                         return btnId2 == R.id.recent_apps
1477                                 ? false
1478                                 : onHomeLongClick(mView.getHomeButton().getCurrentView());
1479                     }
1480                 }
1481             } finally {
1482                 if (stopLockTaskMode) {
1483                     activityManager.stopSystemLockTaskMode();
1484                     // When exiting refresh disabled flags.
1485                     mView.updateNavButtonIcons();
1486                 }
1487             }
1488 
1489             if (sendBackLongPress) {
1490                 KeyButtonView keyButtonView = (KeyButtonView) v;
1491                 keyButtonView.sendEvent(KeyEvent.ACTION_DOWN, KeyEvent.FLAG_LONG_PRESS);
1492                 keyButtonView.sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED);
1493                 return true;
1494             }
1495         } catch (RemoteException e) {
1496             Log.d(TAG, "Unable to reach activity manager", e);
1497         }
1498         return false;
1499     }
1500 
onAccessibilityClick(View v)1501     private void onAccessibilityClick(View v) {
1502         final Display display = v.getDisplay();
1503         mAccessibilityManager.notifyAccessibilityButtonClicked(
1504                 display != null ? display.getDisplayId() : mDisplayTracker.getDefaultDisplayId());
1505     }
1506 
onAccessibilityLongClick(View v)1507     private boolean onAccessibilityLongClick(View v) {
1508         final Intent intent = new Intent(AccessibilityManager.ACTION_CHOOSE_ACCESSIBILITY_BUTTON);
1509         intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
1510         final String chooserClassName = AccessibilityButtonChooserActivity.class.getName();
1511         intent.setClassName(CHOOSER_PACKAGE_NAME, chooserClassName);
1512         mContext.startActivityAsUser(intent, mUserTracker.getUserHandle());
1513         return true;
1514     }
1515 
updateAccessibilityStateFlags()1516     void updateAccessibilityStateFlags() {
1517         mLongPressHomeEnabled = mNavBarHelper.getLongPressHomeEnabled();
1518         if (mView != null) {
1519             int a11yFlags = mNavBarHelper.getA11yButtonState();
1520             boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
1521             boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
1522             mView.setAccessibilityButtonState(clickable, longClickable);
1523         }
1524         updateSystemUiStateFlags();
1525     }
1526 
updateSystemUiStateFlags()1527     public void updateSystemUiStateFlags() {
1528         int a11yFlags = mNavBarHelper.getA11yButtonState();
1529         boolean clickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_CLICKABLE) != 0;
1530         boolean longClickable = (a11yFlags & SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE) != 0;
1531 
1532         mSysUiFlagsContainer.setFlag(SYSUI_STATE_A11Y_BUTTON_CLICKABLE, clickable)
1533                 .setFlag(SYSUI_STATE_A11Y_BUTTON_LONG_CLICKABLE, longClickable)
1534                 .setFlag(SYSUI_STATE_NAV_BAR_HIDDEN, !isNavBarWindowVisible())
1535                 .setFlag(SYSUI_STATE_IME_SHOWING,
1536                         (mNavigationIconHints & NAVIGATION_HINT_BACK_ALT) != 0)
1537                 .setFlag(SYSUI_STATE_IME_SWITCHER_SHOWING,
1538                         (mNavigationIconHints & NAVIGATION_HINT_IME_SWITCHER_SHOWN) != 0)
1539                 .setFlag(SYSUI_STATE_ALLOW_GESTURE_IGNORING_BAR_VISIBILITY,
1540                         allowSystemGestureIgnoringBarVisibility())
1541                 .commitUpdate(mDisplayId);
1542     }
1543 
updateAssistantEntrypoints(boolean assistantAvailable, boolean longPressHomeEnabled)1544     private void updateAssistantEntrypoints(boolean assistantAvailable,
1545             boolean longPressHomeEnabled) {
1546         if (mOverviewProxyService.getProxy() != null) {
1547             try {
1548                 mOverviewProxyService.getProxy().onAssistantAvailable(assistantAvailable,
1549                         longPressHomeEnabled);
1550             } catch (RemoteException e) {
1551                 Log.w(TAG, "Unable to send assistant availability data to launcher");
1552             }
1553         }
1554         reconfigureHomeLongClick();
1555     }
1556 
1557     // ----- Methods that DisplayNavigationBarController talks to -----
1558 
1559     /** Applies auto dimming animation on navigation bar when touched. */
touchAutoDim()1560     public void touchAutoDim() {
1561         getBarTransitions().setAutoDim(false);
1562         mHandler.removeCallbacks(mAutoDim);
1563         int state = mStatusBarStateController.getState();
1564         if (state != StatusBarState.KEYGUARD && state != StatusBarState.SHADE_LOCKED) {
1565             mHandler.postDelayed(mAutoDim, AUTODIM_TIMEOUT_MS);
1566         }
1567     }
1568 
setLightBarController(LightBarController lightBarController)1569     public void setLightBarController(LightBarController lightBarController) {
1570         mLightBarController = lightBarController;
1571         if (mLightBarController != null) {
1572             mLightBarController.setNavigationBar(
1573                     getBarTransitions().getLightTransitionsController());
1574         }
1575     }
1576 
setWindowVisible(boolean visible)1577     private void setWindowVisible(boolean visible) {
1578         mRegionSamplingHelper.setWindowVisible(visible);
1579         mView.setWindowVisible(visible);
1580     }
1581 
1582     /** Sets {@link AutoHideController} to the navigation bar. */
setAutoHideController(AutoHideController autoHideController)1583     private void setAutoHideController(AutoHideController autoHideController) {
1584         mAutoHideController = autoHideController;
1585         if (mAutoHideController != null) {
1586             mAutoHideController.setNavigationBar(mAutoHideUiElement);
1587         }
1588         mView.setAutoHideController(autoHideController);
1589     }
1590 
isTransientShown()1591     private boolean isTransientShown() {
1592         return mTransientShown;
1593     }
1594 
checkBarModes()1595     private void checkBarModes() {
1596         // We only have status bar on default display now.
1597         if (mIsOnDefaultDisplay) {
1598             mCentralSurfacesOptionalLazy.get().ifPresent(CentralSurfaces::checkBarModes);
1599         } else {
1600             checkNavBarModes();
1601         }
1602     }
1603 
isNavBarWindowVisible()1604     public boolean isNavBarWindowVisible() {
1605         return mNavigationBarWindowState == WINDOW_STATE_SHOWING;
1606     }
1607 
allowSystemGestureIgnoringBarVisibility()1608     private boolean allowSystemGestureIgnoringBarVisibility() {
1609         return mBehavior != BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
1610     }
1611 
1612     /**
1613      * Checks current navigation bar mode and make transitions.
1614      */
checkNavBarModes()1615     public void checkNavBarModes() {
1616         final boolean anim =
1617                 mCentralSurfacesOptionalLazy.get().map(CentralSurfaces::isDeviceInteractive)
1618                         .orElse(false)
1619                 && mNavigationBarWindowState != WINDOW_STATE_HIDDEN;
1620         getBarTransitions().transitionTo(mTransitionMode, anim);
1621     }
1622 
disableAnimationsDuringHide(long delay)1623     public void disableAnimationsDuringHide(long delay) {
1624         mView.setLayoutTransitionsEnabled(false);
1625         mHandler.postDelayed(mEnableLayoutTransitions,
1626                 delay + StackStateAnimator.ANIMATION_DURATION_GO_TO_FULL_SHADE);
1627     }
1628 
1629     /**
1630      * Performs transitions on navigation bar.
1631      *
1632      * @param barMode transition bar mode.
1633      * @param animate shows animations if {@code true}.
1634      */
transitionTo(@ransitionMode int barMode, boolean animate)1635     public void transitionTo(@TransitionMode int barMode, boolean animate) {
1636         getBarTransitions().transitionTo(barMode, animate);
1637     }
1638 
getBarTransitions()1639     public NavigationBarTransitions getBarTransitions() {
1640         return mNavigationBarTransitions;
1641     }
1642 
finishBarAnimations()1643     public void finishBarAnimations() {
1644         getBarTransitions().finishAnimations();
1645     }
1646 
getBarLayoutParams(int rotation)1647     private WindowManager.LayoutParams getBarLayoutParams(int rotation) {
1648         WindowManager.LayoutParams lp = getBarLayoutParamsForRotation(rotation);
1649         lp.paramsForRotation = new WindowManager.LayoutParams[4];
1650         for (int rot = Surface.ROTATION_0; rot <= Surface.ROTATION_270; rot++) {
1651             lp.paramsForRotation[rot] = getBarLayoutParamsForRotation(rot);
1652         }
1653         return lp;
1654     }
1655 
getBarLayoutParamsForRotation(int rotation)1656     private WindowManager.LayoutParams getBarLayoutParamsForRotation(int rotation) {
1657         int width = WindowManager.LayoutParams.MATCH_PARENT;
1658         int height = WindowManager.LayoutParams.MATCH_PARENT;
1659         int insetsHeight = -1;
1660         int gravity = Gravity.BOTTOM;
1661         boolean navBarCanMove = true;
1662         final Context userContext = mUserContextProvider.createCurrentUserContext(mContext);
1663         if (mWindowManager != null && mWindowManager.getCurrentWindowMetrics() != null) {
1664             Rect displaySize = mWindowManager.getCurrentWindowMetrics().getBounds();
1665             navBarCanMove = displaySize.width() != displaySize.height()
1666                     && userContext.getResources().getBoolean(
1667                     com.android.internal.R.bool.config_navBarCanMove);
1668         }
1669         if (!navBarCanMove) {
1670             height = userContext.getResources().getDimensionPixelSize(
1671                     com.android.internal.R.dimen.navigation_bar_frame_height);
1672             insetsHeight = userContext.getResources().getDimensionPixelSize(
1673                     com.android.internal.R.dimen.navigation_bar_height);
1674         } else {
1675             switch (rotation) {
1676                 case ROTATION_UNDEFINED:
1677                 case Surface.ROTATION_0:
1678                 case Surface.ROTATION_180:
1679                     height = userContext.getResources().getDimensionPixelSize(
1680                             com.android.internal.R.dimen.navigation_bar_frame_height);
1681                     insetsHeight = userContext.getResources().getDimensionPixelSize(
1682                             com.android.internal.R.dimen.navigation_bar_height);
1683                     break;
1684                 case Surface.ROTATION_90:
1685                     gravity = Gravity.RIGHT;
1686                     width = userContext.getResources().getDimensionPixelSize(
1687                             com.android.internal.R.dimen.navigation_bar_width);
1688                     break;
1689                 case Surface.ROTATION_270:
1690                     gravity = Gravity.LEFT;
1691                     width = userContext.getResources().getDimensionPixelSize(
1692                             com.android.internal.R.dimen.navigation_bar_width);
1693                     break;
1694             }
1695         }
1696         WindowManager.LayoutParams lp = new WindowManager.LayoutParams(
1697                 width,
1698                 height,
1699                 WindowManager.LayoutParams.TYPE_NAVIGATION_BAR,
1700                 WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
1701                         | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
1702                         | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
1703                         | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
1704                         | WindowManager.LayoutParams.FLAG_SLIPPERY,
1705                 PixelFormat.TRANSLUCENT);
1706         lp.gravity = gravity;
1707         lp.providedInsets = getInsetsFrameProvider(insetsHeight, userContext);
1708 
1709         lp.token = new Binder();
1710         lp.accessibilityTitle = userContext.getString(R.string.nav_bar);
1711         lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC
1712                 | WindowManager.LayoutParams.PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT;
1713         lp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
1714         lp.windowAnimations = 0;
1715         lp.setTitle("NavigationBar" + userContext.getDisplayId());
1716         lp.setFitInsetsTypes(0 /* types */);
1717         lp.setTrustedOverlay();
1718         return lp;
1719     }
1720 
getInsetsFrameProvider(int insetsHeight, Context userContext)1721     private InsetsFrameProvider[] getInsetsFrameProvider(int insetsHeight, Context userContext) {
1722         final InsetsFrameProvider navBarProvider =
1723                 new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.navigationBars());
1724         if (!ENABLE_HIDE_IME_CAPTION_BAR) {
1725             navBarProvider.setInsetsSizeOverrides(new InsetsFrameProvider.InsetsSizeOverride[] {
1726                     new InsetsFrameProvider.InsetsSizeOverride(TYPE_INPUT_METHOD, null)
1727             });
1728         }
1729         if (insetsHeight != -1 && !mEdgeBackGestureHandler.isButtonForcedVisible()) {
1730             navBarProvider.setInsetsSize(Insets.of(0, 0, 0, insetsHeight));
1731         }
1732         final boolean needsScrim = userContext.getResources().getBoolean(
1733                 com.android.internal.R.bool.config_navBarNeedsScrim);
1734         navBarProvider.setFlags(needsScrim ? 0 : FLAG_SUPPRESS_SCRIM, FLAG_SUPPRESS_SCRIM);
1735 
1736         final InsetsFrameProvider tappableElementProvider = new InsetsFrameProvider(
1737                 mInsetsSourceOwner, 0, WindowInsets.Type.tappableElement());
1738         final boolean tapThrough = userContext.getResources().getBoolean(
1739                 com.android.internal.R.bool.config_navBarTapThrough);
1740         if (tapThrough) {
1741             tappableElementProvider.setInsetsSize(Insets.NONE);
1742         }
1743 
1744         final int gestureHeight = userContext.getResources().getDimensionPixelSize(
1745                 com.android.internal.R.dimen.navigation_bar_gesture_height);
1746         final boolean handlingGesture = mEdgeBackGestureHandler.isHandlingGestures();
1747         final InsetsFrameProvider mandatoryGestureProvider = new InsetsFrameProvider(
1748                 mInsetsSourceOwner, 0, WindowInsets.Type.mandatorySystemGestures());
1749         if (handlingGesture) {
1750             mandatoryGestureProvider.setInsetsSize(Insets.of(0, 0, 0, gestureHeight));
1751         }
1752         final int gestureInsetsLeft = handlingGesture
1753                 ? mEdgeBackGestureHandler.getEdgeWidthLeft() : 0;
1754         final int gestureInsetsRight = handlingGesture
1755                 ? mEdgeBackGestureHandler.getEdgeWidthRight() : 0;
1756         return new InsetsFrameProvider[] {
1757                 navBarProvider,
1758                 tappableElementProvider,
1759                 mandatoryGestureProvider,
1760                 new InsetsFrameProvider(mInsetsSourceOwner, 0, WindowInsets.Type.systemGestures())
1761                         .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
1762                         .setInsetsSize(Insets.of(gestureInsetsLeft, 0, 0, 0))
1763                         .setMinimalInsetsSizeInDisplayCutoutSafe(
1764                                 Insets.of(gestureInsetsLeft, 0, 0, 0)),
1765                 new InsetsFrameProvider(mInsetsSourceOwner, 1, WindowInsets.Type.systemGestures())
1766                         .setSource(InsetsFrameProvider.SOURCE_DISPLAY)
1767                         .setInsetsSize(Insets.of(0, 0, gestureInsetsRight, 0))
1768                         .setMinimalInsetsSizeInDisplayCutoutSafe(
1769                                 Insets.of(0, 0, gestureInsetsRight, 0))
1770         };
1771     }
1772 
canShowSecondaryHandle()1773     private boolean canShowSecondaryHandle() {
1774         return mNavBarMode == NAV_BAR_MODE_GESTURAL && mOrientationHandle != null;
1775     }
1776 
1777     private final UserTracker.Callback mUserChangedCallback =
1778             new UserTracker.Callback() {
1779                 @Override
1780                 public void onUserChanged(int newUser, @NonNull Context userContext) {
1781                     // The accessibility settings may be different for the new user
1782                     updateAccessibilityStateFlags();
1783                 }
1784             };
1785 
1786     @VisibleForTesting
getNavigationIconHints()1787     int getNavigationIconHints() {
1788         return mNavigationIconHints;
1789     }
1790 
setNavigationIconHints(int hints)1791     private void setNavigationIconHints(int hints) {
1792         if (hints == mNavigationIconHints) return;
1793         if (!isLargeScreen(mContext)) {
1794             // All IME functions handled by launcher via Sysui flags for large screen
1795             final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
1796             final boolean oldBackAlt =
1797                     (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
1798             if (newBackAlt != oldBackAlt) {
1799                 mView.onImeVisibilityChanged(newBackAlt);
1800                 mImeVisible = newBackAlt;
1801             }
1802 
1803             mView.setNavigationIconHints(hints);
1804         }
1805         if (DEBUG) {
1806             android.widget.Toast.makeText(mContext,
1807                     "Navigation icon hints = " + hints,
1808                     500).show();
1809         }
1810         mNavigationIconHints = hints;
1811     }
1812 
1813     /**
1814      * @param includeFloatingButtons Whether to include the floating rotation and overlay button in
1815      *                               the region for all the buttons
1816      * @param inScreenSpace Whether to return values in screen space or window space
1817      * @param useNearestRegion Whether to use the nearest region instead of the actual button bounds
1818      * @return
1819      */
getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace, boolean useNearestRegion)1820     Region getButtonLocations(boolean includeFloatingButtons, boolean inScreenSpace,
1821             boolean useNearestRegion) {
1822         if (useNearestRegion && !inScreenSpace) {
1823             // We currently don't support getting the nearest region in anything but screen space
1824             useNearestRegion = false;
1825         }
1826         Region region = new Region();
1827         Map<View, Rect> touchRegionCache = mView.getButtonTouchRegionCache();
1828         updateButtonLocation(
1829                 region, touchRegionCache, mView.getBackButton(), inScreenSpace, useNearestRegion);
1830         updateButtonLocation(
1831                 region, touchRegionCache, mView.getHomeButton(), inScreenSpace, useNearestRegion);
1832         updateButtonLocation(region, touchRegionCache, mView.getRecentsButton(), inScreenSpace,
1833                 useNearestRegion);
1834         updateButtonLocation(region, touchRegionCache, mView.getImeSwitchButton(), inScreenSpace,
1835                 useNearestRegion);
1836         updateButtonLocation(
1837                 region, touchRegionCache, mView.getAccessibilityButton(), inScreenSpace,
1838                 useNearestRegion);
1839         if (includeFloatingButtons && mView.getFloatingRotationButton().isVisible()) {
1840             // Note: this button is floating so the nearest region doesn't apply
1841             updateButtonLocation(
1842                     region, mView.getFloatingRotationButton().getCurrentView(), inScreenSpace);
1843         } else {
1844             updateButtonLocation(region, touchRegionCache, mView.getRotateSuggestionButton(),
1845                     inScreenSpace, useNearestRegion);
1846         }
1847         return region;
1848     }
1849 
updateButtonLocation( Region region, Map<View, Rect> touchRegionCache, ButtonDispatcher button, boolean inScreenSpace, boolean useNearestRegion)1850     private void updateButtonLocation(
1851             Region region,
1852             Map<View, Rect> touchRegionCache,
1853             ButtonDispatcher button,
1854             boolean inScreenSpace,
1855             boolean useNearestRegion) {
1856         if (button == null) {
1857             return;
1858         }
1859         View view = button.getCurrentView();
1860         if (view == null || !button.isVisible()) {
1861             return;
1862         }
1863         // If the button is tappable from perspective of NearestTouchFrame, then we'll
1864         // include the regions where the tap is valid instead of just the button layout location
1865         if (useNearestRegion && touchRegionCache.containsKey(view)) {
1866             region.op(touchRegionCache.get(view), Region.Op.UNION);
1867             return;
1868         }
1869         updateButtonLocation(region, view, inScreenSpace);
1870     }
1871 
updateButtonLocation(Region region, View view, boolean inScreenSpace)1872     private void updateButtonLocation(Region region, View view, boolean inScreenSpace) {
1873         Rect bounds = new Rect();
1874         if (inScreenSpace) {
1875             view.getBoundsOnScreen(bounds);
1876         } else {
1877             int[] location = new int[2];
1878             view.getLocationInWindow(location);
1879             bounds.set(location[0], location[1],
1880                     location[0] + view.getWidth(),
1881                     location[1] + view.getHeight());
1882         }
1883         region.op(bounds, Region.Op.UNION);
1884     }
1885 
setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion)1886     void setOrientedHandleSamplingRegion(Rect orientedHandleSamplingRegion) {
1887         mOrientedHandleSamplingRegion = orientedHandleSamplingRegion;
1888         mRegionSamplingHelper.updateSamplingRect();
1889     }
1890 
calculateSamplingRect()1891     private Rect calculateSamplingRect() {
1892         mSamplingBounds.setEmpty();
1893         // TODO: Extend this to 2/3 button layout as well
1894         View view = mView.getHomeHandle().getCurrentView();
1895 
1896         if (view != null) {
1897             int[] pos = new int[2];
1898             view.getLocationOnScreen(pos);
1899             Point displaySize = new Point();
1900             view.getContext().getDisplay().getRealSize(displaySize);
1901             final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin,
1902                     displaySize.y - mView.getNavBarHeight(),
1903                     pos[0] + view.getWidth() + mNavColorSampleMargin,
1904                     displaySize.y);
1905             mSamplingBounds.set(samplingBounds);
1906         }
1907 
1908         return mSamplingBounds;
1909     }
1910 
setNavigationBarLumaSamplingEnabled(boolean enable)1911     void setNavigationBarLumaSamplingEnabled(boolean enable) {
1912         if (enable) {
1913             mRegionSamplingHelper.start(mSamplingBounds);
1914         } else {
1915             mRegionSamplingHelper.stop();
1916         }
1917     }
1918 
setNavBarMode(int mode)1919     private void setNavBarMode(int mode) {
1920         mView.setNavBarMode(mode, mNavigationModeController.getImeDrawsImeNavBar());
1921         if (isGesturalMode(mode)) {
1922             mRegionSamplingHelper.start(mSamplingBounds);
1923         } else {
1924             mRegionSamplingHelper.stop();
1925         }
1926     }
1927 
onBarTransition(int newMode)1928     void onBarTransition(int newMode) {
1929         if (newMode == MODE_OPAQUE) {
1930             // If the nav bar background is opaque, stop auto tinting since we know the icons are
1931             // showing over a dark background
1932             mRegionSamplingHelper.stop();
1933             getBarTransitions().getLightTransitionsController().setIconsDark(
1934                     false /* dark */, true /* animate */);
1935         } else {
1936             mRegionSamplingHelper.start(mSamplingBounds);
1937         }
1938     }
1939 
1940     private final ModeChangedListener mModeChangedListener = new ModeChangedListener() {
1941         @Override
1942         public void onNavigationModeChanged(int mode) {
1943             mNavBarMode = mode;
1944 
1945             if (!QuickStepContract.isGesturalMode(mode)) {
1946                 // Reset the override alpha
1947                 if (getBarTransitions() != null) {
1948                     getBarTransitions().setBackgroundOverrideAlpha(1f);
1949                 }
1950             }
1951             updateScreenPinningGestures();
1952 
1953             if (!canShowSecondaryHandle()) {
1954                 resetSecondaryHandle();
1955             }
1956             setNavBarMode(mode);
1957             mView.setShouldShowSwipeUpUi(mOverviewProxyService.shouldShowSwipeUpUI());
1958         }
1959     };
1960 
1961     private final Gefingerpoken mTouchHandler = new Gefingerpoken() {
1962         private boolean mDeadZoneConsuming;
1963 
1964         @Override
1965         public boolean onInterceptTouchEvent(MotionEvent ev) {
1966             if (isGesturalMode(mNavBarMode) && mImeVisible
1967                     && ev.getAction() == MotionEvent.ACTION_DOWN) {
1968                 SysUiStatsLog.write(SysUiStatsLog.IME_TOUCH_REPORTED,
1969                         (int) ev.getX(), (int) ev.getY());
1970             }
1971             return shouldDeadZoneConsumeTouchEvents(ev);
1972         }
1973 
1974         @Override
1975         public boolean onTouchEvent(MotionEvent ev) {
1976             shouldDeadZoneConsumeTouchEvents(ev);
1977             return false;
1978         }
1979 
1980         private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
1981             int action = event.getActionMasked();
1982             if (action == MotionEvent.ACTION_DOWN) {
1983                 mDeadZoneConsuming = false;
1984             }
1985             if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
1986                 switch (action) {
1987                     case MotionEvent.ACTION_DOWN:
1988                         // Allow gestures starting in the deadzone to be slippery
1989                         mView.setSlippery(true);
1990                         mDeadZoneConsuming = true;
1991                         break;
1992                     case MotionEvent.ACTION_CANCEL:
1993                     case MotionEvent.ACTION_UP:
1994                         // When a gesture started in the deadzone is finished, restore
1995                         // slippery state
1996                         mView.updateSlippery();
1997                         mDeadZoneConsuming = false;
1998                         break;
1999                 }
2000                 return true;
2001             }
2002             return false;
2003         }
2004     };
2005 }
2006