1 /* 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 17 package com.android.systemui.statusbar.phone; 18 19 import android.animation.LayoutTransition; 20 import android.animation.LayoutTransition.TransitionListener; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.animation.ValueAnimator; 24 import android.annotation.DrawableRes; 25 import android.app.ActivityManager; 26 import android.app.StatusBarManager; 27 import android.content.Context; 28 import android.content.res.Configuration; 29 import android.graphics.Point; 30 import android.graphics.Rect; 31 import android.os.Handler; 32 import android.os.Message; 33 import android.os.RemoteException; 34 import android.util.AttributeSet; 35 import android.util.Log; 36 import android.util.SparseArray; 37 import android.view.ContextThemeWrapper; 38 import android.view.Display; 39 import android.view.MotionEvent; 40 import android.view.Surface; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.view.WindowManager; 44 import android.view.inputmethod.InputMethodManager; 45 import android.widget.FrameLayout; 46 47 import com.android.settingslib.Utils; 48 import com.android.systemui.Dependency; 49 import com.android.systemui.DockedStackExistsListener; 50 import com.android.systemui.R; 51 import com.android.systemui.RecentsComponent; 52 import com.android.systemui.plugins.PluginListener; 53 import com.android.systemui.plugins.PluginManager; 54 import com.android.systemui.plugins.statusbar.phone.NavGesture; 55 import com.android.systemui.plugins.statusbar.phone.NavGesture.GestureHelper; 56 import com.android.systemui.stackdivider.Divider; 57 import com.android.systemui.statusbar.policy.DeadZone; 58 import com.android.systemui.statusbar.policy.KeyButtonDrawable; 59 60 import java.io.FileDescriptor; 61 import java.io.PrintWriter; 62 import java.util.function.Consumer; 63 64 public class NavigationBarView extends FrameLayout implements PluginListener<NavGesture> { 65 final static boolean DEBUG = false; 66 final static String TAG = "StatusBar/NavBarView"; 67 68 // slippery nav bar when everything is disabled, e.g. during setup 69 final static boolean SLIPPERY_WHEN_DISABLED = true; 70 71 final static boolean ALTERNATE_CAR_MODE_UI = false; 72 73 final Display mDisplay; 74 View mCurrentView = null; 75 View[] mRotatedViews = new View[4]; 76 77 boolean mVertical; 78 private int mCurrentRotation = -1; 79 80 boolean mShowMenu; 81 boolean mShowAccessibilityButton; 82 boolean mLongClickableAccessibilityButton; 83 int mDisabledFlags = 0; 84 int mNavigationIconHints = 0; 85 86 private KeyButtonDrawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; 87 private KeyButtonDrawable mBackCarModeIcon, mBackLandCarModeIcon; 88 private KeyButtonDrawable mBackAltCarModeIcon, mBackAltLandCarModeIcon; 89 private KeyButtonDrawable mHomeDefaultIcon, mHomeCarModeIcon; 90 private KeyButtonDrawable mRecentIcon; 91 private KeyButtonDrawable mDockedIcon; 92 private KeyButtonDrawable mImeIcon; 93 private KeyButtonDrawable mMenuIcon; 94 private KeyButtonDrawable mAccessibilityIcon; 95 96 private GestureHelper mGestureHelper; 97 private DeadZone mDeadZone; 98 private final NavigationBarTransitions mBarTransitions; 99 100 // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) 101 final static boolean WORKAROUND_INVALID_LAYOUT = true; 102 final static int MSG_CHECK_INVALID_LAYOUT = 8686; 103 104 // performs manual animation in sync with layout transitions 105 private final NavTransitionListener mTransitionListener = new NavTransitionListener(); 106 107 private OnVerticalChangedListener mOnVerticalChangedListener; 108 private boolean mLayoutTransitionsEnabled = true; 109 private boolean mWakeAndUnlocking; 110 private boolean mUseCarModeUi = false; 111 private boolean mInCarMode = false; 112 private boolean mDockedStackExists; 113 114 private final SparseArray<ButtonDispatcher> mButtonDispatchers = new SparseArray<>(); 115 private Configuration mConfiguration; 116 117 private NavigationBarInflaterView mNavigationInflaterView; 118 private RecentsComponent mRecentsComponent; 119 private Divider mDivider; 120 121 private class NavTransitionListener implements TransitionListener { 122 private boolean mBackTransitioning; 123 private boolean mHomeAppearing; 124 private long mStartDelay; 125 private long mDuration; 126 private TimeInterpolator mInterpolator; 127 128 @Override 129 public void startTransition(LayoutTransition transition, ViewGroup container, 130 View view, int transitionType) { 131 if (view.getId() == R.id.back) { 132 mBackTransitioning = true; 133 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 134 mHomeAppearing = true; 135 mStartDelay = transition.getStartDelay(transitionType); 136 mDuration = transition.getDuration(transitionType); 137 mInterpolator = transition.getInterpolator(transitionType); 138 } 139 } 140 141 @Override 142 public void endTransition(LayoutTransition transition, ViewGroup container, 143 View view, int transitionType) { 144 if (view.getId() == R.id.back) { 145 mBackTransitioning = false; 146 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 147 mHomeAppearing = false; 148 } 149 } 150 151 public void onBackAltCleared() { 152 ButtonDispatcher backButton = getBackButton(); 153 154 // When dismissing ime during unlock, force the back button to run the same appearance 155 // animation as home (if we catch this condition early enough). 156 if (!mBackTransitioning && backButton.getVisibility() == VISIBLE 157 && mHomeAppearing && getHomeButton().getAlpha() == 0) { 158 getBackButton().setAlpha(0); 159 ValueAnimator a = ObjectAnimator.ofFloat(backButton, "alpha", 0, 1); 160 a.setStartDelay(mStartDelay); 161 a.setDuration(mDuration); 162 a.setInterpolator(mInterpolator); 163 a.start(); 164 } 165 } 166 } 167 168 private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { 169 @Override 170 public void onClick(View view) { 171 mContext.getSystemService(InputMethodManager.class) 172 .showInputMethodPicker(true /* showAuxiliarySubtypes */); 173 } 174 }; 175 176 private class H extends Handler { 177 public void handleMessage(Message m) { 178 switch (m.what) { 179 case MSG_CHECK_INVALID_LAYOUT: 180 final String how = "" + m.obj; 181 final int w = getWidth(); 182 final int h = getHeight(); 183 final int vw = getCurrentView().getWidth(); 184 final int vh = getCurrentView().getHeight(); 185 186 if (h != vh || w != vw) { 187 Log.w(TAG, String.format( 188 "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", 189 how, w, h, vw, vh)); 190 if (WORKAROUND_INVALID_LAYOUT) { 191 requestLayout(); 192 } 193 } 194 break; 195 } 196 } 197 } 198 199 public NavigationBarView(Context context, AttributeSet attrs) { 200 super(context, attrs); 201 202 mDisplay = ((WindowManager) context.getSystemService( 203 Context.WINDOW_SERVICE)).getDefaultDisplay(); 204 205 mVertical = false; 206 mShowMenu = false; 207 208 mShowAccessibilityButton = false; 209 mLongClickableAccessibilityButton = false; 210 211 mConfiguration = new Configuration(); 212 mConfiguration.updateFrom(context.getResources().getConfiguration()); 213 updateIcons(context, Configuration.EMPTY, mConfiguration); 214 215 mBarTransitions = new NavigationBarTransitions(this); 216 217 mButtonDispatchers.put(R.id.back, new ButtonDispatcher(R.id.back)); 218 mButtonDispatchers.put(R.id.home, new ButtonDispatcher(R.id.home)); 219 mButtonDispatchers.put(R.id.recent_apps, new ButtonDispatcher(R.id.recent_apps)); 220 mButtonDispatchers.put(R.id.menu, new ButtonDispatcher(R.id.menu)); 221 mButtonDispatchers.put(R.id.ime_switcher, new ButtonDispatcher(R.id.ime_switcher)); 222 mButtonDispatchers.put(R.id.accessibility_button, 223 new ButtonDispatcher(R.id.accessibility_button)); 224 } 225 226 public BarTransitions getBarTransitions() { 227 return mBarTransitions; 228 } 229 230 public LightBarTransitionsController getLightTransitionsController() { 231 return mBarTransitions.getLightTransitionsController(); 232 } 233 234 public void setComponents(RecentsComponent recentsComponent, Divider divider) { 235 mRecentsComponent = recentsComponent; 236 mDivider = divider; 237 if (mGestureHelper instanceof NavigationBarGestureHelper) { 238 ((NavigationBarGestureHelper) mGestureHelper).setComponents( 239 recentsComponent, divider, this); 240 } 241 } 242 243 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) { 244 mOnVerticalChangedListener = onVerticalChangedListener; 245 notifyVerticalChangedListener(mVertical); 246 } 247 248 @Override 249 public boolean onTouchEvent(MotionEvent event) { 250 if (mGestureHelper.onTouchEvent(event)) { 251 return true; 252 } 253 return super.onTouchEvent(event); 254 } 255 256 @Override 257 public boolean onInterceptTouchEvent(MotionEvent event) { 258 return mGestureHelper.onInterceptTouchEvent(event); 259 } 260 261 public void abortCurrentGesture() { 262 getHomeButton().abortCurrentGesture(); 263 } 264 265 private H mHandler = new H(); 266 267 public View getCurrentView() { 268 return mCurrentView; 269 } 270 271 public View[] getAllViews() { 272 return mRotatedViews; 273 } 274 275 public ButtonDispatcher getRecentsButton() { 276 return mButtonDispatchers.get(R.id.recent_apps); 277 } 278 279 public ButtonDispatcher getMenuButton() { 280 return mButtonDispatchers.get(R.id.menu); 281 } 282 283 public ButtonDispatcher getBackButton() { 284 return mButtonDispatchers.get(R.id.back); 285 } 286 287 public ButtonDispatcher getHomeButton() { 288 return mButtonDispatchers.get(R.id.home); 289 } 290 291 public ButtonDispatcher getImeSwitchButton() { 292 return mButtonDispatchers.get(R.id.ime_switcher); 293 } 294 295 public ButtonDispatcher getAccessibilityButton() { 296 return mButtonDispatchers.get(R.id.accessibility_button); 297 } 298 299 public SparseArray<ButtonDispatcher> getButtonDispatchers() { 300 return mButtonDispatchers; 301 } 302 303 private void updateCarModeIcons(Context ctx) { 304 mBackCarModeIcon = getDrawable(ctx, 305 R.drawable.ic_sysbar_back_carmode, R.drawable.ic_sysbar_back_carmode); 306 mBackLandCarModeIcon = mBackCarModeIcon; 307 mBackAltCarModeIcon = getDrawable(ctx, 308 R.drawable.ic_sysbar_back_ime_carmode, R.drawable.ic_sysbar_back_ime_carmode); 309 mBackAltLandCarModeIcon = mBackAltCarModeIcon; 310 mHomeCarModeIcon = getDrawable(ctx, 311 R.drawable.ic_sysbar_home_carmode, R.drawable.ic_sysbar_home_carmode); 312 } 313 314 private void updateIcons(Context ctx, Configuration oldConfig, Configuration newConfig) { 315 if (oldConfig.orientation != newConfig.orientation 316 || oldConfig.densityDpi != newConfig.densityDpi) { 317 mDockedIcon = getDrawable(ctx, 318 R.drawable.ic_sysbar_docked, R.drawable.ic_sysbar_docked_dark); 319 } 320 if (oldConfig.densityDpi != newConfig.densityDpi 321 || oldConfig.getLayoutDirection() != newConfig.getLayoutDirection()) { 322 mBackIcon = getDrawable(ctx, R.drawable.ic_sysbar_back, R.drawable.ic_sysbar_back_dark); 323 mBackLandIcon = mBackIcon; 324 mBackAltIcon = getDrawable(ctx, 325 R.drawable.ic_sysbar_back_ime, R.drawable.ic_sysbar_back_ime_dark); 326 mBackAltLandIcon = mBackAltIcon; 327 328 mHomeDefaultIcon = getDrawable(ctx, 329 R.drawable.ic_sysbar_home, R.drawable.ic_sysbar_home_dark); 330 mRecentIcon = getDrawable(ctx, 331 R.drawable.ic_sysbar_recent, R.drawable.ic_sysbar_recent_dark); 332 mMenuIcon = getDrawable(ctx, R.drawable.ic_sysbar_menu, R.drawable.ic_sysbar_menu_dark); 333 mAccessibilityIcon = getDrawable(ctx, R.drawable.ic_sysbar_accessibility_button, 334 R.drawable.ic_sysbar_accessibility_button_dark); 335 336 int dualToneDarkTheme = Utils.getThemeAttr(ctx, R.attr.darkIconTheme); 337 int dualToneLightTheme = Utils.getThemeAttr(ctx, R.attr.lightIconTheme); 338 Context darkContext = new ContextThemeWrapper(ctx, dualToneDarkTheme); 339 Context lightContext = new ContextThemeWrapper(ctx, dualToneLightTheme); 340 mImeIcon = getDrawable(darkContext, lightContext, 341 R.drawable.ic_ime_switcher_default, R.drawable.ic_ime_switcher_default); 342 343 if (ALTERNATE_CAR_MODE_UI) { 344 updateCarModeIcons(ctx); 345 } 346 } 347 } 348 349 private KeyButtonDrawable getDrawable(Context ctx, @DrawableRes int lightIcon, 350 @DrawableRes int darkIcon) { 351 return getDrawable(ctx, ctx, lightIcon, darkIcon); 352 } 353 354 private KeyButtonDrawable getDrawable(Context darkContext, Context lightContext, 355 @DrawableRes int lightIcon, @DrawableRes int darkIcon) { 356 return KeyButtonDrawable.create(lightContext.getDrawable(lightIcon), 357 darkContext.getDrawable(darkIcon)); 358 } 359 360 @Override 361 public void setLayoutDirection(int layoutDirection) { 362 // Reload all the icons 363 updateIcons(getContext(), Configuration.EMPTY, mConfiguration); 364 365 super.setLayoutDirection(layoutDirection); 366 } 367 368 public void notifyScreenOn() { 369 setDisabledFlags(mDisabledFlags, true); 370 } 371 372 public void setNavigationIconHints(int hints) { 373 setNavigationIconHints(hints, false); 374 } 375 376 private KeyButtonDrawable getBackIconWithAlt(boolean carMode, boolean landscape) { 377 return landscape 378 ? carMode ? mBackAltLandCarModeIcon : mBackAltLandIcon 379 : carMode ? mBackAltCarModeIcon : mBackAltIcon; 380 } 381 382 private KeyButtonDrawable getBackIcon(boolean carMode, boolean landscape) { 383 return landscape 384 ? carMode ? mBackLandCarModeIcon : mBackLandIcon 385 : carMode ? mBackCarModeIcon : mBackIcon; 386 } 387 388 public void setNavigationIconHints(int hints, boolean force) { 389 if (!force && hints == mNavigationIconHints) return; 390 final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 391 if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { 392 mTransitionListener.onBackAltCleared(); 393 } 394 if (DEBUG) { 395 android.widget.Toast.makeText(getContext(), 396 "Navigation icon hints = " + hints, 397 500).show(); 398 } 399 400 mNavigationIconHints = hints; 401 402 // We have to replace or restore the back and home button icons when exiting or entering 403 // carmode, respectively. Recents are not available in CarMode in nav bar so change 404 // to recent icon is not required. 405 KeyButtonDrawable backIcon = (backAlt) 406 ? getBackIconWithAlt(mUseCarModeUi, mVertical) 407 : getBackIcon(mUseCarModeUi, mVertical); 408 409 getBackButton().setImageDrawable(backIcon); 410 411 updateRecentsIcon(); 412 413 if (mUseCarModeUi) { 414 getHomeButton().setImageDrawable(mHomeCarModeIcon); 415 } else { 416 getHomeButton().setImageDrawable(mHomeDefaultIcon); 417 } 418 419 // The Accessibility button always overrides the appearance of the IME switcher 420 final boolean showImeButton = 421 !mShowAccessibilityButton && ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) 422 != 0); 423 getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); 424 getImeSwitchButton().setImageDrawable(mImeIcon); 425 426 // Update menu button in case the IME state has changed. 427 setMenuVisibility(mShowMenu, true); 428 getMenuButton().setImageDrawable(mMenuIcon); 429 430 setAccessibilityButtonState(mShowAccessibilityButton, mLongClickableAccessibilityButton); 431 getAccessibilityButton().setImageDrawable(mAccessibilityIcon); 432 433 setDisabledFlags(mDisabledFlags, true); 434 435 mBarTransitions.reapplyDarkIntensity(); 436 } 437 438 public void setDisabledFlags(int disabledFlags) { 439 setDisabledFlags(disabledFlags, false); 440 } 441 442 public void setDisabledFlags(int disabledFlags, boolean force) { 443 if (!force && mDisabledFlags == disabledFlags) return; 444 445 mDisabledFlags = disabledFlags; 446 447 final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 448 449 // Always disable recents when alternate car mode UI is active. 450 boolean disableRecent = mUseCarModeUi 451 || ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); 452 final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 453 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); 454 455 ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); 456 if (navButtons != null) { 457 LayoutTransition lt = navButtons.getLayoutTransition(); 458 if (lt != null) { 459 if (!lt.getTransitionListeners().contains(mTransitionListener)) { 460 lt.addTransitionListener(mTransitionListener); 461 } 462 } 463 } 464 if (inLockTask() && disableRecent && !disableHome) { 465 // Don't hide recents when in lock task, it is used for exiting. 466 // Unless home is hidden, then in DPM locked mode and no exit available. 467 disableRecent = false; 468 } 469 470 getBackButton().setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 471 getHomeButton().setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 472 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 473 } 474 475 private boolean inLockTask() { 476 try { 477 return ActivityManager.getService().isInLockTaskMode(); 478 } catch (RemoteException e) { 479 return false; 480 } 481 } 482 483 public void setLayoutTransitionsEnabled(boolean enabled) { 484 mLayoutTransitionsEnabled = enabled; 485 updateLayoutTransitionsEnabled(); 486 } 487 488 public void setWakeAndUnlocking(boolean wakeAndUnlocking) { 489 setUseFadingAnimations(wakeAndUnlocking); 490 mWakeAndUnlocking = wakeAndUnlocking; 491 updateLayoutTransitionsEnabled(); 492 } 493 494 private void updateLayoutTransitionsEnabled() { 495 boolean enabled = !mWakeAndUnlocking && mLayoutTransitionsEnabled; 496 ViewGroup navButtons = (ViewGroup) getCurrentView().findViewById(R.id.nav_buttons); 497 LayoutTransition lt = navButtons.getLayoutTransition(); 498 if (lt != null) { 499 if (enabled) { 500 lt.enableTransitionType(LayoutTransition.APPEARING); 501 lt.enableTransitionType(LayoutTransition.DISAPPEARING); 502 lt.enableTransitionType(LayoutTransition.CHANGE_APPEARING); 503 lt.enableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 504 } else { 505 lt.disableTransitionType(LayoutTransition.APPEARING); 506 lt.disableTransitionType(LayoutTransition.DISAPPEARING); 507 lt.disableTransitionType(LayoutTransition.CHANGE_APPEARING); 508 lt.disableTransitionType(LayoutTransition.CHANGE_DISAPPEARING); 509 } 510 } 511 } 512 513 private void setUseFadingAnimations(boolean useFadingAnimations) { 514 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) ((ViewGroup) getParent()) 515 .getLayoutParams(); 516 if (lp != null) { 517 boolean old = lp.windowAnimations != 0; 518 if (!old && useFadingAnimations) { 519 lp.windowAnimations = R.style.Animation_NavigationBarFadeIn; 520 } else if (old && !useFadingAnimations) { 521 lp.windowAnimations = 0; 522 } else { 523 return; 524 } 525 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); 526 wm.updateViewLayout((View) getParent(), lp); 527 } 528 } 529 530 public void setMenuVisibility(final boolean show) { 531 setMenuVisibility(show, false); 532 } 533 534 public void setMenuVisibility(final boolean show, final boolean force) { 535 if (!force && mShowMenu == show) return; 536 537 mShowMenu = show; 538 539 // Only show Menu if IME switcher and Accessibility button not shown. 540 final boolean shouldShow = mShowMenu && !mShowAccessibilityButton && 541 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0); 542 543 getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); 544 } 545 546 public void setAccessibilityButtonState(final boolean visible, final boolean longClickable) { 547 mShowAccessibilityButton = visible; 548 mLongClickableAccessibilityButton = longClickable; 549 if (visible) { 550 // Accessibility button overrides Menu and IME switcher buttons. 551 setMenuVisibility(false, true); 552 getImeSwitchButton().setVisibility(View.INVISIBLE); 553 } 554 555 getAccessibilityButton().setVisibility(visible ? View.VISIBLE : View.INVISIBLE); 556 getAccessibilityButton().setLongClickable(longClickable); 557 } 558 559 @Override 560 public void onFinishInflate() { 561 mNavigationInflaterView = (NavigationBarInflaterView) findViewById( 562 R.id.navigation_inflater); 563 mNavigationInflaterView.setButtonDispatchers(mButtonDispatchers); 564 565 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); 566 567 DockedStackExistsListener.register(mDockedListener); 568 updateRotatedViews(); 569 } 570 571 private void updateRotatedViews() { 572 mRotatedViews[Surface.ROTATION_0] = 573 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); 574 mRotatedViews[Surface.ROTATION_270] = 575 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); 576 577 updateCurrentView(); 578 } 579 580 public boolean needsReorient(int rotation) { 581 return mCurrentRotation != rotation; 582 } 583 584 private void updateCurrentView() { 585 final int rot = mDisplay.getRotation(); 586 for (int i=0; i<4; i++) { 587 mRotatedViews[i].setVisibility(View.GONE); 588 } 589 mCurrentView = mRotatedViews[rot]; 590 mCurrentView.setVisibility(View.VISIBLE); 591 mNavigationInflaterView.setAlternativeOrder(rot == Surface.ROTATION_90); 592 for (int i = 0; i < mButtonDispatchers.size(); i++) { 593 mButtonDispatchers.valueAt(i).setCurrentView(mCurrentView); 594 } 595 updateLayoutTransitionsEnabled(); 596 mCurrentRotation = rot; 597 } 598 599 private void updateRecentsIcon() { 600 getRecentsButton().setImageDrawable(mDockedStackExists ? mDockedIcon : mRecentIcon); 601 mBarTransitions.reapplyDarkIntensity(); 602 } 603 604 public boolean isVertical() { 605 return mVertical; 606 } 607 608 public void reorient() { 609 updateCurrentView(); 610 611 mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); 612 613 ((NavigationBarFrame) getRootView()).setDeadZone(mDeadZone); 614 mDeadZone.setDisplayRotation(mCurrentRotation); 615 616 // force the low profile & disabled states into compliance 617 mBarTransitions.init(); 618 setDisabledFlags(mDisabledFlags, true /* force */); 619 setMenuVisibility(mShowMenu, true /* force */); 620 621 if (DEBUG) { 622 Log.d(TAG, "reorient(): rot=" + mCurrentRotation); 623 } 624 625 updateTaskSwitchHelper(); 626 setNavigationIconHints(mNavigationIconHints, true); 627 628 getHomeButton().setVertical(mVertical); 629 } 630 631 private void updateTaskSwitchHelper() { 632 if (mGestureHelper == null) return; 633 boolean isRtl = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); 634 mGestureHelper.setBarState(mVertical, isRtl); 635 } 636 637 @Override 638 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 639 if (DEBUG) Log.d(TAG, String.format( 640 "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); 641 642 final boolean newVertical = w > 0 && h > w; 643 if (newVertical != mVertical) { 644 mVertical = newVertical; 645 //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); 646 reorient(); 647 notifyVerticalChangedListener(newVertical); 648 } 649 650 postCheckForInvalidLayout("sizeChanged"); 651 super.onSizeChanged(w, h, oldw, oldh); 652 } 653 654 private void notifyVerticalChangedListener(boolean newVertical) { 655 if (mOnVerticalChangedListener != null) { 656 mOnVerticalChangedListener.onVerticalChanged(newVertical); 657 } 658 } 659 660 @Override 661 protected void onConfigurationChanged(Configuration newConfig) { 662 super.onConfigurationChanged(newConfig); 663 boolean uiCarModeChanged = updateCarMode(newConfig); 664 updateTaskSwitchHelper(); 665 updateIcons(getContext(), mConfiguration, newConfig); 666 updateRecentsIcon(); 667 if (uiCarModeChanged || mConfiguration.densityDpi != newConfig.densityDpi 668 || mConfiguration.getLayoutDirection() != newConfig.getLayoutDirection()) { 669 // If car mode or density changes, we need to reset the icons. 670 setNavigationIconHints(mNavigationIconHints, true); 671 } 672 mConfiguration.updateFrom(newConfig); 673 } 674 675 /** 676 * If the configuration changed, update the carmode and return that it was updated. 677 */ 678 private boolean updateCarMode(Configuration newConfig) { 679 boolean uiCarModeChanged = false; 680 if (newConfig != null) { 681 int uiMode = newConfig.uiMode & Configuration.UI_MODE_TYPE_MASK; 682 final boolean isCarMode = (uiMode == Configuration.UI_MODE_TYPE_CAR); 683 684 if (isCarMode != mInCarMode) { 685 mInCarMode = isCarMode; 686 if (ALTERNATE_CAR_MODE_UI) { 687 mUseCarModeUi = isCarMode; 688 uiCarModeChanged = true; 689 } else { 690 // Don't use car mode behavior if ALTERNATE_CAR_MODE_UI not set. 691 mUseCarModeUi = false; 692 } 693 } 694 } 695 return uiCarModeChanged; 696 } 697 698 /* 699 @Override 700 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 701 if (DEBUG) Log.d(TAG, String.format( 702 "onLayout: %s (%d,%d,%d,%d)", 703 changed?"changed":"notchanged", left, top, right, bottom)); 704 super.onLayout(changed, left, top, right, bottom); 705 } 706 707 // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else 708 // fails, any touch on the display will fix the layout. 709 @Override 710 public boolean onInterceptTouchEvent(MotionEvent ev) { 711 if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString()); 712 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 713 postCheckForInvalidLayout("touch"); 714 } 715 return super.onInterceptTouchEvent(ev); 716 } 717 */ 718 719 720 private String getResourceName(int resId) { 721 if (resId != 0) { 722 final android.content.res.Resources res = getContext().getResources(); 723 try { 724 return res.getResourceName(resId); 725 } catch (android.content.res.Resources.NotFoundException ex) { 726 return "(unknown)"; 727 } 728 } else { 729 return "(null)"; 730 } 731 } 732 733 private void postCheckForInvalidLayout(final String how) { 734 mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); 735 } 736 737 private static String visibilityToString(int vis) { 738 switch (vis) { 739 case View.INVISIBLE: 740 return "INVISIBLE"; 741 case View.GONE: 742 return "GONE"; 743 default: 744 return "VISIBLE"; 745 } 746 } 747 748 @Override 749 protected void onAttachedToWindow() { 750 super.onAttachedToWindow(); 751 reorient(); 752 onPluginDisconnected(null); // Create default gesture helper 753 Dependency.get(PluginManager.class).addPluginListener(this, 754 NavGesture.class, false /* Only one */); 755 } 756 757 @Override 758 protected void onDetachedFromWindow() { 759 super.onDetachedFromWindow(); 760 Dependency.get(PluginManager.class).removePluginListener(this); 761 if (mGestureHelper != null) { 762 mGestureHelper.destroy(); 763 } 764 } 765 766 @Override 767 public void onPluginConnected(NavGesture plugin, Context context) { 768 mGestureHelper = plugin.getGestureHelper(); 769 updateTaskSwitchHelper(); 770 } 771 772 @Override 773 public void onPluginDisconnected(NavGesture plugin) { 774 NavigationBarGestureHelper defaultHelper = new NavigationBarGestureHelper(getContext()); 775 defaultHelper.setComponents(mRecentsComponent, mDivider, this); 776 if (mGestureHelper != null) { 777 mGestureHelper.destroy(); 778 } 779 mGestureHelper = defaultHelper; 780 updateTaskSwitchHelper(); 781 } 782 783 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 784 pw.println("NavigationBarView {"); 785 final Rect r = new Rect(); 786 final Point size = new Point(); 787 mDisplay.getRealSize(size); 788 789 pw.println(String.format(" this: " + StatusBar.viewInfo(this) 790 + " " + visibilityToString(getVisibility()))); 791 792 getWindowVisibleDisplayFrame(r); 793 final boolean offscreen = r.right > size.x || r.bottom > size.y; 794 pw.println(" window: " 795 + r.toShortString() 796 + " " + visibilityToString(getWindowVisibility()) 797 + (offscreen ? " OFFSCREEN!" : "")); 798 799 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", 800 getResourceName(getCurrentView().getId()), 801 getCurrentView().getWidth(), getCurrentView().getHeight(), 802 visibilityToString(getCurrentView().getVisibility()))); 803 804 pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s", 805 mDisabledFlags, 806 mVertical ? "true" : "false", 807 mShowMenu ? "true" : "false")); 808 809 dumpButton(pw, "back", getBackButton()); 810 dumpButton(pw, "home", getHomeButton()); 811 dumpButton(pw, "rcnt", getRecentsButton()); 812 dumpButton(pw, "menu", getMenuButton()); 813 dumpButton(pw, "a11y", getAccessibilityButton()); 814 815 pw.println(" }"); 816 } 817 818 private static void dumpButton(PrintWriter pw, String caption, ButtonDispatcher button) { 819 pw.print(" " + caption + ": "); 820 if (button == null) { 821 pw.print("null"); 822 } else { 823 pw.print(visibilityToString(button.getVisibility()) 824 + " alpha=" + button.getAlpha() 825 ); 826 } 827 pw.println(); 828 } 829 830 public interface OnVerticalChangedListener { 831 void onVerticalChanged(boolean isVertical); 832 } 833 834 private final Consumer<Boolean> mDockedListener = exists -> mHandler.post(() -> { 835 mDockedStackExists = exists; 836 updateRecentsIcon(); 837 }); 838 } 839