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 323 /** 324 * Fades and translates in/out the security screen. 325 * @param fraction amount of the screen that should show. 326 */ setExpansion(float fraction)327 public void setExpansion(float fraction) { 328 float alpha = MathUtils.map(KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction); 329 mView.setAlpha(MathUtils.constrain(alpha, 0f, 1f)); 330 mView.setTranslationY(fraction * mView.getHeight()); 331 } 332 333 /** 334 * When bouncer was visible and is starting to become hidden. 335 */ onStartingToHide()336 public void onStartingToHide() { 337 mKeyguardSecurityContainerController.onStartingToHide(); 338 } 339 hasDismissActions()340 public boolean hasDismissActions() { 341 return mDismissAction != null || mCancelAction != null; 342 } 343 getCurrentSecurityMode()344 public SecurityMode getCurrentSecurityMode() { 345 return mKeyguardSecurityContainerController.getCurrentSecurityMode(); 346 } 347 getTop()348 public int getTop() { 349 int top = mView.getTop(); 350 // The password view has an extra top padding that should be ignored. 351 if (getCurrentSecurityMode() == SecurityMode.Password) { 352 View messageArea = mView.findViewById(R.id.keyguard_message_area); 353 top += messageArea.getTop(); 354 } 355 return top; 356 } 357 handleBackKey()358 public boolean handleBackKey() { 359 if (mKeyguardSecurityContainerController.getCurrentSecurityMode() 360 != SecurityMode.None) { 361 mKeyguardSecurityContainerController.dismiss( 362 false, KeyguardUpdateMonitor.getCurrentUser()); 363 return true; 364 } 365 return false; 366 } 367 368 /** 369 * In general, we enable unlocking the insecure keyguard with the menu key. However, there are 370 * some cases where we wish to disable it, notably when the menu button placement or technology 371 * is prone to false positives. 372 * 373 * @return true if the menu key should be enabled 374 */ shouldEnableMenuKey()375 public boolean shouldEnableMenuKey() { 376 final Resources res = mView.getResources(); 377 final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); 378 final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); 379 final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); 380 return !configDisabled || isTestHarness || fileOverride; 381 } 382 383 /** 384 * @return true if the current bouncer is password 385 */ dispatchBackKeyEventPreIme()386 public boolean dispatchBackKeyEventPreIme() { 387 if (mKeyguardSecurityContainerController.getCurrentSecurityMode() 388 == SecurityMode.Password) { 389 return true; 390 } 391 return false; 392 } 393 394 /** 395 * Allows the media keys to work when the keyguard is showing. 396 * The media keys should be of no interest to the actual keyguard view(s), 397 * so intercepting them here should not be of any harm. 398 * @param event The key event 399 * @return whether the event was consumed as a media key. 400 */ interceptMediaKey(KeyEvent event)401 public boolean interceptMediaKey(KeyEvent event) { 402 int keyCode = event.getKeyCode(); 403 if (event.getAction() == KeyEvent.ACTION_DOWN) { 404 switch (keyCode) { 405 case KeyEvent.KEYCODE_MEDIA_PLAY: 406 case KeyEvent.KEYCODE_MEDIA_PAUSE: 407 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 408 /* Suppress PLAY/PAUSE toggle when phone is ringing or 409 * in-call to avoid music playback */ 410 if (mTelephonyManager != null && 411 mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { 412 return true; // suppress key event 413 } 414 case KeyEvent.KEYCODE_MUTE: 415 case KeyEvent.KEYCODE_HEADSETHOOK: 416 case KeyEvent.KEYCODE_MEDIA_STOP: 417 case KeyEvent.KEYCODE_MEDIA_NEXT: 418 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 419 case KeyEvent.KEYCODE_MEDIA_REWIND: 420 case KeyEvent.KEYCODE_MEDIA_RECORD: 421 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 422 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 423 handleMediaKeyEvent(event); 424 return true; 425 } 426 427 case KeyEvent.KEYCODE_VOLUME_UP: 428 case KeyEvent.KEYCODE_VOLUME_DOWN: 429 case KeyEvent.KEYCODE_VOLUME_MUTE: { 430 if (KEYGUARD_MANAGES_VOLUME) { 431 // Volume buttons should only function for music (local or remote). 432 // TODO: Actually handle MUTE. 433 mAudioManager.adjustSuggestedStreamVolume( 434 keyCode == KeyEvent.KEYCODE_VOLUME_UP 435 ? AudioManager.ADJUST_RAISE 436 : AudioManager.ADJUST_LOWER /* direction */, 437 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); 438 // Don't execute default volume behavior 439 return true; 440 } else { 441 return false; 442 } 443 } 444 } 445 } else if (event.getAction() == KeyEvent.ACTION_UP) { 446 switch (keyCode) { 447 case KeyEvent.KEYCODE_MUTE: 448 case KeyEvent.KEYCODE_HEADSETHOOK: 449 case KeyEvent.KEYCODE_MEDIA_PLAY: 450 case KeyEvent.KEYCODE_MEDIA_PAUSE: 451 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 452 case KeyEvent.KEYCODE_MEDIA_STOP: 453 case KeyEvent.KEYCODE_MEDIA_NEXT: 454 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 455 case KeyEvent.KEYCODE_MEDIA_REWIND: 456 case KeyEvent.KEYCODE_MEDIA_RECORD: 457 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 458 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 459 handleMediaKeyEvent(event); 460 return true; 461 } 462 } 463 } 464 return false; 465 } 466 467 handleMediaKeyEvent(KeyEvent keyEvent)468 private void handleMediaKeyEvent(KeyEvent keyEvent) { 469 mAudioManager.dispatchMediaKeyEvent(keyEvent); 470 } 471 finish(boolean strongAuth, int currentUser)472 public void finish(boolean strongAuth, int currentUser) { 473 mSecurityCallback.finish(strongAuth, currentUser); 474 } 475 476 /** 477 * Apply keyguard configuration from the currently active resources. This can be called when the 478 * device configuration changes, to re-apply some resources that are qualified on the device 479 * configuration. 480 */ updateResources()481 public void updateResources() { 482 int gravity; 483 484 Resources resources = mView.getResources(); 485 486 if (resources.getBoolean(R.bool.can_use_one_handed_bouncer) 487 && resources.getBoolean( 488 com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) { 489 gravity = resources.getInteger( 490 R.integer.keyguard_host_view_one_handed_gravity); 491 } else { 492 gravity = resources.getInteger(R.integer.keyguard_host_view_gravity); 493 } 494 495 // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout. 496 // We're just changing the gravity here though (which can't be applied to RelativeLayout), 497 // so only attempt the update if mView is inside a FrameLayout. 498 if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) { 499 FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams(); 500 if (lp.gravity != gravity) { 501 lp.gravity = gravity; 502 mView.setLayoutParams(lp); 503 } 504 } 505 506 if (mKeyguardSecurityContainerController != null) { 507 mKeyguardSecurityContainerController.updateResources(); 508 } 509 } 510 511 /** Update keyguard position based on a tapped X coordinate. */ updateKeyguardPosition(float x)512 public void updateKeyguardPosition(float x) { 513 if (mKeyguardSecurityContainerController != null) { 514 mKeyguardSecurityContainerController.updateKeyguardPosition(x); 515 } 516 } 517 } 518