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.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.LayoutTransition; 22 import android.app.StatusBarManager; 23 import android.content.Context; 24 import android.content.res.Resources; 25 import android.graphics.Point; 26 import android.graphics.Rect; 27 import android.graphics.drawable.Drawable; 28 import android.os.Handler; 29 import android.os.Message; 30 import android.os.ServiceManager; 31 import android.util.AttributeSet; 32 import android.util.Slog; 33 import android.view.animation.AccelerateInterpolator; 34 import android.view.Display; 35 import android.view.MotionEvent; 36 import android.view.View; 37 import android.view.Surface; 38 import android.view.ViewGroup; 39 import android.view.WindowManager; 40 import android.widget.ImageView; 41 import android.widget.LinearLayout; 42 43 import java.io.FileDescriptor; 44 import java.io.PrintWriter; 45 46 import com.android.internal.statusbar.IStatusBarService; 47 import com.android.systemui.R; 48 import com.android.systemui.statusbar.BaseStatusBar; 49 import com.android.systemui.statusbar.DelegateViewHelper; 50 import com.android.systemui.statusbar.policy.DeadZone; 51 52 public class NavigationBarView extends LinearLayout { 53 final static boolean DEBUG = false; 54 final static String TAG = "PhoneStatusBar/NavigationBarView"; 55 56 final static boolean NAVBAR_ALWAYS_AT_RIGHT = true; 57 58 // slippery nav bar when everything is disabled, e.g. during setup 59 final static boolean SLIPPERY_WHEN_DISABLED= true; 60 61 final static boolean ANIMATE_HIDE_TRANSITION = false; // turned off because it introduces unsightly delay when videos goes to full screen 62 63 protected IStatusBarService mBarService; 64 final Display mDisplay; 65 View mCurrentView = null; 66 View[] mRotatedViews = new View[4]; 67 68 int mBarSize; 69 boolean mVertical; 70 boolean mScreenOn; 71 72 boolean mHidden, mLowProfile, mShowMenu; 73 int mDisabledFlags = 0; 74 int mNavigationIconHints = 0; 75 76 private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; 77 private Drawable mRecentIcon; 78 private Drawable mRecentLandIcon; 79 80 private DelegateViewHelper mDelegateHelper; 81 private DeadZone mDeadZone; 82 83 // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) 84 final static boolean WORKAROUND_INVALID_LAYOUT = true; 85 final static int MSG_CHECK_INVALID_LAYOUT = 8686; 86 87 private class H extends Handler { 88 public void handleMessage(Message m) { 89 switch (m.what) { 90 case MSG_CHECK_INVALID_LAYOUT: 91 final String how = "" + m.obj; 92 final int w = getWidth(); 93 final int h = getHeight(); 94 final int vw = mCurrentView.getWidth(); 95 final int vh = mCurrentView.getHeight(); 96 97 if (h != vh || w != vw) { 98 Slog.w(TAG, String.format( 99 "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", 100 how, w, h, vw, vh)); 101 if (WORKAROUND_INVALID_LAYOUT) { 102 requestLayout(); 103 } 104 } 105 break; 106 } 107 } 108 } 109 110 public void setDelegateView(View view) { 111 mDelegateHelper.setDelegateView(view); 112 } 113 114 public void setBar(BaseStatusBar phoneStatusBar) { 115 mDelegateHelper.setBar(phoneStatusBar); 116 } 117 118 @Override 119 public boolean onTouchEvent(MotionEvent event) { 120 if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { 121 mDeadZone.poke(event); 122 } 123 if (mDelegateHelper != null) { 124 boolean ret = mDelegateHelper.onInterceptTouchEvent(event); 125 if (ret) return true; 126 } 127 return super.onTouchEvent(event); 128 } 129 130 @Override 131 public boolean onInterceptTouchEvent(MotionEvent event) { 132 return mDelegateHelper.onInterceptTouchEvent(event); 133 } 134 135 private H mHandler = new H(); 136 137 public View getRecentsButton() { 138 return mCurrentView.findViewById(R.id.recent_apps); 139 } 140 141 public View getMenuButton() { 142 return mCurrentView.findViewById(R.id.menu); 143 } 144 145 public View getBackButton() { 146 return mCurrentView.findViewById(R.id.back); 147 } 148 149 public View getHomeButton() { 150 return mCurrentView.findViewById(R.id.home); 151 } 152 153 // for when home is disabled, but search isn't 154 public View getSearchLight() { 155 return mCurrentView.findViewById(R.id.search_light); 156 } 157 158 public NavigationBarView(Context context, AttributeSet attrs) { 159 super(context, attrs); 160 161 mHidden = false; 162 163 mDisplay = ((WindowManager)context.getSystemService( 164 Context.WINDOW_SERVICE)).getDefaultDisplay(); 165 mBarService = IStatusBarService.Stub.asInterface( 166 ServiceManager.getService(Context.STATUS_BAR_SERVICE)); 167 168 final Resources res = mContext.getResources(); 169 mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); 170 mVertical = false; 171 mShowMenu = false; 172 mDelegateHelper = new DelegateViewHelper(this); 173 174 getIcons(res); 175 } 176 177 private void getIcons(Resources res) { 178 mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); 179 mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); 180 mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 181 mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 182 mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); 183 mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); 184 } 185 186 @Override 187 public void setLayoutDirection(int layoutDirection) { 188 getIcons(mContext.getResources()); 189 190 super.setLayoutDirection(layoutDirection); 191 } 192 193 public void notifyScreenOn(boolean screenOn) { 194 mScreenOn = screenOn; 195 setDisabledFlags(mDisabledFlags, true); 196 } 197 198 View.OnTouchListener mLightsOutListener = new View.OnTouchListener() { 199 @Override 200 public boolean onTouch(View v, MotionEvent ev) { 201 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 202 // even though setting the systemUI visibility below will turn these views 203 // on, we need them to come up faster so that they can catch this motion 204 // event 205 setLowProfile(false, false, false); 206 207 try { 208 mBarService.setSystemUiVisibility(0, View.SYSTEM_UI_FLAG_LOW_PROFILE); 209 } catch (android.os.RemoteException ex) { 210 } 211 } 212 return false; 213 } 214 }; 215 216 public void setNavigationIconHints(int hints) { 217 setNavigationIconHints(hints, false); 218 } 219 220 public void setNavigationIconHints(int hints, boolean force) { 221 if (!force && hints == mNavigationIconHints) return; 222 223 if (DEBUG) { 224 android.widget.Toast.makeText(mContext, 225 "Navigation icon hints = " + hints, 226 500).show(); 227 } 228 229 mNavigationIconHints = hints; 230 231 getBackButton().setAlpha( 232 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); 233 getHomeButton().setAlpha( 234 (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); 235 getRecentsButton().setAlpha( 236 (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); 237 238 ((ImageView)getBackButton()).setImageDrawable( 239 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) 240 ? (mVertical ? mBackAltLandIcon : mBackAltIcon) 241 : (mVertical ? mBackLandIcon : mBackIcon)); 242 243 ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); 244 245 setDisabledFlags(mDisabledFlags, true); 246 } 247 248 public void setDisabledFlags(int disabledFlags) { 249 setDisabledFlags(disabledFlags, false); 250 } 251 252 public void setDisabledFlags(int disabledFlags, boolean force) { 253 if (!force && mDisabledFlags == disabledFlags) return; 254 255 mDisabledFlags = disabledFlags; 256 257 final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 258 final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); 259 final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 260 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); 261 final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0); 262 263 if (SLIPPERY_WHEN_DISABLED) { 264 setSlippery(disableHome && disableRecent && disableBack && disableSearch); 265 } 266 267 if (!mScreenOn && mCurrentView != null) { 268 ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); 269 LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition(); 270 if (lt != null) { 271 lt.disableTransitionType( 272 LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING | 273 LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING); 274 } 275 } 276 277 getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 278 getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 279 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 280 281 getSearchLight().setVisibility((disableHome && !disableSearch) ? View.VISIBLE : View.GONE); 282 } 283 284 public void setSlippery(boolean newSlippery) { 285 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 286 if (lp != null) { 287 boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; 288 if (!oldSlippery && newSlippery) { 289 lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; 290 } else if (oldSlippery && !newSlippery) { 291 lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; 292 } else { 293 return; 294 } 295 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); 296 wm.updateViewLayout(this, lp); 297 } 298 } 299 300 public void setMenuVisibility(final boolean show) { 301 setMenuVisibility(show, false); 302 } 303 304 public void setMenuVisibility(final boolean show, final boolean force) { 305 if (!force && mShowMenu == show) return; 306 307 mShowMenu = show; 308 309 getMenuButton().setVisibility(mShowMenu ? View.VISIBLE : View.INVISIBLE); 310 } 311 312 public void setLowProfile(final boolean lightsOut) { 313 setLowProfile(lightsOut, true, false); 314 } 315 316 public void setLowProfile(final boolean lightsOut, final boolean animate, final boolean force) { 317 if (!force && lightsOut == mLowProfile) return; 318 319 mLowProfile = lightsOut; 320 321 if (DEBUG) Slog.d(TAG, "setting lights " + (lightsOut?"out":"on")); 322 323 final View navButtons = mCurrentView.findViewById(R.id.nav_buttons); 324 final View lowLights = mCurrentView.findViewById(R.id.lights_out); 325 326 // ok, everyone, stop it right there 327 navButtons.animate().cancel(); 328 lowLights.animate().cancel(); 329 330 if (!animate) { 331 navButtons.setAlpha(lightsOut ? 0f : 1f); 332 333 lowLights.setAlpha(lightsOut ? 1f : 0f); 334 lowLights.setVisibility(lightsOut ? View.VISIBLE : View.GONE); 335 } else { 336 navButtons.animate() 337 .alpha(lightsOut ? 0f : 1f) 338 .setDuration(lightsOut ? 750 : 250) 339 .start(); 340 341 lowLights.setOnTouchListener(mLightsOutListener); 342 if (lowLights.getVisibility() == View.GONE) { 343 lowLights.setAlpha(0f); 344 lowLights.setVisibility(View.VISIBLE); 345 } 346 lowLights.animate() 347 .alpha(lightsOut ? 1f : 0f) 348 .setDuration(lightsOut ? 750 : 250) 349 .setInterpolator(new AccelerateInterpolator(2.0f)) 350 .setListener(lightsOut ? null : new AnimatorListenerAdapter() { 351 @Override 352 public void onAnimationEnd(Animator _a) { 353 lowLights.setVisibility(View.GONE); 354 } 355 }) 356 .start(); 357 } 358 } 359 360 public void setHidden(final boolean hide) { 361 if (hide == mHidden) return; 362 363 mHidden = hide; 364 Slog.d(TAG, 365 (hide ? "HIDING" : "SHOWING") + " navigation bar"); 366 367 // bring up the lights no matter what 368 setLowProfile(false); 369 } 370 371 @Override 372 public void onFinishInflate() { 373 mRotatedViews[Surface.ROTATION_0] = 374 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); 375 376 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); 377 378 mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT 379 ? findViewById(R.id.rot90) 380 : findViewById(R.id.rot270); 381 382 mCurrentView = mRotatedViews[Surface.ROTATION_0]; 383 } 384 385 public void reorient() { 386 final int rot = mDisplay.getRotation(); 387 for (int i=0; i<4; i++) { 388 mRotatedViews[i].setVisibility(View.GONE); 389 } 390 mCurrentView = mRotatedViews[rot]; 391 mCurrentView.setVisibility(View.VISIBLE); 392 393 mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); 394 395 // force the low profile & disabled states into compliance 396 setLowProfile(mLowProfile, false, true /* force */); 397 setDisabledFlags(mDisabledFlags, true /* force */); 398 setMenuVisibility(mShowMenu, true /* force */); 399 400 if (DEBUG) { 401 Slog.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); 402 } 403 404 setNavigationIconHints(mNavigationIconHints, true); 405 } 406 407 @Override 408 protected void onLayout(boolean changed, int l, int t, int r, int b) { 409 super.onLayout(changed, l, t, r, b); 410 mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton()); 411 } 412 413 @Override 414 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 415 if (DEBUG) Slog.d(TAG, String.format( 416 "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); 417 418 final boolean newVertical = w > 0 && h > w; 419 if (newVertical != mVertical) { 420 mVertical = newVertical; 421 //Slog.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); 422 reorient(); 423 } 424 425 postCheckForInvalidLayout("sizeChanged"); 426 super.onSizeChanged(w, h, oldw, oldh); 427 } 428 429 /* 430 @Override 431 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 432 if (DEBUG) Slog.d(TAG, String.format( 433 "onLayout: %s (%d,%d,%d,%d)", 434 changed?"changed":"notchanged", left, top, right, bottom)); 435 super.onLayout(changed, left, top, right, bottom); 436 } 437 438 // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else 439 // fails, any touch on the display will fix the layout. 440 @Override 441 public boolean onInterceptTouchEvent(MotionEvent ev) { 442 if (DEBUG) Slog.d(TAG, "onInterceptTouchEvent: " + ev.toString()); 443 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 444 postCheckForInvalidLayout("touch"); 445 } 446 return super.onInterceptTouchEvent(ev); 447 } 448 */ 449 450 451 private String getResourceName(int resId) { 452 if (resId != 0) { 453 final android.content.res.Resources res = mContext.getResources(); 454 try { 455 return res.getResourceName(resId); 456 } catch (android.content.res.Resources.NotFoundException ex) { 457 return "(unknown)"; 458 } 459 } else { 460 return "(null)"; 461 } 462 } 463 464 private void postCheckForInvalidLayout(final String how) { 465 mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); 466 } 467 468 private static String visibilityToString(int vis) { 469 switch (vis) { 470 case View.INVISIBLE: 471 return "INVISIBLE"; 472 case View.GONE: 473 return "GONE"; 474 default: 475 return "VISIBLE"; 476 } 477 } 478 479 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 480 pw.println("NavigationBarView {"); 481 final Rect r = new Rect(); 482 final Point size = new Point(); 483 mDisplay.getRealSize(size); 484 485 pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) 486 + " " + visibilityToString(getVisibility()))); 487 488 getWindowVisibleDisplayFrame(r); 489 final boolean offscreen = r.right > size.x || r.bottom > size.y; 490 pw.println(" window: " 491 + r.toShortString() 492 + " " + visibilityToString(getWindowVisibility()) 493 + (offscreen ? " OFFSCREEN!" : "")); 494 495 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", 496 getResourceName(mCurrentView.getId()), 497 mCurrentView.getWidth(), mCurrentView.getHeight(), 498 visibilityToString(mCurrentView.getVisibility()))); 499 500 pw.println(String.format(" disabled=0x%08x vertical=%s hidden=%s low=%s menu=%s", 501 mDisabledFlags, 502 mVertical ? "true" : "false", 503 mHidden ? "true" : "false", 504 mLowProfile ? "true" : "false", 505 mShowMenu ? "true" : "false")); 506 507 final View back = getBackButton(); 508 final View home = getHomeButton(); 509 final View recent = getRecentsButton(); 510 final View menu = getMenuButton(); 511 512 pw.println(" back: " 513 + PhoneStatusBar.viewInfo(back) 514 + " " + visibilityToString(back.getVisibility()) 515 ); 516 pw.println(" home: " 517 + PhoneStatusBar.viewInfo(home) 518 + " " + visibilityToString(home.getVisibility()) 519 ); 520 pw.println(" rcnt: " 521 + PhoneStatusBar.viewInfo(recent) 522 + " " + visibilityToString(recent.getVisibility()) 523 ); 524 pw.println(" menu: " 525 + PhoneStatusBar.viewInfo(menu) 526 + " " + visibilityToString(menu.getVisibility()) 527 ); 528 pw.println(" }"); 529 } 530 531 } 532