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