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