/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.keyguard; import android.app.PendingIntent; import android.graphics.Bitmap; import android.graphics.drawable.BitmapDrawable; import com.android.internal.policy.IKeyguardShowCallback; import com.android.internal.widget.LockPatternUtils; import android.app.Activity; import android.app.ActivityManager; import android.appwidget.AppWidgetManager; import android.content.Context; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Canvas; import android.graphics.ColorFilter; import android.graphics.PixelFormat; import android.graphics.PorterDuff; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.IBinder; import android.os.Parcelable; import android.os.RemoteException; import android.os.SystemProperties; import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.view.KeyEvent; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; import android.view.ViewManager; import android.view.WindowManager; import android.widget.FrameLayout; /** * Manages creating, showing, hiding and resetting the keyguard. Calls back * via {@link KeyguardViewMediator.ViewMediatorCallback} to poke * the wake lock and report that the keyguard is done, which is in turn, * reported to this class by the current {@link KeyguardViewBase}. */ public class KeyguardViewManager { private final static boolean DEBUG = KeyguardViewMediator.DEBUG; private static String TAG = "KeyguardViewManager"; public final static String IS_SWITCHING_USER = "is_switching_user"; // Delay dismissing keyguard to allow animations to complete. private static final int HIDE_KEYGUARD_DELAY = 500; // Timeout used for keypresses static final int DIGIT_PRESS_WAKE_MILLIS = 5000; private final Context mContext; private final ViewManager mViewManager; private final KeyguardViewMediator.ViewMediatorCallback mViewMediatorCallback; private WindowManager.LayoutParams mWindowLayoutParams; private boolean mNeedsInput = false; private ViewManagerHost mKeyguardHost; private KeyguardHostView mKeyguardView; private boolean mScreenOn = false; private LockPatternUtils mLockPatternUtils; private KeyguardUpdateMonitorCallback mBackgroundChanger = new KeyguardUpdateMonitorCallback() { @Override public void onSetBackground(Bitmap bmp) { mKeyguardHost.setCustomBackground(bmp != null ? new BitmapDrawable(mContext.getResources(), bmp) : null); updateShowWallpaper(bmp == null); } }; public interface ShowListener { void onShown(IBinder windowToken); }; /** * @param context Used to create views. * @param viewManager Keyguard will be attached to this. * @param callback Used to notify of changes. * @param lockPatternUtils */ public KeyguardViewManager(Context context, ViewManager viewManager, KeyguardViewMediator.ViewMediatorCallback callback, LockPatternUtils lockPatternUtils) { mContext = context; mViewManager = viewManager; mViewMediatorCallback = callback; mLockPatternUtils = lockPatternUtils; } /** * Show the keyguard. Will handle creating and attaching to the view manager * lazily. */ public synchronized void show(Bundle options) { if (DEBUG) Log.d(TAG, "show(); mKeyguardView==" + mKeyguardView); boolean enableScreenRotation = shouldEnableScreenRotation(); maybeCreateKeyguardLocked(enableScreenRotation, false, options); maybeEnableScreenRotation(enableScreenRotation); // Disable common aspects of the system/status/navigation bars that are not appropriate or // useful on any keyguard screen but can be re-shown by dialogs or SHOW_WHEN_LOCKED // activities. Other disabled bits are handled by the KeyguardViewMediator talking // directly to the status bar service. int visFlags = View.STATUS_BAR_DISABLE_HOME; if (shouldEnableTranslucentDecor()) { mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS | WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION; } if (DEBUG) Log.v(TAG, "show:setSystemUiVisibility(" + Integer.toHexString(visFlags)+")"); mKeyguardHost.setSystemUiVisibility(visFlags); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); mKeyguardHost.setVisibility(View.VISIBLE); mKeyguardView.show(); mKeyguardView.requestFocus(); } private boolean shouldEnableScreenRotation() { Resources res = mContext.getResources(); return SystemProperties.getBoolean("lockscreen.rot_override",false) || res.getBoolean(R.bool.config_enableLockScreenRotation); } private boolean shouldEnableTranslucentDecor() { Resources res = mContext.getResources(); return res.getBoolean(R.bool.config_enableLockScreenTranslucentDecor); } class ViewManagerHost extends FrameLayout { private static final int BACKGROUND_COLOR = 0x70000000; private Drawable mCustomBackground; // This is a faster way to draw the background on devices without hardware acceleration private final Drawable mBackgroundDrawable = new Drawable() { @Override public void draw(Canvas canvas) { if (mCustomBackground != null) { final Rect bounds = mCustomBackground.getBounds(); final int vWidth = getWidth(); final int vHeight = getHeight(); final int restore = canvas.save(); canvas.translate(-(bounds.width() - vWidth) / 2, -(bounds.height() - vHeight) / 2); mCustomBackground.draw(canvas); canvas.restoreToCount(restore); } else { canvas.drawColor(BACKGROUND_COLOR, PorterDuff.Mode.SRC); } } @Override public void setAlpha(int alpha) { } @Override public void setColorFilter(ColorFilter cf) { } @Override public int getOpacity() { return PixelFormat.TRANSLUCENT; } }; public ViewManagerHost(Context context) { super(context); setBackground(mBackgroundDrawable); } public void setCustomBackground(Drawable d) { mCustomBackground = d; if (d != null) { d.setColorFilter(BACKGROUND_COLOR, PorterDuff.Mode.SRC_OVER); } computeCustomBackgroundBounds(); invalidate(); } private void computeCustomBackgroundBounds() { if (mCustomBackground == null) return; // Nothing to do if (!isLaidOut()) return; // We'll do this later final int bgWidth = mCustomBackground.getIntrinsicWidth(); final int bgHeight = mCustomBackground.getIntrinsicHeight(); final int vWidth = getWidth(); final int vHeight = getHeight(); final float bgAspect = (float) bgWidth / bgHeight; final float vAspect = (float) vWidth / vHeight; if (bgAspect > vAspect) { mCustomBackground.setBounds(0, 0, (int) (vHeight * bgAspect), vHeight); } else { mCustomBackground.setBounds(0, 0, vWidth, (int) (vWidth / bgAspect)); } } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); computeCustomBackgroundBounds(); } @Override protected void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); if (mKeyguardHost.getVisibility() == View.VISIBLE) { // only propagate configuration messages if we're currently showing maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, null); } else { if (DEBUG) Log.v(TAG, "onConfigurationChanged: view not visible"); } } @Override public boolean dispatchKeyEvent(KeyEvent event) { if (mKeyguardView != null) { // Always process back and menu keys, regardless of focus if (event.getAction() == KeyEvent.ACTION_DOWN) { int keyCode = event.getKeyCode(); if (keyCode == KeyEvent.KEYCODE_BACK && mKeyguardView.handleBackKey()) { return true; } else if (keyCode == KeyEvent.KEYCODE_MENU && mKeyguardView.handleMenuKey()) { return true; } } // Always process media keys, regardless of focus if (mKeyguardView.dispatchKeyEvent(event)) { return true; } } return super.dispatchKeyEvent(event); } } SparseArray mStateContainer = new SparseArray(); private void maybeCreateKeyguardLocked(boolean enableScreenRotation, boolean force, Bundle options) { if (mKeyguardHost != null) { mKeyguardHost.saveHierarchyState(mStateContainer); } if (mKeyguardHost == null) { if (DEBUG) Log.d(TAG, "keyguard host is null, creating it..."); mKeyguardHost = new ViewManagerHost(mContext); int flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR | WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN | WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; if (!mNeedsInput) { flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } final int stretch = ViewGroup.LayoutParams.MATCH_PARENT; final int type = WindowManager.LayoutParams.TYPE_KEYGUARD; WindowManager.LayoutParams lp = new WindowManager.LayoutParams( stretch, stretch, type, flags, PixelFormat.TRANSLUCENT); lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; lp.windowAnimations = R.style.Animation_LockScreen; lp.screenOrientation = enableScreenRotation ? ActivityInfo.SCREEN_ORIENTATION_USER : ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; if (ActivityManager.isHighEndGfx()) { lp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED; } lp.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SET_NEEDS_MENU_KEY; lp.inputFeatures |= WindowManager.LayoutParams.INPUT_FEATURE_DISABLE_USER_ACTIVITY; lp.setTitle("Keyguard"); mWindowLayoutParams = lp; mViewManager.addView(mKeyguardHost, lp); KeyguardUpdateMonitor.getInstance(mContext).registerCallback(mBackgroundChanger); } if (force || mKeyguardView == null) { mKeyguardHost.setCustomBackground(null); mKeyguardHost.removeAllViews(); inflateKeyguardView(options); mKeyguardView.requestFocus(); } updateUserActivityTimeoutInWindowLayoutParams(); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); mKeyguardHost.restoreHierarchyState(mStateContainer); } private void inflateKeyguardView(Bundle options) { View v = mKeyguardHost.findViewById(R.id.keyguard_host_view); if (v != null) { mKeyguardHost.removeView(v); } final LayoutInflater inflater = LayoutInflater.from(mContext); View view = inflater.inflate(R.layout.keyguard_host_view, mKeyguardHost, true); mKeyguardView = (KeyguardHostView) view.findViewById(R.id.keyguard_host_view); mKeyguardView.setLockPatternUtils(mLockPatternUtils); mKeyguardView.setViewMediatorCallback(mViewMediatorCallback); mKeyguardView.initializeSwitchingUserState(options != null && options.getBoolean(IS_SWITCHING_USER)); // HACK // The keyguard view will have set up window flags in onFinishInflate before we set // the view mediator callback. Make sure it knows the correct IME state. if (mViewMediatorCallback != null) { KeyguardPasswordView kpv = (KeyguardPasswordView) mKeyguardView.findViewById( R.id.keyguard_password_view); if (kpv != null) { mViewMediatorCallback.setNeedsInput(kpv.needsInput()); } } if (options != null) { int widgetToShow = options.getInt(LockPatternUtils.KEYGUARD_SHOW_APPWIDGET, AppWidgetManager.INVALID_APPWIDGET_ID); if (widgetToShow != AppWidgetManager.INVALID_APPWIDGET_ID) { mKeyguardView.goToWidget(widgetToShow); } } } public void updateUserActivityTimeout() { updateUserActivityTimeoutInWindowLayoutParams(); mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); } private void updateUserActivityTimeoutInWindowLayoutParams() { // Use the user activity timeout requested by the keyguard view, if any. if (mKeyguardView != null) { long timeout = mKeyguardView.getUserActivityTimeout(); if (timeout >= 0) { mWindowLayoutParams.userActivityTimeout = timeout; return; } } // Otherwise, use the default timeout. mWindowLayoutParams.userActivityTimeout = KeyguardViewMediator.AWAKE_INTERVAL_DEFAULT_MS; } private void maybeEnableScreenRotation(boolean enableScreenRotation) { // TODO: move this outside if (enableScreenRotation) { if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen On!"); mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_USER; } else { if (DEBUG) Log.d(TAG, "Rotation sensor for lock screen Off!"); mWindowLayoutParams.screenOrientation = ActivityInfo.SCREEN_ORIENTATION_NOSENSOR; } mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); } void updateShowWallpaper(boolean show) { if (show) { mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; } else { mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; } mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); } public void setNeedsInput(boolean needsInput) { mNeedsInput = needsInput; if (mWindowLayoutParams != null) { if (needsInput) { mWindowLayoutParams.flags &= ~WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } else { mWindowLayoutParams.flags |= WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; } try { mViewManager.updateViewLayout(mKeyguardHost, mWindowLayoutParams); } catch (java.lang.IllegalArgumentException e) { // TODO: Ensure this method isn't called on views that are changing... Log.w(TAG,"Can't update input method on " + mKeyguardHost + " window not attached"); } } } /** * Reset the state of the view. */ public synchronized void reset(Bundle options) { if (DEBUG) Log.d(TAG, "reset()"); // User might have switched, check if we need to go back to keyguard // TODO: It's preferable to stay and show the correct lockscreen or unlock if none maybeCreateKeyguardLocked(shouldEnableScreenRotation(), true, options); } public synchronized void onScreenTurnedOff() { if (DEBUG) Log.d(TAG, "onScreenTurnedOff()"); mScreenOn = false; if (mKeyguardView != null) { mKeyguardView.onScreenTurnedOff(); } } public synchronized void onScreenTurnedOn(final IKeyguardShowCallback callback) { if (DEBUG) Log.d(TAG, "onScreenTurnedOn()"); mScreenOn = true; // If keyguard is not showing, we need to inform PhoneWindowManager with a null // token so it doesn't wait for us to draw... final IBinder token = isShowing() ? mKeyguardHost.getWindowToken() : null; if (DEBUG && token == null) Slog.v(TAG, "send wm null token: " + (mKeyguardHost == null ? "host was null" : "not showing")); if (mKeyguardView != null) { mKeyguardView.onScreenTurnedOn(); // Caller should wait for this window to be shown before turning // on the screen. if (callback != null) { if (mKeyguardHost.getVisibility() == View.VISIBLE) { // Keyguard may be in the process of being shown, but not yet // updated with the window manager... give it a chance to do so. mKeyguardHost.post(new Runnable() { @Override public void run() { try { callback.onShown(token); } catch (RemoteException e) { Slog.w(TAG, "Exception calling onShown():", e); } } }); } else { try { callback.onShown(token); } catch (RemoteException e) { Slog.w(TAG, "Exception calling onShown():", e); } } } } else if (callback != null) { try { callback.onShown(token); } catch (RemoteException e) { Slog.w(TAG, "Exception calling onShown():", e); } } } public synchronized void verifyUnlock() { if (DEBUG) Log.d(TAG, "verifyUnlock()"); show(null); mKeyguardView.verifyUnlock(); } /** * Hides the keyguard view */ public synchronized void hide() { if (DEBUG) Log.d(TAG, "hide()"); if (mKeyguardHost != null) { mKeyguardHost.setVisibility(View.GONE); // We really only want to preserve keyguard state for configuration changes. Hence // we should clear state of widgets (e.g. Music) when we hide keyguard so it can // start with a fresh state when we return. mStateContainer.clear(); // Don't do this right away, so we can let the view continue to animate // as it goes away. if (mKeyguardView != null) { final KeyguardViewBase lastView = mKeyguardView; mKeyguardView = null; mKeyguardHost.postDelayed(new Runnable() { @Override public void run() { synchronized (KeyguardViewManager.this) { lastView.cleanUp(); // Let go of any large bitmaps. mKeyguardHost.setCustomBackground(null); updateShowWallpaper(true); mKeyguardHost.removeView(lastView); mViewMediatorCallback.keyguardGone(); } } }, HIDE_KEYGUARD_DELAY); } } } /** * Dismisses the keyguard by going to the next screen or making it gone. */ public synchronized void dismiss() { if (mScreenOn) { mKeyguardView.dismiss(); } } /** * @return Whether the keyguard is showing */ public synchronized boolean isShowing() { return (mKeyguardHost != null && mKeyguardHost.getVisibility() == View.VISIBLE); } public void showAssistant() { if (mKeyguardView != null) { mKeyguardView.showAssistant(); } } public void dispatch(MotionEvent event) { if (mKeyguardView != null) { mKeyguardView.dispatch(event); } } public void launchCamera() { if (mKeyguardView != null) { mKeyguardView.launchCamera(); } } }