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