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 17package com.android.keyguard; 18 19import android.app.PendingIntent; 20import android.graphics.Bitmap; 21import android.graphics.drawable.BitmapDrawable; 22import com.android.internal.policy.IKeyguardShowCallback; 23import com.android.internal.widget.LockPatternUtils; 24 25import android.app.Activity; 26import android.app.ActivityManager; 27import android.appwidget.AppWidgetManager; 28import android.content.Context; 29import android.content.pm.ActivityInfo; 30import android.content.res.Configuration; 31import android.content.res.Resources; 32import android.graphics.Canvas; 33import android.graphics.ColorFilter; 34import android.graphics.PixelFormat; 35import android.graphics.PorterDuff; 36import android.graphics.Rect; 37import android.graphics.drawable.Drawable; 38import android.os.Bundle; 39import android.os.IBinder; 40import android.os.Parcelable; 41import android.os.RemoteException; 42import android.os.SystemProperties; 43import android.util.Log; 44import android.util.Slog; 45import android.util.SparseArray; 46import android.view.KeyEvent; 47import android.view.LayoutInflater; 48import android.view.MotionEvent; 49import android.view.View; 50import android.view.ViewGroup; 51import android.view.ViewManager; 52import android.view.WindowManager; 53import android.widget.FrameLayout; 54 55/** 56 * Manages creating, showing, hiding and resetting the keyguard. Calls back 57 * via {@link KeyguardViewMediator.ViewMediatorCallback} to poke 58 * the wake lock and report that the keyguard is done, which is in turn, 59 * reported to this class by the current {@link KeyguardViewBase}. 60 */ 61public class KeyguardViewManager { 62 private final static boolean DEBUG = KeyguardViewMediator.DEBUG; 63 private static String TAG = "KeyguardViewManager"; 64 public final static String IS_SWITCHING_USER = "is_switching_user"; 65 66 // Delay dismissing keyguard to allow animations to complete. 67 private static final int HIDE_KEYGUARD_DELAY = 500; 68 69 // Timeout used for keypresses 70 static final int DIGIT_PRESS_WAKE_MILLIS = 5000; 71 72 private final Context mContext; 73 private final ViewManager mViewManager; 74 private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; 75 76 private WindowManager.LayoutParams mWindowLayoutParams; 77 private boolean mNeedsInput = false; 78 79 private ViewManagerHost mKeyguardHost; 80 private KeyguardHostView mKeyguardView; 81 82 private boolean mScreenOn = false; 83 private LockPatternUtils mLockPatternUtils; 84 85 private KeyguardUpdateMonitorCallback mBackgroundChanger = new KeyguardUpdateMonitorCallback() { 86 @Override 87 public void onSetBackground(Bitmap bmp) { 88 mKeyguardHost.setCustomBackground(bmp != null ? 89 new BitmapDrawable(mContext.getResources(), bmp) : null); 90 updateShowWallpaper(bmp == null); 91 } 92 }; 93 94 public interface ShowListener { 95 void onShown(IBinder windowToken); 96 }; 97 98 /** 99 * @param context Used to create views. 100 * @param viewManager Keyguard will be attached to this. 101 * @param callback Used to notify of changes. 102 * @param lockPatternUtils 103 */ 104 public KeyguardViewManager(Context context, ViewManager viewManager, 105 KeyguardViewMediator.ViewMediatorCallback callback, 106 LockPatternUtils lockPatternUtils) { 107 mContext = context; 108 mViewManager = viewManager; 109 mViewMediatorCallback = callback; 110 mLockPatternUtils = lockPatternUtils; 111 } 112 113 /** 114 * Show the keyguard. Will handle creating and attaching to the view manager 115 * lazily. 116 */ 117 public synchronized void show(Bundle options) { 118 if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); 119 120 boolean enableScreenRotation = shouldEnableScreenRotation(); 121 122 maybeCreateKeyguardLocked(enableScreenRotation, false, options); 123 maybeEnableScreenRotation(enableScreenRotation); 124 125 // Disable common aspects of the system/status/navigation bars that are not appropriate or 126 // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED 127 // activities. Other disabled bits are handled by the KeyguardViewMediator talking 128 // directly to the status bar service. 129 int visFlags = View.STATUS_BAR_DISABLE_HOME; 130 if (shouldEnableTranslucentDecor()) { 131 mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS 132 | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; 133 } 134 if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")"); 135 mKeyguardHost.setSystemUiVisibility(visFlags); 136 137 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 138 mKeyguardHost.setVisibility(View.VISIBLE); 139 mKeyguardView.show(); 140 mKeyguardView.requestFocus(); 141 } 142 143 private boolean shouldEnableScreenRotation() { 144 Resources res = mContext.getResources(); 145 return SystemProperties.getBoolean("lockscreen.rot_override",false) 146 || res.getBoolean(R.bool.config_enableLockScreenRotation); 147 } 148 149 private boolean shouldEnableTranslucentDecor() { 150 Resources res = mContext.getResources(); 151 return res.getBoolean(R.bool.config_enableLockScreenTranslucentDecor); 152 } 153 154 class ViewManagerHost extends FrameLayout { 155 private static final int BACKGROUND_COLOR = 0x70000000; 156 157 private Drawable mCustomBackground; 158 159 // This is a faster way to draw the background on devices without hardware acceleration 160 private final Drawable mBackgroundDrawable = new Drawable() { 161 @Override 162 public void draw(Canvas canvas) { 163 if (mCustomBackground != null) { 164 final Rect bounds = mCustomBackground.getBounds(); 165 final int vWidth = getWidth(); 166 final int vHeight = getHeight(); 167 168 final int restore = canvas.save(); 169 canvas.translate(-(bounds.width() - vWidth) / 2, 170 -(bounds.height() - vHeight) / 2); 171 mCustomBackground.draw(canvas); 172 canvas.restoreToCount(restore); 173 } else { 174 canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC); 175 } 176 } 177 178 @Override 179 public void setAlpha(int alpha) { 180 } 181 182 @Override 183 public void setColorFilter(ColorFilter cf) { 184 } 185 186 @Override 187 public int getOpacity() { 188 return PixelFormat.TRANSLUCENT; 189 } 190 }; 191 192 public ViewManagerHost(Context context) { 193 super(context); 194 setBackground(mBackgroundDrawable); 195 } 196 197 public void setCustomBackground(Drawable d) { 198 mCustomBackground = d; 199 if (d != null) { 200 d.setColorFilter(BACKGROUND_COLOR, PorterDuff.Mode.SRC_OVER); 201 } 202 computeCustomBackgroundBounds(); 203 invalidate(); 204 } 205 206 private void computeCustomBackgroundBounds() { 207 if (mCustomBackground == null) return; // Nothing to do 208 if (!isLaidOut()) return; // We'll do this later 209 210 final int bgWidth = mCustomBackground.getIntrinsicWidth(); 211 final int bgHeight = mCustomBackground.getIntrinsicHeight(); 212 final int vWidth = getWidth(); 213 final int vHeight = getHeight(); 214 215 final float bgAspect = (float) bgWidth / bgHeight; 216 final float vAspect = (float) vWidth / vHeight; 217 218 if (bgAspect > vAspect) { 219 mCustomBackground.setBounds(0, 0, (int) (vHeight * bgAspect), vHeight); 220 } else { 221 mCustomBackground.setBounds(0, 0, vWidth, (int) (vWidth / bgAspect)); 222 } 223 } 224 225 @Override 226 protected void onSizeChanged(int w, int h, int oldw, int oldh) { 227 super.onSizeChanged(w, h, oldw, oldh); 228 computeCustomBackgroundBounds(); 229 } 230 231 @Override 232 protected void onConfigurationChanged(Configuration newConfig) { 233 super.onConfigurationChanged(newConfig); 234 if (mKeyguardHost.getVisibility() == View.VISIBLE) { 235 // only propagate configuration messages if we're currently showing 236 maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, null); 237 } else { 238 if (DEBUG) Log.v(TAG, "onConfigurationChanged: view not visible"); 239 } 240 } 241 242 @Override 243 public boolean dispatchKeyEvent(KeyEvent event) { 244 if (mKeyguardView != null) { 245 // Always process back and menu keys, regardless of focus 246 if (event.getAction() == KeyEvent.ACTION_DOWN) { 247 int keyCode = event.getKeyCode(); 248 if (keyCode == KeyEvent.KEYCODE_BACK && mKeyguardView.handleBackKey()) { 249 return true; 250 } else if (keyCode == KeyEvent.KEYCODE_MENU && mKeyguardView.handleMenuKey()) { 251 return true; 252 } 253 } 254 // Always process media keys, regardless of focus 255 if (mKeyguardView.dispatchKeyEvent(event)) { 256 return true; 257 } 258 } 259 return super.dispatchKeyEvent(event); 260 } 261 } 262 263 SparseArray<Parcelable> mStateContainer = new SparseArray<Parcelable>(); 264 265 private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force, 266 Bundle options) { 267 if (mKeyguardHost != null) { 268 mKeyguardHost.saveHierarchyState(mStateContainer); 269 } 270 271 if (mKeyguardHost == null) { 272 if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); 273 274 mKeyguardHost = new ViewManagerHost(mContext); 275 276 int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN 277 | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR 278 | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN 279 | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 280 281 if (!mNeedsInput) { 282 flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 283 } 284 285 final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; 286 final int type = WindowManager.LayoutParams.TYPE_KEYGUARD; 287 WindowManager.LayoutParams lp = new WindowManager.LayoutParams( 288 stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); 289 lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; 290 lp.windowAnimations = R.style.Animation_LockScreen; 291 lp.screenOrientation = enableScreenRotation ? 292 ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 293 294 if (ActivityManager.isHighEndGfx()) { 295 lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; 296 lp.privateFlags |= 297 WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; 298 } 299 lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; 300 lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; 301 lp.setTitle("Keyguard"); 302 mWindowLayoutParams = lp; 303 mViewManager.addView(mKeyguardHost, lp); 304 305 KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mBackgroundChanger); 306 } 307 308 if (force || mKeyguardView == null) { 309 mKeyguardHost.setCustomBackground(null); 310 mKeyguardHost.removeAllViews(); 311 inflateKeyguardView(options); 312 mKeyguardView.requestFocus(); 313 } 314 updateUserActivityTimeoutInWindowLayoutParams(); 315 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 316 317 mKeyguardHost.restoreHierarchyState(mStateContainer); 318 } 319 320 private void inflateKeyguardView(Bundle options) { 321 View v = mKeyguardHost.findViewById(R.id.keyguard_host_view); 322 if (v != null) { 323 mKeyguardHost.removeView(v); 324 } 325 final LayoutInflater inflater = LayoutInflater.from(mContext); 326 View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true); 327 mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); 328 mKeyguardView.setLockPatternUtils(mLockPatternUtils); 329 mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); 330 mKeyguardView.initializeSwitchingUserState(options != null && 331 options.getBoolean(IS_SWITCHING_USER)); 332 333 // HACK 334 // The keyguard view will have set up window flags in onFinishInflate before we set 335 // the view mediator callback. Make sure it knows the correct IME state. 336 if (mViewMediatorCallback != null) { 337 KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById( 338 R.id.keyguard_password_view); 339 340 if (kpv != null) { 341 mViewMediatorCallback.setNeedsInput(kpv.needsInput()); 342 } 343 } 344 345 if (options != null) { 346 int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, 347 AppWidgetManager.INVALID_APPWIDGET_ID); 348 if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { 349 mKeyguardView.goToWidget(widgetToShow); 350 } 351 } 352 } 353 354 public void updateUserActivityTimeout() { 355 updateUserActivityTimeoutInWindowLayoutParams(); 356 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 357 } 358 359 private void updateUserActivityTimeoutInWindowLayoutParams() { 360 // Use the user activity timeout requested by the keyguard view, if any. 361 if (mKeyguardView != null) { 362 long timeout = mKeyguardView.getUserActivityTimeout(); 363 if (timeout >= 0) { 364 mWindowLayoutParams.userActivityTimeout = timeout; 365 return; 366 } 367 } 368 369 // Otherwise, use the default timeout. 370 mWindowLayoutParams.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS; 371 } 372 373 private void maybeEnableScreenRotation(boolean enableScreenRotation) { 374 // TODO: move this outside 375 if (enableScreenRotation) { 376 if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!"); 377 mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; 378 } else { 379 if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!"); 380 mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; 381 } 382 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 383 } 384 385 void updateShowWallpaper(boolean show) { 386 if (show) { 387 mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 388 } else { 389 mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; 390 } 391 392 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 393 } 394 395 public void setNeedsInput(boolean needsInput) { 396 mNeedsInput = needsInput; 397 if (mWindowLayoutParams != null) { 398 if (needsInput) { 399 mWindowLayoutParams.flags &= 400 ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 401 } else { 402 mWindowLayoutParams.flags |= 403 WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; 404 } 405 406 try { 407 mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); 408 } catch (java.lang.IllegalArgumentException e) { 409 // TODO: Ensure this method isn't called on views that are changing... 410 Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached"); 411 } 412 } 413 } 414 415 /** 416 * Reset the state of the view. 417 */ 418 public synchronized void reset(Bundle options) { 419 if (DEBUG) Log.d(TAG, "reset()"); 420 // User might have switched, check if we need to go back to keyguard 421 // TODO: It's preferable to stay and show the correct lockscreen or unlock if none 422 maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, options); 423 } 424 425 public synchronized void onScreenTurnedOff() { 426 if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); 427 mScreenOn = false; 428 if (mKeyguardView != null) { 429 mKeyguardView.onScreenTurnedOff(); 430 } 431 } 432 433 public synchronized void onScreenTurnedOn(final IKeyguardShowCallback callback) { 434 if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); 435 mScreenOn = true; 436 437 // If keyguard is not showing, we need to inform PhoneWindowManager with a null 438 // token so it doesn't wait for us to draw... 439 final IBinder token = isShowing() ? mKeyguardHost.getWindowToken() : null; 440 441 if (DEBUG && token == null) Slog.v(TAG, "send wm null token: " 442 + (mKeyguardHost == null ? "host was null" : "not showing")); 443 444 if (mKeyguardView != null) { 445 mKeyguardView.onScreenTurnedOn(); 446 447 // Caller should wait for this window to be shown before turning 448 // on the screen. 449 if (callback != null) { 450 if (mKeyguardHost.getVisibility() == View.VISIBLE) { 451 // Keyguard may be in the process of being shown, but not yet 452 // updated with the window manager... give it a chance to do so. 453 mKeyguardHost.post(new Runnable() { 454 @Override 455 public void run() { 456 try { 457 callback.onShown(token); 458 } catch (RemoteException e) { 459 Slog.w(TAG, "Exception calling onShown():", e); 460 } 461 } 462 }); 463 } else { 464 try { 465 callback.onShown(token); 466 } catch (RemoteException e) { 467 Slog.w(TAG, "Exception calling onShown():", e); 468 } 469 } 470 } 471 } else if (callback != null) { 472 try { 473 callback.onShown(token); 474 } catch (RemoteException e) { 475 Slog.w(TAG, "Exception calling onShown():", e); 476 } 477 } 478 } 479 480 public synchronized void verifyUnlock() { 481 if (DEBUG) Log.d(TAG, "verifyUnlock()"); 482 show(null); 483 mKeyguardView.verifyUnlock(); 484 } 485 486 /** 487 * Hides the keyguard view 488 */ 489 public synchronized void hide() { 490 if (DEBUG) Log.d(TAG, "hide()"); 491 492 if (mKeyguardHost != null) { 493 mKeyguardHost.setVisibility(View.GONE); 494 495 // We really only want to preserve keyguard state for configuration changes. Hence 496 // we should clear state of widgets (e.g. Music) when we hide keyguard so it can 497 // start with a fresh state when we return. 498 mStateContainer.clear(); 499 500 // Don't do this right away, so we can let the view continue to animate 501 // as it goes away. 502 if (mKeyguardView != null) { 503 final KeyguardViewBase lastView = mKeyguardView; 504 mKeyguardView = null; 505 mKeyguardHost.postDelayed(new Runnable() { 506 @Override 507 public void run() { 508 synchronized (KeyguardViewManager.this) { 509 lastView.cleanUp(); 510 // Let go of any large bitmaps. 511 mKeyguardHost.setCustomBackground(null); 512 updateShowWallpaper(true); 513 mKeyguardHost.removeView(lastView); 514 mViewMediatorCallback.keyguardGone(); 515 } 516 } 517 }, HIDE_KEYGUARD_DELAY); 518 } 519 } 520 } 521 522 /** 523 * Dismisses the keyguard by going to the next screen or making it gone. 524 */ 525 public synchronized void dismiss() { 526 if (mScreenOn) { 527 mKeyguardView.dismiss(); 528 } 529 } 530 531 /** 532 * @return Whether the keyguard is showing 533 */ 534 public synchronized boolean isShowing() { 535 return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE); 536 } 537 538 public void showAssistant() { 539 if (mKeyguardView != null) { 540 mKeyguardView.showAssistant(); 541 } 542 } 543 544 public void dispatch(MotionEvent event) { 545 if (mKeyguardView != null) { 546 mKeyguardView.dispatch(event); 547 } 548 } 549 550 public void launchCamera() { 551 if (mKeyguardView != null) { 552 mKeyguardView.launchCamera(); 553 } 554 } 555} 556