OSDN Git Service

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