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