NavigationBarView.java revision 8cd9e2db31ce16f38c9d0b642645d7b594110d3e
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 17package com.android.systemui.statusbar.phone; 18 19import android.animation.LayoutTransition; 20import android.animation.LayoutTransition.TransitionListener; 21import android.animation.ObjectAnimator; 22import android.animation.TimeInterpolator; 23import android.animation.ValueAnimator; 24import android.app.StatusBarManager; 25import android.content.Context; 26import android.content.res.Configuration; 27import android.content.res.Resources; 28import android.graphics.Point; 29import android.graphics.Rect; 30import android.graphics.drawable.Drawable; 31import android.os.Handler; 32import android.os.Message; 33import android.util.AttributeSet; 34import android.util.Log; 35import android.view.Display; 36import android.view.MotionEvent; 37import android.view.Surface; 38import android.view.View; 39import android.view.ViewGroup; 40import android.view.WindowManager; 41import android.view.inputmethod.InputMethodManager; 42import android.widget.ImageView; 43import android.widget.LinearLayout; 44import com.android.systemui.R; 45import com.android.systemui.statusbar.BaseStatusBar; 46import com.android.systemui.statusbar.DelegateViewHelper; 47import com.android.systemui.statusbar.policy.DeadZone; 48import com.android.systemui.statusbar.policy.KeyButtonView; 49 50import java.io.FileDescriptor; 51import java.io.PrintWriter; 52import java.util.ArrayList; 53 54public class NavigationBarView extends LinearLayout { 55 final static boolean DEBUG = false; 56 final static String TAG = "PhoneStatusBar/NavigationBarView"; 57 58 // slippery nav bar when everything is disabled, e.g. during setup 59 final static boolean SLIPPERY_WHEN_DISABLED = true; 60 61 final Display mDisplay; 62 View mCurrentView = null; 63 View[] mRotatedViews = new View[4]; 64 65 int mBarSize; 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 mRecentIcon; 75 private Drawable mRecentLandIcon; 76 77 private NavigationBarViewTaskSwitchHelper mTaskSwitchHelper; 78 private DelegateViewHelper mDelegateHelper; 79 private DeadZone mDeadZone; 80 private final NavigationBarTransitions mBarTransitions; 81 82 // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) 83 final static boolean WORKAROUND_INVALID_LAYOUT = true; 84 final static int MSG_CHECK_INVALID_LAYOUT = 8686; 85 86 // performs manual animation in sync with layout transitions 87 private final NavTransitionListener mTransitionListener = new NavTransitionListener(); 88 89 private OnVerticalChangedListener mOnVerticalChangedListener; 90 private boolean mIsLayoutRtl; 91 92 private class NavTransitionListener implements TransitionListener { 93 private boolean mBackTransitioning; 94 private boolean mHomeAppearing; 95 private long mStartDelay; 96 private long mDuration; 97 private TimeInterpolator mInterpolator; 98 99 @Override 100 public void startTransition(LayoutTransition transition, ViewGroup container, 101 View view, int transitionType) { 102 if (view.getId() == R.id.back) { 103 mBackTransitioning = true; 104 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 105 mHomeAppearing = true; 106 mStartDelay = transition.getStartDelay(transitionType); 107 mDuration = transition.getDuration(transitionType); 108 mInterpolator = transition.getInterpolator(transitionType); 109 } 110 } 111 112 @Override 113 public void endTransition(LayoutTransition transition, ViewGroup container, 114 View view, int transitionType) { 115 if (view.getId() == R.id.back) { 116 mBackTransitioning = false; 117 } else if (view.getId() == R.id.home && transitionType == LayoutTransition.APPEARING) { 118 mHomeAppearing = false; 119 } 120 } 121 122 public void onBackAltCleared() { 123 // When dismissing ime during unlock, force the back button to run the same appearance 124 // animation as home (if we catch this condition early enough). 125 if (!mBackTransitioning && getBackButton().getVisibility() == VISIBLE 126 && mHomeAppearing && getHomeButton().getAlpha() == 0) { 127 getBackButton().setAlpha(0); 128 ValueAnimator a = ObjectAnimator.ofFloat(getBackButton(), "alpha", 0, 1); 129 a.setStartDelay(mStartDelay); 130 a.setDuration(mDuration); 131 a.setInterpolator(mInterpolator); 132 a.start(); 133 } 134 } 135 } 136 137 private final OnClickListener mImeSwitcherClickListener = new OnClickListener() { 138 @Override 139 public void onClick(View view) { 140 ((InputMethodManager) mContext.getSystemService(Context.INPUT_METHOD_SERVICE)) 141 .showInputMethodPicker(); 142 } 143 }; 144 145 private class H extends Handler { 146 public void handleMessage(Message m) { 147 switch (m.what) { 148 case MSG_CHECK_INVALID_LAYOUT: 149 final String how = "" + m.obj; 150 final int w = getWidth(); 151 final int h = getHeight(); 152 final int vw = mCurrentView.getWidth(); 153 final int vh = mCurrentView.getHeight(); 154 155 if (h != vh || w != vw) { 156 Log.w(TAG, String.format( 157 "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", 158 how, w, h, vw, vh)); 159 if (WORKAROUND_INVALID_LAYOUT) { 160 requestLayout(); 161 } 162 } 163 break; 164 } 165 } 166 } 167 168 public NavigationBarView(Context context, AttributeSet attrs) { 169 super(context, attrs); 170 171 mDisplay = ((WindowManager)context.getSystemService( 172 Context.WINDOW_SERVICE)).getDefaultDisplay(); 173 174 final Resources res = getContext().getResources(); 175 mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); 176 mVertical = false; 177 mShowMenu = false; 178 mDelegateHelper = new DelegateViewHelper(this); 179 mTaskSwitchHelper = new NavigationBarViewTaskSwitchHelper(context); 180 181 getIcons(res); 182 183 mBarTransitions = new NavigationBarTransitions(this); 184 } 185 186 public BarTransitions getBarTransitions() { 187 return mBarTransitions; 188 } 189 190 public void setDelegateView(View view) { 191 mDelegateHelper.setDelegateView(view); 192 } 193 194 public void setBar(BaseStatusBar phoneStatusBar) { 195 mTaskSwitchHelper.setBar(phoneStatusBar); 196 mDelegateHelper.setBar(phoneStatusBar); 197 } 198 199 public void setOnVerticalChangedListener(OnVerticalChangedListener onVerticalChangedListener) { 200 mOnVerticalChangedListener = onVerticalChangedListener; 201 } 202 203 @Override 204 public boolean onTouchEvent(MotionEvent event) { 205 if (mTaskSwitchHelper.onTouchEvent(event)) { 206 return true; 207 } 208 if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { 209 mDeadZone.poke(event); 210 } 211 if (mDelegateHelper != null) { 212 boolean ret = mDelegateHelper.onInterceptTouchEvent(event); 213 if (ret) return true; 214 } 215 return super.onTouchEvent(event); 216 } 217 218 @Override 219 public boolean onInterceptTouchEvent(MotionEvent event) { 220 return mTaskSwitchHelper.onInterceptTouchEvent(event) || 221 mDelegateHelper.onInterceptTouchEvent(event); 222 } 223 224 private H mHandler = new H(); 225 226 public View getCurrentView() { 227 return mCurrentView; 228 } 229 230 public View getRecentsButton() { 231 return mCurrentView.findViewById(R.id.recent_apps); 232 } 233 234 public View getMenuButton() { 235 return mCurrentView.findViewById(R.id.menu); 236 } 237 238 public View getBackButton() { 239 return mCurrentView.findViewById(R.id.back); 240 } 241 242 public View getHomeButton() { 243 return mCurrentView.findViewById(R.id.home); 244 } 245 246 public View getImeSwitchButton() { 247 return mCurrentView.findViewById(R.id.ime_switcher); 248 } 249 250 private void getIcons(Resources res) { 251 mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); 252 mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); 253 mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 254 mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 255 mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); 256 mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); 257 } 258 259 @Override 260 public void setLayoutDirection(int layoutDirection) { 261 getIcons(getContext().getResources()); 262 263 super.setLayoutDirection(layoutDirection); 264 } 265 266 public void notifyScreenOn(boolean screenOn) { 267 mScreenOn = screenOn; 268 setDisabledFlags(mDisabledFlags, true); 269 } 270 271 public void setNavigationIconHints(int hints) { 272 setNavigationIconHints(hints, false); 273 } 274 275 public void setNavigationIconHints(int hints, boolean force) { 276 if (!force && hints == mNavigationIconHints) return; 277 final boolean backAlt = (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0; 278 if ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) != 0 && !backAlt) { 279 mTransitionListener.onBackAltCleared(); 280 } 281 if (DEBUG) { 282 android.widget.Toast.makeText(getContext(), 283 "Navigation icon hints = " + hints, 284 500).show(); 285 } 286 287 mNavigationIconHints = hints; 288 289 ((ImageView)getBackButton()).setImageDrawable(backAlt 290 ? (mVertical ? mBackAltLandIcon : mBackAltIcon) 291 : (mVertical ? mBackLandIcon : mBackIcon)); 292 293 ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); 294 295 final boolean showImeButton = ((hints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) != 0); 296 getImeSwitchButton().setVisibility(showImeButton ? View.VISIBLE : View.INVISIBLE); 297 // Update menu button in case the IME state has changed. 298 setMenuVisibility(mShowMenu, true); 299 300 301 setDisabledFlags(mDisabledFlags, true); 302 } 303 304 public void setDisabledFlags(int disabledFlags) { 305 setDisabledFlags(disabledFlags, false); 306 } 307 308 public void setDisabledFlags(int disabledFlags, boolean force) { 309 if (!force && mDisabledFlags == disabledFlags) return; 310 311 mDisabledFlags = disabledFlags; 312 313 final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 314 final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); 315 final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 316 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); 317 final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0); 318 319 if (SLIPPERY_WHEN_DISABLED) { 320 setSlippery(disableHome && disableRecent && disableBack && disableSearch); 321 } 322 323 ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); 324 if (navButtons != null) { 325 LayoutTransition lt = navButtons.getLayoutTransition(); 326 if (lt != null) { 327 if (!lt.getTransitionListeners().contains(mTransitionListener)) { 328 lt.addTransitionListener(mTransitionListener); 329 } 330 if (!mScreenOn && mCurrentView != null) { 331 lt.disableTransitionType( 332 LayoutTransition.CHANGE_APPEARING | 333 LayoutTransition.CHANGE_DISAPPEARING | 334 LayoutTransition.APPEARING | 335 LayoutTransition.DISAPPEARING); 336 } 337 } 338 } 339 340 getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 341 getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 342 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 343 344 mBarTransitions.applyBackButtonQuiescentAlpha(mBarTransitions.getMode(), true /*animate*/); 345 } 346 347 private void setVisibleOrGone(View view, boolean visible) { 348 if (view != null) { 349 view.setVisibility(visible ? VISIBLE : GONE); 350 } 351 } 352 353 public void setSlippery(boolean newSlippery) { 354 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 355 if (lp != null) { 356 boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; 357 if (!oldSlippery && newSlippery) { 358 lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; 359 } else if (oldSlippery && !newSlippery) { 360 lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; 361 } else { 362 return; 363 } 364 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); 365 wm.updateViewLayout(this, lp); 366 } 367 } 368 369 public void setMenuVisibility(final boolean show) { 370 setMenuVisibility(show, false); 371 } 372 373 public void setMenuVisibility(final boolean show, final boolean force) { 374 if (!force && mShowMenu == show) return; 375 376 mShowMenu = show; 377 378 // Only show Menu if IME switcher not shown. 379 final boolean shouldShow = mShowMenu && 380 ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_IME_SHOWN) == 0); 381 getMenuButton().setVisibility(shouldShow ? View.VISIBLE : View.INVISIBLE); 382 } 383 384 @Override 385 public void onFinishInflate() { 386 mRotatedViews[Surface.ROTATION_0] = 387 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); 388 389 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); 390 391 mRotatedViews[Surface.ROTATION_270] = mRotatedViews[Surface.ROTATION_90]; 392 393 mCurrentView = mRotatedViews[Surface.ROTATION_0]; 394 395 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); 396 397 updateRTLOrder(); 398 } 399 400 public boolean isVertical() { 401 return mVertical; 402 } 403 404 public void reorient() { 405 final int rot = mDisplay.getRotation(); 406 for (int i=0; i<4; i++) { 407 mRotatedViews[i].setVisibility(View.GONE); 408 } 409 mCurrentView = mRotatedViews[rot]; 410 mCurrentView.setVisibility(View.VISIBLE); 411 412 getImeSwitchButton().setOnClickListener(mImeSwitcherClickListener); 413 414 mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); 415 416 // force the low profile & disabled states into compliance 417 mBarTransitions.init(mVertical); 418 setDisabledFlags(mDisabledFlags, true /* force */); 419 setMenuVisibility(mShowMenu, true /* force */); 420 421 if (DEBUG) { 422 Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); 423 } 424 425 // swap to x coordinate if orientation is not in vertical 426 if (mDelegateHelper != null) { 427 mDelegateHelper.setSwapXY(mVertical); 428 } 429 boolean isRTL = (getLayoutDirection() == View.LAYOUT_DIRECTION_RTL); 430 mTaskSwitchHelper.setBarState(mVertical, isRTL); 431 432 setNavigationIconHints(mNavigationIconHints, true); 433 } 434 435 @Override 436 protected void onLayout(boolean changed, int l, int t, int r, int b) { 437 super.onLayout(changed, l, t, r, b); 438 mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton()); 439 } 440 441 @Override 442 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 443 if (DEBUG) Log.d(TAG, String.format( 444 "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); 445 446 final boolean newVertical = w > 0 && h > w; 447 if (newVertical != mVertical) { 448 mVertical = newVertical; 449 //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); 450 reorient(); 451 if (mOnVerticalChangedListener != null) { 452 mOnVerticalChangedListener.onVerticalChanged(newVertical); 453 } 454 } 455 456 postCheckForInvalidLayout("sizeChanged"); 457 super.onSizeChanged(w, h, oldw, oldh); 458 } 459 460 @Override 461 protected void onConfigurationChanged(Configuration newConfig) { 462 super.onConfigurationChanged(newConfig); 463 updateRTLOrder(); 464 } 465 466 /** 467 * In landscape, the LinearLayout is not auto mirrored since it is vertical. Therefore we 468 * have to do it manually 469 */ 470 private void updateRTLOrder() { 471 boolean isLayoutRtl = getResources().getConfiguration() 472 .getLayoutDirection() == LAYOUT_DIRECTION_RTL; 473 if (mIsLayoutRtl != isLayoutRtl) { 474 475 // We swap all children of the 90 and 270 degree layouts, since they are vertical 476 View rotation90 = mRotatedViews[Surface.ROTATION_90]; 477 swapChildrenOrderIfVertical(rotation90.findViewById(R.id.nav_buttons)); 478 479 View rotation270 = mRotatedViews[Surface.ROTATION_270]; 480 if (rotation90 != rotation270) { 481 swapChildrenOrderIfVertical(rotation270.findViewById(R.id.nav_buttons)); 482 } 483 mIsLayoutRtl = isLayoutRtl; 484 } 485 } 486 487 488 /** 489 * Swaps the children order of a LinearLayout if it's orientation is Vertical 490 * 491 * @param group The LinearLayout to swap the children from. 492 */ 493 private void swapChildrenOrderIfVertical(View group) { 494 if (group instanceof LinearLayout) { 495 LinearLayout linearLayout = (LinearLayout) group; 496 if (linearLayout.getOrientation() == VERTICAL) { 497 int childCount = linearLayout.getChildCount(); 498 ArrayList<View> childList = new ArrayList<>(childCount); 499 for (int i = 0; i < childCount; i++) { 500 childList.add(linearLayout.getChildAt(i)); 501 } 502 linearLayout.removeAllViews(); 503 for (int i = childCount - 1; i >= 0; i--) { 504 linearLayout.addView(childList.get(i)); 505 } 506 } 507 } 508 } 509 510 /* 511 @Override 512 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 513 if (DEBUG) Log.d(TAG, String.format( 514 "onLayout: %s (%d,%d,%d,%d)", 515 changed?"changed":"notchanged", left, top, right, bottom)); 516 super.onLayout(changed, left, top, right, bottom); 517 } 518 519 // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else 520 // fails, any touch on the display will fix the layout. 521 @Override 522 public boolean onInterceptTouchEvent(MotionEvent ev) { 523 if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString()); 524 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 525 postCheckForInvalidLayout("touch"); 526 } 527 return super.onInterceptTouchEvent(ev); 528 } 529 */ 530 531 532 private String getResourceName(int resId) { 533 if (resId != 0) { 534 final android.content.res.Resources res = getContext().getResources(); 535 try { 536 return res.getResourceName(resId); 537 } catch (android.content.res.Resources.NotFoundException ex) { 538 return "(unknown)"; 539 } 540 } else { 541 return "(null)"; 542 } 543 } 544 545 private void postCheckForInvalidLayout(final String how) { 546 mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); 547 } 548 549 private static String visibilityToString(int vis) { 550 switch (vis) { 551 case View.INVISIBLE: 552 return "INVISIBLE"; 553 case View.GONE: 554 return "GONE"; 555 default: 556 return "VISIBLE"; 557 } 558 } 559 560 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 561 pw.println("NavigationBarView {"); 562 final Rect r = new Rect(); 563 final Point size = new Point(); 564 mDisplay.getRealSize(size); 565 566 pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) 567 + " " + visibilityToString(getVisibility()))); 568 569 getWindowVisibleDisplayFrame(r); 570 final boolean offscreen = r.right > size.x || r.bottom > size.y; 571 pw.println(" window: " 572 + r.toShortString() 573 + " " + visibilityToString(getWindowVisibility()) 574 + (offscreen ? " OFFSCREEN!" : "")); 575 576 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", 577 getResourceName(mCurrentView.getId()), 578 mCurrentView.getWidth(), mCurrentView.getHeight(), 579 visibilityToString(mCurrentView.getVisibility()))); 580 581 pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s", 582 mDisabledFlags, 583 mVertical ? "true" : "false", 584 mShowMenu ? "true" : "false")); 585 586 dumpButton(pw, "back", getBackButton()); 587 dumpButton(pw, "home", getHomeButton()); 588 dumpButton(pw, "rcnt", getRecentsButton()); 589 dumpButton(pw, "menu", getMenuButton()); 590 591 pw.println(" }"); 592 } 593 594 private static void dumpButton(PrintWriter pw, String caption, View button) { 595 pw.print(" " + caption + ": "); 596 if (button == null) { 597 pw.print("null"); 598 } else { 599 pw.print(PhoneStatusBar.viewInfo(button) 600 + " " + visibilityToString(button.getVisibility()) 601 + " alpha=" + button.getAlpha() 602 ); 603 if (button instanceof KeyButtonView) { 604 pw.print(" drawingAlpha=" + ((KeyButtonView)button).getDrawingAlpha()); 605 pw.print(" quiescentAlpha=" + ((KeyButtonView)button).getQuiescentAlpha()); 606 } 607 } 608 pw.println(); 609 } 610 611 public interface OnVerticalChangedListener { 612 void onVerticalChanged(boolean isVertical); 613 } 614} 615