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 android.app.ActivityManager;
20 import android.content.Context;
21 import android.os.Handler;
22 import android.os.UserHandle;
23 import android.os.UserManager;
24 import android.util.Slog;
25 import android.view.KeyEvent;
26 import android.view.LayoutInflater;
27 import android.view.View;
28 import android.view.ViewGroup;
29 import android.view.ViewTreeObserver;
30 import android.view.accessibility.AccessibilityEvent;
31 
32 import com.android.internal.widget.LockPatternUtils;
33 import com.android.keyguard.KeyguardHostView;
34 import com.android.keyguard.KeyguardSecurityView;
35 import com.android.keyguard.KeyguardUpdateMonitor;
36 import com.android.keyguard.KeyguardUpdateMonitorCallback;
37 import com.android.keyguard.R;
38 import com.android.keyguard.ViewMediatorCallback;
39 import com.android.systemui.DejankUtils;
40 import com.android.systemui.classifier.FalsingManager;
41 import com.android.systemui.keyguard.DismissCallbackRegistry;
42 
43 import static com.android.keyguard.KeyguardHostView.OnDismissAction;
44 import static com.android.keyguard.KeyguardSecurityModel.SecurityMode;
45 
46 /**
47  * A class which manages the bouncer on the lockscreen.
48  */
49 public class KeyguardBouncer {
50 
51     final static private String TAG = "KeyguardBouncer";
52 
53     protected final Context mContext;
54     protected final ViewMediatorCallback mCallback;
55     protected final LockPatternUtils mLockPatternUtils;
56     protected final ViewGroup mContainer;
57     private final FalsingManager mFalsingManager;
58     private final DismissCallbackRegistry mDismissCallbackRegistry;
59     private final Handler mHandler;
60     protected KeyguardHostView mKeyguardView;
61     protected ViewGroup mRoot;
62     private boolean mShowingSoon;
63     private int mBouncerPromptReason;
64     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
65             new KeyguardUpdateMonitorCallback() {
66                 @Override
67                 public void onStrongAuthStateChanged(int userId) {
68                     mBouncerPromptReason = mCallback.getBouncerPromptReason();
69                 }
70             };
71     private final Runnable mRemoveViewRunnable = this::removeView;
72 
KeyguardBouncer(Context context, ViewMediatorCallback callback, LockPatternUtils lockPatternUtils, ViewGroup container, DismissCallbackRegistry dismissCallbackRegistry)73     public KeyguardBouncer(Context context, ViewMediatorCallback callback,
74             LockPatternUtils lockPatternUtils, ViewGroup container,
75             DismissCallbackRegistry dismissCallbackRegistry) {
76         mContext = context;
77         mCallback = callback;
78         mLockPatternUtils = lockPatternUtils;
79         mContainer = container;
80         KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mUpdateMonitorCallback);
81         mFalsingManager = FalsingManager.getInstance(mContext);
82         mDismissCallbackRegistry = dismissCallbackRegistry;
83         mHandler = new Handler();
84     }
85 
show(boolean resetSecuritySelection)86     public void show(boolean resetSecuritySelection) {
87         final int keyguardUserId = KeyguardUpdateMonitor.getCurrentUser();
88         if (keyguardUserId == UserHandle.USER_SYSTEM && UserManager.isSplitSystemUser()) {
89             // In split system user mode, we never unlock system user.
90             return;
91         }
92         mFalsingManager.onBouncerShown();
93         ensureView();
94         if (resetSecuritySelection) {
95             // showPrimarySecurityScreen() updates the current security method. This is needed in
96             // case we are already showing and the current security method changed.
97             mKeyguardView.showPrimarySecurityScreen();
98         }
99         if (mRoot.getVisibility() == View.VISIBLE || mShowingSoon) {
100             return;
101         }
102 
103         final int activeUserId = ActivityManager.getCurrentUser();
104         final boolean isSystemUser =
105                 UserManager.isSplitSystemUser() && activeUserId == UserHandle.USER_SYSTEM;
106         final boolean allowDismissKeyguard = !isSystemUser && activeUserId == keyguardUserId;
107 
108         // If allowed, try to dismiss the Keyguard. If no security auth (password/pin/pattern) is
109         // set, this will dismiss the whole Keyguard. Otherwise, show the bouncer.
110         if (allowDismissKeyguard && mKeyguardView.dismiss(activeUserId)) {
111             return;
112         }
113 
114         // This condition may indicate an error on Android, so log it.
115         if (!allowDismissKeyguard) {
116             Slog.w(TAG, "User can't dismiss keyguard: " + activeUserId + " != " + keyguardUserId);
117         }
118 
119         mShowingSoon = true;
120 
121         // Split up the work over multiple frames.
122         DejankUtils.postAfterTraversal(mShowRunnable);
123     }
124 
125     private final Runnable mShowRunnable = new Runnable() {
126         @Override
127         public void run() {
128             mRoot.setVisibility(View.VISIBLE);
129             mKeyguardView.onResume();
130             showPromptReason(mBouncerPromptReason);
131             if (mKeyguardView.getHeight() != 0) {
132                 mKeyguardView.startAppearAnimation();
133             } else {
134                 mKeyguardView.getViewTreeObserver().addOnPreDrawListener(
135                         new ViewTreeObserver.OnPreDrawListener() {
136                             @Override
137                             public boolean onPreDraw() {
138                                 mKeyguardView.getViewTreeObserver().removeOnPreDrawListener(this);
139                                 mKeyguardView.startAppearAnimation();
140                                 return true;
141                             }
142                         });
143                 mKeyguardView.requestLayout();
144             }
145             mShowingSoon = false;
146             mKeyguardView.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
147         }
148     };
149 
150     /**
151      * Show a string explaining why the security view needs to be solved.
152      *
153      * @param reason a flag indicating which string should be shown, see
154      *               {@link KeyguardSecurityView#PROMPT_REASON_NONE}
155      *               and {@link KeyguardSecurityView#PROMPT_REASON_RESTART}
156      */
showPromptReason(int reason)157     public void showPromptReason(int reason) {
158         mKeyguardView.showPromptReason(reason);
159     }
160 
showMessage(String message, int color)161     public void showMessage(String message, int color) {
162         mKeyguardView.showMessage(message, color);
163     }
164 
cancelShowRunnable()165     private void cancelShowRunnable() {
166         DejankUtils.removeCallbacks(mShowRunnable);
167         mShowingSoon = false;
168     }
169 
showWithDismissAction(OnDismissAction r, Runnable cancelAction)170     public void showWithDismissAction(OnDismissAction r, Runnable cancelAction) {
171         ensureView();
172         mKeyguardView.setOnDismissAction(r, cancelAction);
173         show(false /* resetSecuritySelection */);
174     }
175 
hide(boolean destroyView)176     public void hide(boolean destroyView) {
177         if (isShowing()) {
178             mDismissCallbackRegistry.notifyDismissCancelled();
179         }
180         mFalsingManager.onBouncerHidden();
181         cancelShowRunnable();
182         if (mKeyguardView != null) {
183             mKeyguardView.cancelDismissAction();
184             mKeyguardView.cleanUp();
185         }
186         if (mRoot != null) {
187             mRoot.setVisibility(View.INVISIBLE);
188             if (destroyView) {
189 
190                 // We have a ViewFlipper that unregisters a broadcast when being detached, which may
191                 // be slow because of AM lock contention during unlocking. We can delay it a bit.
192                 mHandler.postDelayed(mRemoveViewRunnable, 50);
193             }
194         }
195     }
196 
197     /**
198      * See {@link StatusBarKeyguardViewManager#startPreHideAnimation}.
199      */
startPreHideAnimation(Runnable runnable)200     public void startPreHideAnimation(Runnable runnable) {
201         if (mKeyguardView != null) {
202             mKeyguardView.startDisappearAnimation(runnable);
203         } else if (runnable != null) {
204             runnable.run();
205         }
206     }
207 
208     /**
209      * Reset the state of the view.
210      */
reset()211     public void reset() {
212         cancelShowRunnable();
213         inflateView();
214         mFalsingManager.onBouncerHidden();
215     }
216 
onScreenTurnedOff()217     public void onScreenTurnedOff() {
218         if (mKeyguardView != null && mRoot != null && mRoot.getVisibility() == View.VISIBLE) {
219             mKeyguardView.onPause();
220         }
221     }
222 
isShowing()223     public boolean isShowing() {
224         return mShowingSoon || (mRoot != null && mRoot.getVisibility() == View.VISIBLE);
225     }
226 
prepare()227     public void prepare() {
228         boolean wasInitialized = mRoot != null;
229         ensureView();
230         if (wasInitialized) {
231             mKeyguardView.showPrimarySecurityScreen();
232         }
233         mBouncerPromptReason = mCallback.getBouncerPromptReason();
234     }
235 
ensureView()236     protected void ensureView() {
237         mHandler.removeCallbacks(mRemoveViewRunnable);
238         if (mRoot == null) {
239             inflateView();
240         }
241     }
242 
inflateView()243     protected void inflateView() {
244         removeView();
245         mHandler.removeCallbacks(mRemoveViewRunnable);
246         mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
247         mKeyguardView = (KeyguardHostView) mRoot.findViewById(R.id.keyguard_host_view);
248         mKeyguardView.setLockPatternUtils(mLockPatternUtils);
249         mKeyguardView.setViewMediatorCallback(mCallback);
250         mContainer.addView(mRoot, mContainer.getChildCount());
251         mRoot.setVisibility(View.INVISIBLE);
252     }
253 
removeView()254     protected void removeView() {
255         if (mRoot != null && mRoot.getParent() == mContainer) {
256             mContainer.removeView(mRoot);
257             mRoot = null;
258         }
259     }
260 
onBackPressed()261     public boolean onBackPressed() {
262         return mKeyguardView != null && mKeyguardView.handleBackKey();
263     }
264 
265     /**
266      * @return True if and only if the security method should be shown before showing the
267      * notifications on Keyguard, like SIM PIN/PUK.
268      */
needsFullscreenBouncer()269     public boolean needsFullscreenBouncer() {
270         ensureView();
271         if (mKeyguardView != null) {
272             SecurityMode mode = mKeyguardView.getSecurityMode();
273             return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
274         }
275         return false;
276     }
277 
278     /**
279      * Like {@link #needsFullscreenBouncer}, but uses the currently visible security method, which
280      * makes this method much faster.
281      */
isFullscreenBouncer()282     public boolean isFullscreenBouncer() {
283         if (mKeyguardView != null) {
284             SecurityMode mode = mKeyguardView.getCurrentSecurityMode();
285             return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
286         }
287         return false;
288     }
289 
290     /**
291      * WARNING: This method might cause Binder calls.
292      */
isSecure()293     public boolean isSecure() {
294         return mKeyguardView == null || mKeyguardView.getSecurityMode() != SecurityMode.None;
295     }
296 
shouldDismissOnMenuPressed()297     public boolean shouldDismissOnMenuPressed() {
298         return mKeyguardView.shouldEnableMenuKey();
299     }
300 
interceptMediaKey(KeyEvent event)301     public boolean interceptMediaKey(KeyEvent event) {
302         ensureView();
303         return mKeyguardView.interceptMediaKey(event);
304     }
305 
notifyKeyguardAuthenticated(boolean strongAuth)306     public void notifyKeyguardAuthenticated(boolean strongAuth) {
307         ensureView();
308         mKeyguardView.finish(strongAuth, KeyguardUpdateMonitor.getCurrentUser());
309     }
310 }
311