1/* 2 * Copyright (C) 2008 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 17package com.android.internal.policy.impl; 18 19import com.android.internal.R; 20import com.android.internal.policy.impl.KeyguardUpdateMonitor.InfoCallbackImpl; 21import com.android.internal.policy.impl.KeyguardUpdateMonitor.SimStateCallback; 22import com.android.internal.telephony.IccCard.State; 23import com.android.internal.widget.LockPatternUtils; 24import com.android.internal.widget.SlidingTab; 25import com.android.internal.widget.WaveView; 26import com.android.internal.widget.multiwaveview.GlowPadView; 27 28import android.app.ActivityManager; 29import android.app.ActivityManagerNative; 30import android.app.SearchManager; 31import android.content.ActivityNotFoundException; 32import android.content.ComponentName; 33import android.content.Context; 34import android.content.Intent; 35import android.content.res.Configuration; 36import android.content.res.Resources; 37import android.os.Vibrator; 38import android.view.KeyEvent; 39import android.view.LayoutInflater; 40import android.view.View; 41import android.view.ViewGroup; 42import android.widget.*; 43import android.util.Log; 44import android.util.Slog; 45import android.media.AudioManager; 46import android.os.RemoteException; 47import android.provider.MediaStore; 48 49import java.io.File; 50 51/** 52 * The screen within {@link LockPatternKeyguardView} that shows general 53 * information about the device depending on its state, and how to get 54 * past it, as applicable. 55 */ 56class LockScreen extends LinearLayout implements KeyguardScreen { 57 58 private static final int ON_RESUME_PING_DELAY = 500; // delay first ping until the screen is on 59 private static final boolean DBG = false; 60 private static final String TAG = "LockScreen"; 61 private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key"; 62 private static final int WAIT_FOR_ANIMATION_TIMEOUT = 0; 63 private static final int STAY_ON_WHILE_GRABBED_TIMEOUT = 30000; 64 private static final String ASSIST_ICON_METADATA_NAME = 65 "com.android.systemui.action_assist_icon"; 66 67 private LockPatternUtils mLockPatternUtils; 68 private KeyguardUpdateMonitor mUpdateMonitor; 69 private KeyguardScreenCallback mCallback; 70 71 // set to 'true' to show the ring/silence target when camera isn't available 72 private boolean mEnableRingSilenceFallback = false; 73 74 // current configuration state of keyboard and display 75 private int mCreationOrientation; 76 77 private boolean mSilentMode; 78 private AudioManager mAudioManager; 79 private boolean mEnableMenuKeyInLockScreen; 80 81 private KeyguardStatusViewManager mStatusViewManager; 82 private UnlockWidgetCommonMethods mUnlockWidgetMethods; 83 private View mUnlockWidget; 84 private boolean mCameraDisabled; 85 private boolean mSearchDisabled; 86 // Is there a vibrator 87 private final boolean mHasVibrator; 88 89 InfoCallbackImpl mInfoCallback = new InfoCallbackImpl() { 90 91 @Override 92 public void onRingerModeChanged(int state) { 93 boolean silent = AudioManager.RINGER_MODE_NORMAL != state; 94 if (silent != mSilentMode) { 95 mSilentMode = silent; 96 mUnlockWidgetMethods.updateResources(); 97 } 98 } 99 100 @Override 101 public void onDevicePolicyManagerStateChanged() { 102 updateTargets(); 103 } 104 105 }; 106 107 SimStateCallback mSimStateCallback = new SimStateCallback() { 108 public void onSimStateChanged(State simState) { 109 updateTargets(); 110 } 111 }; 112 113 private interface UnlockWidgetCommonMethods { 114 // Update resources based on phone state 115 public void updateResources(); 116 117 // Get the view associated with this widget 118 public View getView(); 119 120 // Reset the view 121 public void reset(boolean animate); 122 123 // Animate the widget if it supports ping() 124 public void ping(); 125 126 // Enable or disable a target. ResourceId is the id of the *drawable* associated with the 127 // target. 128 public void setEnabled(int resourceId, boolean enabled); 129 130 // Get the target position for the given resource. Returns -1 if not found. 131 public int getTargetPosition(int resourceId); 132 133 // Clean up when this widget is going away 134 public void cleanUp(); 135 } 136 137 class SlidingTabMethods implements SlidingTab.OnTriggerListener, UnlockWidgetCommonMethods { 138 private final SlidingTab mSlidingTab; 139 140 SlidingTabMethods(SlidingTab slidingTab) { 141 mSlidingTab = slidingTab; 142 } 143 144 public void updateResources() { 145 boolean vibe = mSilentMode 146 && (mAudioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE); 147 148 mSlidingTab.setRightTabResources( 149 mSilentMode ? ( vibe ? R.drawable.ic_jog_dial_vibrate_on 150 : R.drawable.ic_jog_dial_sound_off ) 151 : R.drawable.ic_jog_dial_sound_on, 152 mSilentMode ? R.drawable.jog_tab_target_yellow 153 : R.drawable.jog_tab_target_gray, 154 mSilentMode ? R.drawable.jog_tab_bar_right_sound_on 155 : R.drawable.jog_tab_bar_right_sound_off, 156 mSilentMode ? R.drawable.jog_tab_right_sound_on 157 : R.drawable.jog_tab_right_sound_off); 158 } 159 160 /** {@inheritDoc} */ 161 public void onTrigger(View v, int whichHandle) { 162 if (whichHandle == SlidingTab.OnTriggerListener.LEFT_HANDLE) { 163 mCallback.goToUnlockScreen(); 164 } else if (whichHandle == SlidingTab.OnTriggerListener.RIGHT_HANDLE) { 165 toggleRingMode(); 166 mCallback.pokeWakelock(); 167 } 168 } 169 170 /** {@inheritDoc} */ 171 public void onGrabbedStateChange(View v, int grabbedState) { 172 if (grabbedState == SlidingTab.OnTriggerListener.RIGHT_HANDLE) { 173 mSilentMode = isSilentMode(); 174 mSlidingTab.setRightHintText(mSilentMode ? R.string.lockscreen_sound_on_label 175 : R.string.lockscreen_sound_off_label); 176 } 177 // Don't poke the wake lock when returning to a state where the handle is 178 // not grabbed since that can happen when the system (instead of the user) 179 // cancels the grab. 180 if (grabbedState != SlidingTab.OnTriggerListener.NO_HANDLE) { 181 mCallback.pokeWakelock(); 182 } 183 } 184 185 public View getView() { 186 return mSlidingTab; 187 } 188 189 public void reset(boolean animate) { 190 mSlidingTab.reset(animate); 191 } 192 193 public void ping() { 194 } 195 196 public void setEnabled(int resourceId, boolean enabled) { 197 // Not used 198 } 199 200 public int getTargetPosition(int resourceId) { 201 return -1; // Not supported 202 } 203 204 public void cleanUp() { 205 mSlidingTab.setOnTriggerListener(null); 206 } 207 } 208 209 class WaveViewMethods implements WaveView.OnTriggerListener, UnlockWidgetCommonMethods { 210 211 private final WaveView mWaveView; 212 213 WaveViewMethods(WaveView waveView) { 214 mWaveView = waveView; 215 } 216 /** {@inheritDoc} */ 217 public void onTrigger(View v, int whichHandle) { 218 if (whichHandle == WaveView.OnTriggerListener.CENTER_HANDLE) { 219 requestUnlockScreen(); 220 } 221 } 222 223 /** {@inheritDoc} */ 224 public void onGrabbedStateChange(View v, int grabbedState) { 225 // Don't poke the wake lock when returning to a state where the handle is 226 // not grabbed since that can happen when the system (instead of the user) 227 // cancels the grab. 228 if (grabbedState == WaveView.OnTriggerListener.CENTER_HANDLE) { 229 mCallback.pokeWakelock(STAY_ON_WHILE_GRABBED_TIMEOUT); 230 } 231 } 232 233 public void updateResources() { 234 } 235 236 public View getView() { 237 return mWaveView; 238 } 239 public void reset(boolean animate) { 240 mWaveView.reset(); 241 } 242 public void ping() { 243 } 244 public void setEnabled(int resourceId, boolean enabled) { 245 // Not used 246 } 247 public int getTargetPosition(int resourceId) { 248 return -1; // Not supported 249 } 250 public void cleanUp() { 251 mWaveView.setOnTriggerListener(null); 252 } 253 } 254 255 class GlowPadViewMethods implements GlowPadView.OnTriggerListener, 256 UnlockWidgetCommonMethods { 257 private final GlowPadView mGlowPadView; 258 259 GlowPadViewMethods(GlowPadView glowPadView) { 260 mGlowPadView = glowPadView; 261 } 262 263 public boolean isTargetPresent(int resId) { 264 return mGlowPadView.getTargetPosition(resId) != -1; 265 } 266 267 public void updateResources() { 268 int resId; 269 if (mCameraDisabled && mEnableRingSilenceFallback) { 270 // Fall back to showing ring/silence if camera is disabled... 271 resId = mSilentMode ? R.array.lockscreen_targets_when_silent 272 : R.array.lockscreen_targets_when_soundon; 273 } else { 274 resId = R.array.lockscreen_targets_with_camera; 275 } 276 if (mGlowPadView.getTargetResourceId() != resId) { 277 mGlowPadView.setTargetResources(resId); 278 } 279 280 // Update the search icon with drawable from the search .apk 281 if (!mSearchDisabled) { 282 Intent intent = SearchManager.getAssistIntent(mContext); 283 if (intent != null) { 284 // XXX Hack. We need to substitute the icon here but haven't formalized 285 // the public API. The "_google" metadata will be going away, so 286 // DON'T USE IT! 287 ComponentName component = intent.getComponent(); 288 boolean replaced = mGlowPadView.replaceTargetDrawablesIfPresent(component, 289 ASSIST_ICON_METADATA_NAME + "_google", 290 com.android.internal.R.drawable.ic_action_assist_generic); 291 292 if (!replaced && !mGlowPadView.replaceTargetDrawablesIfPresent(component, 293 ASSIST_ICON_METADATA_NAME, 294 com.android.internal.R.drawable.ic_action_assist_generic)) { 295 Slog.w(TAG, "Couldn't grab icon from package " + component); 296 } 297 } 298 } 299 300 setEnabled(com.android.internal.R.drawable.ic_lockscreen_camera, !mCameraDisabled); 301 setEnabled(com.android.internal.R.drawable.ic_action_assist_generic, !mSearchDisabled); 302 } 303 304 public void onGrabbed(View v, int handle) { 305 306 } 307 308 public void onReleased(View v, int handle) { 309 310 } 311 312 public void onTrigger(View v, int target) { 313 final int resId = mGlowPadView.getResourceIdForTarget(target); 314 switch (resId) { 315 case com.android.internal.R.drawable.ic_action_assist_generic: 316 Intent assistIntent = SearchManager.getAssistIntent(mContext); 317 if (assistIntent != null) { 318 launchActivity(assistIntent); 319 } else { 320 Log.w(TAG, "Failed to get intent for assist activity"); 321 } 322 mCallback.pokeWakelock(); 323 break; 324 325 case com.android.internal.R.drawable.ic_lockscreen_camera: 326 launchActivity(new Intent(MediaStore.INTENT_ACTION_STILL_IMAGE_CAMERA)); 327 mCallback.pokeWakelock(); 328 break; 329 330 case com.android.internal.R.drawable.ic_lockscreen_silent: 331 toggleRingMode(); 332 mCallback.pokeWakelock(); 333 break; 334 335 case com.android.internal.R.drawable.ic_lockscreen_unlock_phantom: 336 case com.android.internal.R.drawable.ic_lockscreen_unlock: 337 mCallback.goToUnlockScreen(); 338 break; 339 } 340 } 341 342 private void launchActivity(Intent intent) { 343 intent.setFlags( 344 Intent.FLAG_ACTIVITY_NEW_TASK 345 | Intent.FLAG_ACTIVITY_SINGLE_TOP 346 | Intent.FLAG_ACTIVITY_CLEAR_TOP); 347 try { 348 ActivityManagerNative.getDefault().dismissKeyguardOnNextActivity(); 349 } catch (RemoteException e) { 350 Log.w(TAG, "can't dismiss keyguard on launch"); 351 } 352 try { 353 mContext.startActivity(intent); 354 } catch (ActivityNotFoundException e) { 355 Log.w(TAG, "Activity not found for intent + " + intent.getAction()); 356 } 357 } 358 359 public void onGrabbedStateChange(View v, int handle) { 360 // Don't poke the wake lock when returning to a state where the handle is 361 // not grabbed since that can happen when the system (instead of the user) 362 // cancels the grab. 363 if (handle != GlowPadView.OnTriggerListener.NO_HANDLE) { 364 mCallback.pokeWakelock(); 365 } 366 } 367 368 public View getView() { 369 return mGlowPadView; 370 } 371 372 public void reset(boolean animate) { 373 mGlowPadView.reset(animate); 374 } 375 376 public void ping() { 377 mGlowPadView.ping(); 378 } 379 380 public void setEnabled(int resourceId, boolean enabled) { 381 mGlowPadView.setEnableTarget(resourceId, enabled); 382 } 383 384 public int getTargetPosition(int resourceId) { 385 return mGlowPadView.getTargetPosition(resourceId); 386 } 387 388 public void cleanUp() { 389 mGlowPadView.setOnTriggerListener(null); 390 } 391 392 public void onFinishFinalAnimation() { 393 394 } 395 } 396 397 private void requestUnlockScreen() { 398 // Delay hiding lock screen long enough for animation to finish 399 postDelayed(new Runnable() { 400 public void run() { 401 mCallback.goToUnlockScreen(); 402 } 403 }, WAIT_FOR_ANIMATION_TIMEOUT); 404 } 405 406 private void toggleRingMode() { 407 // toggle silent mode 408 mSilentMode = !mSilentMode; 409 if (mSilentMode) { 410 mAudioManager.setRingerMode(mHasVibrator 411 ? AudioManager.RINGER_MODE_VIBRATE 412 : AudioManager.RINGER_MODE_SILENT); 413 } else { 414 mAudioManager.setRingerMode(AudioManager.RINGER_MODE_NORMAL); 415 } 416 } 417 418 /** 419 * In general, we enable unlocking the insecure key guard with the menu key. However, there are 420 * some cases where we wish to disable it, notably when the menu button placement or technology 421 * is prone to false positives. 422 * 423 * @return true if the menu key should be enabled 424 */ 425 private 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 433 /** 434 * @param context Used to setup the view. 435 * @param configuration The current configuration. Used to use when selecting layout, etc. 436 * @param lockPatternUtils Used to know the state of the lock pattern settings. 437 * @param updateMonitor Used to register for updates on various keyguard related 438 * state, and query the initial state at setup. 439 * @param callback Used to communicate back to the host keyguard view. 440 */ 441 LockScreen(Context context, Configuration configuration, LockPatternUtils lockPatternUtils, 442 KeyguardUpdateMonitor updateMonitor, 443 KeyguardScreenCallback callback) { 444 super(context); 445 mLockPatternUtils = lockPatternUtils; 446 mUpdateMonitor = updateMonitor; 447 mCallback = callback; 448 mEnableMenuKeyInLockScreen = shouldEnableMenuKey(); 449 mCreationOrientation = configuration.orientation; 450 451 if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { 452 Log.v(TAG, "***** CREATING LOCK SCREEN", new RuntimeException()); 453 Log.v(TAG, "Cur orient=" + mCreationOrientation 454 + " res orient=" + context.getResources().getConfiguration().orientation); 455 } 456 457 final LayoutInflater inflater = LayoutInflater.from(context); 458 if (DBG) Log.v(TAG, "Creation orientation = " + mCreationOrientation); 459 if (mCreationOrientation != Configuration.ORIENTATION_LANDSCAPE) { 460 inflater.inflate(R.layout.keyguard_screen_tab_unlock, this, true); 461 } else { 462 inflater.inflate(R.layout.keyguard_screen_tab_unlock_land, this, true); 463 } 464 465 mStatusViewManager = new KeyguardStatusViewManager(this, mUpdateMonitor, mLockPatternUtils, 466 mCallback, false); 467 468 setFocusable(true); 469 setFocusableInTouchMode(true); 470 setDescendantFocusability(ViewGroup.FOCUS_BLOCK_DESCENDANTS); 471 472 Vibrator vibrator = (Vibrator) context.getSystemService(Context.VIBRATOR_SERVICE); 473 mHasVibrator = vibrator == null ? false : vibrator.hasVibrator(); 474 mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE); 475 mSilentMode = isSilentMode(); 476 mUnlockWidget = findViewById(R.id.unlock_widget); 477 mUnlockWidgetMethods = createUnlockMethods(mUnlockWidget); 478 479 if (DBG) Log.v(TAG, "*** LockScreen accel is " 480 + (mUnlockWidget.isHardwareAccelerated() ? "on":"off")); 481 } 482 483 private UnlockWidgetCommonMethods createUnlockMethods(View unlockWidget) { 484 if (unlockWidget instanceof SlidingTab) { 485 SlidingTab slidingTabView = (SlidingTab) unlockWidget; 486 slidingTabView.setHoldAfterTrigger(true, false); 487 slidingTabView.setLeftHintText(R.string.lockscreen_unlock_label); 488 slidingTabView.setLeftTabResources( 489 R.drawable.ic_jog_dial_unlock, 490 R.drawable.jog_tab_target_green, 491 R.drawable.jog_tab_bar_left_unlock, 492 R.drawable.jog_tab_left_unlock); 493 SlidingTabMethods slidingTabMethods = new SlidingTabMethods(slidingTabView); 494 slidingTabView.setOnTriggerListener(slidingTabMethods); 495 return slidingTabMethods; 496 } else if (unlockWidget instanceof WaveView) { 497 WaveView waveView = (WaveView) unlockWidget; 498 WaveViewMethods waveViewMethods = new WaveViewMethods(waveView); 499 waveView.setOnTriggerListener(waveViewMethods); 500 return waveViewMethods; 501 } else if (unlockWidget instanceof GlowPadView) { 502 GlowPadView glowPadView = (GlowPadView) unlockWidget; 503 GlowPadViewMethods glowPadViewMethods = new GlowPadViewMethods(glowPadView); 504 glowPadView.setOnTriggerListener(glowPadViewMethods); 505 return glowPadViewMethods; 506 } else { 507 throw new IllegalStateException("Unrecognized unlock widget: " + unlockWidget); 508 } 509 } 510 511 private void updateTargets() { 512 boolean disabledByAdmin = mLockPatternUtils.getDevicePolicyManager() 513 .getCameraDisabled(null); 514 boolean disabledBySimState = mUpdateMonitor.isSimLocked(); 515 boolean cameraTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods) 516 ? ((GlowPadViewMethods) mUnlockWidgetMethods) 517 .isTargetPresent(com.android.internal.R.drawable.ic_lockscreen_camera) 518 : false; 519 boolean searchTargetPresent = (mUnlockWidgetMethods instanceof GlowPadViewMethods) 520 ? ((GlowPadViewMethods) mUnlockWidgetMethods) 521 .isTargetPresent(com.android.internal.R.drawable.ic_action_assist_generic) 522 : false; 523 524 if (disabledByAdmin) { 525 Log.v(TAG, "Camera disabled by Device Policy"); 526 } else if (disabledBySimState) { 527 Log.v(TAG, "Camera disabled by Sim State"); 528 } 529 boolean searchActionAvailable = SearchManager.getAssistIntent(mContext) != null; 530 mCameraDisabled = disabledByAdmin || disabledBySimState || !cameraTargetPresent; 531 mSearchDisabled = disabledBySimState || !searchActionAvailable || !searchTargetPresent; 532 mUnlockWidgetMethods.updateResources(); 533 } 534 535 private boolean isSilentMode() { 536 return mAudioManager.getRingerMode() != AudioManager.RINGER_MODE_NORMAL; 537 } 538 539 @Override 540 public boolean onKeyDown(int keyCode, KeyEvent event) { 541 if (keyCode == KeyEvent.KEYCODE_MENU && mEnableMenuKeyInLockScreen) { 542 mCallback.goToUnlockScreen(); 543 } 544 return false; 545 } 546 547 void updateConfiguration() { 548 Configuration newConfig = getResources().getConfiguration(); 549 if (newConfig.orientation != mCreationOrientation) { 550 mCallback.recreateMe(newConfig); 551 } 552 } 553 554 @Override 555 protected void onAttachedToWindow() { 556 super.onAttachedToWindow(); 557 if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { 558 Log.v(TAG, "***** LOCK ATTACHED TO WINDOW"); 559 Log.v(TAG, "Cur orient=" + mCreationOrientation 560 + ", new config=" + getResources().getConfiguration()); 561 } 562 updateConfiguration(); 563 } 564 565 /** {@inheritDoc} */ 566 @Override 567 protected void onConfigurationChanged(Configuration newConfig) { 568 super.onConfigurationChanged(newConfig); 569 if (LockPatternKeyguardView.DEBUG_CONFIGURATION) { 570 Log.w(TAG, "***** LOCK CONFIG CHANGING", new RuntimeException()); 571 Log.v(TAG, "Cur orient=" + mCreationOrientation 572 + ", new config=" + newConfig); 573 } 574 updateConfiguration(); 575 } 576 577 /** {@inheritDoc} */ 578 public boolean needsInput() { 579 return false; 580 } 581 582 /** {@inheritDoc} */ 583 public void onPause() { 584 mUpdateMonitor.removeCallback(mInfoCallback); 585 mUpdateMonitor.removeCallback(mSimStateCallback); 586 mStatusViewManager.onPause(); 587 mUnlockWidgetMethods.reset(false); 588 } 589 590 private final Runnable mOnResumePing = new Runnable() { 591 public void run() { 592 mUnlockWidgetMethods.ping(); 593 } 594 }; 595 596 /** {@inheritDoc} */ 597 public void onResume() { 598 // We don't want to show the camera target if SIM state prevents us from 599 // launching the camera. So watch for SIM changes... 600 mUpdateMonitor.registerSimStateCallback(mSimStateCallback); 601 mUpdateMonitor.registerInfoCallback(mInfoCallback); 602 603 mStatusViewManager.onResume(); 604 postDelayed(mOnResumePing, ON_RESUME_PING_DELAY); 605 } 606 607 /** {@inheritDoc} */ 608 public void cleanUp() { 609 mUpdateMonitor.removeCallback(mInfoCallback); // this must be first 610 mUpdateMonitor.removeCallback(mSimStateCallback); 611 mUnlockWidgetMethods.cleanUp(); 612 mLockPatternUtils = null; 613 mUpdateMonitor = null; 614 mCallback = null; 615 } 616} 617