blob: 2978772cac5edc80dbd9afd84d5aa1718ad2b0cf [file] [log] [blame]
Alan Viverette3da604b2020-06-10 18:34:39 +00001/*
2 * Copyright (C) 2008 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
17package com.android.systemui.statusbar.phone;
18
19import static android.view.WindowManagerPolicyConstants.NAV_BAR_MODE_GESTURAL;
20
21import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_HOME_DISABLED;
22import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED;
23import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_OVERVIEW_DISABLED;
24import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_QUICK_SETTINGS_EXPANDED;
25import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
26import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SEARCH_DISABLED;
27import static com.android.systemui.shared.system.QuickStepContract.isGesturalMode;
28import static com.android.systemui.statusbar.phone.BarTransitions.MODE_OPAQUE;
29import static com.android.systemui.util.Utils.isGesturalModeOnDefaultDisplay;
30
31import android.animation.LayoutTransition;
32import android.animation.LayoutTransition.TransitionListener;
33import android.animation.ObjectAnimator;
34import android.animation.PropertyValuesHolder;
35import android.animation.TimeInterpolator;
36import android.animation.ValueAnimator;
37import android.annotation.DrawableRes;
38import android.app.StatusBarManager;
39import android.content.Context;
40import android.content.res.Configuration;
41import android.graphics.Canvas;
42import android.graphics.Point;
43import android.graphics.Rect;
44import android.graphics.Region;
45import android.graphics.Region.Op;
46import android.os.Bundle;
47import android.util.AttributeSet;
48import android.util.Log;
49import android.util.SparseArray;
50import android.view.Display;
51import android.view.MotionEvent;
52import android.view.Surface;
53import android.view.View;
54import android.view.ViewGroup;
55import android.view.ViewTreeObserver.InternalInsetsInfo;
56import android.view.ViewTreeObserver.OnComputeInternalInsetsListener;
57import android.view.WindowInsets;
58import android.view.WindowManager;
59import android.view.accessibility.AccessibilityNodeInfo;
60import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
61import android.view.inputmethod.InputMethodManager;
62import android.widget.FrameLayout;
63
64import com.android.internal.annotations.VisibleForTesting;
65import com.android.systemui.Dependency;
66import com.android.systemui.Interpolators;
67import com.android.systemui.R;
68import com.android.systemui.assist.AssistHandleViewController;
69import com.android.systemui.model.SysUiState;
70import com.android.systemui.recents.OverviewProxyService;
71import com.android.systemui.recents.Recents;
72import com.android.systemui.recents.RecentsOnboarding;
73import com.android.systemui.shared.plugins.PluginManager;
74import com.android.systemui.shared.system.ActivityManagerWrapper;
75import com.android.systemui.shared.system.QuickStepContract;
76import com.android.systemui.shared.system.WindowManagerWrapper;
77import com.android.systemui.stackdivider.Divider;
78import com.android.systemui.statusbar.CommandQueue;
79import com.android.systemui.statusbar.NavigationBarController;
80import com.android.systemui.statusbar.policy.DeadZone;
81import com.android.systemui.statusbar.policy.KeyButtonDrawable;
82
83import java.io.FileDescriptor;
84import java.io.PrintWriter;
85import java.util.function.Consumer;
86
87public class NavigationBarView extends FrameLayout implements
88 NavigationModeController.ModeChangedListener {
89 final static boolean DEBUG = false;
90 final static String TAG = "StatusBar/NavBarView";
91
92 // slippery nav bar when everything is disabled, e.g. during setup
93 final static boolean SLIPPERY_WHEN_DISABLED = true;
94
95 final static boolean ALTERNATE_CAR_MODE_UI = false;
96 private final RegionSamplingHelper mRegionSamplingHelper;
97 private final int mNavColorSampleMargin;
98 private final SysUiState mSysUiFlagContainer;
99 private final PluginManager mPluginManager;
100
101 View mCurrentView = null;
102 private View mVertical;
103 private View mHorizontal;
104
105 /** Indicates that navigation bar is vertical. */
106 private boolean mIsVertical;
107 private int mCurrentRotation = -1;
108
109 boolean mLongClickableAccessibilityButton;
110 int mDisabledFlags = 0;
111 int mNavigationIconHints = 0;
112 private int mNavBarMode;
113
114 private Rect mHomeButtonBounds = new Rect();
115 private Rect mBackButtonBounds = new Rect();
116 private Rect mRecentsButtonBounds = new Rect();
117 private Rect mRotationButtonBounds = new Rect();
118 private final Region mActiveRegion = new Region();
119 private int[] mTmpPosition = new int[2];
120
121 private KeyButtonDrawable mBackIcon;
122 private KeyButtonDrawable mHomeDefaultIcon;
123 private KeyButtonDrawable mRecentIcon;
124 private KeyButtonDrawable mDockedIcon;
125
126 private EdgeBackGestureHandler mEdgeBackGestureHandler;
127 private final DeadZone mDeadZone;
128 private boolean mDeadZoneConsuming = false;
129 private final NavigationBarTransitions mBarTransitions;
130 private final OverviewProxyService mOverviewProxyService;
131
132 // performs manual animation in sync with layout transitions
133 private final NavTransitionListener mTransitionListener = new NavTransitionListener();
134
135 private OnVerticalChangedListener mOnVerticalChangedListener;
136 private boolean mLayoutTransitionsEnabled = true;
137 private boolean mWakeAndUnlocking;
138 private boolean mUseCarModeUi = false;
139 private boolean mInCarMode = false;
140 private boolean mDockedStackExists;
141 private boolean mImeVisible;
142
143 private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>();
144 private final ContextualButtonGroup mContextualButtonGroup;
145 private Configuration mConfiguration;
146 private Configuration mTmpLastConfiguration;
147
148 private NavigationBarInflaterView mNavigationInflaterView;
149 private RecentsOnboarding mRecentsOnboarding;
150 private NotificationPanelViewController mPanelView;
151 private FloatingRotationButton mFloatingRotationButton;
152 private RotationButtonController mRotationButtonController;
153
154 /**
155 * Helper that is responsible for showing the right toast when a disallowed activity operation
156 * occurred. In pinned mode, we show instructions on how to break out of this mode, whilst in
157 * fully locked mode we only show that unlocking is blocked.
158 */
159 private ScreenPinningNotify mScreenPinningNotify;
160 private Rect mSamplingBounds = new Rect();
161
162 private class NavTransitionListener implements TransitionListener {
163 private boolean mBackTransitioning;
164 private boolean mHomeAppearing;
165 private long mStartDelay;
166 private long mDuration;
167 private TimeInterpolator mInterpolator;
168
169 @Override
170 public void startTransition(LayoutTransition transition, ViewGroup container,
171 View view, int transitionType) {
172 if (view.getId() == R.id.back) {
173 mBackTransitioning = true;
174 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
175 mHomeAppearing = true;
176 mStartDelay = transition.getStartDelay(transitionType);
177 mDuration = transition.getDuration(transitionType);
178 mInterpolator = transition.getInterpolator(transitionType);
179 }
180 }
181
182 @Override
183 public void endTransition(LayoutTransition transition, ViewGroup container,
184 View view, int transitionType) {
185 if (view.getId() == R.id.back) {
186 mBackTransitioning = false;
187 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) {
188 mHomeAppearing = false;
189 }
190 }
191
192 public void onBackAltCleared() {
193 ButtonDispatcher backButton = getBackButton();
194
195 // When dismissing ime during unlock, force the back button to run the same appearance
196 // animation as home (if we catch this condition early enough).
197 if (!mBackTransitioning && backButton.getVisibility() == VISIBLE
198 && mHomeAppearing && getHomeButton().getAlpha() == 0) {
199 getBackButton().setAlpha(0);
200 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1);
201 a.setStartDelay(mStartDelay);
202 a.setDuration(mDuration);
203 a.setInterpolator(mInterpolator);
204 a.start();
205 }
206 }
207 }
208
209 private final OnClickListener mImeSwitcherClickListener = new OnClickListener() {
210 @Override
211 public void onClick(View view) {
212 mContext.getSystemService(InputMethodManager.class).showInputMethodPickerFromSystem(
213 true /* showAuxiliarySubtypes */, getContext().getDisplayId());
214 }
215 };
216
217 private final AccessibilityDelegate mQuickStepAccessibilityDelegate =
218 new AccessibilityDelegate() {
219 private AccessibilityAction mToggleOverviewAction;
220
221 @Override
222 public void onInitializeAccessibilityNodeInfo(View host,
223 AccessibilityNodeInfo info) {
224 super.onInitializeAccessibilityNodeInfo(host, info);
225 if (mToggleOverviewAction == null) {
226 mToggleOverviewAction = new AccessibilityAction(
227 R.id.action_toggle_overview, getContext().getString(
228 R.string.quick_step_accessibility_toggle_overview));
229 }
230 info.addAction(mToggleOverviewAction);
231 }
232
233 @Override
234 public boolean performAccessibilityAction(View host, int action, Bundle args) {
235 if (action == R.id.action_toggle_overview) {
236 Dependency.get(Recents.class).toggleRecentApps();
237 } else {
238 return super.performAccessibilityAction(host, action, args);
239 }
240 return true;
241 }
242 };
243
244 private final OnComputeInternalInsetsListener mOnComputeInternalInsetsListener = info -> {
245 // When the nav bar is in 2-button or 3-button mode, or when IME is visible in fully
246 // gestural mode, the entire nav bar should be touchable.
247 if (!mEdgeBackGestureHandler.isHandlingGestures() || mImeVisible) {
248 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_FRAME);
249 return;
250 }
251
252 info.setTouchableInsets(InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
253 ButtonDispatcher imeSwitchButton = getImeSwitchButton();
254 if (imeSwitchButton.getVisibility() == VISIBLE) {
255 // If the IME is not up, but the ime switch button is visible, then make sure that
256 // button is touchable
257 int[] loc = new int[2];
258 View buttonView = imeSwitchButton.getCurrentView();
259 buttonView.getLocationInWindow(loc);
260 info.touchableRegion.set(loc[0], loc[1], loc[0] + buttonView.getWidth(),
261 loc[1] + buttonView.getHeight());
262 return;
263 }
264 info.touchableRegion.setEmpty();
265 };
266
267 public NavigationBarView(Context context, AttributeSet attrs) {
268 super(context, attrs);
269
270 mIsVertical = false;
271 mLongClickableAccessibilityButton = false;
272 mNavBarMode = Dependency.get(NavigationModeController.class).addListener(this);
273 boolean isGesturalMode = isGesturalMode(mNavBarMode);
274
275 mSysUiFlagContainer = Dependency.get(SysUiState.class);
276 mPluginManager = Dependency.get(PluginManager.class);
277 // Set up the context group of buttons
278 mContextualButtonGroup = new ContextualButtonGroup(R.id.menu_container);
279 final ContextualButton imeSwitcherButton = new ContextualButton(R.id.ime_switcher,
280 R.drawable.ic_ime_switcher_default);
281 final RotationContextButton rotateSuggestionButton = new RotationContextButton(
282 R.id.rotate_suggestion, R.drawable.ic_sysbar_rotate_button);
283 final ContextualButton accessibilityButton =
284 new ContextualButton(R.id.accessibility_button,
285 R.drawable.ic_sysbar_accessibility_button);
286 mContextualButtonGroup.addButton(imeSwitcherButton);
287 if (!isGesturalMode) {
288 mContextualButtonGroup.addButton(rotateSuggestionButton);
289 }
290 mContextualButtonGroup.addButton(accessibilityButton);
291
292 mOverviewProxyService = Dependency.get(OverviewProxyService.class);
293 mRecentsOnboarding = new RecentsOnboarding(context, mOverviewProxyService);
294 mFloatingRotationButton = new FloatingRotationButton(context);
295 mRotationButtonController = new RotationButtonController(context,
296 R.style.RotateButtonCCWStart90,
297 isGesturalMode ? mFloatingRotationButton : rotateSuggestionButton);
298
299 mConfiguration = new Configuration();
300 mTmpLastConfiguration = new Configuration();
301 mConfiguration.updateFrom(context.getResources().getConfiguration());
302
303 mScreenPinningNotify = new ScreenPinningNotify(mContext);
304 mBarTransitions = new NavigationBarTransitions(this, Dependency.get(CommandQueue.class));
305
306 mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back));
307 mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home));
308 mButtonDispatchers.put(R.id.home_handle, new ButtonDispatcher(R.id.home_handle));
309 mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps));
310 mButtonDispatchers.put(R.id.ime_switcher, imeSwitcherButton);
311 mButtonDispatchers.put(R.id.accessibility_button, accessibilityButton);
312 mButtonDispatchers.put(R.id.rotate_suggestion, rotateSuggestionButton);
313 mButtonDispatchers.put(R.id.menu_container, mContextualButtonGroup);
314 mDeadZone = new DeadZone(this);
315
316 mNavColorSampleMargin = getResources()
317 .getDimensionPixelSize(R.dimen.navigation_handle_sample_horizontal_margin);
318 mEdgeBackGestureHandler = new EdgeBackGestureHandler(
319 context, mOverviewProxyService, mSysUiFlagContainer, mPluginManager);
320 mRegionSamplingHelper = new RegionSamplingHelper(this,
321 new RegionSamplingHelper.SamplingCallback() {
322 @Override
323 public void onRegionDarknessChanged(boolean isRegionDark) {
324 getLightTransitionsController().setIconsDark(!isRegionDark ,
325 true /* animate */);
326 }
327
328 @Override
329 public Rect getSampledRegion(View sampledView) {
330 updateSamplingRect();
331 return mSamplingBounds;
332 }
333
334 @Override
335 public boolean isSamplingEnabled() {
336 return isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode);
337 }
338 });
339 }
340
341 public NavigationBarTransitions getBarTransitions() {
342 return mBarTransitions;
343 }
344
345 public LightBarTransitionsController getLightTransitionsController() {
346 return mBarTransitions.getLightTransitionsController();
347 }
348
349 public void setComponents(NotificationPanelViewController panel) {
350 mPanelView = panel;
351 updatePanelSystemUiStateFlags();
352 }
353
354 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) {
355 mOnVerticalChangedListener = onVerticalChangedListener;
356 notifyVerticalChangedListener(mIsVertical);
357 }
358
359 @Override
360 public boolean onInterceptTouchEvent(MotionEvent event) {
361 return shouldDeadZoneConsumeTouchEvents(event) || super.onInterceptTouchEvent(event);
362 }
363
364 @Override
365 public boolean onTouchEvent(MotionEvent event) {
366 shouldDeadZoneConsumeTouchEvents(event);
367 return super.onTouchEvent(event);
368 }
369
370 void onTransientStateChanged(boolean isTransient) {
371 mEdgeBackGestureHandler.onNavBarTransientStateChanged(isTransient);
372 }
373
374 void onBarTransition(int newMode) {
375 if (newMode == MODE_OPAQUE) {
376 // If the nav bar background is opaque, stop auto tinting since we know the icons are
377 // showing over a dark background
378 mRegionSamplingHelper.stop();
379 getLightTransitionsController().setIconsDark(false /* dark */, true /* animate */);
380 } else {
381 mRegionSamplingHelper.start(mSamplingBounds);
382 }
383 }
384
385 private boolean shouldDeadZoneConsumeTouchEvents(MotionEvent event) {
386 int action = event.getActionMasked();
387 if (action == MotionEvent.ACTION_DOWN) {
388 mDeadZoneConsuming = false;
389 }
390 if (mDeadZone.onTouchEvent(event) || mDeadZoneConsuming) {
391 switch (action) {
392 case MotionEvent.ACTION_DOWN:
393 // Allow gestures starting in the deadzone to be slippery
394 setSlippery(true);
395 mDeadZoneConsuming = true;
396 break;
397 case MotionEvent.ACTION_CANCEL:
398 case MotionEvent.ACTION_UP:
399 // When a gesture started in the deadzone is finished, restore slippery state
400 updateSlippery();
401 mDeadZoneConsuming = false;
402 break;
403 }
404 return true;
405 }
406 return false;
407 }
408
409 public void abortCurrentGesture() {
410 getHomeButton().abortCurrentGesture();
411 }
412
413 public View getCurrentView() {
414 return mCurrentView;
415 }
416
417 public RotationButtonController getRotationButtonController() {
418 return mRotationButtonController;
419 }
420
421 public FloatingRotationButton getFloatingRotationButton() {
422 return mFloatingRotationButton;
423 }
424
425 public ButtonDispatcher getRecentsButton() {
426 return mButtonDispatchers.get(R.id.recent_apps);
427 }
428
429 public ButtonDispatcher getBackButton() {
430 return mButtonDispatchers.get(R.id.back);
431 }
432
433 public ButtonDispatcher getHomeButton() {
434 return mButtonDispatchers.get(R.id.home);
435 }
436
437 public ButtonDispatcher getImeSwitchButton() {
438 return mButtonDispatchers.get(R.id.ime_switcher);
439 }
440
441 public ButtonDispatcher getAccessibilityButton() {
442 return mButtonDispatchers.get(R.id.accessibility_button);
443 }
444
445 public RotationContextButton getRotateSuggestionButton() {
446 return (RotationContextButton) mButtonDispatchers.get(R.id.rotate_suggestion);
447 }
448
449 public ButtonDispatcher getHomeHandle() {
450 return mButtonDispatchers.get(R.id.home_handle);
451 }
452
453 public SparseArray<ButtonDispatcher> getButtonDispatchers() {
454 return mButtonDispatchers;
455 }
456
457 public boolean isRecentsButtonVisible() {
458 return getRecentsButton().getVisibility() == View.VISIBLE;
459 }
460
461 public boolean isOverviewEnabled() {
462 return (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) == 0;
463 }
464
465 public boolean isQuickStepSwipeUpEnabled() {
466 return mOverviewProxyService.shouldShowSwipeUpUI() && isOverviewEnabled();
467 }
468
469 private void reloadNavIcons() {
470 updateIcons(Configuration.EMPTY);
471 }
472
473 private void updateIcons(Configuration oldConfig) {
474 final boolean orientationChange = oldConfig.orientation != mConfiguration.orientation;
475 final boolean densityChange = oldConfig.densityDpi != mConfiguration.densityDpi;
476 final boolean dirChange = oldConfig.getLayoutDirection() != mConfiguration.getLayoutDirection();
477
478 if (orientationChange || densityChange) {
479 mDockedIcon = getDrawable(R.drawable.ic_sysbar_docked);
480 mHomeDefaultIcon = getHomeDrawable();
481 }
482 if (densityChange || dirChange) {
483 mRecentIcon = getDrawable(R.drawable.ic_sysbar_recent);
484 mContextualButtonGroup.updateIcons();
485 }
486 if (orientationChange || densityChange || dirChange) {
487 mBackIcon = getBackDrawable();
488 }
489 }
490
491 public KeyButtonDrawable getBackDrawable() {
492 KeyButtonDrawable drawable = getDrawable(getBackDrawableRes());
493 orientBackButton(drawable);
494 return drawable;
495 }
496
497 public @DrawableRes int getBackDrawableRes() {
498 return chooseNavigationIconDrawableRes(R.drawable.ic_sysbar_back,
499 R.drawable.ic_sysbar_back_quick_step);
500 }
501
502 public KeyButtonDrawable getHomeDrawable() {
503 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
504 KeyButtonDrawable drawable = quickStepEnabled
505 ? getDrawable(R.drawable.ic_sysbar_home_quick_step)
506 : getDrawable(R.drawable.ic_sysbar_home);
507 orientHomeButton(drawable);
508 return drawable;
509 }
510
511 private void orientBackButton(KeyButtonDrawable drawable) {
512 final boolean useAltBack =
513 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
514 final boolean isRtl = mConfiguration.getLayoutDirection() == View.LAYOUT_DIRECTION_RTL;
515 float degrees = useAltBack ? (isRtl ? 90 : -90) : 0;
516 if (drawable.getRotation() == degrees) {
517 return;
518 }
519
520 if (isGesturalMode(mNavBarMode)) {
521 drawable.setRotation(degrees);
522 return;
523 }
524
525 // Animate the back button's rotation to the new degrees and only in portrait move up the
526 // back button to line up with the other buttons
527 float targetY = !mOverviewProxyService.shouldShowSwipeUpUI() && !mIsVertical && useAltBack
528 ? - getResources().getDimension(R.dimen.navbar_back_button_ime_offset)
529 : 0;
530 ObjectAnimator navBarAnimator = ObjectAnimator.ofPropertyValuesHolder(drawable,
531 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_ROTATE, degrees),
532 PropertyValuesHolder.ofFloat(KeyButtonDrawable.KEY_DRAWABLE_TRANSLATE_Y, targetY));
533 navBarAnimator.setInterpolator(Interpolators.FAST_OUT_SLOW_IN);
534 navBarAnimator.setDuration(200);
535 navBarAnimator.start();
536 }
537
538 private void orientHomeButton(KeyButtonDrawable drawable) {
539 drawable.setRotation(mIsVertical ? 90 : 0);
540 }
541
542 private KeyButtonDrawable chooseNavigationIconDrawable(@DrawableRes int icon,
543 @DrawableRes int quickStepIcon) {
544 return getDrawable(chooseNavigationIconDrawableRes(icon, quickStepIcon));
545 }
546
547 private @DrawableRes int chooseNavigationIconDrawableRes(@DrawableRes int icon,
548 @DrawableRes int quickStepIcon) {
549 final boolean quickStepEnabled = mOverviewProxyService.shouldShowSwipeUpUI();
550 return quickStepEnabled ? quickStepIcon : icon;
551 }
552
553 private KeyButtonDrawable getDrawable(@DrawableRes int icon) {
554 return KeyButtonDrawable.create(mContext, icon, true /* hasShadow */);
555 }
556
557 private KeyButtonDrawable getDrawable(@DrawableRes int icon, boolean hasShadow) {
558 return KeyButtonDrawable.create(mContext, icon, hasShadow);
559 }
560
561 /** To be called when screen lock/unlock state changes */
562 public void onScreenStateChanged(boolean isScreenOn) {
563 if (isScreenOn) {
564 if (isGesturalModeOnDefaultDisplay(getContext(), mNavBarMode)) {
565 mRegionSamplingHelper.start(mSamplingBounds);
566 }
567 } else {
568 mRegionSamplingHelper.stop();
569 }
570 }
571
572 public void setWindowVisible(boolean visible) {
573 mRegionSamplingHelper.setWindowVisible(visible);
574 mRotationButtonController.onNavigationBarWindowVisibilityChange(visible);
575 }
576
577 @Override
578 public void setLayoutDirection(int layoutDirection) {
579 reloadNavIcons();
580
581 super.setLayoutDirection(layoutDirection);
582 }
583
584 public void setNavigationIconHints(int hints) {
585 if (hints == mNavigationIconHints) return;
586 final boolean newBackAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
587 final boolean oldBackAlt =
588 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
589 if (newBackAlt != oldBackAlt) {
590 onImeVisibilityChanged(newBackAlt);
591 }
592
593 if (DEBUG) {
594 android.widget.Toast.makeText(getContext(),
595 "Navigation icon hints = " + hints,
596 500).show();
597 }
598 mNavigationIconHints = hints;
599 updateNavButtonIcons();
600 }
601
602 private void onImeVisibilityChanged(boolean visible) {
603 if (!visible) {
604 mTransitionListener.onBackAltCleared();
605 }
606 mImeVisible = visible;
607 mRotationButtonController.getRotationButton().setCanShowRotationButton(!mImeVisible);
608 }
609
610 public void setDisabledFlags(int disabledFlags) {
611 if (mDisabledFlags == disabledFlags) return;
612
613 final boolean overviewEnabledBefore = isOverviewEnabled();
614 mDisabledFlags = disabledFlags;
615
616 // Update icons if overview was just enabled to ensure the correct icons are present
617 if (!overviewEnabledBefore && isOverviewEnabled()) {
618 reloadNavIcons();
619 }
620
621 updateNavButtonIcons();
622 updateSlippery();
623 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
624 updateDisabledSystemUiStateFlags();
625 }
626
627 public void updateNavButtonIcons() {
628 // We have to replace or restore the back and home button icons when exiting or entering
629 // carmode, respectively. Recents are not available in CarMode in nav bar so change
630 // to recent icon is not required.
631 final boolean useAltBack =
632 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0;
633 KeyButtonDrawable backIcon = mBackIcon;
634 orientBackButton(backIcon);
635 KeyButtonDrawable homeIcon = mHomeDefaultIcon;
636 if (!mUseCarModeUi) {
637 orientHomeButton(homeIcon);
638 }
639 getHomeButton().setImageDrawable(homeIcon);
640 getBackButton().setImageDrawable(backIcon);
641
642 updateRecentsIcon();
643
644 // Update IME button visibility, a11y and rotate button always overrides the appearance
645 mContextualButtonGroup.setButtonVisibility(R.id.ime_switcher,
646 (mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0);
647
648 mBarTransitions.reapplyDarkIntensity();
649
650 boolean disableHome = isGesturalMode(mNavBarMode)
651 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
652
653 // Always disable recents when alternate car mode UI is active and for secondary displays.
654 boolean disableRecent = isRecentsButtonDisabled();
655
656 // Disable the home handle if both hone and recents are disabled
657 boolean disableHomeHandle = disableRecent
658 && ((mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0);
659
660 boolean disableBack = !useAltBack && (mEdgeBackGestureHandler.isHandlingGestures()
661 || ((mDisabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0));
662
663 // When screen pinning, don't hide back and home when connected service or back and
664 // recents buttons when disconnected from launcher service in screen pinning mode,
665 // as they are used for exiting.
666 final boolean pinningActive = ActivityManagerWrapper.getInstance().isScreenPinningActive();
667 if (mOverviewProxyService.isEnabled()) {
668 // Force disable recents when not in legacy mode
669 disableRecent |= !QuickStepContract.isLegacyMode(mNavBarMode);
670 if (pinningActive && !QuickStepContract.isGesturalMode(mNavBarMode)) {
671 disableBack = disableHome = false;
672 }
673 } else if (pinningActive) {
674 disableBack = disableRecent = false;
675 }
676
677 ViewGroup navButtons = getCurrentView().findViewById(R.id.nav_buttons);
678 if (navButtons != null) {
679 LayoutTransition lt = navButtons.getLayoutTransition();
680 if (lt != null) {
681 if (!lt.getTransitionListeners().contains(mTransitionListener)) {
682 lt.addTransitionListener(mTransitionListener);
683 }
684 }
685 }
686
687 getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE);
688 getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE);
689 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE);
690 getHomeHandle().setVisibility(disableHomeHandle ? View.INVISIBLE : View.VISIBLE);
691 }
692
693 @VisibleForTesting
694 boolean isRecentsButtonDisabled() {
695 return mUseCarModeUi || !isOverviewEnabled()
696 || getContext().getDisplayId() != Display.DEFAULT_DISPLAY;
697 }
698
699 private Display getContextDisplay() {
700 return getContext().getDisplay();
701 }
702
703 public void setLayoutTransitionsEnabled(boolean enabled) {
704 mLayoutTransitionsEnabled = enabled;
705 updateLayoutTransitionsEnabled();
706 }
707
708 public void setWakeAndUnlocking(boolean wakeAndUnlocking) {
709 setUseFadingAnimations(wakeAndUnlocking);
710 mWakeAndUnlocking = wakeAndUnlocking;
711 updateLayoutTransitionsEnabled();
712 }
713
714 private void updateLayoutTransitionsEnabled() {
715 boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled;
716 ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons);
717 LayoutTransition lt = navButtons.getLayoutTransition();
718 if (lt != null) {
719 if (enabled) {
720 lt.enableTransitionType(LayoutTransition.APPEARING);
721 lt.enableTransitionType(LayoutTransition.DISAPPEARING);
722 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING);
723 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
724 } else {
725 lt.disableTransitionType(LayoutTransition.APPEARING);
726 lt.disableTransitionType(LayoutTransition.DISAPPEARING);
727 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING);
728 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING);
729 }
730 }
731 }
732
733 private void setUseFadingAnimations(boolean useFadingAnimations) {
734 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent())
735 .getLayoutParams();
736 if (lp != null) {
737 boolean old = lp.windowAnimations != 0;
738 if (!old && useFadingAnimations) {
739 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn;
740 } else if (old && !useFadingAnimations) {
741 lp.windowAnimations = 0;
742 } else {
743 return;
744 }
745 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE);
746 wm.updateViewLayout((View) getParent(), lp);
747 }
748 }
749
750 public void onStatusBarPanelStateChanged() {
751 updateSlippery();
752 updatePanelSystemUiStateFlags();
753 }
754
755 public void updateDisabledSystemUiStateFlags() {
756 int displayId = mContext.getDisplayId();
757
758 mSysUiFlagContainer.setFlag(SYSUI_STATE_SCREEN_PINNING,
759 ActivityManagerWrapper.getInstance().isScreenPinningActive())
760 .setFlag(SYSUI_STATE_OVERVIEW_DISABLED,
761 (mDisabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0)
762 .setFlag(SYSUI_STATE_HOME_DISABLED,
763 (mDisabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0)
764 .setFlag(SYSUI_STATE_SEARCH_DISABLED,
765 (mDisabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0)
766 .commitUpdate(displayId);
767 }
768
769 public void updatePanelSystemUiStateFlags() {
770 int displayId = mContext.getDisplayId();
771 if (SysUiState.DEBUG) {
772 Log.d(TAG, "Updating panel sysui state flags: panelView=" + mPanelView);
773 }
774 if (mPanelView != null) {
775 if (SysUiState.DEBUG) {
776 Log.d(TAG, "Updating panel sysui state flags: fullyExpanded="
777 + mPanelView.isFullyExpanded() + " inQs=" + mPanelView.isInSettings());
778 }
779 mSysUiFlagContainer.setFlag(SYSUI_STATE_NOTIFICATION_PANEL_EXPANDED,
780 mPanelView.isFullyExpanded() && !mPanelView.isInSettings())
781 .setFlag(SYSUI_STATE_QUICK_SETTINGS_EXPANDED,
782 mPanelView.isInSettings())
783 .commitUpdate(displayId);
784 }
785 }
786
787 public void updateStates() {
788 final boolean showSwipeUpUI = mOverviewProxyService.shouldShowSwipeUpUI();
789
790 if (mNavigationInflaterView != null) {
791 // Reinflate the navbar if needed, no-op unless the swipe up state changes
792 mNavigationInflaterView.onLikelyDefaultLayoutChange();
793 }
794
795 updateSlippery();
796 reloadNavIcons();
797 updateNavButtonIcons();
798 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
799 WindowManagerWrapper.getInstance().setNavBarVirtualKeyHapticFeedbackEnabled(!showSwipeUpUI);
800 getHomeButton().setAccessibilityDelegate(
801 showSwipeUpUI ? mQuickStepAccessibilityDelegate : null);
802 }
803
804 /**
805 * Updates the {@link WindowManager.LayoutParams.FLAG_SLIPPERY} state dependent on if swipe up
806 * is enabled, or the notifications is fully opened without being in an animated state. If
807 * slippery is enabled, touch events will leave the nav bar window and enter into the fullscreen
808 * app/home window, if not nav bar will receive a cancelled touch event once gesture leaves bar.
809 */
810 public void updateSlippery() {
811 setSlippery(!isQuickStepSwipeUpEnabled() ||
812 (mPanelView.isFullyExpanded() && !mPanelView.isCollapsing()));
813 }
814
815 private void setSlippery(boolean slippery) {
816 setWindowFlag(WindowManager.LayoutParams.FLAG_SLIPPERY, slippery);
817 }
818
819 private void setWindowFlag(int flags, boolean enable) {
820 final ViewGroup navbarView = ((ViewGroup) getParent());
821 if (navbarView == null) {
822 return;
823 }
824 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) navbarView.getLayoutParams();
825 if (lp == null || enable == ((lp.flags & flags) != 0)) {
826 return;
827 }
828 if (enable) {
829 lp.flags |= flags;
830 } else {
831 lp.flags &= ~flags;
832 }
833 WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
834 wm.updateViewLayout(navbarView, lp);
835 }
836
837 @Override
838 public void onNavigationModeChanged(int mode) {
839 mNavBarMode = mode;
840 mBarTransitions.onNavigationModeChanged(mNavBarMode);
841 mEdgeBackGestureHandler.onNavigationModeChanged(mNavBarMode);
842 mRecentsOnboarding.onNavigationModeChanged(mNavBarMode);
843 getRotateSuggestionButton().onNavigationModeChanged(mNavBarMode);
844
845 if (isGesturalMode(mNavBarMode)) {
846 mRegionSamplingHelper.start(mSamplingBounds);
847 } else {
848 mRegionSamplingHelper.stop();
849 }
850 }
851
852 public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) {
853 mLongClickableAccessibilityButton = longClickable;
854 getAccessibilityButton().setLongClickable(longClickable);
855 mContextualButtonGroup.setButtonVisibility(R.id.accessibility_button, visible);
856 }
857
858 void hideRecentsOnboarding() {
859 mRecentsOnboarding.hide(true);
860 }
861
862 @Override
863 public void onFinishInflate() {
864 super.onFinishInflate();
865 mNavigationInflaterView = findViewById(R.id.navigation_inflater);
866 mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers);
867
868 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener);
869
870 Divider divider = Dependency.get(Divider.class);
871 divider.registerInSplitScreenListener(mDockedListener);
872 updateOrientationViews();
873 reloadNavIcons();
874 }
875
876 @Override
877 protected void onDraw(Canvas canvas) {
878 mDeadZone.onDraw(canvas);
879 super.onDraw(canvas);
880 }
881
882 private void updateSamplingRect() {
883 mSamplingBounds.setEmpty();
884 // TODO: Extend this to 2/3 button layout as well
885 View view = getHomeHandle().getCurrentView();
886
887 if (view != null) {
888 int[] pos = new int[2];
889 view.getLocationOnScreen(pos);
890 Point displaySize = new Point();
891 view.getContext().getDisplay().getRealSize(displaySize);
892 final Rect samplingBounds = new Rect(pos[0] - mNavColorSampleMargin,
893 displaySize.y - getNavBarHeight(),
894 pos[0] + view.getWidth() + mNavColorSampleMargin,
895 displaySize.y);
896 mSamplingBounds.set(samplingBounds);
897 }
898 }
899
900 @Override
901 protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
902 super.onLayout(changed, left, top, right, bottom);
903
904 mActiveRegion.setEmpty();
905 updateButtonLocation(getBackButton(), mBackButtonBounds, true);
906 updateButtonLocation(getHomeButton(), mHomeButtonBounds, false);
907 updateButtonLocation(getRecentsButton(), mRecentsButtonBounds, false);
908 updateButtonLocation(getRotateSuggestionButton(), mRotationButtonBounds, true);
909 // TODO: Handle button visibility changes
910 mOverviewProxyService.onActiveNavBarRegionChanges(mActiveRegion);
911 mRecentsOnboarding.setNavBarHeight(getMeasuredHeight());
912 }
913
914 private void updateButtonLocation(ButtonDispatcher button, Rect buttonBounds,
915 boolean isActive) {
916 View view = button.getCurrentView();
917 if (view == null) {
918 buttonBounds.setEmpty();
919 return;
920 }
921 // Temporarily reset the translation back to origin to get the position in window
922 final float posX = view.getTranslationX();
923 final float posY = view.getTranslationY();
924 view.setTranslationX(0);
925 view.setTranslationY(0);
926
927 if (isActive) {
928 view.getLocationOnScreen(mTmpPosition);
929 buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
930 mTmpPosition[0] + view.getMeasuredWidth(),
931 mTmpPosition[1] + view.getMeasuredHeight());
932 mActiveRegion.op(buttonBounds, Op.UNION);
933 }
934 view.getLocationInWindow(mTmpPosition);
935 buttonBounds.set(mTmpPosition[0], mTmpPosition[1],
936 mTmpPosition[0] + view.getMeasuredWidth(),
937 mTmpPosition[1] + view.getMeasuredHeight());
938 view.setTranslationX(posX);
939 view.setTranslationY(posY);
940 }
941
942 private void updateOrientationViews() {
943 mHorizontal = findViewById(R.id.horizontal);
944 mVertical = findViewById(R.id.vertical);
945
946 updateCurrentView();
947 }
948
949 boolean needsReorient(int rotation) {
950 return mCurrentRotation != rotation;
951 }
952
953 private void updateCurrentView() {
954 resetViews();
955 mCurrentView = mIsVertical ? mVertical : mHorizontal;
956 mCurrentView.setVisibility(View.VISIBLE);
957 mNavigationInflaterView.setVertical(mIsVertical);
958 mCurrentRotation = getContextDisplay().getRotation();
959 mNavigationInflaterView.setAlternativeOrder(mCurrentRotation == Surface.ROTATION_90);
960 mNavigationInflaterView.updateButtonDispatchersCurrentView();
961 updateLayoutTransitionsEnabled();
962 }
963
964 private void resetViews() {
965 mHorizontal.setVisibility(View.GONE);
966 mVertical.setVisibility(View.GONE);
967 }
968
969 private void updateRecentsIcon() {
970 mDockedIcon.setRotation(mDockedStackExists && mIsVertical ? 90 : 0);
971 getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon);
972 mBarTransitions.reapplyDarkIntensity();
973 }
974
975 public void showPinningEnterExitToast(boolean entering) {
976 if (entering) {
977 mScreenPinningNotify.showPinningStartToast();
978 } else {
979 mScreenPinningNotify.showPinningExitToast();
980 }
981 }
982
983 public void showPinningEscapeToast() {
984 mScreenPinningNotify.showEscapeToast(
985 mNavBarMode == NAV_BAR_MODE_GESTURAL, isRecentsButtonVisible());
986 }
987
988 public boolean isVertical() {
989 return mIsVertical;
990 }
991
992 public void reorient() {
993 updateCurrentView();
994
995 ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone);
996 mDeadZone.onConfigurationChanged(mCurrentRotation);
997
998 // force the low profile & disabled states into compliance
999 mBarTransitions.init();
1000
1001 if (DEBUG) {
1002 Log.d(TAG, "reorient(): rot=" + mCurrentRotation);
1003 }
1004
1005 // Resolve layout direction if not resolved since components changing layout direction such
1006 // as changing languages will recreate this view and the direction will be resolved later
1007 if (!isLayoutDirectionResolved()) {
1008 resolveLayoutDirection();
1009 }
1010 updateNavButtonIcons();
1011
1012 getHomeButton().setVertical(mIsVertical);
1013 }
1014
1015 @Override
1016 protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
1017 int w = MeasureSpec.getSize(widthMeasureSpec);
1018 int h = MeasureSpec.getSize(heightMeasureSpec);
1019 if (DEBUG) Log.d(TAG, String.format(
1020 "onMeasure: (%dx%d) old: (%dx%d)", w, h, getMeasuredWidth(), getMeasuredHeight()));
1021
1022 final boolean newVertical = w > 0 && h > w
1023 && !isGesturalMode(mNavBarMode);
1024 if (newVertical != mIsVertical) {
1025 mIsVertical = newVertical;
1026 if (DEBUG) {
1027 Log.d(TAG, String.format("onMeasure: h=%d, w=%d, vert=%s", h, w,
1028 mIsVertical ? "y" : "n"));
1029 }
1030 reorient();
1031 notifyVerticalChangedListener(newVertical);
1032 }
1033
1034 if (isGesturalMode(mNavBarMode)) {
1035 // Update the nav bar background to match the height of the visible nav bar
1036 int height = mIsVertical
1037 ? getResources().getDimensionPixelSize(
1038 com.android.internal.R.dimen.navigation_bar_height_landscape)
1039 : getResources().getDimensionPixelSize(
1040 com.android.internal.R.dimen.navigation_bar_height);
1041 int frameHeight = getResources().getDimensionPixelSize(
1042 com.android.internal.R.dimen.navigation_bar_frame_height);
1043 mBarTransitions.setBackgroundFrame(new Rect(0, frameHeight - height, w, h));
1044 }
1045
1046 super.onMeasure(widthMeasureSpec, heightMeasureSpec);
1047 }
1048
1049 private int getNavBarHeight() {
1050 return mIsVertical
1051 ? getResources().getDimensionPixelSize(
1052 com.android.internal.R.dimen.navigation_bar_height_landscape)
1053 : getResources().getDimensionPixelSize(
1054 com.android.internal.R.dimen.navigation_bar_height);
1055 }
1056
1057 private void notifyVerticalChangedListener(boolean newVertical) {
1058 if (mOnVerticalChangedListener != null) {
1059 mOnVerticalChangedListener.onVerticalChanged(newVertical);
1060 }
1061 }
1062
1063 @Override
1064 protected void onConfigurationChanged(Configuration newConfig) {
1065 super.onConfigurationChanged(newConfig);
1066 mTmpLastConfiguration.updateFrom(mConfiguration);
1067 mConfiguration.updateFrom(newConfig);
1068 boolean uiCarModeChanged = updateCarMode();
1069 updateIcons(mTmpLastConfiguration);
1070 updateRecentsIcon();
1071 mRecentsOnboarding.onConfigurationChanged(mConfiguration);
1072 if (uiCarModeChanged || mTmpLastConfiguration.densityDpi != mConfiguration.densityDpi
1073 || mTmpLastConfiguration.getLayoutDirection() != mConfiguration.getLayoutDirection()) {
1074 // If car mode or density changes, we need to reset the icons.
1075 updateNavButtonIcons();
1076 }
1077 }
1078
1079 /**
1080 * If the configuration changed, update the carmode and return that it was updated.
1081 */
1082 private boolean updateCarMode() {
1083 boolean uiCarModeChanged = false;
1084 if (mConfiguration != null) {
1085 int uiMode = mConfiguration.uiMode & Configuration.UI_MODE_TYPE_MASK;
1086 final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR);
1087
1088 if (isCarMode != mInCarMode) {
1089 mInCarMode = isCarMode;
1090 if (ALTERNATE_CAR_MODE_UI) {
1091 mUseCarModeUi = isCarMode;
1092 uiCarModeChanged = true;
1093 } else {
1094 // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set.
1095 mUseCarModeUi = false;
1096 }
1097 }
1098 }
1099 return uiCarModeChanged;
1100 }
1101
1102 private String getResourceName(int resId) {
1103 if (resId != 0) {
1104 final android.content.res.Resources res = getContext().getResources();
1105 try {
1106 return res.getResourceName(resId);
1107 } catch (android.content.res.Resources.NotFoundException ex) {
1108 return "(unknown)";
1109 }
1110 } else {
1111 return "(null)";
1112 }
1113 }
1114
1115 private static String visibilityToString(int vis) {
1116 switch (vis) {
1117 case View.INVISIBLE:
1118 return "INVISIBLE";
1119 case View.GONE:
1120 return "GONE";
1121 default:
1122 return "VISIBLE";
1123 }
1124 }
1125
1126 @Override
1127 protected void onAttachedToWindow() {
1128 super.onAttachedToWindow();
1129 requestApplyInsets();
1130 reorient();
1131 onNavigationModeChanged(mNavBarMode);
1132 setUpSwipeUpOnboarding(isQuickStepSwipeUpEnabled());
1133 if (mRotationButtonController != null) {
1134 mRotationButtonController.registerListeners();
1135 }
1136
1137 mEdgeBackGestureHandler.onNavBarAttached();
1138 getViewTreeObserver().addOnComputeInternalInsetsListener(mOnComputeInternalInsetsListener);
1139 }
1140
1141 @Override
1142 protected void onDetachedFromWindow() {
1143 super.onDetachedFromWindow();
1144 Dependency.get(NavigationModeController.class).removeListener(this);
1145 setUpSwipeUpOnboarding(false);
1146 for (int i = 0; i < mButtonDispatchers.size(); ++i) {
1147 mButtonDispatchers.valueAt(i).onDestroy();
1148 }
1149 if (mRotationButtonController != null) {
1150 mRotationButtonController.unregisterListeners();
1151 }
1152
1153 mEdgeBackGestureHandler.onNavBarDetached();
1154 getViewTreeObserver().removeOnComputeInternalInsetsListener(
1155 mOnComputeInternalInsetsListener);
1156 }
1157
1158 private void setUpSwipeUpOnboarding(boolean connectedToOverviewProxy) {
1159 if (connectedToOverviewProxy) {
1160 mRecentsOnboarding.onConnectedToLauncher();
1161 } else {
1162 mRecentsOnboarding.onDisconnectedFromLauncher();
1163 }
1164 }
1165
1166 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
1167 pw.println("NavigationBarView {");
1168 final Rect r = new Rect();
1169 final Point size = new Point();
1170 getContextDisplay().getRealSize(size);
1171
1172 pw.println(String.format(" this: " + StatusBar.viewInfo(this)
1173 + " " + visibilityToString(getVisibility())));
1174
1175 getWindowVisibleDisplayFrame(r);
1176 final boolean offscreen = r.right > size.x || r.bottom > size.y;
1177 pw.println(" window: "
1178 + r.toShortString()
1179 + " " + visibilityToString(getWindowVisibility())
1180 + (offscreen ? " OFFSCREEN!" : ""));
1181
1182 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s %f",
1183 getResourceName(getCurrentView().getId()),
1184 getCurrentView().getWidth(), getCurrentView().getHeight(),
1185 visibilityToString(getCurrentView().getVisibility()),
1186 getCurrentView().getAlpha()));
1187
1188 pw.println(String.format(" disabled=0x%08x vertical=%s darkIntensity=%.2f",
1189 mDisabledFlags,
1190 mIsVertical ? "true" : "false",
1191 getLightTransitionsController().getCurrentDarkIntensity()));
1192
1193 dumpButton(pw, "back", getBackButton());
1194 dumpButton(pw, "home", getHomeButton());
1195 dumpButton(pw, "rcnt", getRecentsButton());
1196 dumpButton(pw, "rota", getRotateSuggestionButton());
1197 dumpButton(pw, "a11y", getAccessibilityButton());
1198
1199 pw.println(" }");
1200
1201 mContextualButtonGroup.dump(pw);
1202 mRecentsOnboarding.dump(pw);
1203 mRegionSamplingHelper.dump(pw);
1204 mEdgeBackGestureHandler.dump(pw);
1205 }
1206
1207 @Override
1208 public WindowInsets onApplyWindowInsets(WindowInsets insets) {
1209 int leftInset = insets.getSystemWindowInsetLeft();
1210 int rightInset = insets.getSystemWindowInsetRight();
1211 setPadding(leftInset, insets.getSystemWindowInsetTop(), rightInset,
1212 insets.getSystemWindowInsetBottom());
1213 // we're passing the insets onto the gesture handler since the back arrow is only
1214 // conditionally added and doesn't always get all the insets.
1215 mEdgeBackGestureHandler.setInsets(leftInset, rightInset);
1216
1217 // this allows assist handle to be drawn outside its bound so that it can align screen
1218 // bottom by translating its y position.
1219 final boolean shouldClip =
1220 !isGesturalMode(mNavBarMode) || insets.getSystemWindowInsetBottom() == 0;
1221 setClipChildren(shouldClip);
1222 setClipToPadding(shouldClip);
1223
1224 NavigationBarController navigationBarController =
1225 Dependency.get(NavigationBarController.class);
1226 AssistHandleViewController controller =
1227 navigationBarController == null
1228 ? null : navigationBarController.getAssistHandlerViewController();
1229 if (controller != null) {
1230 controller.setBottomOffset(insets.getSystemWindowInsetBottom());
1231 }
1232 return super.onApplyWindowInsets(insets);
1233 }
1234
1235 private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) {
1236 pw.print(" " + caption + ": ");
1237 if (button == null) {
1238 pw.print("null");
1239 } else {
1240 pw.print(visibilityToString(button.getVisibility())
1241 + " alpha=" + button.getAlpha()
1242 );
1243 }
1244 pw.println();
1245 }
1246
1247 public interface OnVerticalChangedListener {
1248 void onVerticalChanged(boolean isVertical);
1249 }
1250
1251 private final Consumer<Boolean> mDockedListener = exists -> post(() -> {
1252 mDockedStackExists = exists;
1253 updateRecentsIcon();
1254 });
1255}