blob: 6aa0a4bfd1c7870830559ef8f48545255ec86721 [file] [log] [blame]
/*
* 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<Parcelable> mStateContainer = new SparseArray<Parcelable>();
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();
}
}
}