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