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
17package com.android.systemui.statusbar.phone;
18
19import android.content.ComponentCallbacks2;
20import android.content.Context;
21import android.os.Bundle;
22import android.os.SystemClock;
23import android.os.Trace;
24import android.view.KeyEvent;
25import android.view.View;
26import android.view.ViewGroup;
27import android.view.ViewRootImpl;
28import android.view.WindowManagerGlobal;
29
30import com.android.internal.widget.LockPatternUtils;
31import com.android.keyguard.KeyguardUpdateMonitor;
32import com.android.keyguard.ViewMediatorCallback;
33import com.android.systemui.SystemUIFactory;
34import com.android.systemui.statusbar.CommandQueue;
35import com.android.systemui.statusbar.RemoteInputController;
36
37import static com.android.keyguard.KeyguardHostView.OnDismissAction;
38
39/**
40 * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
41 * via {@link ViewMediatorCallback} to poke the wake lock and report that the keyguard is done,
42 * which is in turn, reported to this class by the current
43 * {@link com.android.keyguard.KeyguardViewBase}.
44 */
45public class StatusBarKeyguardViewManager implements RemoteInputController.Callback {
46
47    // When hiding the Keyguard with timing supplied from WindowManager, better be early than late.
48    private static final long HIDE_TIMING_CORRECTION_MS = -3 * 16;
49
50    // Delay for showing the navigation bar when the bouncer appears. This should be kept in sync
51    // with the appear animations of the PIN/pattern/password views.
52    private static final long NAV_BAR_SHOW_DELAY_BOUNCER = 320;
53
54    private static final long WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS = 200;
55
56    // Duration of the Keyguard dismissal animation in case the user is currently locked. This is to
57    // make everything a bit slower to bridge a gap until the user is unlocked and home screen has
58    // dranw its first frame.
59    private static final long KEYGUARD_DISMISS_DURATION_LOCKED = 2000;
60
61    private static String TAG = "StatusBarKeyguardViewManager";
62
63    protected final Context mContext;
64
65    protected LockPatternUtils mLockPatternUtils;
66    protected ViewMediatorCallback mViewMediatorCallback;
67    protected PhoneStatusBar mPhoneStatusBar;
68    private ScrimController mScrimController;
69    private FingerprintUnlockController mFingerprintUnlockController;
70
71    private ViewGroup mContainer;
72    private StatusBarWindowManager mStatusBarWindowManager;
73
74    private boolean mDeviceInteractive = false;
75    private boolean mScreenTurnedOn;
76    protected KeyguardBouncer mBouncer;
77    protected boolean mShowing;
78    protected boolean mOccluded;
79    protected boolean mRemoteInputActive;
80
81    protected boolean mFirstUpdate = true;
82    protected boolean mLastShowing;
83    protected boolean mLastOccluded;
84    private boolean mLastBouncerShowing;
85    private boolean mLastBouncerDismissible;
86    protected boolean mLastRemoteInputActive;
87
88    private OnDismissAction mAfterKeyguardGoneAction;
89    private boolean mDeviceWillWakeUp;
90    private boolean mDeferScrimFadeOut;
91
92    public StatusBarKeyguardViewManager(Context context, ViewMediatorCallback callback,
93            LockPatternUtils lockPatternUtils) {
94        mContext = context;
95        mViewMediatorCallback = callback;
96        mLockPatternUtils = lockPatternUtils;
97    }
98
99    public void registerStatusBar(PhoneStatusBar phoneStatusBar,
100            ViewGroup container, StatusBarWindowManager statusBarWindowManager,
101            ScrimController scrimController,
102            FingerprintUnlockController fingerprintUnlockController) {
103        mPhoneStatusBar = phoneStatusBar;
104        mContainer = container;
105        mStatusBarWindowManager = statusBarWindowManager;
106        mScrimController = scrimController;
107        mFingerprintUnlockController = fingerprintUnlockController;
108        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
109                mViewMediatorCallback, mLockPatternUtils, mStatusBarWindowManager, container);
110    }
111
112    /**
113     * Show the keyguard.  Will handle creating and attaching to the view manager
114     * lazily.
115     */
116    public void show(Bundle options) {
117        mShowing = true;
118        mStatusBarWindowManager.setKeyguardShowing(true);
119        mScrimController.abortKeyguardFadingOut();
120        reset();
121    }
122
123    /**
124     * Shows the notification keyguard or the bouncer depending on
125     * {@link KeyguardBouncer#needsFullscreenBouncer()}.
126     */
127    protected void showBouncerOrKeyguard() {
128        if (mBouncer.needsFullscreenBouncer()) {
129
130            // The keyguard might be showing (already). So we need to hide it.
131            mPhoneStatusBar.hideKeyguard();
132            mBouncer.show(true /* resetSecuritySelection */);
133        } else {
134            mPhoneStatusBar.showKeyguard();
135            mBouncer.hide(false /* destroyView */);
136            mBouncer.prepare();
137        }
138    }
139
140    private void showBouncer() {
141        if (mShowing) {
142            mBouncer.show(false /* resetSecuritySelection */);
143        }
144        updateStates();
145    }
146
147    public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
148            boolean afterKeyguardGone) {
149        if (mShowing) {
150            if (!afterKeyguardGone) {
151                mBouncer.showWithDismissAction(r, cancelAction);
152            } else {
153                mBouncer.show(false /* resetSecuritySelection */);
154                mAfterKeyguardGoneAction = r;
155            }
156        }
157        updateStates();
158    }
159
160    /**
161     * Reset the state of the view.
162     */
163    public void reset() {
164        if (mShowing) {
165            if (mOccluded) {
166                mPhoneStatusBar.hideKeyguard();
167                mPhoneStatusBar.stopWaitingForKeyguardExit();
168                mBouncer.hide(false /* destroyView */);
169            } else {
170                showBouncerOrKeyguard();
171            }
172            KeyguardUpdateMonitor.getInstance(mContext).sendKeyguardReset();
173            updateStates();
174        }
175    }
176
177    public void onStartedGoingToSleep() {
178        mPhoneStatusBar.onStartedGoingToSleep();
179    }
180
181    public void onFinishedGoingToSleep() {
182        mDeviceInteractive = false;
183        mPhoneStatusBar.onFinishedGoingToSleep();
184        mBouncer.onScreenTurnedOff();
185    }
186
187    public void onStartedWakingUp() {
188        Trace.beginSection("StatusBarKeyguardViewManager#onStartedWakingUp");
189        mDeviceInteractive = true;
190        mDeviceWillWakeUp = false;
191        mPhoneStatusBar.onStartedWakingUp();
192        Trace.endSection();
193    }
194
195    public void onScreenTurningOn() {
196        Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurningOn");
197        mPhoneStatusBar.onScreenTurningOn();
198        Trace.endSection();
199    }
200
201    public boolean isScreenTurnedOn() {
202        return mScreenTurnedOn;
203    }
204
205    public void onScreenTurnedOn() {
206        Trace.beginSection("StatusBarKeyguardViewManager#onScreenTurnedOn");
207        mScreenTurnedOn = true;
208        if (mDeferScrimFadeOut) {
209            mDeferScrimFadeOut = false;
210            animateScrimControllerKeyguardFadingOut(0, WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
211                    true /* skipFirstFrame */);
212            updateStates();
213        }
214        mPhoneStatusBar.onScreenTurnedOn();
215        Trace.endSection();
216    }
217
218    @Override
219    public void onRemoteInputActive(boolean active) {
220        mRemoteInputActive = active;
221        updateStates();
222    }
223
224    public void onScreenTurnedOff() {
225        mScreenTurnedOn = false;
226        mPhoneStatusBar.onScreenTurnedOff();
227    }
228
229    public void notifyDeviceWakeUpRequested() {
230        mDeviceWillWakeUp = !mDeviceInteractive;
231    }
232
233    public void verifyUnlock() {
234        dismiss();
235    }
236
237    public void setNeedsInput(boolean needsInput) {
238        mStatusBarWindowManager.setKeyguardNeedsInput(needsInput);
239    }
240
241    public boolean isUnlockWithWallpaper() {
242        return mStatusBarWindowManager.isShowingWallpaper();
243    }
244
245    public void setOccluded(boolean occluded, boolean animate) {
246        if (occluded && !mOccluded && mShowing) {
247            if (mPhoneStatusBar.isInLaunchTransition()) {
248                mOccluded = true;
249                mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(null /* beforeFading */,
250                        new Runnable() {
251                            @Override
252                            public void run() {
253                                mStatusBarWindowManager.setKeyguardOccluded(mOccluded);
254                                reset();
255                            }
256                        });
257                return;
258            }
259        }
260        mOccluded = occluded;
261        mPhoneStatusBar.updateMediaMetaData(false, animate && !occluded);
262        mStatusBarWindowManager.setKeyguardOccluded(occluded);
263        reset();
264        if (animate && !occluded) {
265            mPhoneStatusBar.animateKeyguardUnoccluding();
266        }
267    }
268
269    public boolean isOccluded() {
270        return mOccluded;
271    }
272
273    /**
274     * Starts the animation before we dismiss Keyguard, i.e. an disappearing animation on the
275     * security view of the bouncer.
276     *
277     * @param finishRunnable the runnable to be run after the animation finished, or {@code null} if
278     *                       no action should be run
279     */
280    public void startPreHideAnimation(Runnable finishRunnable) {
281        if (mBouncer.isShowing()) {
282            mBouncer.startPreHideAnimation(finishRunnable);
283        } else if (finishRunnable != null) {
284            finishRunnable.run();
285        }
286    }
287
288    /**
289     * Hides the keyguard view
290     */
291    public void hide(long startTime, long fadeoutDuration) {
292        mShowing = false;
293
294        if (KeyguardUpdateMonitor.getInstance(mContext).needsSlowUnlockTransition()) {
295            fadeoutDuration = KEYGUARD_DISMISS_DURATION_LOCKED;
296        }
297        long uptimeMillis = SystemClock.uptimeMillis();
298        long delay = Math.max(0, startTime + HIDE_TIMING_CORRECTION_MS - uptimeMillis);
299
300        if (mPhoneStatusBar.isInLaunchTransition() ) {
301            mPhoneStatusBar.fadeKeyguardAfterLaunchTransition(new Runnable() {
302                @Override
303                public void run() {
304                    mStatusBarWindowManager.setKeyguardShowing(false);
305                    mStatusBarWindowManager.setKeyguardFadingAway(true);
306                    mBouncer.hide(true /* destroyView */);
307                    updateStates();
308                    mScrimController.animateKeyguardFadingOut(
309                            PhoneStatusBar.FADE_KEYGUARD_START_DELAY,
310                            PhoneStatusBar.FADE_KEYGUARD_DURATION, null,
311                            false /* skipFirstFrame */);
312                }
313            }, new Runnable() {
314                @Override
315                public void run() {
316                    mPhoneStatusBar.hideKeyguard();
317                    mStatusBarWindowManager.setKeyguardFadingAway(false);
318                    mViewMediatorCallback.keyguardGone();
319                    executeAfterKeyguardGoneAction();
320                }
321            });
322        } else {
323            if (mFingerprintUnlockController.getMode()
324                    == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK_PULSING) {
325                mFingerprintUnlockController.startKeyguardFadingAway();
326                mPhoneStatusBar.setKeyguardFadingAway(startTime, 0, 240);
327                mStatusBarWindowManager.setKeyguardFadingAway(true);
328                mPhoneStatusBar.fadeKeyguardWhilePulsing();
329                animateScrimControllerKeyguardFadingOut(0, 240, new Runnable() {
330                    @Override
331                    public void run() {
332                        mPhoneStatusBar.hideKeyguard();
333                    }
334                }, false /* skipFirstFrame */);
335            } else {
336                mFingerprintUnlockController.startKeyguardFadingAway();
337                mPhoneStatusBar.setKeyguardFadingAway(startTime, delay, fadeoutDuration);
338                boolean staying = mPhoneStatusBar.hideKeyguard();
339                if (!staying) {
340                    mStatusBarWindowManager.setKeyguardFadingAway(true);
341                    if (mFingerprintUnlockController.getMode()
342                            == FingerprintUnlockController.MODE_WAKE_AND_UNLOCK) {
343                        if (!mScreenTurnedOn) {
344                            mDeferScrimFadeOut = true;
345                        } else {
346
347                            // Screen is already on, don't defer with fading out.
348                            animateScrimControllerKeyguardFadingOut(0,
349                                    WAKE_AND_UNLOCK_SCRIM_FADEOUT_DURATION_MS,
350                                    true /* skipFirstFrame */);
351                        }
352                    } else {
353                        animateScrimControllerKeyguardFadingOut(delay, fadeoutDuration,
354                                false /* skipFirstFrame */);
355                    }
356                } else {
357                    mScrimController.animateGoingToFullShade(delay, fadeoutDuration);
358                    mPhoneStatusBar.finishKeyguardFadingAway();
359                }
360            }
361            mStatusBarWindowManager.setKeyguardShowing(false);
362            mBouncer.hide(true /* destroyView */);
363            mViewMediatorCallback.keyguardGone();
364            executeAfterKeyguardGoneAction();
365            updateStates();
366        }
367    }
368
369    public void onDensityOrFontScaleChanged() {
370        mBouncer.hide(true /* destroyView */);
371    }
372
373    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
374            boolean skipFirstFrame) {
375        animateScrimControllerKeyguardFadingOut(delay, duration, null /* endRunnable */,
376                skipFirstFrame);
377    }
378
379    private void animateScrimControllerKeyguardFadingOut(long delay, long duration,
380            final Runnable endRunnable, boolean skipFirstFrame) {
381        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "Fading out", 0);
382        mScrimController.animateKeyguardFadingOut(delay, duration, new Runnable() {
383            @Override
384            public void run() {
385                if (endRunnable != null) {
386                    endRunnable.run();
387                }
388                mStatusBarWindowManager.setKeyguardFadingAway(false);
389                mPhoneStatusBar.finishKeyguardFadingAway();
390                mFingerprintUnlockController.finishKeyguardFadingAway();
391                WindowManagerGlobal.getInstance().trimMemory(
392                        ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN);
393                Trace.asyncTraceEnd(Trace.TRACE_TAG_VIEW, "Fading out", 0);
394            }
395        }, skipFirstFrame);
396    }
397
398    private void executeAfterKeyguardGoneAction() {
399        if (mAfterKeyguardGoneAction != null) {
400            mAfterKeyguardGoneAction.onDismiss();
401            mAfterKeyguardGoneAction = null;
402        }
403    }
404
405    /**
406     * Dismisses the keyguard by going to the next screen or making it gone.
407     */
408    public void dismiss() {
409        if (mDeviceInteractive || mDeviceWillWakeUp) {
410            showBouncer();
411        }
412    }
413
414    /**
415     * WARNING: This method might cause Binder calls.
416     */
417    public boolean isSecure() {
418        return mBouncer.isSecure();
419    }
420
421    /**
422     * @return Whether the keyguard is showing
423     */
424    public boolean isShowing() {
425        return mShowing;
426    }
427
428    /**
429     * Notifies this manager that the back button has been pressed.
430     *
431     * @return whether the back press has been handled
432     */
433    public boolean onBackPressed() {
434        if (mBouncer.isShowing()) {
435            mPhoneStatusBar.endAffordanceLaunch();
436            reset();
437            return true;
438        }
439        return false;
440    }
441
442    public boolean isBouncerShowing() {
443        return mBouncer.isShowing();
444    }
445
446    private long getNavBarShowDelay() {
447        if (mPhoneStatusBar.isKeyguardFadingAway()) {
448            return mPhoneStatusBar.getKeyguardFadingAwayDelay();
449        } else {
450
451            // Keyguard is not going away, thus we are showing the navigation bar because the
452            // bouncer is appearing.
453            return NAV_BAR_SHOW_DELAY_BOUNCER;
454        }
455    }
456
457    private Runnable mMakeNavigationBarVisibleRunnable = new Runnable() {
458        @Override
459        public void run() {
460            mPhoneStatusBar.getNavigationBarView().setVisibility(View.VISIBLE);
461        }
462    };
463
464    protected void updateStates() {
465        int vis = mContainer.getSystemUiVisibility();
466        boolean showing = mShowing;
467        boolean occluded = mOccluded;
468        boolean bouncerShowing = mBouncer.isShowing();
469        boolean bouncerDismissible = !mBouncer.isFullscreenBouncer();
470        boolean remoteInputActive = mRemoteInputActive;
471
472        if ((bouncerDismissible || !showing || remoteInputActive) !=
473                (mLastBouncerDismissible || !mLastShowing || mLastRemoteInputActive)
474                || mFirstUpdate) {
475            if (bouncerDismissible || !showing || remoteInputActive) {
476                mContainer.setSystemUiVisibility(vis & ~View.STATUS_BAR_DISABLE_BACK);
477            } else {
478                mContainer.setSystemUiVisibility(vis | View.STATUS_BAR_DISABLE_BACK);
479            }
480        }
481
482        boolean navBarVisible = isNavBarVisible();
483        boolean lastNavBarVisible = getLastNavBarVisible();
484        if (navBarVisible != lastNavBarVisible || mFirstUpdate) {
485            if (mPhoneStatusBar.getNavigationBarView() != null) {
486                if (navBarVisible) {
487                    long delay = getNavBarShowDelay();
488                    if (delay == 0) {
489                        mMakeNavigationBarVisibleRunnable.run();
490                    } else {
491                        mContainer.postOnAnimationDelayed(mMakeNavigationBarVisibleRunnable,
492                                delay);
493                    }
494                } else {
495                    mContainer.removeCallbacks(mMakeNavigationBarVisibleRunnable);
496                    mPhoneStatusBar.getNavigationBarView().setVisibility(View.GONE);
497                }
498            }
499        }
500
501        if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
502            mStatusBarWindowManager.setBouncerShowing(bouncerShowing);
503            mPhoneStatusBar.setBouncerShowing(bouncerShowing);
504            mScrimController.setBouncerShowing(bouncerShowing);
505        }
506
507        KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
508        if ((showing && !occluded) != (mLastShowing && !mLastOccluded) || mFirstUpdate) {
509            updateMonitor.onKeyguardVisibilityChanged(showing && !occluded);
510        }
511        if (bouncerShowing != mLastBouncerShowing || mFirstUpdate) {
512            updateMonitor.sendKeyguardBouncerChanged(bouncerShowing);
513        }
514
515        mFirstUpdate = false;
516        mLastShowing = showing;
517        mLastOccluded = occluded;
518        mLastBouncerShowing = bouncerShowing;
519        mLastBouncerDismissible = bouncerDismissible;
520        mLastRemoteInputActive = remoteInputActive;
521
522        mPhoneStatusBar.onKeyguardViewManagerStatesUpdated();
523    }
524
525    /**
526     * @return Whether the navigation bar should be made visible based on the current state.
527     */
528    protected boolean isNavBarVisible() {
529        return !(mShowing && !mOccluded) || mBouncer.isShowing() || mRemoteInputActive;
530    }
531
532    /**
533     * @return Whether the navigation bar was made visible based on the last known state.
534     */
535    protected boolean getLastNavBarVisible() {
536        return !(mLastShowing && !mLastOccluded) || mLastBouncerShowing || mLastRemoteInputActive;
537    }
538
539    public boolean shouldDismissOnMenuPressed() {
540        return mBouncer.shouldDismissOnMenuPressed();
541    }
542
543    public boolean interceptMediaKey(KeyEvent event) {
544        return mBouncer.interceptMediaKey(event);
545    }
546
547    public void onActivityDrawn() {
548        if (mPhoneStatusBar.isCollapsing()) {
549            mPhoneStatusBar.addPostCollapseAction(new Runnable() {
550                @Override
551                public void run() {
552                    mViewMediatorCallback.readyForKeyguardDone();
553                }
554            });
555        } else {
556            mViewMediatorCallback.readyForKeyguardDone();
557        }
558    }
559
560    public boolean shouldDisableWindowAnimationsForUnlock() {
561        return mPhoneStatusBar.isInLaunchTransition();
562    }
563
564    public boolean isGoingToNotificationShade() {
565        return mPhoneStatusBar.isGoingToNotificationShade();
566    }
567
568    public boolean isSecure(int userId) {
569        return mBouncer.isSecure() || mLockPatternUtils.isSecure(userId);
570    }
571
572    public boolean isInputRestricted() {
573        return mViewMediatorCallback.isInputRestricted();
574    }
575
576    public void keyguardGoingAway() {
577        mPhoneStatusBar.keyguardGoingAway();
578    }
579
580    public void animateCollapsePanels(float speedUpFactor) {
581        mPhoneStatusBar.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */,
582                false /* delayed */, speedUpFactor);
583    }
584
585    /**
586     * Notifies that the user has authenticated by other means than using the bouncer, for example,
587     * fingerprint.
588     */
589    public void notifyKeyguardAuthenticated(boolean strongAuth) {
590        mBouncer.notifyKeyguardAuthenticated(strongAuth);
591    }
592
593    public void showBouncerMessage(String message, int color) {
594        mBouncer.showMessage(message, color);
595    }
596
597    public ViewRootImpl getViewRootImpl() {
598        return mPhoneStatusBar.getStatusBarView().getViewRootImpl();
599    }
600}
601