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 static com.android.keyguard.KeyguardSecurityModel.SecurityMode; 20 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction; 21 22 import android.content.Context; 23 import android.content.res.ColorStateList; 24 import android.os.Handler; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.util.Log; 28 import android.util.MathUtils; 29 import android.util.Slog; 30 import android.util.StatsLog; 31 import android.view.KeyEvent; 32 import android.view.LayoutInflater; 33 import android.view.View; 34 import android.view.ViewGroup; 35 import android.view.ViewTreeObserver; 36 import android.view.WindowInsets; 37 38 import com.android.internal.widget.LockPatternUtils; 39 import com.android.keyguard.KeyguardHostView; 40 import com.android.keyguard.KeyguardSecurityView; 41 import com.android.keyguard.KeyguardUpdateMonitor; 42 import com.android.keyguard.KeyguardUpdateMonitorCallback; 43 import com.android.keyguard.ViewMediatorCallback; 44 import com.android.systemui.DejankUtils; 45 import com.android.systemui.R; 46 import com.android.systemui.keyguard.DismissCallbackRegistry; 47 import com.android.systemui.plugins.FalsingManager; 48 49 import java.io.PrintWriter; 50 51 /** 52 * A class which manages the bouncer on the lockscreen. 53 */ 54 public class KeyguardBouncer { 55 56 private static final String TAG = "KeyguardBouncer"; 57 static final long BOUNCER_FACE_DELAY = 1200; 58 static final float ALPHA_EXPANSION_THRESHOLD = 0.95f; 59 static final float EXPANSION_HIDDEN = 1f; 60 static final float EXPANSION_VISIBLE = 0f; 61 62 protected final Context mContext; 63 protected final ViewMediatorCallback mCallback; 64 protected final LockPatternUtils mLockPatternUtils; 65 protected final ViewGroup mContainer; 66 private final FalsingManager mFalsingManager; 67 private final DismissCallbackRegistry mDismissCallbackRegistry; 68 private final Handler mHandler; 69 private final BouncerExpansionCallback mExpansionCallback; 70 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 71 private final UnlockMethodCache mUnlockMethodCache; 72 private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback = 73 new KeyguardUpdateMonitorCallback() { 74 @Override 75 public void onStrongAuthStateChanged(int userId) { 76 mBouncerPromptReason = mCallback.getBouncerPromptReason(); 77 } 78 }; 79 private final Runnable mRemoveViewRunnable = this::removeView; 80 private final KeyguardBypassController mKeyguardBypassController; 81 protected KeyguardHostView mKeyguardView; 82 private final Runnable mResetRunnable = ()-> { 83 if (mKeyguardView != null) { 84 mKeyguardView.resetSecurityContainer(); 85 } 86 }; 87 88 private int mStatusBarHeight; 89 private float mExpansion = EXPANSION_HIDDEN; 90 protected ViewGroup mRoot; 91 private boolean mShowingSoon; 92 private int mBouncerPromptReason; 93 private boolean mIsAnimatingAway; 94 private boolean mIsScrimmed; 95 private ViewGroup mLockIconContainer; 96 KeyguardBouncer(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils, ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager, BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache, KeyguardUpdateMonitor keyguardUpdateMonitor, KeyguardBypassController keyguardBypassController, Handler handler)97 public KeyguardBouncer(Context context, ViewMediatorCallback callback, 98 LockPatternUtils lockPatternUtils, ViewGroup container, 99 DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager, 100 BouncerExpansionCallback expansionCallback, UnlockMethodCache unlockMethodCache, 101 KeyguardUpdateMonitor keyguardUpdateMonitor, 102 KeyguardBypassController keyguardBypassController, Handler handler) { 103 mContext = context; 104 mCallback = callback; 105 mLockPatternUtils = lockPatternUtils; 106 mContainer = container; 107 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 108 mFalsingManager = falsingManager; 109 mDismissCallbackRegistry = dismissCallbackRegistry; 110 mExpansionCallback = expansionCallback; 111 mHandler = handler; 112 mUnlockMethodCache = unlockMethodCache; 113 mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback); 114 mKeyguardBypassController = keyguardBypassController; 115 } 116 show(boolean resetSecuritySelection)117 public void show(boolean resetSecuritySelection) { 118 show(resetSecuritySelection, true /* scrimmed */); 119 } 120 121 /** 122 * Shows the bouncer. 123 * 124 * @param resetSecuritySelection Cleans keyguard view 125 * @param isScrimmed true when the bouncer show show scrimmed, false when the user will be 126 * dragging it and translation should be deferred. 127 */ show(boolean resetSecuritySelection, boolean isScrimmed)128 public void show(boolean resetSecuritySelection, boolean isScrimmed) { 129 final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser(); 130 if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) { 131 // In split system user mode, we never unlock system user. 132 return; 133 } 134 ensureView(); 135 mIsScrimmed = isScrimmed; 136 137 // On the keyguard, we want to show the bouncer when the user drags up, but it's 138 // not correct to end the falsing session. We still need to verify if those touches 139 // are valid. 140 // Later, at the end of the animation, when the bouncer is at the top of the screen, 141 // onFullyShown() will be called and FalsingManager will stop recording touches. 142 if (isScrimmed) { 143 setExpansion(EXPANSION_VISIBLE); 144 } 145 146 if (resetSecuritySelection) { 147 // showPrimarySecurityScreen() updates the current security method. This is needed in 148 // case we are already showing and the current security method changed. 149 showPrimarySecurityScreen(); 150 } 151 152 if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) { 153 return; 154 } 155 156 final int activeUserId = KeyguardUpdateMonitor.getCurrentUser(); 157 final boolean isSystemUser = 158 UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM; 159 final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId; 160 161 // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) is 162 // set, this will dismiss the whole Keyguard. Otherwise, show the bouncer. 163 if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) { 164 return; 165 } 166 167 // This condition may indicate an error on Android, so log it. 168 if (!allowDismissKeyguard) { 169 Slog.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " + keyguardUserId); 170 } 171 172 mShowingSoon = true; 173 174 // Split up the work over multiple frames. 175 DejankUtils.removeCallbacks(mResetRunnable); 176 if (mUnlockMethodCache.isFaceAuthEnabled() && !needsFullscreenBouncer() 177 && !mKeyguardUpdateMonitor.userNeedsStrongAuth() 178 && !mKeyguardBypassController.getBypassEnabled()) { 179 mHandler.postDelayed(mShowRunnable, BOUNCER_FACE_DELAY); 180 } else { 181 DejankUtils.postAfterTraversal(mShowRunnable); 182 } 183 184 mCallback.onBouncerVisiblityChanged(true /* shown */); 185 mExpansionCallback.onStartingToShow(); 186 } 187 isScrimmed()188 public boolean isScrimmed() { 189 return mIsScrimmed; 190 } 191 getLockIconContainer()192 public ViewGroup getLockIconContainer() { 193 return mRoot == null || mRoot.getVisibility() != View.VISIBLE ? null : mLockIconContainer; 194 } 195 196 /** 197 * This method must be called at the end of the bouncer animation when 198 * the translation is performed manually by the user, otherwise FalsingManager 199 * will never be notified and its internal state will be out of sync. 200 */ onFullyShown()201 private void onFullyShown() { 202 mFalsingManager.onBouncerShown(); 203 if (mKeyguardView == null) { 204 Log.wtf(TAG, "onFullyShown when view was null"); 205 } else { 206 mKeyguardView.onResume(); 207 } 208 } 209 210 /** 211 * @see #onFullyShown() 212 */ onFullyHidden()213 private void onFullyHidden() { 214 cancelShowRunnable(); 215 if (mRoot != null) { 216 mRoot.setVisibility(View.INVISIBLE); 217 } 218 mFalsingManager.onBouncerHidden(); 219 DejankUtils.postAfterTraversal(mResetRunnable); 220 } 221 222 private final Runnable mShowRunnable = new Runnable() { 223 @Override 224 public void run() { 225 mRoot.setVisibility(View.VISIBLE); 226 showPromptReason(mBouncerPromptReason); 227 final CharSequence customMessage = mCallback.consumeCustomMessage(); 228 if (customMessage != null) { 229 mKeyguardView.showErrorMessage(customMessage); 230 } 231 // We might still be collapsed and the view didn't have time to layout yet or still 232 // be small, let's wait on the predraw to do the animation in that case. 233 if (mKeyguardView.getHeight() != 0 && mKeyguardView.getHeight() != mStatusBarHeight) { 234 mKeyguardView.startAppearAnimation(); 235 } else { 236 mKeyguardView.getViewTreeObserver().addOnPreDrawListener( 237 new ViewTreeObserver.OnPreDrawListener() { 238 @Override 239 public boolean onPreDraw() { 240 mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this); 241 mKeyguardView.startAppearAnimation(); 242 return true; 243 } 244 }); 245 mKeyguardView.requestLayout(); 246 } 247 mShowingSoon = false; 248 if (mExpansion == EXPANSION_VISIBLE) { 249 mKeyguardView.onResume(); 250 mKeyguardView.resetSecurityContainer(); 251 } 252 StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, 253 StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__SHOWN); 254 } 255 }; 256 257 /** 258 * Show a string explaining why the security view needs to be solved. 259 * 260 * @param reason a flag indicating which string should be shown, see 261 * {@link KeyguardSecurityView#PROMPT_REASON_NONE} 262 * and {@link KeyguardSecurityView#PROMPT_REASON_RESTART} 263 */ showPromptReason(int reason)264 public void showPromptReason(int reason) { 265 if (mKeyguardView != null) { 266 mKeyguardView.showPromptReason(reason); 267 } else { 268 Log.w(TAG, "Trying to show prompt reason on empty bouncer"); 269 } 270 } 271 showMessage(String message, ColorStateList colorState)272 public void showMessage(String message, ColorStateList colorState) { 273 if (mKeyguardView != null) { 274 mKeyguardView.showMessage(message, colorState); 275 } else { 276 Log.w(TAG, "Trying to show message on empty bouncer"); 277 } 278 } 279 cancelShowRunnable()280 private void cancelShowRunnable() { 281 DejankUtils.removeCallbacks(mShowRunnable); 282 mHandler.removeCallbacks(mShowRunnable); 283 mShowingSoon = false; 284 } 285 showWithDismissAction(OnDismissAction r, Runnable cancelAction)286 public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) { 287 ensureView(); 288 mKeyguardView.setOnDismissAction(r, cancelAction); 289 show(false /* resetSecuritySelection */); 290 } 291 hide(boolean destroyView)292 public void hide(boolean destroyView) { 293 if (isShowing()) { 294 StatsLog.write(StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED, 295 StatsLog.KEYGUARD_BOUNCER_STATE_CHANGED__STATE__HIDDEN); 296 mDismissCallbackRegistry.notifyDismissCancelled(); 297 } 298 mIsScrimmed = false; 299 mFalsingManager.onBouncerHidden(); 300 mCallback.onBouncerVisiblityChanged(false /* shown */); 301 cancelShowRunnable(); 302 if (mKeyguardView != null) { 303 mKeyguardView.cancelDismissAction(); 304 mKeyguardView.cleanUp(); 305 } 306 mIsAnimatingAway = false; 307 if (mRoot != null) { 308 mRoot.setVisibility(View.INVISIBLE); 309 if (destroyView) { 310 311 // We have a ViewFlipper that unregisters a broadcast when being detached, which may 312 // be slow because of AM lock contention during unlocking. We can delay it a bit. 313 mHandler.postDelayed(mRemoveViewRunnable, 50); 314 } 315 } 316 } 317 318 /** 319 * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}. 320 */ startPreHideAnimation(Runnable runnable)321 public void startPreHideAnimation(Runnable runnable) { 322 mIsAnimatingAway = true; 323 if (mKeyguardView != null) { 324 mKeyguardView.startDisappearAnimation(runnable); 325 } else if (runnable != null) { 326 runnable.run(); 327 } 328 } 329 330 /** 331 * Reset the state of the view. 332 */ reset()333 public void reset() { 334 cancelShowRunnable(); 335 inflateView(); 336 mFalsingManager.onBouncerHidden(); 337 } 338 onScreenTurnedOff()339 public void onScreenTurnedOff() { 340 if (mKeyguardView != null && mRoot != null && mRoot.getVisibility() == View.VISIBLE) { 341 mKeyguardView.onPause(); 342 } 343 } 344 isShowing()345 public boolean isShowing() { 346 return (mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE)) 347 && mExpansion == EXPANSION_VISIBLE && !isAnimatingAway(); 348 } 349 350 /** 351 * {@link #show(boolean)} was called but we're not showing yet, or being dragged. 352 */ inTransit()353 public boolean inTransit() { 354 return mShowingSoon || mExpansion != EXPANSION_HIDDEN && mExpansion != EXPANSION_VISIBLE; 355 } 356 357 /** 358 * @return {@code true} when bouncer's pre-hide animation already started but isn't completely 359 * hidden yet, {@code false} otherwise. 360 */ isAnimatingAway()361 public boolean isAnimatingAway() { 362 return mIsAnimatingAway; 363 } 364 prepare()365 public void prepare() { 366 boolean wasInitialized = mRoot != null; 367 ensureView(); 368 if (wasInitialized) { 369 showPrimarySecurityScreen(); 370 } 371 mBouncerPromptReason = mCallback.getBouncerPromptReason(); 372 } 373 showPrimarySecurityScreen()374 private void showPrimarySecurityScreen() { 375 mKeyguardView.showPrimarySecurityScreen(); 376 KeyguardSecurityView keyguardSecurityView = mKeyguardView.getCurrentSecurityView(); 377 if (keyguardSecurityView != null) { 378 mLockIconContainer = ((ViewGroup) keyguardSecurityView) 379 .findViewById(R.id.lock_icon_container); 380 } 381 } 382 383 /** 384 * Current notification panel expansion 385 * @param fraction 0 when notification panel is collapsed and 1 when expanded. 386 * @see StatusBarKeyguardViewManager#onPanelExpansionChanged 387 */ setExpansion(float fraction)388 public void setExpansion(float fraction) { 389 float oldExpansion = mExpansion; 390 mExpansion = fraction; 391 if (mKeyguardView != null && !mIsAnimatingAway) { 392 float alpha = MathUtils.map(ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction); 393 mKeyguardView.setAlpha(MathUtils.constrain(alpha, 0f, 1f)); 394 mKeyguardView.setTranslationY(fraction * mKeyguardView.getHeight()); 395 } 396 397 if (fraction == EXPANSION_VISIBLE && oldExpansion != EXPANSION_VISIBLE) { 398 onFullyShown(); 399 mExpansionCallback.onFullyShown(); 400 } else if (fraction == EXPANSION_HIDDEN && oldExpansion != EXPANSION_HIDDEN) { 401 onFullyHidden(); 402 mExpansionCallback.onFullyHidden(); 403 } else if (fraction != EXPANSION_VISIBLE && oldExpansion == EXPANSION_VISIBLE) { 404 mExpansionCallback.onStartingToHide(); 405 } 406 } 407 willDismissWithAction()408 public boolean willDismissWithAction() { 409 return mKeyguardView != null && mKeyguardView.hasDismissActions(); 410 } 411 getTop()412 public int getTop() { 413 if (mKeyguardView == null) { 414 return 0; 415 } 416 417 int top = mKeyguardView.getTop(); 418 // The password view has an extra top padding that should be ignored. 419 if (mKeyguardView.getCurrentSecurityMode() == SecurityMode.Password) { 420 View messageArea = mKeyguardView.findViewById(R.id.keyguard_message_area); 421 top += messageArea.getTop(); 422 } 423 return top; 424 } 425 ensureView()426 protected void ensureView() { 427 // Removal of the view might be deferred to reduce unlock latency, 428 // in this case we need to force the removal, otherwise we'll 429 // end up in an unpredictable state. 430 boolean forceRemoval = mHandler.hasCallbacks(mRemoveViewRunnable); 431 if (mRoot == null || forceRemoval) { 432 inflateView(); 433 } 434 } 435 inflateView()436 protected void inflateView() { 437 removeView(); 438 mHandler.removeCallbacks(mRemoveViewRunnable); 439 mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null); 440 mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view); 441 mKeyguardView.setLockPatternUtils(mLockPatternUtils); 442 mKeyguardView.setViewMediatorCallback(mCallback); 443 mContainer.addView(mRoot, mContainer.getChildCount()); 444 mStatusBarHeight = mRoot.getResources().getDimensionPixelOffset( 445 com.android.systemui.R.dimen.status_bar_height); 446 mRoot.setVisibility(View.INVISIBLE); 447 mRoot.setAccessibilityPaneTitle(mKeyguardView.getAccessibilityTitleForCurrentMode()); 448 449 final WindowInsets rootInsets = mRoot.getRootWindowInsets(); 450 if (rootInsets != null) { 451 mRoot.dispatchApplyWindowInsets(rootInsets); 452 } 453 } 454 removeView()455 protected void removeView() { 456 if (mRoot != null && mRoot.getParent() == mContainer) { 457 mContainer.removeView(mRoot); 458 mRoot = null; 459 } 460 } 461 onBackPressed()462 public boolean onBackPressed() { 463 return mKeyguardView != null && mKeyguardView.handleBackKey(); 464 } 465 466 /** 467 * @return True if and only if the security method should be shown before showing the 468 * notifications on Keyguard, like SIM PIN/PUK. 469 */ needsFullscreenBouncer()470 public boolean needsFullscreenBouncer() { 471 ensureView(); 472 if (mKeyguardView != null) { 473 SecurityMode mode = mKeyguardView.getSecurityMode(); 474 return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk; 475 } 476 return false; 477 } 478 479 /** 480 * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which 481 * makes this method much faster. 482 */ isFullscreenBouncer()483 public boolean isFullscreenBouncer() { 484 if (mKeyguardView != null) { 485 SecurityMode mode = mKeyguardView.getCurrentSecurityMode(); 486 return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk; 487 } 488 return false; 489 } 490 491 /** 492 * WARNING: This method might cause Binder calls. 493 */ isSecure()494 public boolean isSecure() { 495 return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None; 496 } 497 shouldDismissOnMenuPressed()498 public boolean shouldDismissOnMenuPressed() { 499 return mKeyguardView.shouldEnableMenuKey(); 500 } 501 interceptMediaKey(KeyEvent event)502 public boolean interceptMediaKey(KeyEvent event) { 503 ensureView(); 504 return mKeyguardView.interceptMediaKey(event); 505 } 506 notifyKeyguardAuthenticated(boolean strongAuth)507 public void notifyKeyguardAuthenticated(boolean strongAuth) { 508 ensureView(); 509 mKeyguardView.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser()); 510 } 511 dump(PrintWriter pw)512 public void dump(PrintWriter pw) { 513 pw.println("KeyguardBouncer"); 514 pw.println(" isShowing(): " + isShowing()); 515 pw.println(" mStatusBarHeight: " + mStatusBarHeight); 516 pw.println(" mExpansion: " + mExpansion); 517 pw.println(" mKeyguardView; " + mKeyguardView); 518 pw.println(" mShowingSoon: " + mKeyguardView); 519 pw.println(" mBouncerPromptReason: " + mBouncerPromptReason); 520 pw.println(" mIsAnimatingAway: " + mIsAnimatingAway); 521 } 522 523 public interface BouncerExpansionCallback { onFullyShown()524 void onFullyShown(); onStartingToHide()525 void onStartingToHide(); onStartingToShow()526 void onStartingToShow(); onFullyHidden()527 void onFullyHidden(); 528 } 529 } 530