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