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