1 /* 2 * Copyright (C) 2020 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.keyguard; 18 19 import android.app.ActivityManager; 20 import android.content.res.ColorStateList; 21 import android.content.res.Resources; 22 import android.media.AudioManager; 23 import android.os.SystemClock; 24 import android.service.trust.TrustAgentService; 25 import android.telephony.TelephonyManager; 26 import android.util.Log; 27 import android.util.MathUtils; 28 import android.view.KeyEvent; 29 import android.view.View; 30 import android.view.View.OnKeyListener; 31 import android.view.ViewTreeObserver; 32 import android.widget.FrameLayout; 33 34 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; 35 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 36 import com.android.keyguard.dagger.KeyguardBouncerScope; 37 import com.android.settingslib.Utils; 38 import com.android.systemui.R; 39 import com.android.systemui.plugins.ActivityStarter; 40 import com.android.systemui.statusbar.phone.KeyguardBouncer; 41 import com.android.systemui.util.ViewController; 42 43 import java.io.File; 44 45 import javax.inject.Inject; 46 47 /** Controller for a {@link KeyguardHostView}. */ 48 @KeyguardBouncerScope 49 public class KeyguardHostViewController extends ViewController<KeyguardHostView> { 50 private static final String TAG = "KeyguardViewBase"; 51 public static final boolean DEBUG = KeyguardConstants.DEBUG; 52 // Whether the volume keys should be handled by keyguard. If true, then 53 // they will be handled here for specific media types such as music, otherwise 54 // the audio service will bring up the volume dialog. 55 private static final boolean KEYGUARD_MANAGES_VOLUME = false; 56 57 private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; 58 59 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 60 private final KeyguardSecurityContainerController mKeyguardSecurityContainerController; 61 private final TelephonyManager mTelephonyManager; 62 private final ViewMediatorCallback mViewMediatorCallback; 63 private final AudioManager mAudioManager; 64 65 private ActivityStarter.OnDismissAction mDismissAction; 66 private Runnable mCancelAction; 67 68 private final KeyguardUpdateMonitorCallback mUpdateCallback = 69 new KeyguardUpdateMonitorCallback() { 70 @Override 71 public void onUserSwitchComplete(int userId) { 72 mKeyguardSecurityContainerController.showPrimarySecurityScreen( 73 false /* turning off */); 74 } 75 76 @Override 77 public void onTrustGrantedWithFlags(int flags, int userId) { 78 if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; 79 boolean bouncerVisible = mView.isVisibleToUser(); 80 boolean initiatedByUser = 81 (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; 82 boolean dismissKeyguard = 83 (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; 84 85 if (initiatedByUser || dismissKeyguard) { 86 if (mViewMediatorCallback.isScreenOn() 87 && (bouncerVisible || dismissKeyguard)) { 88 if (!bouncerVisible) { 89 // The trust agent dismissed the keyguard without the user proving 90 // that they are present (by swiping up to show the bouncer). That's 91 // fine if the user proved presence via some other way to the trust 92 //agent. 93 Log.i(TAG, "TrustAgent dismissed Keyguard."); 94 } 95 mSecurityCallback.dismiss(false /* authenticated */, userId, 96 /* bypassSecondaryLockScreen */ false); 97 } else { 98 mViewMediatorCallback.playTrustedSound(); 99 } 100 } 101 } 102 }; 103 104 private final SecurityCallback mSecurityCallback = new SecurityCallback() { 105 106 @Override 107 public boolean dismiss(boolean authenticated, int targetUserId, 108 boolean bypassSecondaryLockScreen) { 109 return mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish( 110 authenticated, targetUserId, bypassSecondaryLockScreen); 111 } 112 113 @Override 114 public void userActivity() { 115 mViewMediatorCallback.userActivity(); 116 } 117 118 @Override 119 public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { 120 mViewMediatorCallback.setNeedsInput(needsInput); 121 } 122 123 /** 124 * Authentication has happened and it's time to dismiss keyguard. This function 125 * should clean up and inform KeyguardViewMediator. 126 * 127 * @param strongAuth whether the user has authenticated with strong authentication like 128 * pattern, password or PIN but not by trust agents or fingerprint 129 * @param targetUserId a user that needs to be the foreground user at the dismissal 130 * completion. 131 */ 132 @Override 133 public void finish(boolean strongAuth, int targetUserId) { 134 // If there's a pending runnable because the user interacted with a widget 135 // and we're leaving keyguard, then run it. 136 boolean deferKeyguardDone = false; 137 if (mDismissAction != null) { 138 deferKeyguardDone = mDismissAction.onDismiss(); 139 mDismissAction = null; 140 mCancelAction = null; 141 } 142 if (mViewMediatorCallback != null) { 143 if (deferKeyguardDone) { 144 mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId); 145 } else { 146 mViewMediatorCallback.keyguardDone(strongAuth, targetUserId); 147 } 148 } 149 } 150 151 @Override 152 public void reset() { 153 mViewMediatorCallback.resetKeyguard(); 154 } 155 156 @Override 157 public void onCancelClicked() { 158 mViewMediatorCallback.onCancelClicked(); 159 } 160 }; 161 162 private OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event); 163 164 @Inject KeyguardHostViewController(KeyguardHostView view, KeyguardUpdateMonitor keyguardUpdateMonitor, AudioManager audioManager, TelephonyManager telephonyManager, ViewMediatorCallback viewMediatorCallback, KeyguardSecurityContainerController.Factory keyguardSecurityContainerControllerFactory)165 public KeyguardHostViewController(KeyguardHostView view, 166 KeyguardUpdateMonitor keyguardUpdateMonitor, 167 AudioManager audioManager, 168 TelephonyManager telephonyManager, 169 ViewMediatorCallback viewMediatorCallback, 170 KeyguardSecurityContainerController.Factory 171 keyguardSecurityContainerControllerFactory) { 172 super(view); 173 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 174 mAudioManager = audioManager; 175 mTelephonyManager = telephonyManager; 176 mViewMediatorCallback = viewMediatorCallback; 177 mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create( 178 mSecurityCallback); 179 } 180 181 /** Initialize the Controller. */ onInit()182 public void onInit() { 183 mKeyguardSecurityContainerController.init(); 184 updateResources(); 185 } 186 187 @Override onViewAttached()188 protected void onViewAttached() { 189 mView.setViewMediatorCallback(mViewMediatorCallback); 190 // Update ViewMediator with the current input method requirements 191 mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput()); 192 mKeyguardUpdateMonitor.registerCallback(mUpdateCallback); 193 mView.setOnKeyListener(mOnKeyListener); 194 mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); 195 } 196 197 @Override onViewDetached()198 protected void onViewDetached() { 199 mKeyguardUpdateMonitor.removeCallback(mUpdateCallback); 200 mView.setOnKeyListener(null); 201 } 202 203 /** Called before this view is being removed. */ cleanUp()204 public void cleanUp() { 205 mKeyguardSecurityContainerController.onPause(); 206 } 207 resetSecurityContainer()208 public void resetSecurityContainer() { 209 mKeyguardSecurityContainerController.reset(); 210 } 211 212 /** 213 * Dismisses the keyguard by going to the next screen or making it gone. 214 * @param targetUserId a user that needs to be the foreground user at the dismissal completion. 215 * @return True if the keyguard is done. 216 */ dismiss(int targetUserId)217 public boolean dismiss(int targetUserId) { 218 return mSecurityCallback.dismiss(false, targetUserId, false); 219 } 220 221 /** 222 * Called when the Keyguard is actively shown on the screen. 223 */ onResume()224 public void onResume() { 225 if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); 226 mKeyguardSecurityContainerController.onResume(KeyguardSecurityView.SCREEN_ON); 227 mView.requestFocus(); 228 } 229 getAccessibilityTitleForCurrentMode()230 public CharSequence getAccessibilityTitleForCurrentMode() { 231 return mKeyguardSecurityContainerController.getTitle(); 232 } 233 234 /** 235 * Starts the animation when the Keyguard gets shown. 236 */ appear(int statusBarHeight)237 public void appear(int statusBarHeight) { 238 // We might still be collapsed and the view didn't have time to layout yet or still 239 // be small, let's wait on the predraw to do the animation in that case. 240 if (mView.getHeight() != 0 && mView.getHeight() != statusBarHeight) { 241 mKeyguardSecurityContainerController.startAppearAnimation(); 242 } else { 243 mView.getViewTreeObserver().addOnPreDrawListener( 244 new ViewTreeObserver.OnPreDrawListener() { 245 @Override 246 public boolean onPreDraw() { 247 mView.getViewTreeObserver().removeOnPreDrawListener(this); 248 mKeyguardSecurityContainerController.startAppearAnimation(); 249 return true; 250 } 251 }); 252 mView.requestLayout(); 253 } 254 } 255 256 /** 257 * Show a string explaining why the security view needs to be solved. 258 * 259 * @param reason a flag indicating which string should be shown, see 260 * {@link KeyguardSecurityView#PROMPT_REASON_NONE}, 261 * {@link KeyguardSecurityView#PROMPT_REASON_RESTART}, 262 * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and 263 * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}. 264 */ showPromptReason(int reason)265 public void showPromptReason(int reason) { 266 mKeyguardSecurityContainerController.showPromptReason(reason); 267 } 268 showMessage(CharSequence message, ColorStateList colorState)269 public void showMessage(CharSequence message, ColorStateList colorState) { 270 mKeyguardSecurityContainerController.showMessage(message, colorState); 271 } 272 showErrorMessage(CharSequence customMessage)273 public void showErrorMessage(CharSequence customMessage) { 274 showMessage(customMessage, Utils.getColorError(mView.getContext())); 275 } 276 277 /** 278 * Sets an action to run when keyguard finishes. 279 * 280 * @param action 281 */ setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction)282 public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) { 283 if (mCancelAction != null) { 284 mCancelAction.run(); 285 mCancelAction = null; 286 } 287 mDismissAction = action; 288 mCancelAction = cancelAction; 289 } 290 cancelDismissAction()291 public void cancelDismissAction() { 292 setOnDismissAction(null, null); 293 } 294 startDisappearAnimation(Runnable finishRunnable)295 public void startDisappearAnimation(Runnable finishRunnable) { 296 if (!mKeyguardSecurityContainerController.startDisappearAnimation(finishRunnable) 297 && finishRunnable != null) { 298 finishRunnable.run(); 299 } 300 } 301 302 /** 303 * Called when the Keyguard is not actively shown anymore on the screen. 304 */ onPause()305 public void onPause() { 306 if (DEBUG) { 307 Log.d(TAG, String.format("screen off, instance %s at %s", 308 Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); 309 } 310 mKeyguardSecurityContainerController.showPrimarySecurityScreen(true); 311 mKeyguardSecurityContainerController.onPause(); 312 mView.clearFocus(); 313 } 314 315 /** 316 * Called when the view needs to be shown. 317 */ showPrimarySecurityScreen()318 public void showPrimarySecurityScreen() { 319 if (DEBUG) Log.d(TAG, "show()"); 320 mKeyguardSecurityContainerController.showPrimarySecurityScreen(false); 321 } 322 setExpansion(float fraction)323 public void setExpansion(float fraction) { 324 float alpha = MathUtils.map(KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction); 325 mView.setAlpha(MathUtils.constrain(alpha, 0f, 1f)); 326 mView.setTranslationY(fraction * mView.getHeight()); 327 } 328 329 /** 330 * When bouncer was visible and is starting to become hidden. 331 */ onStartingToHide()332 public void onStartingToHide() { 333 mKeyguardSecurityContainerController.onStartingToHide(); 334 } 335 hasDismissActions()336 public boolean hasDismissActions() { 337 return mDismissAction != null || mCancelAction != null; 338 } 339 getCurrentSecurityMode()340 public SecurityMode getCurrentSecurityMode() { 341 return mKeyguardSecurityContainerController.getCurrentSecurityMode(); 342 } 343 getTop()344 public int getTop() { 345 int top = mView.getTop(); 346 // The password view has an extra top padding that should be ignored. 347 if (getCurrentSecurityMode() == SecurityMode.Password) { 348 View messageArea = mView.findViewById(R.id.keyguard_message_area); 349 top += messageArea.getTop(); 350 } 351 return top; 352 } 353 handleBackKey()354 public boolean handleBackKey() { 355 if (mKeyguardSecurityContainerController.getCurrentSecurityMode() 356 != SecurityMode.None) { 357 mKeyguardSecurityContainerController.dismiss( 358 false, KeyguardUpdateMonitor.getCurrentUser()); 359 return true; 360 } 361 return false; 362 } 363 364 /** 365 * In general, we enable unlocking the insecure keyguard with the menu key. However, there are 366 * some cases where we wish to disable it, notably when the menu button placement or technology 367 * is prone to false positives. 368 * 369 * @return true if the menu key should be enabled 370 */ shouldEnableMenuKey()371 public boolean shouldEnableMenuKey() { 372 final Resources res = mView.getResources(); 373 final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); 374 final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); 375 final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); 376 return !configDisabled || isTestHarness || fileOverride; 377 } 378 379 /** 380 * @return true if the current bouncer is password 381 */ dispatchBackKeyEventPreIme()382 public boolean dispatchBackKeyEventPreIme() { 383 if (mKeyguardSecurityContainerController.getCurrentSecurityMode() 384 == SecurityMode.Password) { 385 return true; 386 } 387 return false; 388 } 389 390 /** 391 * Allows the media keys to work when the keyguard is showing. 392 * The media keys should be of no interest to the actual keyguard view(s), 393 * so intercepting them here should not be of any harm. 394 * @param event The key event 395 * @return whether the event was consumed as a media key. 396 */ interceptMediaKey(KeyEvent event)397 public boolean interceptMediaKey(KeyEvent event) { 398 int keyCode = event.getKeyCode(); 399 if (event.getAction() == KeyEvent.ACTION_DOWN) { 400 switch (keyCode) { 401 case KeyEvent.KEYCODE_MEDIA_PLAY: 402 case KeyEvent.KEYCODE_MEDIA_PAUSE: 403 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 404 /* Suppress PLAY/PAUSE toggle when phone is ringing or 405 * in-call to avoid music playback */ 406 if (mTelephonyManager != null && 407 mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { 408 return true; // suppress key event 409 } 410 case KeyEvent.KEYCODE_MUTE: 411 case KeyEvent.KEYCODE_HEADSETHOOK: 412 case KeyEvent.KEYCODE_MEDIA_STOP: 413 case KeyEvent.KEYCODE_MEDIA_NEXT: 414 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 415 case KeyEvent.KEYCODE_MEDIA_REWIND: 416 case KeyEvent.KEYCODE_MEDIA_RECORD: 417 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 418 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 419 handleMediaKeyEvent(event); 420 return true; 421 } 422 423 case KeyEvent.KEYCODE_VOLUME_UP: 424 case KeyEvent.KEYCODE_VOLUME_DOWN: 425 case KeyEvent.KEYCODE_VOLUME_MUTE: { 426 if (KEYGUARD_MANAGES_VOLUME) { 427 // Volume buttons should only function for music (local or remote). 428 // TODO: Actually handle MUTE. 429 mAudioManager.adjustSuggestedStreamVolume( 430 keyCode == KeyEvent.KEYCODE_VOLUME_UP 431 ? AudioManager.ADJUST_RAISE 432 : AudioManager.ADJUST_LOWER /* direction */, 433 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); 434 // Don't execute default volume behavior 435 return true; 436 } else { 437 return false; 438 } 439 } 440 } 441 } else if (event.getAction() == KeyEvent.ACTION_UP) { 442 switch (keyCode) { 443 case KeyEvent.KEYCODE_MUTE: 444 case KeyEvent.KEYCODE_HEADSETHOOK: 445 case KeyEvent.KEYCODE_MEDIA_PLAY: 446 case KeyEvent.KEYCODE_MEDIA_PAUSE: 447 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 448 case KeyEvent.KEYCODE_MEDIA_STOP: 449 case KeyEvent.KEYCODE_MEDIA_NEXT: 450 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 451 case KeyEvent.KEYCODE_MEDIA_REWIND: 452 case KeyEvent.KEYCODE_MEDIA_RECORD: 453 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 454 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 455 handleMediaKeyEvent(event); 456 return true; 457 } 458 } 459 } 460 return false; 461 } 462 463 handleMediaKeyEvent(KeyEvent keyEvent)464 private void handleMediaKeyEvent(KeyEvent keyEvent) { 465 mAudioManager.dispatchMediaKeyEvent(keyEvent); 466 } 467 finish(boolean strongAuth, int currentUser)468 public void finish(boolean strongAuth, int currentUser) { 469 mSecurityCallback.finish(strongAuth, currentUser); 470 } 471 472 /** 473 * Apply keyguard configuration from the currently active resources. This can be called when the 474 * device configuration changes, to re-apply some resources that are qualified on the device 475 * configuration. 476 */ updateResources()477 public void updateResources() { 478 int gravity; 479 480 Resources resources = mView.getResources(); 481 482 if (resources.getBoolean(R.bool.can_use_one_handed_bouncer) 483 && resources.getBoolean( 484 com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) { 485 gravity = resources.getInteger( 486 R.integer.keyguard_host_view_one_handed_gravity); 487 } else { 488 gravity = resources.getInteger(R.integer.keyguard_host_view_gravity); 489 } 490 491 // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout. 492 // We're just changing the gravity here though (which can't be applied to RelativeLayout), 493 // so only attempt the update if mView is inside a FrameLayout. 494 if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) { 495 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams(); 496 if (lp.gravity != gravity) { 497 lp.gravity = gravity; 498 mView.setLayoutParams(lp); 499 } 500 } 501 502 if (mKeyguardSecurityContainerController != null) { 503 mKeyguardSecurityContainerController.updateResources(); 504 } 505 } 506 507 /** Update keyguard position based on a tapped X coordinate. */ updateKeyguardPosition(float x)508 public void updateKeyguardPosition(float x) { 509 if (mKeyguardSecurityContainerController != null) { 510 mKeyguardSecurityContainerController.updateKeyguardPosition(x); 511 } 512 } 513 } 514