blob: 1cf6359af37fdf92b2fa2cb4eeb2d5901d809766 [file] [log] [blame]
/*
* Copyright (C) 2020 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.car.keyguard;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.os.Bundle;
import android.os.UserHandle;
import android.util.Log;
import android.view.Gravity;
import android.view.View;
import android.view.ViewRootImpl;
import android.view.WindowManager;
import androidx.annotation.MainThread;
import androidx.annotation.VisibleForTesting;
import com.android.car.ui.FocusParkingView;
import com.android.internal.widget.LockPatternView;
import com.android.keyguard.KeyguardUpdateMonitor;
import com.android.keyguard.KeyguardViewController;
import com.android.keyguard.ViewMediatorCallback;
import com.android.systemui.R;
import com.android.systemui.car.systembar.CarSystemBarController;
import com.android.systemui.car.window.OverlayViewController;
import com.android.systemui.car.window.OverlayViewGlobalStateController;
import com.android.systemui.car.window.SystemUIOverlayWindowController;
import com.android.systemui.dagger.SysUISingleton;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.shade.NotificationPanelViewController;
import com.android.systemui.statusbar.phone.BiometricUnlockController;
import com.android.systemui.statusbar.phone.CentralSurfaces;
import com.android.systemui.statusbar.phone.KeyguardBouncer;
import com.android.systemui.statusbar.phone.KeyguardBouncer.Factory;
import com.android.systemui.statusbar.phone.KeyguardBypassController;
import com.android.systemui.statusbar.phone.panelstate.PanelExpansionStateManager;
import com.android.systemui.statusbar.policy.KeyguardStateController;
import com.android.systemui.toast.SystemUIToast;
import com.android.systemui.toast.ToastFactory;
import com.android.systemui.util.concurrency.DelayableExecutor;
import javax.inject.Inject;
import dagger.Lazy;
/**
* Automotive implementation of the {@link KeyguardViewController}. It controls the Keyguard View
* that is mounted to the SystemUIOverlayWindow.
*/
@SysUISingleton
public class CarKeyguardViewController extends OverlayViewController implements
KeyguardViewController {
private static final String TAG = "CarKeyguardViewController";
private static final boolean DEBUG = true;
private static final float TOAST_PARAMS_HORIZONTAL_WEIGHT = 1.0f;
private static final float TOAST_PARAMS_VERTICAL_WEIGHT = 1.0f;
private final Context mContext;
private final DelayableExecutor mMainExecutor;
private final WindowManager mWindowManager;
private final ToastFactory mToastFactory;
private final FocusParkingView mFocusParkingView;
private final KeyguardStateController mKeyguardStateController;
private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
private final Lazy<BiometricUnlockController> mBiometricUnlockControllerLazy;
private final ViewMediatorCallback mViewMediatorCallback;
private final CarSystemBarController mCarSystemBarController;
private final Factory mKeyguardBouncerFactory;
// Needed to instantiate mBouncer.
private final KeyguardBouncer.BouncerExpansionCallback mExpansionCallback =
new KeyguardBouncer.BouncerExpansionCallback() {
@Override
public void onFullyShown() {
LockPatternView patternView = getLayout().findViewById(R.id.lockPatternView);
if (patternView != null) {
patternView.setOnFocusChangeListener(new View.OnFocusChangeListener() {
@Override
public void onFocusChange(View v, boolean hasFocus) {
if (hasFocus) {
makeOverlayToast(R.string.lockpattern_does_not_support_rotary);
}
}
});
}
}
@Override
public void onStartingToHide() {
}
@Override
public void onStartingToShow() {
}
@Override
public void onFullyHidden() {
}
};
private KeyguardBouncer mBouncer;
private OnKeyguardCancelClickedListener mKeyguardCancelClickedListener;
private boolean mShowing;
private boolean mIsOccluded;
private boolean mIsSleeping;
private int mToastShowDurationMillisecond;
@Inject
public 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) {
super(R.id.keyguard_stub, overlayViewGlobalStateController);
mContext = context;
mMainExecutor = mainExecutor;
mWindowManager = windowManager;
mToastFactory = toastFactory;
mFocusParkingView = systemUIOverlayWindowController.getBaseLayout().findViewById(
R.id.focus_parking_view);
mKeyguardStateController = keyguardStateController;
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
mBiometricUnlockControllerLazy = biometricUnlockControllerLazy;
mViewMediatorCallback = viewMediatorCallback;
mCarSystemBarController = carSystemBarController;
mKeyguardBouncerFactory = keyguardBouncerFactory;
mToastShowDurationMillisecond = mContext.getResources().getInteger(
R.integer.car_keyguard_toast_show_duration_millisecond);
}
@Override
protected int getFocusAreaViewId() {
return R.id.keyguard_container;
}
@Override
protected boolean shouldShowNavigationBarInsets() {
return true;
}
@Override
public void onFinishInflate() {
mBouncer = mKeyguardBouncerFactory
.create(getLayout().findViewById(R.id.keyguard_container), mExpansionCallback);
mBiometricUnlockControllerLazy.get().setKeyguardViewController(this);
}
@Override
@MainThread
public void notifyKeyguardAuthenticated(boolean strongAuth) {
if (mBouncer != null) {
mBouncer.notifyKeyguardAuthenticated(strongAuth);
}
}
@Override
@MainThread
public void showBouncer(boolean scrimmed) {
if (mShowing && !mBouncer.isShowing()) {
mBouncer.show(/* resetSecuritySelection= */ false);
}
}
@Override
@MainThread
public void show(Bundle options) {
if (mShowing) return;
mShowing = true;
mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
mCarSystemBarController.showAllKeyguardButtons(/* isSetUp= */ true);
start();
reset(/* hideBouncerWhenShowing= */ false);
notifyKeyguardUpdateMonitor();
}
@Override
@MainThread
public void hide(long startTime, long fadeoutDuration) {
if (!mShowing || mIsSleeping) return;
mViewMediatorCallback.readyForKeyguardDone();
mShowing = false;
mKeyguardStateController.notifyKeyguardState(mShowing, /* occluded= */ false);
mBouncer.hide(/* destroyView= */ true);
mCarSystemBarController.showAllNavigationButtons(/* isSetUp= */ true);
stop();
mKeyguardStateController.notifyKeyguardDoneFading();
mMainExecutor.execute(mViewMediatorCallback::keyguardGone);
notifyKeyguardUpdateMonitor();
}
@Override
public void reset(boolean hideBouncerWhenShowing) {
if (mIsSleeping) return;
mMainExecutor.execute(() -> {
if (mShowing) {
if (mBouncer != null) {
if (!mBouncer.isSecure()) {
dismissAndCollapse();
}
resetBouncer();
}
mKeyguardUpdateMonitor.sendKeyguardReset();
notifyKeyguardUpdateMonitor();
} else {
// This is necessary in order to address an inconsistency between the keyguard
// service and the keyguard views.
// TODO: Investigate the source of the inconsistency.
show(/* options= */ null);
}
});
}
@Override
public void resetAlternateAuth(boolean forceUpdateScrim) {
// no-op
}
@Override
@MainThread
public void onFinishedGoingToSleep() {
if (mBouncer != null) {
mBouncer.onScreenTurnedOff();
}
}
@Override
@MainThread
public void setOccluded(boolean occluded, boolean animate) {
mIsOccluded = occluded;
getOverlayViewGlobalStateController().setOccluded(occluded);
if (occluded) {
mCarSystemBarController.showAllOcclusionButtons(/* isSetup= */ true);
} else {
if (mShowing && mBouncer.isSecure()) {
mCarSystemBarController.showAllKeyguardButtons(/* isSetup= */ true);
} else {
mCarSystemBarController.showAllNavigationButtons(/* isSetUp= */ true);
}
}
}
@Override
@MainThread
public void onCancelClicked() {
if (mBouncer == null) return;
getOverlayViewGlobalStateController().setWindowNeedsInput(/* needsInput= */ false);
mBouncer.hide(/* destroyView= */ true);
mKeyguardCancelClickedListener.onCancelClicked();
}
@Override
public boolean isShowing() {
return mShowing;
}
@Override
@MainThread
public void dismissAndCollapse() {
// If dismissing and collapsing Keyguard is requested (e.g. by a Keyguard-dismissing
// Activity) while Keyguard is occluded, unocclude Keyguard so the user can authenticate to
// dismiss Keyguard.
if (mIsOccluded) {
setOccluded(/* occluded= */ false, /* animate= */ false);
}
if (mBouncer != null && !mBouncer.isSecure()) {
hide(/* startTime= */ 0, /* fadeoutDuration= */ 0);
}
}
@Override
@MainThread
public void startPreHideAnimation(Runnable finishRunnable) {
if (mBouncer == null) return;
mBouncer.startPreHideAnimation(finishRunnable);
}
@Override
public void setNeedsInput(boolean needsInput) {
getOverlayViewGlobalStateController().setWindowNeedsInput(needsInput);
}
@Override
public void onStartedGoingToSleep() {
mIsSleeping = true;
}
@Override
public void onStartedWakingUp() {
mIsSleeping = false;
reset(/* hideBouncerWhenShowing= */ false);
}
/**
* Add listener for keyguard cancel clicked.
*/
public void registerOnKeyguardCancelClickedListener(
OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
mKeyguardCancelClickedListener = keyguardCancelClickedListener;
}
/**
* Remove listener for keyguard cancel clicked.
*/
public void unregisterOnKeyguardCancelClickedListener(
OnKeyguardCancelClickedListener keyguardCancelClickedListener) {
mKeyguardCancelClickedListener = null;
}
@Override
public ViewRootImpl getViewRootImpl() {
return ((View) getLayout().getParent()).getViewRootImpl();
}
@Override
@MainThread
public boolean isBouncerShowing() {
return mBouncer != null && mBouncer.isShowing();
}
@Override
@MainThread
public boolean bouncerIsOrWillBeShowing() {
return mBouncer != null && (mBouncer.isShowing() || mBouncer.inTransit());
}
@Override
public void keyguardGoingAway() {
// no-op
}
@Override
public void setKeyguardGoingAwayState(boolean isKeyguardGoingAway) {
// no-op
}
@Override
public boolean shouldDisableWindowAnimationsForUnlock() {
// TODO(b/205189147): revert the following change after the proper fix is landed.
// Disables the KeyGuard animation to resolve TaskView misalignment issue after display-on.
return true;
}
@Override
public boolean isGoingToNotificationShade() {
return false;
}
@Override
public boolean isUnlockWithWallpaper() {
return false;
}
@Override
public boolean shouldSubtleWindowAnimationsForUnlock() {
return false;
}
@Override
public void blockPanelExpansionFromCurrentTouch() {
// no-op
}
@Override
public void registerCentralSurfaces(
CentralSurfaces centralSurfaces,
NotificationPanelViewController notificationPanelViewController,
PanelExpansionStateManager panelExpansionStateManager,
BiometricUnlockController biometricUnlockController,
View notificationContainer,
KeyguardBypassController bypassController) {
// no-op
}
/**
* Hides Keyguard so that the transitioning Bouncer can be hidden until it is prepared. To be
* called by {@link com.android.systemui.car.userswitcher.FullscreenUserSwitcherViewMediator}
* when a new user is selected.
*/
public void hideKeyguardToPrepareBouncer() {
getLayout().setVisibility(View.INVISIBLE);
}
@VisibleForTesting
void setKeyguardBouncer(KeyguardBouncer keyguardBouncer) {
mBouncer = keyguardBouncer;
}
private void revealKeyguardIfBouncerPrepared() {
int reattemptDelayMillis = 50;
Runnable revealKeyguard = () -> {
if (mBouncer == null) {
if (DEBUG) {
Log.d(TAG, "revealKeyguardIfBouncerPrepared: revealKeyguard request is ignored "
+ "since the Bouncer has not been initialized yet.");
}
return;
}
if (!mBouncer.inTransit() || !mBouncer.isSecure()) {
if (mShowing) {
// Only set the layout as visible if the keyguard should be showing
showInternal();
}
} else {
if (DEBUG) {
Log.d(TAG, "revealKeyguardIfBouncerPrepared: Bouncer is not prepared "
+ "yet so reattempting after " + reattemptDelayMillis + "ms.");
}
mMainExecutor.executeDelayed(this::revealKeyguardIfBouncerPrepared,
reattemptDelayMillis);
}
};
mMainExecutor.execute(revealKeyguard);
}
private void notifyKeyguardUpdateMonitor() {
mKeyguardUpdateMonitor.onKeyguardVisibilityChanged(mShowing);
if (mBouncer != null) {
mKeyguardUpdateMonitor.sendKeyguardBouncerChanged(
bouncerIsOrWillBeShowing(), isBouncerShowing());
}
}
private void resetBouncer() {
mMainExecutor.execute(() -> {
hideInternal();
mBouncer.hide(/* destroyView= */ false);
mBouncer.show(/* resetSecuritySelection= */ true);
revealKeyguardIfBouncerPrepared();
});
}
private void makeOverlayToast(int stringId) {
Resources res = mContext.getResources();
SystemUIToast systemUIToast = mToastFactory.createToast(mContext,
res.getString(stringId), mContext.getPackageName(), UserHandle.myUserId(),
res.getConfiguration().orientation);
if (systemUIToast == null) {
return;
}
View toastView = systemUIToast.getView();
WindowManager.LayoutParams params = new WindowManager.LayoutParams();
params.height = WindowManager.LayoutParams.WRAP_CONTENT;
params.width = WindowManager.LayoutParams.WRAP_CONTENT;
params.format = PixelFormat.TRANSLUCENT;
params.type = WindowManager.LayoutParams.TYPE_STATUS_BAR_SUB_PANEL;
params.y = systemUIToast.getYOffset();
int absGravity = Gravity.getAbsoluteGravity(systemUIToast.getGravity(),
res.getConfiguration().getLayoutDirection());
params.gravity = absGravity;
if ((absGravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {
params.horizontalWeight = TOAST_PARAMS_HORIZONTAL_WEIGHT;
}
if ((absGravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {
params.verticalWeight = TOAST_PARAMS_VERTICAL_WEIGHT;
}
// Make FocusParkingView temporarily unfocusable so it does not steal the focus.
// If FocusParkingView is focusable, it first steals focus and then returns it to Pattern
// Lock, which causes the Toast to appear repeatedly.
mFocusParkingView.setFocusable(false);
mWindowManager.addView(toastView, params);
Animator inAnimator = systemUIToast.getInAnimation();
if (inAnimator != null) {
inAnimator.start();
}
mMainExecutor.executeDelayed(new Runnable() {
@Override
public void run() {
Animator outAnimator = systemUIToast.getOutAnimation();
if (outAnimator != null) {
outAnimator.start();
outAnimator.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationEnd(Animator animator) {
mWindowManager.removeViewImmediate(toastView);
mFocusParkingView.setFocusable(true);
}
});
} else {
mFocusParkingView.setFocusable(true);
}
}
}, mToastShowDurationMillisecond);
}
/**
* Defines a callback for keyguard cancel button clicked listeners.
*/
public interface OnKeyguardCancelClickedListener {
/**
* Called when keyguard cancel button is clicked.
*/
void onCancelClicked();
}
}