NavigationBarView.java revision 6c9df5054a25f179ea7359a1a5e59e7d5d8da122
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.app.ActivityManagerNative; 21import android.app.StatusBarManager; 22import android.app.admin.DevicePolicyManager; 23import android.content.BroadcastReceiver; 24import android.content.Context; 25import android.content.Intent; 26import android.content.IntentFilter; 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.os.RemoteException; 34import android.util.AttributeSet; 35import android.util.Log; 36import android.view.Display; 37import android.view.MotionEvent; 38import android.view.Surface; 39import android.view.View; 40import android.view.View.OnClickListener; 41import android.view.ViewGroup; 42import android.view.WindowManager; 43import android.view.accessibility.AccessibilityManager; 44import android.widget.ImageView; 45import android.widget.LinearLayout; 46 47import com.android.systemui.R; 48import com.android.systemui.statusbar.BaseStatusBar; 49import com.android.systemui.statusbar.DelegateViewHelper; 50import com.android.systemui.statusbar.policy.DeadZone; 51 52import java.io.FileDescriptor; 53import java.io.PrintWriter; 54 55public class NavigationBarView extends LinearLayout { 56 private static final int CAMERA_BUTTON_FADE_DURATION = 200; 57 final static boolean DEBUG = false; 58 final static String TAG = "PhoneStatusBar/NavigationBarView"; 59 60 final static boolean NAVBAR_ALWAYS_AT_RIGHT = true; 61 62 // slippery nav bar when everything is disabled, e.g. during setup 63 final static boolean SLIPPERY_WHEN_DISABLED = true; 64 65 final Display mDisplay; 66 View mCurrentView = null; 67 View[] mRotatedViews = new View[4]; 68 69 int mBarSize; 70 boolean mVertical; 71 boolean mScreenOn; 72 73 boolean mShowMenu; 74 int mDisabledFlags = 0; 75 int mNavigationIconHints = 0; 76 77 private Drawable mBackIcon, mBackLandIcon, mBackAltIcon, mBackAltLandIcon; 78 private Drawable mRecentIcon; 79 private Drawable mRecentLandIcon; 80 81 private DelegateViewHelper mDelegateHelper; 82 private DeadZone mDeadZone; 83 private final NavigationBarTransitions mBarTransitions; 84 85 // workaround for LayoutTransitions leaving the nav buttons in a weird state (bug 5549288) 86 final static boolean WORKAROUND_INVALID_LAYOUT = true; 87 final static int MSG_CHECK_INVALID_LAYOUT = 8686; 88 89 // used to disable the camera icon in navbar when disabled by DPM 90 private boolean mCameraDisabledByDpm; 91 92 private final OnTouchListener mCameraTouchListener = new OnTouchListener() { 93 @Override 94 public boolean onTouch(View cameraButtonView, MotionEvent event) { 95 View searchLight = getSearchLight(); 96 switch (event.getAction()) { 97 case MotionEvent.ACTION_DOWN: 98 // disable search gesture while interacting with camera 99 mDelegateHelper.setDisabled(true); 100 cameraButtonView.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION); 101 if (searchLight != null) { 102 searchLight.animate().alpha(0.0f).setDuration(CAMERA_BUTTON_FADE_DURATION); 103 } 104 break; 105 case MotionEvent.ACTION_UP: 106 case MotionEvent.ACTION_CANCEL: 107 mDelegateHelper.setDisabled(false); 108 cameraButtonView.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION); 109 if (searchLight != null) { 110 searchLight.animate().alpha(1.0f).setDuration(CAMERA_BUTTON_FADE_DURATION); 111 } 112 break; 113 } 114 return KeyguardTouchDelegate.getInstance(getContext()).dispatch(event); 115 } 116 }; 117 118 private class H extends Handler { 119 public void handleMessage(Message m) { 120 switch (m.what) { 121 case MSG_CHECK_INVALID_LAYOUT: 122 final String how = "" + m.obj; 123 final int w = getWidth(); 124 final int h = getHeight(); 125 final int vw = mCurrentView.getWidth(); 126 final int vh = mCurrentView.getHeight(); 127 128 if (h != vh || w != vw) { 129 Log.w(TAG, String.format( 130 "*** Invalid layout in navigation bar (%s this=%dx%d cur=%dx%d)", 131 how, w, h, vw, vh)); 132 if (WORKAROUND_INVALID_LAYOUT) { 133 requestLayout(); 134 } 135 } 136 break; 137 } 138 } 139 } 140 141 public NavigationBarView(Context context, AttributeSet attrs) { 142 super(context, attrs); 143 144 mDisplay = ((WindowManager)context.getSystemService( 145 Context.WINDOW_SERVICE)).getDefaultDisplay(); 146 147 final Resources res = mContext.getResources(); 148 mBarSize = res.getDimensionPixelSize(R.dimen.navigation_bar_size); 149 mVertical = false; 150 mShowMenu = false; 151 mDelegateHelper = new DelegateViewHelper(this); 152 153 getIcons(res); 154 155 mBarTransitions = new NavigationBarTransitions(this); 156 157 mCameraDisabledByDpm = isCameraDisabledByDpm(); 158 watchForDevicePolicyChanges(); 159 } 160 161 private void watchForDevicePolicyChanges() { 162 final IntentFilter filter = new IntentFilter(); 163 filter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 164 mContext.registerReceiver(new BroadcastReceiver() { 165 public void onReceive(Context context, Intent intent) { 166 post(new Runnable() { 167 @Override 168 public void run() { 169 mCameraDisabledByDpm = isCameraDisabledByDpm(); 170 } 171 }); 172 } 173 }, filter); 174 } 175 176 public BarTransitions getBarTransitions() { 177 return mBarTransitions; 178 } 179 180 public void setDelegateView(View view) { 181 mDelegateHelper.setDelegateView(view); 182 } 183 184 public void setBar(BaseStatusBar phoneStatusBar) { 185 mDelegateHelper.setBar(phoneStatusBar); 186 } 187 188 @Override 189 public boolean onTouchEvent(MotionEvent event) { 190 if (mDeadZone != null && event.getAction() == MotionEvent.ACTION_OUTSIDE) { 191 mDeadZone.poke(event); 192 } 193 if (mDelegateHelper != null) { 194 boolean ret = mDelegateHelper.onInterceptTouchEvent(event); 195 if (ret) return true; 196 } 197 return super.onTouchEvent(event); 198 } 199 200 @Override 201 public boolean onInterceptTouchEvent(MotionEvent event) { 202 return mDelegateHelper.onInterceptTouchEvent(event); 203 } 204 205 private H mHandler = new H(); 206 207 public View getCurrentView() { 208 return mCurrentView; 209 } 210 211 public View getRecentsButton() { 212 return mCurrentView.findViewById(R.id.recent_apps); 213 } 214 215 public View getMenuButton() { 216 return mCurrentView.findViewById(R.id.menu); 217 } 218 219 public View getBackButton() { 220 return mCurrentView.findViewById(R.id.back); 221 } 222 223 public View getHomeButton() { 224 return mCurrentView.findViewById(R.id.home); 225 } 226 227 // for when home is disabled, but search isn't 228 public View getSearchLight() { 229 return mCurrentView.findViewById(R.id.search_light); 230 } 231 232 // shown when keyguard is visible and camera is available 233 public View getCameraButton() { 234 return mCurrentView.findViewById(R.id.camera_button); 235 } 236 237 private void getIcons(Resources res) { 238 mBackIcon = res.getDrawable(R.drawable.ic_sysbar_back); 239 mBackLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_land); 240 mBackAltIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 241 mBackAltLandIcon = res.getDrawable(R.drawable.ic_sysbar_back_ime); 242 mRecentIcon = res.getDrawable(R.drawable.ic_sysbar_recent); 243 mRecentLandIcon = res.getDrawable(R.drawable.ic_sysbar_recent_land); 244 } 245 246 @Override 247 public void setLayoutDirection(int layoutDirection) { 248 getIcons(mContext.getResources()); 249 250 super.setLayoutDirection(layoutDirection); 251 } 252 253 public void notifyScreenOn(boolean screenOn) { 254 mScreenOn = screenOn; 255 setDisabledFlags(mDisabledFlags, true); 256 } 257 258 public void setNavigationIconHints(int hints) { 259 setNavigationIconHints(hints, false); 260 } 261 262 public void setNavigationIconHints(int hints, boolean force) { 263 if (!force && hints == mNavigationIconHints) return; 264 265 if (DEBUG) { 266 android.widget.Toast.makeText(mContext, 267 "Navigation icon hints = " + hints, 268 500).show(); 269 } 270 271 mNavigationIconHints = hints; 272 273 getBackButton().setAlpha( 274 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_NOP)) ? 0.5f : 1.0f); 275 getHomeButton().setAlpha( 276 (0 != (hints & StatusBarManager.NAVIGATION_HINT_HOME_NOP)) ? 0.5f : 1.0f); 277 getRecentsButton().setAlpha( 278 (0 != (hints & StatusBarManager.NAVIGATION_HINT_RECENT_NOP)) ? 0.5f : 1.0f); 279 280 ((ImageView)getBackButton()).setImageDrawable( 281 (0 != (hints & StatusBarManager.NAVIGATION_HINT_BACK_ALT)) 282 ? (mVertical ? mBackAltLandIcon : mBackAltIcon) 283 : (mVertical ? mBackLandIcon : mBackIcon)); 284 285 ((ImageView)getRecentsButton()).setImageDrawable(mVertical ? mRecentLandIcon : mRecentIcon); 286 287 setDisabledFlags(mDisabledFlags, true); 288 } 289 290 public void setDisabledFlags(int disabledFlags) { 291 setDisabledFlags(disabledFlags, false); 292 } 293 294 public void setDisabledFlags(int disabledFlags, boolean force) { 295 if (!force && mDisabledFlags == disabledFlags) return; 296 297 mDisabledFlags = disabledFlags; 298 299 final boolean disableHome = ((disabledFlags & View.STATUS_BAR_DISABLE_HOME) != 0); 300 final boolean disableRecent = ((disabledFlags & View.STATUS_BAR_DISABLE_RECENT) != 0); 301 final boolean disableBack = ((disabledFlags & View.STATUS_BAR_DISABLE_BACK) != 0) 302 && ((mNavigationIconHints & StatusBarManager.NAVIGATION_HINT_BACK_ALT) == 0); 303 final boolean disableSearch = ((disabledFlags & View.STATUS_BAR_DISABLE_SEARCH) != 0); 304 305 if (SLIPPERY_WHEN_DISABLED) { 306 setSlippery(disableHome && disableRecent && disableBack && disableSearch); 307 } 308 309 if (!mScreenOn && mCurrentView != null) { 310 ViewGroup navButtons = (ViewGroup) mCurrentView.findViewById(R.id.nav_buttons); 311 LayoutTransition lt = navButtons == null ? null : navButtons.getLayoutTransition(); 312 if (lt != null) { 313 lt.disableTransitionType( 314 LayoutTransition.CHANGE_APPEARING | LayoutTransition.CHANGE_DISAPPEARING | 315 LayoutTransition.APPEARING | LayoutTransition.DISAPPEARING); 316 } 317 } 318 319 getBackButton() .setVisibility(disableBack ? View.INVISIBLE : View.VISIBLE); 320 getHomeButton() .setVisibility(disableHome ? View.INVISIBLE : View.VISIBLE); 321 getRecentsButton().setVisibility(disableRecent ? View.INVISIBLE : View.VISIBLE); 322 323 final boolean shouldShowSearch = disableHome && !disableSearch; 324 getSearchLight().setVisibility(shouldShowSearch ? View.VISIBLE : View.GONE); 325 final View cameraButton = getCameraButton(); 326 if (cameraButton != null) { 327 cameraButton.setVisibility( 328 shouldShowSearch && !mCameraDisabledByDpm ? View.VISIBLE : View.GONE); 329 } 330 } 331 332 private boolean isCameraDisabledByDpm() { 333 final DevicePolicyManager dpm = 334 (DevicePolicyManager) mContext.getSystemService(Context.DEVICE_POLICY_SERVICE); 335 if (dpm != null) { 336 try { 337 final int userId = ActivityManagerNative.getDefault().getCurrentUser().id; 338 final int disabledFlags = dpm.getKeyguardDisabledFeatures(null, userId); 339 final boolean disabledBecauseKeyguardSecure = 340 (disabledFlags & DevicePolicyManager.KEYGUARD_DISABLE_SECURE_CAMERA) != 0 341 && KeyguardTouchDelegate.getInstance(getContext()).isSecure(); 342 return dpm.getCameraDisabled(null) || disabledBecauseKeyguardSecure; 343 } catch (RemoteException e) { 344 Log.e(TAG, "Can't get userId", e); 345 } 346 } 347 return false; 348 } 349 350 public void setSlippery(boolean newSlippery) { 351 WindowManager.LayoutParams lp = (WindowManager.LayoutParams) getLayoutParams(); 352 if (lp != null) { 353 boolean oldSlippery = (lp.flags & WindowManager.LayoutParams.FLAG_SLIPPERY) != 0; 354 if (!oldSlippery && newSlippery) { 355 lp.flags |= WindowManager.LayoutParams.FLAG_SLIPPERY; 356 } else if (oldSlippery && !newSlippery) { 357 lp.flags &= ~WindowManager.LayoutParams.FLAG_SLIPPERY; 358 } else { 359 return; 360 } 361 WindowManager wm = (WindowManager)getContext().getSystemService(Context.WINDOW_SERVICE); 362 wm.updateViewLayout(this, lp); 363 } 364 } 365 366 public void setMenuVisibility(final boolean show) { 367 setMenuVisibility(show, false); 368 } 369 370 public void setMenuVisibility(final boolean show, final boolean force) { 371 if (!force && mShowMenu == show) return; 372 373 mShowMenu = show; 374 375 getMenuButton().setVisibility(mShowMenu ? View.VISIBLE : View.INVISIBLE); 376 } 377 378 @Override 379 public void onFinishInflate() { 380 mRotatedViews[Surface.ROTATION_0] = 381 mRotatedViews[Surface.ROTATION_180] = findViewById(R.id.rot0); 382 383 mRotatedViews[Surface.ROTATION_90] = findViewById(R.id.rot90); 384 385 mRotatedViews[Surface.ROTATION_270] = NAVBAR_ALWAYS_AT_RIGHT 386 ? findViewById(R.id.rot90) 387 : findViewById(R.id.rot270); 388 389 mCurrentView = mRotatedViews[Surface.ROTATION_0]; 390 391 392 final AccessibilityManager accessibilityManager = 393 (AccessibilityManager) mContext.getSystemService(Context.ACCESSIBILITY_SERVICE); 394 if (accessibilityManager.isEnabled()) { 395 // In accessibility mode, we add a simple click handler since swipe is tough to 396 // trigger near screen edges. 397 View camera = getCameraButton(); 398 View searchLight = getSearchLight(); 399 if (camera != null || searchLight != null) { 400 OnClickListener listener = new OnClickListener() { 401 @Override 402 public void onClick(View v) { 403 launchForAccessibilityClick(v); 404 } 405 }; 406 if (camera != null) { 407 camera.setOnClickListener(listener); 408 } 409 if (searchLight != null) { 410 searchLight.setOnClickListener(listener); 411 } 412 } 413 } else { 414 // Add a touch handler for camera icon for all view orientations. 415 for (int i = 0; i < mRotatedViews.length; i++) { 416 View cameraButton = mRotatedViews[i].findViewById(R.id.camera_button); 417 if (cameraButton != null) { 418 cameraButton.setOnTouchListener(mCameraTouchListener); 419 } 420 } 421 } 422 } 423 424 protected void launchForAccessibilityClick(View v) { 425 if (v == getCameraButton()) { 426 KeyguardTouchDelegate.getInstance(getContext()).launchCamera(); 427 } else if (v == getSearchLight()) { 428 KeyguardTouchDelegate.getInstance(getContext()).showAssistant(); 429 } 430 } 431 432 public boolean isVertical() { 433 return mVertical; 434 } 435 436 public void reorient() { 437 final int rot = mDisplay.getRotation(); 438 for (int i=0; i<4; i++) { 439 mRotatedViews[i].setVisibility(View.GONE); 440 } 441 mCurrentView = mRotatedViews[rot]; 442 mCurrentView.setVisibility(View.VISIBLE); 443 444 mDeadZone = (DeadZone) mCurrentView.findViewById(R.id.deadzone); 445 446 // force the low profile & disabled states into compliance 447 mBarTransitions.init(mVertical); 448 setDisabledFlags(mDisabledFlags, true /* force */); 449 setMenuVisibility(mShowMenu, true /* force */); 450 451 if (DEBUG) { 452 Log.d(TAG, "reorient(): rot=" + mDisplay.getRotation()); 453 } 454 455 setNavigationIconHints(mNavigationIconHints, true); 456 } 457 458 @Override 459 protected void onLayout(boolean changed, int l, int t, int r, int b) { 460 super.onLayout(changed, l, t, r, b); 461 mDelegateHelper.setInitialTouchRegion(getHomeButton(), getBackButton(), getRecentsButton()); 462 } 463 464 @Override 465 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 466 if (DEBUG) Log.d(TAG, String.format( 467 "onSizeChanged: (%dx%d) old: (%dx%d)", w, h, oldw, oldh)); 468 469 final boolean newVertical = w > 0 && h > w; 470 if (newVertical != mVertical) { 471 mVertical = newVertical; 472 //Log.v(TAG, String.format("onSizeChanged: h=%d, w=%d, vert=%s", h, w, mVertical?"y":"n")); 473 reorient(); 474 } 475 476 postCheckForInvalidLayout("sizeChanged"); 477 super.onSizeChanged(w, h, oldw, oldh); 478 } 479 480 /* 481 @Override 482 protected void onLayout (boolean changed, int left, int top, int right, int bottom) { 483 if (DEBUG) Log.d(TAG, String.format( 484 "onLayout: %s (%d,%d,%d,%d)", 485 changed?"changed":"notchanged", left, top, right, bottom)); 486 super.onLayout(changed, left, top, right, bottom); 487 } 488 489 // uncomment this for extra defensiveness in WORKAROUND_INVALID_LAYOUT situations: if all else 490 // fails, any touch on the display will fix the layout. 491 @Override 492 public boolean onInterceptTouchEvent(MotionEvent ev) { 493 if (DEBUG) Log.d(TAG, "onInterceptTouchEvent: " + ev.toString()); 494 if (ev.getAction() == MotionEvent.ACTION_DOWN) { 495 postCheckForInvalidLayout("touch"); 496 } 497 return super.onInterceptTouchEvent(ev); 498 } 499 */ 500 501 502 private String getResourceName(int resId) { 503 if (resId != 0) { 504 final android.content.res.Resources res = mContext.getResources(); 505 try { 506 return res.getResourceName(resId); 507 } catch (android.content.res.Resources.NotFoundException ex) { 508 return "(unknown)"; 509 } 510 } else { 511 return "(null)"; 512 } 513 } 514 515 private void postCheckForInvalidLayout(final String how) { 516 mHandler.obtainMessage(MSG_CHECK_INVALID_LAYOUT, 0, 0, how).sendToTarget(); 517 } 518 519 private static String visibilityToString(int vis) { 520 switch (vis) { 521 case View.INVISIBLE: 522 return "INVISIBLE"; 523 case View.GONE: 524 return "GONE"; 525 default: 526 return "VISIBLE"; 527 } 528 } 529 530 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 531 pw.println("NavigationBarView {"); 532 final Rect r = new Rect(); 533 final Point size = new Point(); 534 mDisplay.getRealSize(size); 535 536 pw.println(String.format(" this: " + PhoneStatusBar.viewInfo(this) 537 + " " + visibilityToString(getVisibility()))); 538 539 getWindowVisibleDisplayFrame(r); 540 final boolean offscreen = r.right > size.x || r.bottom > size.y; 541 pw.println(" window: " 542 + r.toShortString() 543 + " " + visibilityToString(getWindowVisibility()) 544 + (offscreen ? " OFFSCREEN!" : "")); 545 546 pw.println(String.format(" mCurrentView: id=%s (%dx%d) %s", 547 getResourceName(mCurrentView.getId()), 548 mCurrentView.getWidth(), mCurrentView.getHeight(), 549 visibilityToString(mCurrentView.getVisibility()))); 550 551 pw.println(String.format(" disabled=0x%08x vertical=%s menu=%s", 552 mDisabledFlags, 553 mVertical ? "true" : "false", 554 mShowMenu ? "true" : "false")); 555 556 final View back = getBackButton(); 557 final View home = getHomeButton(); 558 final View recent = getRecentsButton(); 559 final View menu = getMenuButton(); 560 561 pw.println(" back: " 562 + PhoneStatusBar.viewInfo(back) 563 + " " + visibilityToString(back.getVisibility()) 564 ); 565 pw.println(" home: " 566 + PhoneStatusBar.viewInfo(home) 567 + " " + visibilityToString(home.getVisibility()) 568 ); 569 pw.println(" rcnt: " 570 + PhoneStatusBar.viewInfo(recent) 571 + " " + visibilityToString(recent.getVisibility()) 572 ); 573 pw.println(" menu: " 574 + PhoneStatusBar.viewInfo(menu) 575 + " " + visibilityToString(menu.getVisibility()) 576 ); 577 pw.println(" }"); 578 } 579 580} 581