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