1 /* 2 * Copyright (C) 2020 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.car.keyguard; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.content.Context; 22 import android.content.res.Resources; 23 import android.graphics.PixelFormat; 24 import android.os.Bundle; 25 import android.os.UserHandle; 26 import android.util.Log; 27 import android.view.Gravity; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.view.ViewRootImpl; 31 import android.view.WindowManager; 32 33 import androidx.annotation.MainThread; 34 35 import com.android.car.ui.FocusParkingView; 36 import com.android.internal.widget.LockPatternView; 37 import com.android.keyguard.KeyguardSecurityModel; 38 import com.android.keyguard.KeyguardUpdateMonitor; 39 import com.android.keyguard.KeyguardViewController; 40 import com.android.keyguard.ViewMediatorCallback; 41 import com.android.keyguard.dagger.KeyguardBouncerComponent; 42 import com.android.systemui.R; 43 import com.android.systemui.car.systembar.CarSystemBarController; 44 import com.android.systemui.car.window.OverlayViewController; 45 import com.android.systemui.car.window.OverlayViewGlobalStateController; 46 import com.android.systemui.car.window.SystemUIOverlayWindowController; 47 import com.android.systemui.dagger.SysUISingleton; 48 import com.android.systemui.dagger.qualifiers.Main; 49 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor; 50 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerCallbackInteractor.PrimaryBouncerExpansionCallback; 51 import com.android.systemui.keyguard.domain.interactor.PrimaryBouncerInteractor; 52 import com.android.systemui.keyguard.ui.binder.KeyguardBouncerViewBinder; 53 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBouncerViewModel; 54 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel; 55 import com.android.systemui.shade.NotificationPanelViewController; 56 import com.android.systemui.shade.ShadeExpansionStateManager; 57 import com.android.systemui.statusbar.phone.BiometricUnlockController; 58 import com.android.systemui.statusbar.phone.CentralSurfaces; 59 import com.android.systemui.statusbar.phone.KeyguardBypassController; 60 import com.android.systemui.statusbar.policy.KeyguardStateController; 61 import com.android.systemui.toast.SystemUIToast; 62 import com.android.systemui.toast.ToastFactory; 63 import com.android.systemui.util.concurrency.DelayableExecutor; 64 65 import javax.inject.Inject; 66 67 import dagger.Lazy; 68 69 /** 70 * Automotive implementation of the {@link KeyguardViewController}. It controls the Keyguard View 71 * that is mounted to the SystemUIOverlayWindow. 72 */ 73 @SysUISingleton 74 public class CarKeyguardViewController extends OverlayViewController implements 75 KeyguardViewController { 76 private static final String TAG = "CarKeyguardViewController"; 77 private static final boolean DEBUG = true; 78 private static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f; 79 private static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f; 80 81 private final Context mContext; 82 private final DelayableExecutor mMainExecutor; 83 private final WindowManager mWindowManager; 84 private final ToastFactory mToastFactory; 85 private final FocusParkingView mFocusParkingView; 86 private final KeyguardStateController mKeyguardStateController; 87 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 88 private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy; 89 private final ViewMediatorCallback mViewMediatorCallback; 90 private final CarSystemBarController mCarSystemBarController; 91 private final PrimaryBouncerInteractor mPrimaryBouncerInteractor; 92 private final KeyguardSecurityModel mKeyguardSecurityModel; 93 private final KeyguardBouncerViewModel mKeyguardBouncerViewModel; 94 private final KeyguardBouncerComponent.Factory mKeyguardBouncerComponentFactory; 95 private final PrimaryBouncerExpansionCallback mExpansionCallback = 96 new PrimaryBouncerExpansionCallback() { 97 @Override 98 public void onFullyShown() { 99 LockPatternView patternView = getLayout().findViewById(R.id.lockPatternView); 100 if (patternView != null) { 101 patternView.setOnFocusChangeListener(new View.OnFocusChangeListener() { 102 @Override 103 public void onFocusChange(View v, boolean hasFocus) { 104 if (hasFocus) { 105 makeOverlayToast(R.string.lockpattern_does_not_support_rotary); 106 } 107 } 108 }); 109 } 110 } 111 112 @Override 113 public void onStartingToHide() { 114 } 115 116 @Override 117 public void onStartingToShow() { 118 } 119 120 @Override 121 public void onFullyHidden() { 122 } 123 124 @Override 125 public void onVisibilityChanged(boolean isVisible) { 126 } 127 128 @Override 129 public void onExpansionChanged(float bouncerHideAmount) { 130 } 131 }; 132 133 private OnKeyguardCancelClickedListener mKeyguardCancelClickedListener; 134 private boolean mShowing; 135 private boolean mIsOccluded; 136 private boolean mIsSleeping; 137 private int mToastShowDurationMillisecond; 138 private ViewGroup mKeyguardContainer; 139 private PrimaryBouncerToGoneTransitionViewModel mPrimaryBouncerToGoneTransitionViewModel; 140 141 @Inject CarKeyguardViewController( Context context, @Main DelayableExecutor mainExecutor, WindowManager windowManager, ToastFactory toastFactory, SystemUIOverlayWindowController systemUIOverlayWindowController, OverlayViewGlobalStateController overlayViewGlobalStateController, KeyguardStateController keyguardStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, Lazy<BiometricUnlockController> biometricUnlockControllerLazy, ViewMediatorCallback viewMediatorCallback, CarSystemBarController carSystemBarController, PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, PrimaryBouncerInteractor primaryBouncerInteractor, KeyguardSecurityModel keyguardSecurityModel, KeyguardBouncerViewModel keyguardBouncerViewModel, PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory)142 public CarKeyguardViewController( 143 Context context, 144 @Main DelayableExecutor mainExecutor, 145 WindowManager windowManager, 146 ToastFactory toastFactory, 147 SystemUIOverlayWindowController systemUIOverlayWindowController, 148 OverlayViewGlobalStateController overlayViewGlobalStateController, 149 KeyguardStateController keyguardStateController, 150 KeyguardUpdateMonitor keyguardUpdateMonitor, 151 Lazy<BiometricUnlockController> biometricUnlockControllerLazy, 152 ViewMediatorCallback viewMediatorCallback, 153 CarSystemBarController carSystemBarController, 154 PrimaryBouncerCallbackInteractor primaryBouncerCallbackInteractor, 155 PrimaryBouncerInteractor primaryBouncerInteractor, 156 KeyguardSecurityModel keyguardSecurityModel, 157 KeyguardBouncerViewModel keyguardBouncerViewModel, 158 PrimaryBouncerToGoneTransitionViewModel primaryBouncerToGoneTransitionViewModel, 159 KeyguardBouncerComponent.Factory keyguardBouncerComponentFactory) { 160 161 super(R.id.keyguard_stub, overlayViewGlobalStateController); 162 163 mContext = context; 164 mMainExecutor = mainExecutor; 165 mWindowManager = windowManager; 166 mToastFactory = toastFactory; 167 mFocusParkingView = systemUIOverlayWindowController.getBaseLayout().findViewById( 168 R.id.focus_parking_view); 169 mKeyguardStateController = keyguardStateController; 170 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 171 mBiometricUnlockControllerLazy = biometricUnlockControllerLazy; 172 mViewMediatorCallback = viewMediatorCallback; 173 mCarSystemBarController = carSystemBarController; 174 mPrimaryBouncerInteractor = primaryBouncerInteractor; 175 mKeyguardSecurityModel = keyguardSecurityModel; 176 mKeyguardBouncerViewModel = keyguardBouncerViewModel; 177 mKeyguardBouncerComponentFactory = keyguardBouncerComponentFactory; 178 mPrimaryBouncerToGoneTransitionViewModel = primaryBouncerToGoneTransitionViewModel; 179 180 mToastShowDurationMillisecond = mContext.getResources().getInteger( 181 R.integer.car_keyguard_toast_show_duration_millisecond); 182 primaryBouncerCallbackInteractor.addBouncerExpansionCallback(mExpansionCallback); 183 } 184 185 @Override getFocusAreaViewId()186 protected int getFocusAreaViewId() { 187 return R.id.keyguard_container; 188 } 189 190 @Override shouldShowNavigationBarInsets()191 protected boolean shouldShowNavigationBarInsets() { 192 return true; 193 } 194 195 @Override onFinishInflate()196 public void onFinishInflate() { 197 mKeyguardContainer = getLayout().findViewById(R.id.keyguard_container); 198 KeyguardBouncerViewBinder.bind(mKeyguardContainer, 199 mKeyguardBouncerViewModel, mPrimaryBouncerToGoneTransitionViewModel, 200 mKeyguardBouncerComponentFactory); 201 mBiometricUnlockControllerLazy.get().setKeyguardViewController(this); 202 } 203 204 @Override 205 @MainThread notifyKeyguardAuthenticated(boolean strongAuth)206 public void notifyKeyguardAuthenticated(boolean strongAuth) { 207 mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth); 208 } 209 210 @Override 211 @MainThread showPrimaryBouncer(boolean scrimmed)212 public void showPrimaryBouncer(boolean scrimmed) { 213 if (mShowing && !mPrimaryBouncerInteractor.isFullyShowing()) { 214 mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); 215 } 216 } 217 218 @Override 219 @MainThread show(Bundle options)220 public void show(Bundle options) { 221 if (mShowing) return; 222 223 mShowing = true; 224 mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false); 225 mCarSystemBarController.showAllKeyguardButtons(/* isSetUp= */ true); 226 start(); 227 reset(/* hideBouncerWhenShowing= */ false); 228 notifyKeyguardUpdateMonitor(); 229 } 230 231 @Override 232 @MainThread hide(long startTime, long fadeoutDuration)233 public void hide(long startTime, long fadeoutDuration) { 234 if (!mShowing || mIsSleeping) return; 235 236 mViewMediatorCallback.readyForKeyguardDone(); 237 mShowing = false; 238 mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false); 239 mPrimaryBouncerInteractor.hide(); 240 mCarSystemBarController.showAllNavigationButtons(/* isSetUp= */ true); 241 stop(); 242 mKeyguardStateController.notifyKeyguardDoneFading(); 243 mMainExecutor.execute(mViewMediatorCallback::keyguardGone); 244 notifyKeyguardUpdateMonitor(); 245 } 246 247 @Override reset(boolean hideBouncerWhenShowing)248 public void reset(boolean hideBouncerWhenShowing) { 249 if (mIsSleeping) return; 250 251 mMainExecutor.execute(() -> { 252 if (mShowing) { 253 if (!isSecure()) { 254 dismissAndCollapse(); 255 } 256 resetBouncer(); 257 mKeyguardUpdateMonitor.sendKeyguardReset(); 258 notifyKeyguardUpdateMonitor(); 259 } else { 260 // This is necessary in order to address an inconsistency between the keyguard 261 // service and the keyguard views. 262 // TODO: Investigate the source of the inconsistency. 263 show(/* options= */ null); 264 } 265 }); 266 } 267 268 @Override hideAlternateBouncer(boolean forceUpdateScrim)269 public void hideAlternateBouncer(boolean forceUpdateScrim) { 270 // no-op 271 } 272 273 @Override 274 @MainThread onFinishedGoingToSleep()275 public void onFinishedGoingToSleep() { 276 mPrimaryBouncerInteractor.hide(); 277 } 278 279 @Override 280 @MainThread setOccluded(boolean occluded, boolean animate)281 public void setOccluded(boolean occluded, boolean animate) { 282 mIsOccluded = occluded; 283 getOverlayViewGlobalStateController().setOccluded(occluded); 284 if (occluded) { 285 mCarSystemBarController.showAllOcclusionButtons(/* isSetup= */ true); 286 } else { 287 if (mShowing && isSecure()) { 288 mCarSystemBarController.showAllKeyguardButtons(/* isSetup= */ true); 289 } else { 290 mCarSystemBarController.showAllNavigationButtons(/* isSetUp= */ true); 291 } 292 } 293 } 294 295 @Override 296 @MainThread onCancelClicked()297 public void onCancelClicked() { 298 getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false); 299 mPrimaryBouncerInteractor.hide(); 300 mKeyguardCancelClickedListener.onCancelClicked(); 301 } 302 303 @Override 304 @MainThread dismissAndCollapse()305 public void dismissAndCollapse() { 306 // If dismissing and collapsing Keyguard is requested (e.g. by a Keyguard-dismissing 307 // Activity) while Keyguard is occluded, unocclude Keyguard so the user can authenticate to 308 // dismiss Keyguard. 309 if (mIsOccluded) { 310 setOccluded(/* occluded= */ false, /* animate= */ false); 311 } 312 if (!isSecure()) { 313 hide(/* startTime= */ 0, /* fadeoutDuration= */ 0); 314 } 315 } 316 317 @Override 318 @MainThread startPreHideAnimation(Runnable finishRunnable)319 public void startPreHideAnimation(Runnable finishRunnable) { 320 if (isBouncerShowing()) { 321 mPrimaryBouncerInteractor.startDisappearAnimation(finishRunnable); 322 } else if (finishRunnable != null) { 323 finishRunnable.run(); 324 } 325 } 326 327 @Override setNeedsInput(boolean needsInput)328 public void setNeedsInput(boolean needsInput) { 329 getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput); 330 } 331 332 @Override onStartedGoingToSleep()333 public void onStartedGoingToSleep() { 334 mIsSleeping = true; 335 } 336 337 @Override onStartedWakingUp()338 public void onStartedWakingUp() { 339 mIsSleeping = false; 340 reset(/* hideBouncerWhenShowing= */ false); 341 } 342 343 /** 344 * Add listener for keyguard cancel clicked. 345 */ registerOnKeyguardCancelClickedListener( OnKeyguardCancelClickedListener keyguardCancelClickedListener)346 public void registerOnKeyguardCancelClickedListener( 347 OnKeyguardCancelClickedListener keyguardCancelClickedListener) { 348 mKeyguardCancelClickedListener = keyguardCancelClickedListener; 349 } 350 351 /** 352 * Remove listener for keyguard cancel clicked. 353 */ unregisterOnKeyguardCancelClickedListener( OnKeyguardCancelClickedListener keyguardCancelClickedListener)354 public void unregisterOnKeyguardCancelClickedListener( 355 OnKeyguardCancelClickedListener keyguardCancelClickedListener) { 356 mKeyguardCancelClickedListener = null; 357 } 358 359 @Override getViewRootImpl()360 public ViewRootImpl getViewRootImpl() { 361 return ((View) getLayout().getParent()).getViewRootImpl(); 362 } 363 364 @Override 365 @MainThread isBouncerShowing()366 public boolean isBouncerShowing() { 367 return mPrimaryBouncerInteractor.isFullyShowing(); 368 } 369 370 @Override 371 @MainThread primaryBouncerIsOrWillBeShowing()372 public boolean primaryBouncerIsOrWillBeShowing() { 373 return mPrimaryBouncerInteractor.isFullyShowing() 374 || mPrimaryBouncerInteractor.isInTransit(); 375 } 376 377 @Override keyguardGoingAway()378 public void keyguardGoingAway() { 379 // no-op 380 } 381 382 @Override setKeyguardGoingAwayState(boolean isKeyguardGoingAway)383 public void setKeyguardGoingAwayState(boolean isKeyguardGoingAway) { 384 // no-op 385 } 386 387 @Override shouldDisableWindowAnimationsForUnlock()388 public boolean shouldDisableWindowAnimationsForUnlock() { 389 // TODO(b/205189147): revert the following change after the proper fix is landed. 390 // Disables the KeyGuard animation to resolve TaskView misalignment issue after display-on. 391 return true; 392 } 393 394 @Override isGoingToNotificationShade()395 public boolean isGoingToNotificationShade() { 396 return false; 397 } 398 399 @Override isUnlockWithWallpaper()400 public boolean isUnlockWithWallpaper() { 401 return false; 402 } 403 404 @Override shouldSubtleWindowAnimationsForUnlock()405 public boolean shouldSubtleWindowAnimationsForUnlock() { 406 return false; 407 } 408 409 @Override blockPanelExpansionFromCurrentTouch()410 public void blockPanelExpansionFromCurrentTouch() { 411 // no-op 412 } 413 414 @Override registerCentralSurfaces( CentralSurfaces centralSurfaces, NotificationPanelViewController notificationPanelViewController, ShadeExpansionStateManager shadeExpansionStateManager, BiometricUnlockController biometricUnlockController, View notificationContainer, KeyguardBypassController bypassController)415 public void registerCentralSurfaces( 416 CentralSurfaces centralSurfaces, 417 NotificationPanelViewController notificationPanelViewController, 418 ShadeExpansionStateManager shadeExpansionStateManager, 419 BiometricUnlockController biometricUnlockController, 420 View notificationContainer, 421 KeyguardBypassController bypassController) { 422 // no-op 423 } 424 425 /** 426 * Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be 427 * called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator} 428 * when a new user is selected. 429 */ hideKeyguardToPrepareBouncer()430 public void hideKeyguardToPrepareBouncer() { 431 getLayout().setVisibility(View.INVISIBLE); 432 } 433 434 @Override setAllowRotaryFocus(boolean allowRotaryFocus)435 public boolean setAllowRotaryFocus(boolean allowRotaryFocus) { 436 boolean changed = super.setAllowRotaryFocus(allowRotaryFocus); 437 // When focus on keyguard becomes allowed, focus needs to be restored back to the pin entry 438 // view. Depending on the timing of the calls, pinView may believe it is focused 439 // (isFocused()=true) but the root view does not believe anything is focused 440 // (findFocus()=null). To guarantee that the view is fully focused, it is necessary to 441 // clear and refocus the element. 442 if (changed && allowRotaryFocus && getLayout() != null) { 443 View pinView = getLayout().findViewById(R.id.pinEntry); 444 if (pinView != null) { 445 pinView.clearFocus(); 446 pinView.requestFocus(); 447 } 448 } 449 return changed; 450 } 451 revealKeyguardIfBouncerPrepared()452 private void revealKeyguardIfBouncerPrepared() { 453 int reattemptDelayMillis = 50; 454 Runnable revealKeyguard = () -> { 455 if (!mPrimaryBouncerInteractor.isInTransit() || !isSecure()) { 456 if (mShowing) { 457 // Only set the layout as visible if the keyguard should be showing 458 showInternal(); 459 } 460 } else { 461 if (DEBUG) { 462 Log.d(TAG, "revealKeyguardIfBouncerPrepared: Bouncer is not prepared " 463 + "yet so reattempting after " + reattemptDelayMillis + "ms."); 464 } 465 mMainExecutor.executeDelayed(this::revealKeyguardIfBouncerPrepared, 466 reattemptDelayMillis); 467 } 468 }; 469 mMainExecutor.execute(revealKeyguard); 470 } 471 notifyKeyguardUpdateMonitor()472 private void notifyKeyguardUpdateMonitor() { 473 mKeyguardUpdateMonitor.sendPrimaryBouncerChanged( 474 primaryBouncerIsOrWillBeShowing(), isBouncerShowing()); 475 } 476 477 /** 478 * WARNING: This method might cause Binder calls. 479 */ isSecure()480 private boolean isSecure() { 481 return mKeyguardSecurityModel.getSecurityMode( 482 KeyguardUpdateMonitor.getCurrentUser()) != KeyguardSecurityModel.SecurityMode.None; 483 } 484 485 resetBouncer()486 private void resetBouncer() { 487 mMainExecutor.execute(() -> { 488 hideInternal(); 489 mPrimaryBouncerInteractor.hide(); 490 mPrimaryBouncerInteractor.show(/* isScrimmed= */ true); 491 revealKeyguardIfBouncerPrepared(); 492 }); 493 } 494 makeOverlayToast(int stringId)495 private void makeOverlayToast(int stringId) { 496 Resources res = mContext.getResources(); 497 498 SystemUIToast systemUIToast = mToastFactory.createToast(mContext, 499 res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(), 500 res.getConfiguration().orientation); 501 502 if (systemUIToast == null) { 503 return; 504 } 505 506 View toastView = systemUIToast.getView(); 507 508 WindowManager.LayoutParams params = new WindowManager.LayoutParams(); 509 params.height = WindowManager.LayoutParams.WRAP_CONTENT; 510 params.width = WindowManager.LayoutParams.WRAP_CONTENT; 511 params.format = PixelFormat.TRANSLUCENT; 512 params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL; 513 params.y = systemUIToast.getYOffset(); 514 515 int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(), 516 res.getConfiguration().getLayoutDirection()); 517 params.gravity = absGravity; 518 519 if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) { 520 params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT; 521 } 522 if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) { 523 params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT; 524 } 525 526 // Make FocusParkingView temporarily unfocusable so it does not steal the focus. 527 // If FocusParkingView is focusable, it first steals focus and then returns it to Pattern 528 // Lock, which causes the Toast to appear repeatedly. 529 mFocusParkingView.setFocusable(false); 530 mWindowManager.addView(toastView, params); 531 532 Animator inAnimator = systemUIToast.getInAnimation(); 533 if (inAnimator != null) { 534 inAnimator.start(); 535 } 536 537 mMainExecutor.executeDelayed(new Runnable() { 538 @Override 539 public void run() { 540 Animator outAnimator = systemUIToast.getOutAnimation(); 541 if (outAnimator != null) { 542 outAnimator.start(); 543 outAnimator.addListener(new AnimatorListenerAdapter() { 544 @Override 545 public void onAnimationEnd(Animator animator) { 546 mWindowManager.removeViewImmediate(toastView); 547 mFocusParkingView.setFocusable(true); 548 } 549 }); 550 } else { 551 mFocusParkingView.setFocusable(true); 552 } 553 } 554 }, mToastShowDurationMillisecond); 555 } 556 557 /** 558 * Defines a callback for keyguard cancel button clicked listeners. 559 */ 560 public interface OnKeyguardCancelClickedListener { 561 /** 562 * Called when keyguard cancel button is clicked. 563 */ onCancelClicked()564 void onCancelClicked(); 565 } 566 } 567