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.Activity; 20 import android.app.ActivityManager; 21 import android.app.ActivityOptions; 22 import android.app.SearchManager; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.res.Resources; 26 import android.graphics.Canvas; 27 import android.media.AudioManager; 28 import android.media.IAudioService; 29 import android.os.Bundle; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.SystemClock; 33 import android.os.UserHandle; 34 import android.telephony.TelephonyManager; 35 import android.util.AttributeSet; 36 import android.util.Log; 37 import android.util.Slog; 38 import android.view.KeyEvent; 39 import android.view.MotionEvent; 40 import android.view.View; 41 import android.widget.FrameLayout; 42 43 import com.android.internal.widget.LockPatternUtils; 44 import com.android.keyguard.KeyguardHostView.OnDismissAction; 45 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback; 46 import com.android.keyguard.KeyguardSecurityModel.SecurityMode; 47 48 import java.io.File; 49 50 /** 51 * Base class for keyguard view. {@link #reset} is where you should 52 * reset the state of your view. Use the {@link KeyguardViewCallback} via 53 * {@link #getCallback()} to send information back (such as poking the wake lock, 54 * or finishing the keyguard). 55 * 56 * Handles intercepting of media keys that still work when the keyguard is 57 * showing. 58 */ 59 public abstract class KeyguardViewBase extends FrameLayout implements SecurityCallback { 60 61 private AudioManager mAudioManager; 62 private TelephonyManager mTelephonyManager = null; 63 protected ViewMediatorCallback mViewMediatorCallback; 64 protected LockPatternUtils mLockPatternUtils; 65 private OnDismissAction mDismissAction; 66 67 // Whether the volume keys should be handled by keyguard. If true, then 68 // they will be handled here for specific media types such as music, otherwise 69 // the audio service will bring up the volume dialog. 70 private static final boolean KEYGUARD_MANAGES_VOLUME = false; 71 public static final boolean DEBUG = KeyguardConstants.DEBUG; 72 private static final String TAG = "KeyguardViewBase"; 73 74 private KeyguardSecurityContainer mSecurityContainer; 75 76 public KeyguardViewBase(Context context) { 77 this(context, null); 78 } 79 80 public KeyguardViewBase(Context context, AttributeSet attrs) { 81 super(context, attrs); 82 } 83 84 @Override 85 protected void dispatchDraw(Canvas canvas) { 86 super.dispatchDraw(canvas); 87 if (mViewMediatorCallback != null) { 88 mViewMediatorCallback.keyguardDoneDrawing(); 89 } 90 } 91 92 /** 93 * Sets an action to run when keyguard finishes. 94 * 95 * @param action 96 */ 97 public void setOnDismissAction(OnDismissAction action) { 98 mDismissAction = action; 99 } 100 101 @Override 102 protected void onFinishInflate() { 103 mSecurityContainer = 104 (KeyguardSecurityContainer) findViewById(R.id.keyguard_security_container); 105 mLockPatternUtils = new LockPatternUtils(mContext); 106 mSecurityContainer.setLockPatternUtils(mLockPatternUtils); 107 mSecurityContainer.setSecurityCallback(this); 108 mSecurityContainer.showPrimarySecurityScreen(false); 109 // mSecurityContainer.updateSecurityViews(false /* not bouncing */); 110 } 111 112 /** 113 * Called when the view needs to be shown. 114 */ 115 public void show() { 116 if (DEBUG) Log.d(TAG, "show()"); 117 mSecurityContainer.showPrimarySecurityScreen(false); 118 } 119 120 /** 121 * Dismisses the keyguard by going to the next screen or making it gone. 122 * 123 * @return True if the keyguard is done. 124 */ 125 public boolean dismiss() { 126 return dismiss(false); 127 } 128 129 protected void showBouncer(boolean show) { 130 CharSequence what = getContext().getResources().getText( 131 show ? R.string.keyguard_accessibility_show_bouncer 132 : R.string.keyguard_accessibility_hide_bouncer); 133 announceForAccessibility(what); 134 announceCurrentSecurityMethod(); 135 } 136 137 public boolean handleBackKey() { 138 if (mSecurityContainer.getCurrentSecuritySelection() == SecurityMode.Account) { 139 // go back to primary screen 140 mSecurityContainer.showPrimarySecurityScreen(false /*turningOff*/); 141 return true; 142 } 143 if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) { 144 mSecurityContainer.dismiss(false); 145 return true; 146 } 147 return false; 148 } 149 150 protected void announceCurrentSecurityMethod() { 151 mSecurityContainer.announceCurrentSecurityMethod(); 152 } 153 154 protected KeyguardSecurityContainer getSecurityContainer() { 155 return mSecurityContainer; 156 } 157 158 @Override 159 public boolean dismiss(boolean authenticated) { 160 return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated); 161 } 162 163 /** 164 * Authentication has happened and it's time to dismiss keyguard. This function 165 * should clean up and inform KeyguardViewMediator. 166 */ 167 @Override 168 public void finish() { 169 // If the alternate unlock was suppressed, it can now be safely 170 // enabled because the user has left keyguard. 171 KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); 172 173 // If there's a pending runnable because the user interacted with a widget 174 // and we're leaving keyguard, then run it. 175 boolean deferKeyguardDone = false; 176 if (mDismissAction != null) { 177 deferKeyguardDone = mDismissAction.onDismiss(); 178 mDismissAction = null; 179 } 180 if (mViewMediatorCallback != null) { 181 if (deferKeyguardDone) { 182 mViewMediatorCallback.keyguardDonePending(); 183 } else { 184 mViewMediatorCallback.keyguardDone(true); 185 } 186 } 187 } 188 189 @Override 190 public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) { 191 if (mViewMediatorCallback != null) { 192 mViewMediatorCallback.setNeedsInput(needsInput); 193 } 194 } 195 196 public void userActivity() { 197 if (mViewMediatorCallback != null) { 198 mViewMediatorCallback.userActivity(); 199 } 200 } 201 202 protected void onUserActivityTimeoutChanged() { 203 if (mViewMediatorCallback != null) { 204 mViewMediatorCallback.onUserActivityTimeoutChanged(); 205 } 206 } 207 208 /** 209 * Called when the Keyguard is not actively shown anymore on the screen. 210 */ 211 public void onPause() { 212 if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s", 213 Integer.toHexString(hashCode()), SystemClock.uptimeMillis())); 214 // Once the screen turns off, we no longer consider this to be first boot and we want the 215 // biometric unlock to start next time keyguard is shown. 216 KeyguardUpdateMonitor.getInstance(mContext).setAlternateUnlockEnabled(true); 217 mSecurityContainer.showPrimarySecurityScreen(true); 218 mSecurityContainer.onPause(); 219 clearFocus(); 220 } 221 222 /** 223 * Called when the Keyguard is actively shown on the screen. 224 */ 225 public void onResume() { 226 if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode())); 227 mSecurityContainer.showPrimarySecurityScreen(false); 228 mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON); 229 requestFocus(); 230 } 231 232 /** 233 * Starts the animation when the Keyguard gets shown. 234 */ 235 public void startAppearAnimation() { 236 mSecurityContainer.startAppearAnimation(); 237 } 238 239 public void startDisappearAnimation(Runnable finishRunnable) { 240 if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) { 241 finishRunnable.run(); 242 } 243 } 244 245 /** 246 * Verify that the user can get past the keyguard securely. This is called, 247 * for example, when the phone disables the keyguard but then wants to launch 248 * something else that requires secure access. 249 * 250 * The result will be propogated back via {@link KeyguardViewCallback#keyguardDone(boolean)} 251 */ 252 public void verifyUnlock() { 253 SecurityMode securityMode = mSecurityContainer.getSecurityMode(); 254 if (securityMode == KeyguardSecurityModel.SecurityMode.None) { 255 if (mViewMediatorCallback != null) { 256 mViewMediatorCallback.keyguardDone(true); 257 } 258 } else if (securityMode != KeyguardSecurityModel.SecurityMode.Pattern 259 && securityMode != KeyguardSecurityModel.SecurityMode.PIN 260 && securityMode != KeyguardSecurityModel.SecurityMode.Password) { 261 // can only verify unlock when in pattern/password mode 262 if (mViewMediatorCallback != null) { 263 mViewMediatorCallback.keyguardDone(false); 264 } 265 } else { 266 // otherwise, go to the unlock screen, see if they can verify it 267 mSecurityContainer.verifyUnlock(); 268 } 269 } 270 271 /** 272 * Called before this view is being removed. 273 */ 274 abstract public void cleanUp(); 275 276 /** 277 * Gets the desired user activity timeout in milliseconds, or -1 if the 278 * default should be used. 279 */ 280 abstract public long getUserActivityTimeout(); 281 282 @Override 283 public boolean dispatchKeyEvent(KeyEvent event) { 284 if (interceptMediaKey(event)) { 285 return true; 286 } 287 return super.dispatchKeyEvent(event); 288 } 289 290 /** 291 * Allows the media keys to work when the keyguard is showing. 292 * The media keys should be of no interest to the actual keyguard view(s), 293 * so intercepting them here should not be of any harm. 294 * @param event The key event 295 * @return whether the event was consumed as a media key. 296 */ 297 public boolean interceptMediaKey(KeyEvent event) { 298 final int keyCode = event.getKeyCode(); 299 if (event.getAction() == KeyEvent.ACTION_DOWN) { 300 switch (keyCode) { 301 case KeyEvent.KEYCODE_MEDIA_PLAY: 302 case KeyEvent.KEYCODE_MEDIA_PAUSE: 303 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 304 /* Suppress PLAY/PAUSE toggle when phone is ringing or 305 * in-call to avoid music playback */ 306 if (mTelephonyManager == null) { 307 mTelephonyManager = (TelephonyManager) getContext().getSystemService( 308 Context.TELEPHONY_SERVICE); 309 } 310 if (mTelephonyManager != null && 311 mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) { 312 return true; // suppress key event 313 } 314 case KeyEvent.KEYCODE_MUTE: 315 case KeyEvent.KEYCODE_HEADSETHOOK: 316 case KeyEvent.KEYCODE_MEDIA_STOP: 317 case KeyEvent.KEYCODE_MEDIA_NEXT: 318 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 319 case KeyEvent.KEYCODE_MEDIA_REWIND: 320 case KeyEvent.KEYCODE_MEDIA_RECORD: 321 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 322 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 323 handleMediaKeyEvent(event); 324 return true; 325 } 326 327 case KeyEvent.KEYCODE_VOLUME_UP: 328 case KeyEvent.KEYCODE_VOLUME_DOWN: 329 case KeyEvent.KEYCODE_VOLUME_MUTE: { 330 if (KEYGUARD_MANAGES_VOLUME) { 331 synchronized (this) { 332 if (mAudioManager == null) { 333 mAudioManager = (AudioManager) getContext().getSystemService( 334 Context.AUDIO_SERVICE); 335 } 336 } 337 // Volume buttons should only function for music (local or remote). 338 // TODO: Actually handle MUTE. 339 mAudioManager.adjustSuggestedStreamVolume( 340 keyCode == KeyEvent.KEYCODE_VOLUME_UP 341 ? AudioManager.ADJUST_RAISE 342 : AudioManager.ADJUST_LOWER /* direction */, 343 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */); 344 // Don't execute default volume behavior 345 return true; 346 } else { 347 return false; 348 } 349 } 350 } 351 } else if (event.getAction() == KeyEvent.ACTION_UP) { 352 switch (keyCode) { 353 case KeyEvent.KEYCODE_MUTE: 354 case KeyEvent.KEYCODE_HEADSETHOOK: 355 case KeyEvent.KEYCODE_MEDIA_PLAY: 356 case KeyEvent.KEYCODE_MEDIA_PAUSE: 357 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE: 358 case KeyEvent.KEYCODE_MEDIA_STOP: 359 case KeyEvent.KEYCODE_MEDIA_NEXT: 360 case KeyEvent.KEYCODE_MEDIA_PREVIOUS: 361 case KeyEvent.KEYCODE_MEDIA_REWIND: 362 case KeyEvent.KEYCODE_MEDIA_RECORD: 363 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD: 364 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: { 365 handleMediaKeyEvent(event); 366 return true; 367 } 368 } 369 } 370 return false; 371 } 372 373 private void handleMediaKeyEvent(KeyEvent keyEvent) { 374 synchronized (this) { 375 if (mAudioManager == null) { 376 mAudioManager = (AudioManager) getContext().getSystemService( 377 Context.AUDIO_SERVICE); 378 } 379 } 380 mAudioManager.dispatchMediaKeyEvent(keyEvent); 381 } 382 383 @Override 384 public void dispatchSystemUiVisibilityChanged(int visibility) { 385 super.dispatchSystemUiVisibilityChanged(visibility); 386 387 if (!(mContext instanceof Activity)) { 388 setSystemUiVisibility(STATUS_BAR_DISABLE_BACK); 389 } 390 } 391 392 /** 393 * In general, we enable unlocking the insecure keyguard with the menu key. However, there are 394 * some cases where we wish to disable it, notably when the menu button placement or technology 395 * is prone to false positives. 396 * 397 * @return true if the menu key should be enabled 398 */ 399 private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; 400 private boolean shouldEnableMenuKey() { 401 final Resources res = getResources(); 402 final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen); 403 final boolean isTestHarness = ActivityManager.isRunningInTestHarness(); 404 final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists(); 405 return !configDisabled || isTestHarness || fileOverride; 406 } 407 408 public boolean handleMenuKey() { 409 // The following enables the MENU key to work for testing automation 410 if (shouldEnableMenuKey()) { 411 dismiss(); 412 return true; 413 } 414 return false; 415 } 416 417 public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) { 418 mViewMediatorCallback = viewMediatorCallback; 419 // Update ViewMediator with the current input method requirements 420 mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput()); 421 } 422 423 protected KeyguardActivityLauncher getActivityLauncher() { 424 return mActivityLauncher; 425 } 426 427 private final KeyguardActivityLauncher mActivityLauncher = new KeyguardActivityLauncher() { 428 @Override 429 Context getContext() { 430 return mContext; 431 } 432 433 @Override 434 void setOnDismissAction(OnDismissAction action) { 435 KeyguardViewBase.this.setOnDismissAction(action); 436 } 437 438 @Override 439 LockPatternUtils getLockPatternUtils() { 440 return mLockPatternUtils; 441 } 442 443 @Override 444 void requestDismissKeyguard() { 445 KeyguardViewBase.this.dismiss(false); 446 } 447 }; 448 449 public void showAssistant() { 450 final Intent intent = ((SearchManager) mContext.getSystemService(Context.SEARCH_SERVICE)) 451 .getAssistIntent(mContext, true, UserHandle.USER_CURRENT); 452 453 if (intent == null) return; 454 455 final ActivityOptions opts = ActivityOptions.makeCustomAnimation(mContext, 456 R.anim.keyguard_action_assist_enter, R.anim.keyguard_action_assist_exit, 457 getHandler(), null); 458 459 intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 460 mActivityLauncher.launchActivityWithAnimation(intent, false, opts.toBundle(), null, null); 461 } 462 463 public void launchCamera() { 464 mActivityLauncher.launchCamera(getHandler(), null); 465 } 466 467 public void setLockPatternUtils(LockPatternUtils utils) { 468 mLockPatternUtils = utils; 469 mSecurityContainer.setLockPatternUtils(utils); 470 } 471 472 public SecurityMode getSecurityMode() { 473 return mSecurityContainer.getSecurityMode(); 474 } 475 476 protected abstract void onUserSwitching(boolean switching); 477 478 protected abstract void onCreateOptions(Bundle options); 479 480 protected abstract void onExternalMotionEvent(MotionEvent event); 481 482 } 483