1 /* 2 * Copyright (C) 2007 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.Context; 21 import android.content.res.ColorStateList; 22 import android.content.res.Resources; 23 import android.graphics.Canvas; 24 import android.media.AudioManager; 25 import android.os.SystemClock; 26 import android.service.trust.TrustAgentService; 27 import android.telephony.TelephonyManager; 28 import android.util.AttributeSet; 29 import android.util.Log; 30 import android.view.KeyEvent; 31 import android.widget.FrameLayout; 32 33 import androidx.annotation.VisibleForTesting; 34 35 import com.android.internal.widget.LockPatternUtils; 36 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; 37 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 38 import com.android.settingslib.Utils; 39 import com.android.systemui.Dependency; 40 import com.android.systemui.R; 41 import com.android.systemui.plugins.ActivityStarter.OnDismissAction; 42 43 import java.io.File; 44 45 /** 46 * Base class for keyguard view. {@link #reset} is where you should 47 * reset the state of your view. Use the {@link KeyguardViewCallback} via 48 * {@link #getCallback()} to send information back (such as poking the wake lock, 49 * or finishing the keyguard). 50 * 51 * Handles intercepting of media keys that still work when the keyguard is 52 * showing. 53 */ 54 public class KeyguardHostView extends FrameLayout implements SecurityCallback { 55 56 private AudioManager mAudioManager; 57 private TelephonyManager mTelephonyManager = null; 58 protected ViewMediatorCallback mViewMediatorCallback; 59 protected LockPatternUtils mLockPatternUtils; 60 private OnDismissAction mDismissAction; 61 private Runnable mCancelAction; 62 63 private final KeyguardUpdateMonitorCallback mUpdateCallback = 64 new KeyguardUpdateMonitorCallback() { 65 66 @Override 67 public void onUserSwitchComplete(int userId) { 68 getSecurityContainer().showPrimarySecurityScreen(false /* turning off */); 69 } 70 71 @Override 72 public void onTrustGrantedWithFlags(int flags, int userId) { 73 if (userId != KeyguardUpdateMonitor.getCurrentUser()) return; 74 if (!isAttachedToWindow()) return; 75 boolean bouncerVisible = isVisibleToUser(); 76 boolean initiatedByUser = 77 (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0; 78 boolean dismissKeyguard = 79 (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0; 80 81 if (initiatedByUser || dismissKeyguard) { 82 if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) { 83 if (!bouncerVisible) { 84 // The trust agent dismissed the keyguard without the user proving 85 // that they are present (by swiping up to show the bouncer). That's fine if 86 // the user proved presence via some other way to the trust agent. 87 Log.i(TAG, "TrustAgent dismissed Keyguard."); 88 } 89 dismiss(false /* authenticated */, userId, 90 /* bypassSecondaryLockScreen */ false); 91 } else { 92 mViewMediatorCallback.playTrustedSound(); 93 } 94 } 95 } 96 }; 97 98 // Whether the volume keys should be handled by keyguard. If true, then 99 // they will be handled here for specific media types such as music, otherwise 100 // the audio service will bring up the volume dialog. 101 private static final boolean KEYGUARD_MANAGES_VOLUME = false; 102 public static final boolean DEBUG = KeyguardConstants.DEBUG; 103 private static final String TAG = "KeyguardViewBase"; 104 105 @VisibleForTesting 106 protected KeyguardSecurityContainer mSecurityContainer; 107 KeyguardHostView(Context context)108 public KeyguardHostView(Context context) { 109 this(context, null); 110 } 111 KeyguardHostView(Context context, AttributeSet attrs)112 public KeyguardHostView(Context context, AttributeSet attrs) { 113 super(context, attrs); 114 Dependency.get(KeyguardUpdateMonitor.class).registerCallback(mUpdateCallback); 115 } 116 117 @Override dispatchDraw(Canvas canvas)118 protected void dispatchDraw(Canvas canvas) { 119 super.dispatchDraw(canvas); 120 if (mViewMediatorCallback != null) { 121 mViewMediatorCallback.keyguardDoneDrawing(); 122 } 123 } 124 125 /** 126 * Sets an action to run when keyguard finishes. 127 * 128 * @param action 129 */ setOnDismissAction(OnDismissAction action, Runnable cancelAction)130 public void setOnDismissAction(OnDismissAction action, Runnable cancelAction) { 131 if (mCancelAction != null) { 132 mCancelAction.run(); 133 mCancelAction = null; 134 } 135 mDismissAction = action; 136 mCancelAction = cancelAction; 137 } 138 hasDismissActions()139 public boolean hasDismissActions() { 140 return mDismissAction != null || mCancelAction != null; 141 } 142 cancelDismissAction()143 public void cancelDismissAction() { 144 setOnDismissAction(null, null); 145 } 146 147 @Override onFinishInflate()148 protected void onFinishInflate() { 149 mSecurityContainer = 150 findViewById(R.id.keyguard_security_container); 151 mLockPatternUtils = new LockPatternUtils(mContext); 152 mSecurityContainer.setLockPatternUtils(mLockPatternUtils); 153 mSecurityContainer.setSecurityCallback(this); 154 mSecurityContainer.showPrimarySecurityScreen(false); 155 } 156 157 /** 158 * Called when the view needs to be shown. 159 */ showPrimarySecurityScreen()160 public void showPrimarySecurityScreen() { 161 if (DEBUG) Log.d(TAG, "show()"); 162 mSecurityContainer.showPrimarySecurityScreen(false); 163 } 164 getCurrentSecurityView()165 public KeyguardSecurityView getCurrentSecurityView() { 166 return mSecurityContainer != null ? mSecurityContainer.getCurrentSecurityView() : null; 167 } 168 169 /** 170 * Show a string explaining why the security view needs to be solved. 171 * 172 * @param reason a flag indicating which string should be shown, see 173 * {@link KeyguardSecurityView#PROMPT_REASON_NONE}, 174 * {@link KeyguardSecurityView#PROMPT_REASON_RESTART}, 175 * {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and 176 * {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}. 177 */ showPromptReason(int reason)178 public void showPromptReason(int reason) { 179 mSecurityContainer.showPromptReason(reason); 180 } 181 showMessage(CharSequence message, ColorStateList colorState)182 public void showMessage(CharSequence message, ColorStateList colorState) { 183 mSecurityContainer.showMessage(message, colorState); 184 } 185 showErrorMessage(CharSequence message)186 public void showErrorMessage(CharSequence message) { 187 showMessage(message, Utils.getColorError(mContext)); 188 } 189 190 /** 191 * Dismisses the keyguard by going to the next screen or making it gone. 192 * @param targetUserId a user that needs to be the foreground user at the dismissal completion. 193 * @return True if the keyguard is done. 194 */ dismiss(int targetUserId)195 public boolean dismiss(int targetUserId) { 196 return dismiss(false, targetUserId, false); 197 } 198 handleBackKey()199 public boolean handleBackKey() { 200 if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) { 201 mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser()); 202 return true; 203 } 204 return false; 205 } 206 getSecurityContainer()207 protected KeyguardSecurityContainer getSecurityContainer() { 208 return mSecurityContainer; 209 } 210 211 @Override dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen)212 public boolean dismiss(boolean authenticated, int targetUserId, 213 boolean bypassSecondaryLockScreen) { 214 return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId, 215 bypassSecondaryLockScreen); 216 } 217 218 /** 219 * Authentication has happened and it's time to dismiss keyguard. This function 220 * should clean up and inform KeyguardViewMediator. 221 * 222 * @param strongAuth whether the user has authenticated with strong authentication like 223 * pattern, password or PIN but not by trust agents or fingerprint 224 * @param targetUserId a user that needs to be the foreground user at the dismissal completion. 225 */ 226 @Override finish(boolean strongAuth, int targetUserId)227 public void finish(boolean strongAuth, int targetUserId) { 228 // If there's a pending runnable because the user interacted with a widget 229 // and we're leaving keyguard, then run it. 230 boolean deferKeyguardDone = false; 231 if (mDismissAction != null) { 232 deferKeyguardDone = mDismissAction.onDismiss(); 233 mDismissAction = null; 234 mCancelAction = null; 235 } 236 if (mViewMediatorCallback != null) { 237 if (deferKeyguardDone) { 238 mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId); 239 } else { 240 mViewMediatorCallback.keyguardDone(strongAuth, targetUserId); 241 } 242 } 243 } 244 245 @Override reset()246 public void reset() { 247 mViewMediatorCallback.resetKeyguard(); 248 } 249 250 @Override onCancelClicked()251 public void onCancelClicked() { 252 mViewMediatorCallback.onCancelClicked(); 253 } 254 resetSecurityContainer()255 public void resetSecurityContainer() { 256 mSecurityContainer.reset(); 257 } 258 259 @Override onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)260 public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { 261 if (mViewMediatorCallback != null) { 262 mViewMediatorCallback.setNeedsInput(needsInput); 263 } 264 } 265 getAccessibilityTitleForCurrentMode()266 public CharSequence getAccessibilityTitleForCurrentMode() { 267 return mSecurityContainer.getTitle(); 268 } 269 userActivity()270 public void userActivity() { 271 if (mViewMediatorCallback != null) { 272 mViewMediatorCallback.userActivity(); 273 } 274 } 275 276 /** 277 * Called when the Keyguard is not actively shown anymore on the screen. 278 */ onPause()279 public void onPause() { 280 if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s", 281 Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); 282 mSecurityContainer.showPrimarySecurityScreen(true); 283 mSecurityContainer.onPause(); 284 clearFocus(); 285 } 286 287 /** 288 * Called when the Keyguard is actively shown on the screen. 289 */ onResume()290 public void onResume() { 291 if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); 292 mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON); 293 requestFocus(); 294 } 295 296 /** 297 * Starts the animation when the Keyguard gets shown. 298 */ startAppearAnimation()299 public void startAppearAnimation() { 300 mSecurityContainer.startAppearAnimation(); 301 } 302 startDisappearAnimation(Runnable finishRunnable)303 public void startDisappearAnimation(Runnable finishRunnable) { 304 if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) { 305 finishRunnable.run(); 306 } 307 } 308 309 /** 310 * Called before this view is being removed. 311 */ cleanUp()312 public void cleanUp() { 313 getSecurityContainer().onPause(); 314 } 315 316 @Override dispatchKeyEvent(KeyEvent event)317 public boolean dispatchKeyEvent(KeyEvent event) { 318 if (interceptMediaKey(event)) { 319 return true; 320 } 321 return super.dispatchKeyEvent(event); 322 } 323 324 /** 325 * Allows the media keys to work when the keyguard is showing. 326 * The media keys should be of no interest to the actual keyguard view(s), 327 * so intercepting them here should not be of any harm. 328 * @param event The key event 329 * @return whether the event was consumed as a media key. 330 */ interceptMediaKey(KeyEvent event)331 public boolean interceptMediaKey(KeyEvent event) { 332 final int keyCode = event.getKeyCode(); 333 if (event.getAction() == KeyEvent.ACTION_DOWN) { 334 switch (keyCode) { 335 case KeyEvent.KEYCODE_MEDIA_PLAY: 336 case KeyEvent.KEYCODE_MEDIA_PAUSE: 337 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 338 /* Suppress PLAY/PAUSE toggle when phone is ringing or 339 * in-call to avoid music playback */ 340 if (mTelephonyManager == null) { 341 mTelephonyManager = (TelephonyManager) getContext().getSystemService( 342 Context.TELEPHONY_SERVICE); 343 } 344 if (mTelephonyManager != null && 345 mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { 346 return true; // suppress key event 347 } 348 case KeyEvent.KEYCODE_MUTE: 349 case KeyEvent.KEYCODE_HEADSETHOOK: 350 case KeyEvent.KEYCODE_MEDIA_STOP: 351 case KeyEvent.KEYCODE_MEDIA_NEXT: 352 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 353 case KeyEvent.KEYCODE_MEDIA_REWIND: 354 case KeyEvent.KEYCODE_MEDIA_RECORD: 355 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 356 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 357 handleMediaKeyEvent(event); 358 return true; 359 } 360 361 case KeyEvent.KEYCODE_VOLUME_UP: 362 case KeyEvent.KEYCODE_VOLUME_DOWN: 363 case KeyEvent.KEYCODE_VOLUME_MUTE: { 364 if (KEYGUARD_MANAGES_VOLUME) { 365 synchronized (this) { 366 if (mAudioManager == null) { 367 mAudioManager = (AudioManager) getContext().getSystemService( 368 Context.AUDIO_SERVICE); 369 } 370 } 371 // Volume buttons should only function for music (local or remote). 372 // TODO: Actually handle MUTE. 373 mAudioManager.adjustSuggestedStreamVolume( 374 keyCode == KeyEvent.KEYCODE_VOLUME_UP 375 ? AudioManager.ADJUST_RAISE 376 : AudioManager.ADJUST_LOWER /* direction */, 377 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); 378 // Don't execute default volume behavior 379 return true; 380 } else { 381 return false; 382 } 383 } 384 } 385 } else if (event.getAction() == KeyEvent.ACTION_UP) { 386 switch (keyCode) { 387 case KeyEvent.KEYCODE_MUTE: 388 case KeyEvent.KEYCODE_HEADSETHOOK: 389 case KeyEvent.KEYCODE_MEDIA_PLAY: 390 case KeyEvent.KEYCODE_MEDIA_PAUSE: 391 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 392 case KeyEvent.KEYCODE_MEDIA_STOP: 393 case KeyEvent.KEYCODE_MEDIA_NEXT: 394 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 395 case KeyEvent.KEYCODE_MEDIA_REWIND: 396 case KeyEvent.KEYCODE_MEDIA_RECORD: 397 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 398 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 399 handleMediaKeyEvent(event); 400 return true; 401 } 402 } 403 } 404 return false; 405 } 406 handleMediaKeyEvent(KeyEvent keyEvent)407 private void handleMediaKeyEvent(KeyEvent keyEvent) { 408 synchronized (this) { 409 if (mAudioManager == null) { 410 mAudioManager = (AudioManager) getContext().getSystemService( 411 Context.AUDIO_SERVICE); 412 } 413 } 414 mAudioManager.dispatchMediaKeyEvent(keyEvent); 415 } 416 417 /** 418 * In general, we enable unlocking the insecure keyguard with the menu key. However, there are 419 * some cases where we wish to disable it, notably when the menu button placement or technology 420 * is prone to false positives. 421 * 422 * @return true if the menu key should be enabled 423 */ 424 private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; shouldEnableMenuKey()425 public boolean shouldEnableMenuKey() { 426 final Resources res = getResources(); 427 final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); 428 final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); 429 final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); 430 return !configDisabled || isTestHarness || fileOverride; 431 } 432 setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)433 public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) { 434 mViewMediatorCallback = viewMediatorCallback; 435 // Update ViewMediator with the current input method requirements 436 mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput()); 437 } 438 setLockPatternUtils(LockPatternUtils utils)439 public void setLockPatternUtils(LockPatternUtils utils) { 440 mLockPatternUtils = utils; 441 mSecurityContainer.setLockPatternUtils(utils); 442 } 443 getSecurityMode()444 public SecurityMode getSecurityMode() { 445 return mSecurityContainer.getSecurityMode(); 446 } 447 getCurrentSecurityMode()448 public SecurityMode getCurrentSecurityMode() { 449 return mSecurityContainer.getCurrentSecurityMode(); 450 } 451 452 /** 453 * When bouncer was visible and is starting to become hidden. 454 */ onStartingToHide()455 public void onStartingToHide() { 456 mSecurityContainer.onStartingToHide(); 457 } 458 } 459