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