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