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