1 /* 2 * Copyright (C) 2014 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.content.ComponentCallbacks2; 20 import android.content.Context; 21 import android.os.Bundle; 22 import android.os.SystemClock; 23 import android.os.Trace; 24 import android.view.KeyEvent; 25 import android.view.View; 26 import android.view.ViewGroup; 27 import android.view.ViewRootImpl; 28 import android.view.WindowManagerGlobal; 29 30 import com.android.internal.widget.LockPatternUtils; 31 import com.android.keyguard.KeyguardUpdateMonitor; 32 import com.android.keyguard.ViewMediatorCallback; 33 import com.android.systemui.SystemUIFactory; 34 import com.android.systemui.statusbar.CommandQueue; 35 import com.android.systemui.statusbar.RemoteInputController; 36 37 import static com.android.keyguard.KeyguardHostView.OnDismissAction; 38 39 /** 40 * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back 41 * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done, 42 * which is in turn, reported to this class by the current 43 * {@link com.android.keyguard.KeyguardViewBase}. 44 */ 45 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback { 46 47 // When hiding the Keyguard with timing supplied from WindowManager, better be early than late. 48 private static final long HIDE_TIMING_CORRECTION_MS = -3 * 16; 49 50 // Delay for showing the navigation bar when the bouncer appears. This should be kept in sync 51 // with the appear animations of the PIN/pattern/password views. 52 private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320; 53 54 private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200; 55 56 // Duration of the Keyguard dismissal animation in case the user is currently locked. This is to 57 // make everything a bit slower to bridge a gap until the user is unlocked and home screen has 58 // dranw its first frame. 59 private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000; 60 61 private static String TAG = "StatusBarKeyguardViewManager"; 62 63 protected final Context mContext; 64 65 protected LockPatternUtils mLockPatternUtils; 66 protected ViewMediatorCallback mViewMediatorCallback; 67 protected PhoneStatusBar mPhoneStatusBar; 68 private ScrimController mScrimController; 69 private FingerprintUnlockController mFingerprintUnlockController; 70 71 private ViewGroup mContainer; 72 private StatusBarWindowManager mStatusBarWindowManager; 73 74 private boolean mDeviceInteractive = false; 75 private boolean mScreenTurnedOn; 76 protected KeyguardBouncer mBouncer; 77 protected boolean mShowing; 78 protected boolean mOccluded; 79 protected boolean mRemoteInputActive; 80 81 protected boolean mFirstUpdate = true; 82 protected boolean mLastShowing; 83 protected boolean mLastOccluded; 84 private boolean mLastBouncerShowing; 85 private boolean mLastBouncerDismissible; 86 protected boolean mLastRemoteInputActive; 87 88 private OnDismissAction mAfterKeyguardGoneAction; 89 private boolean mDeviceWillWakeUp; 90 private boolean mDeferScrimFadeOut; 91 92 public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback, 93 LockPatternUtils lockPatternUtils) { 94 mContext = context; 95 mViewMediatorCallback = callback; 96 mLockPatternUtils = lockPatternUtils; 97 } 98 99 public void registerStatusBar(PhoneStatusBar phoneStatusBar, 100 ViewGroup container, StatusBarWindowManager statusBarWindowManager, 101 ScrimController scrimController, 102 FingerprintUnlockController fingerprintUnlockController) { 103 mPhoneStatusBar = phoneStatusBar; 104 mContainer = container; 105 mStatusBarWindowManager = statusBarWindowManager; 106 mScrimController = scrimController; 107 mFingerprintUnlockController = fingerprintUnlockController; 108 mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext, 109 mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container); 110 } 111 112 /** 113 * Show the keyguard. Will handle creating and attaching to the view manager 114 * lazily. 115 */ 116 public void show(Bundle options) { 117 mShowing = true; 118 mStatusBarWindowManager.setKeyguardShowing(true); 119 mScrimController.abortKeyguardFadingOut(); 120 reset(); 121 } 122 123 /** 124 * Shows the notification keyguard or the bouncer depending on 125 * {@link KeyguardBouncer#needsFullscreenBouncer()}. 126 */ 127 protected void showBouncerOrKeyguard() { 128 if (mBouncer.needsFullscreenBouncer()) { 129 130 // The keyguard might be showing (already). So we need to hide it. 131 mPhoneStatusBar.hideKeyguard(); 132 mBouncer.show(true /* resetSecuritySelection */); 133 } else { 134 mPhoneStatusBar.showKeyguard(); 135 mBouncer.hide(false /* destroyView */); 136 mBouncer.prepare(); 137 } 138 } 139 140 private void showBouncer() { 141 if (mShowing) { 142 mBouncer.show(false /* resetSecuritySelection */); 143 } 144 updateStates(); 145 } 146 147 public void dismissWithAction(OnDismissAction r, Runnable cancelAction, 148 boolean afterKeyguardGone) { 149 if (mShowing) { 150 if (!afterKeyguardGone) { 151 mBouncer.showWithDismissAction(r, cancelAction); 152 } else { 153 mBouncer.show(false /* resetSecuritySelection */); 154 mAfterKeyguardGoneAction = r; 155 } 156 } 157 updateStates(); 158 } 159 160 /** 161 * Reset the state of the view. 162 */ 163 public void reset() { 164 if (mShowing) { 165 if (mOccluded) { 166 mPhoneStatusBar.hideKeyguard(); 167 mPhoneStatusBar.stopWaitingForKeyguardExit(); 168 mBouncer.hide(false /* destroyView */); 169 } else { 170 showBouncerOrKeyguard(); 171 } 172 KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset(); 173 updateStates(); 174 } 175 } 176 177 public void onStartedGoingToSleep() { 178 mPhoneStatusBar.onStartedGoingToSleep(); 179 } 180 181 public void onFinishedGoingToSleep() { 182 mDeviceInteractive = false; 183 mPhoneStatusBar.onFinishedGoingToSleep(); 184 mBouncer.onScreenTurnedOff(); 185 } 186 187 public void onStartedWakingUp() { 188 Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp"); 189 mDeviceInteractive = true; 190 mDeviceWillWakeUp = false; 191 mPhoneStatusBar.onStartedWakingUp(); 192 Trace.endSection(); 193 } 194 195 public void onScreenTurningOn() { 196 Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn"); 197 mPhoneStatusBar.onScreenTurningOn(); 198 Trace.endSection(); 199 } 200 201 public boolean isScreenTurnedOn() { 202 return mScreenTurnedOn; 203 } 204 205 public void onScreenTurnedOn() { 206 Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn"); 207 mScreenTurnedOn = true; 208 if (mDeferScrimFadeOut) { 209 mDeferScrimFadeOut = false; 210 animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, 211 true /* skipFirstFrame */); 212 updateStates(); 213 } 214 mPhoneStatusBar.onScreenTurnedOn(); 215 Trace.endSection(); 216 } 217 218 @Override 219 public void onRemoteInputActive(boolean active) { 220 mRemoteInputActive = active; 221 updateStates(); 222 } 223 224 public void onScreenTurnedOff() { 225 mScreenTurnedOn = false; 226 mPhoneStatusBar.onScreenTurnedOff(); 227 } 228 229 public void notifyDeviceWakeUpRequested() { 230 mDeviceWillWakeUp = !mDeviceInteractive; 231 } 232 233 public void verifyUnlock() { 234 dismiss(); 235 } 236 237 public void setNeedsInput(boolean needsInput) { 238 mStatusBarWindowManager.setKeyguardNeedsInput(needsInput); 239 } 240 241 public boolean isUnlockWithWallpaper() { 242 return mStatusBarWindowManager.isShowingWallpaper(); 243 } 244 245 public void setOccluded(boolean occluded) { 246 if (occluded && !mOccluded && mShowing) { 247 if (mPhoneStatusBar.isInLaunchTransition()) { 248 mOccluded = true; 249 mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */, 250 new Runnable() { 251 @Override 252 public void run() { 253 mStatusBarWindowManager.setKeyguardOccluded(mOccluded); 254 reset(); 255 } 256 }); 257 return; 258 } 259 } 260 mOccluded = occluded; 261 mPhoneStatusBar.updateMediaMetaData(false, false); 262 mStatusBarWindowManager.setKeyguardOccluded(occluded); 263 reset(); 264 } 265 266 public boolean isOccluded() { 267 return mOccluded; 268 } 269 270 /** 271 * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the 272 * security view of the bouncer. 273 * 274 * @param finishRunnable the runnable to be run after the animation finished, or {@code null} if 275 * no action should be run 276 */ 277 public void startPreHideAnimation(Runnable finishRunnable) { 278 if (mBouncer.isShowing()) { 279 mBouncer.startPreHideAnimation(finishRunnable); 280 } else if (finishRunnable != null) { 281 finishRunnable.run(); 282 } 283 } 284 285 /** 286 * Hides the keyguard view 287 */ 288 public void hide(long startTime, long fadeoutDuration) { 289 mShowing = false; 290 291 if (!KeyguardUpdateMonitor.getInstance(mContext).isUserUnlocked()) { 292 fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED; 293 } 294 long uptimeMillis = SystemClock.uptimeMillis(); 295 long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis); 296 297 if (mPhoneStatusBar.isInLaunchTransition() ) { 298 mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() { 299 @Override 300 public void run() { 301 mStatusBarWindowManager.setKeyguardShowing(false); 302 mStatusBarWindowManager.setKeyguardFadingAway(true); 303 mBouncer.hide(true /* destroyView */); 304 updateStates(); 305 mScrimController.animateKeyguardFadingOut( 306 PhoneStatusBar.FADE_KEYGUARD_START_DELAY, 307 PhoneStatusBar.FADE_KEYGUARD_DURATION, null, 308 false /* skipFirstFrame */); 309 } 310 }, new Runnable() { 311 @Override 312 public void run() { 313 mPhoneStatusBar.hideKeyguard(); 314 mStatusBarWindowManager.setKeyguardFadingAway(false); 315 mViewMediatorCallback.keyguardGone(); 316 executeAfterKeyguardGoneAction(); 317 } 318 }); 319 } else { 320 if (mFingerprintUnlockController.getMode() 321 == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) { 322 mFingerprintUnlockController.startKeyguardFadingAway(); 323 mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240); 324 mStatusBarWindowManager.setKeyguardFadingAway(true); 325 mPhoneStatusBar.fadeKeyguardWhilePulsing(); 326 animateScrimControllerKeyguardFadingOut(0, 240, new Runnable() { 327 @Override 328 public void run() { 329 mPhoneStatusBar.hideKeyguard(); 330 } 331 }, false /* skipFirstFrame */); 332 } else { 333 mFingerprintUnlockController.startKeyguardFadingAway(); 334 mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration); 335 boolean staying = mPhoneStatusBar.hideKeyguard(); 336 if (!staying) { 337 mStatusBarWindowManager.setKeyguardFadingAway(true); 338 if (mFingerprintUnlockController.getMode() 339 == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) { 340 if (!mScreenTurnedOn) { 341 mDeferScrimFadeOut = true; 342 } else { 343 344 // Screen is already on, don't defer with fading out. 345 animateScrimControllerKeyguardFadingOut(0, 346 WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS, 347 true /* skipFirstFrame */); 348 } 349 } else { 350 animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration, 351 false /* skipFirstFrame */); 352 } 353 } else { 354 mScrimController.animateGoingToFullShade(delay, fadeoutDuration); 355 mPhoneStatusBar.finishKeyguardFadingAway(); 356 } 357 } 358 mStatusBarWindowManager.setKeyguardShowing(false); 359 mBouncer.hide(true /* destroyView */); 360 mViewMediatorCallback.keyguardGone(); 361 executeAfterKeyguardGoneAction(); 362 updateStates(); 363 } 364 } 365 366 public void onDensityOrFontScaleChanged() { 367 mBouncer.hide(true /* destroyView */); 368 } 369 370 private void animateScrimControllerKeyguardFadingOut(long delay, long duration, 371 boolean skipFirstFrame) { 372 animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */, 373 skipFirstFrame); 374 } 375 376 private void animateScrimControllerKeyguardFadingOut(long delay, long duration, 377 final Runnable endRunnable, boolean skipFirstFrame) { 378 Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0); 379 mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() { 380 @Override 381 public void run() { 382 if (endRunnable != null) { 383 endRunnable.run(); 384 } 385 mStatusBarWindowManager.setKeyguardFadingAway(false); 386 mPhoneStatusBar.finishKeyguardFadingAway(); 387 mFingerprintUnlockController.finishKeyguardFadingAway(); 388 WindowManagerGlobal.getInstance().trimMemory( 389 ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); 390 Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0); 391 } 392 }, skipFirstFrame); 393 } 394 395 private void executeAfterKeyguardGoneAction() { 396 if (mAfterKeyguardGoneAction != null) { 397 mAfterKeyguardGoneAction.onDismiss(); 398 mAfterKeyguardGoneAction = null; 399 } 400 } 401 402 /** 403 * Dismisses the keyguard by going to the next screen or making it gone. 404 */ 405 public void dismiss() { 406 if (mDeviceInteractive || mDeviceWillWakeUp) { 407 showBouncer(); 408 } 409 } 410 411 /** 412 * WARNING: This method might cause Binder calls. 413 */ 414 public boolean isSecure() { 415 return mBouncer.isSecure(); 416 } 417 418 /** 419 * @return Whether the keyguard is showing 420 */ 421 public boolean isShowing() { 422 return mShowing; 423 } 424 425 /** 426 * Notifies this manager that the back button has been pressed. 427 * 428 * @return whether the back press has been handled 429 */ 430 public boolean onBackPressed() { 431 if (mBouncer.isShowing()) { 432 mPhoneStatusBar.endAffordanceLaunch(); 433 reset(); 434 return true; 435 } 436 return false; 437 } 438 439 public boolean isBouncerShowing() { 440 return mBouncer.isShowing(); 441 } 442 443 private long getNavBarShowDelay() { 444 if (mPhoneStatusBar.isKeyguardFadingAway()) { 445 return mPhoneStatusBar.getKeyguardFadingAwayDelay(); 446 } else { 447 448 // Keyguard is not going away, thus we are showing the navigation bar because the 449 // bouncer is appearing. 450 return NAV_BAR_SHOW_DELAY_BOUNCER; 451 } 452 } 453 454 private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() { 455 @Override 456 public void run() { 457 mPhoneStatusBar.getNavigationBarView().setVisibility(View.VISIBLE); 458 } 459 }; 460 461 protected void updateStates() { 462 int vis = mContainer.getSystemUiVisibility(); 463 boolean showing = mShowing; 464 boolean occluded = mOccluded; 465 boolean bouncerShowing = mBouncer.isShowing(); 466 boolean bouncerDismissible = !mBouncer.isFullscreenBouncer(); 467 boolean remoteInputActive = mRemoteInputActive; 468 469 if ((bouncerDismissible || !showing || remoteInputActive) != 470 (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive) 471 || mFirstUpdate) { 472 if (bouncerDismissible || !showing || remoteInputActive) { 473 mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK); 474 } else { 475 mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK); 476 } 477 } 478 479 boolean navBarVisible = isNavBarVisible(); 480 boolean lastNavBarVisible = getLastNavBarVisible(); 481 if (navBarVisible != lastNavBarVisible || mFirstUpdate) { 482 if (mPhoneStatusBar.getNavigationBarView() != null) { 483 if (navBarVisible) { 484 long delay = getNavBarShowDelay(); 485 if (delay == 0) { 486 mMakeNavigationBarVisibleRunnable.run(); 487 } else { 488 mContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable, 489 delay); 490 } 491 } else { 492 mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable); 493 mPhoneStatusBar.getNavigationBarView().setVisibility(View.GONE); 494 } 495 } 496 } 497 498 if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { 499 mStatusBarWindowManager.setBouncerShowing(bouncerShowing); 500 mPhoneStatusBar.setBouncerShowing(bouncerShowing); 501 mScrimController.setBouncerShowing(bouncerShowing); 502 } 503 504 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 505 if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) { 506 updateMonitor.onKeyguardVisibilityChanged(showing && !occluded); 507 } 508 if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) { 509 updateMonitor.sendKeyguardBouncerChanged(bouncerShowing); 510 } 511 512 mFirstUpdate = false; 513 mLastShowing = showing; 514 mLastOccluded = occluded; 515 mLastBouncerShowing = bouncerShowing; 516 mLastBouncerDismissible = bouncerDismissible; 517 mLastRemoteInputActive = remoteInputActive; 518 519 mPhoneStatusBar.onKeyguardViewManagerStatesUpdated(); 520 } 521 522 /** 523 * @return Whether the navigation bar should be made visible based on the current state. 524 */ 525 protected boolean isNavBarVisible() { 526 return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive; 527 } 528 529 /** 530 * @return Whether the navigation bar was made visible based on the last known state. 531 */ 532 protected boolean getLastNavBarVisible() { 533 return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive; 534 } 535 536 public boolean shouldDismissOnMenuPressed() { 537 return mBouncer.shouldDismissOnMenuPressed(); 538 } 539 540 public boolean interceptMediaKey(KeyEvent event) { 541 return mBouncer.interceptMediaKey(event); 542 } 543 544 public void onActivityDrawn() { 545 if (mPhoneStatusBar.isCollapsing()) { 546 mPhoneStatusBar.addPostCollapseAction(new Runnable() { 547 @Override 548 public void run() { 549 mViewMediatorCallback.readyForKeyguardDone(); 550 } 551 }); 552 } else { 553 mViewMediatorCallback.readyForKeyguardDone(); 554 } 555 } 556 557 public boolean shouldDisableWindowAnimationsForUnlock() { 558 return mPhoneStatusBar.isInLaunchTransition(); 559 } 560 561 public boolean isGoingToNotificationShade() { 562 return mPhoneStatusBar.isGoingToNotificationShade(); 563 } 564 565 public boolean isSecure(int userId) { 566 return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId); 567 } 568 569 public boolean isInputRestricted() { 570 return mViewMediatorCallback.isInputRestricted(); 571 } 572 573 public void keyguardGoingAway() { 574 mPhoneStatusBar.keyguardGoingAway(); 575 } 576 577 public void animateCollapsePanels(float speedUpFactor) { 578 mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */, 579 false /* delayed */, speedUpFactor); 580 } 581 582 /** 583 * Notifies that the user has authenticated by other means than using the bouncer, for example, 584 * fingerprint. 585 */ 586 public void notifyKeyguardAuthenticated(boolean strongAuth) { 587 mBouncer.notifyKeyguardAuthenticated(strongAuth); 588 } 589 590 public void showBouncerMessage(String message, int color) { 591 mBouncer.showMessage(message, color); 592 } 593 594 public ViewRootImpl getViewRootImpl() { 595 return mPhoneStatusBar.getStatusBarView().getViewRootImpl(); 596 } 597 } 598