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