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