| /* |
| * 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 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 static boolean USE_UPPER_CASE = true; |
| public final static String IS_SWITCHING_USER = "is_switching_user"; |
| |
| // 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; |
| |
| 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 (shouldEnableTransparentBars()) { |
| visFlags |= View.SYSTEM_UI_FLAG_TRANSPARENT_STATUS |
| | View.SYSTEM_UI_FLAG_TRANSPARENT_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 shouldEnableTransparentBars() { |
| Resources res = mContext.getResources(); |
| return res.getBoolean(R.bool.config_enableLockScreenTransparentBars); |
| } |
| |
| class ViewManagerHost extends FrameLayout { |
| private static final int BACKGROUND_COLOR = 0x70000000; |
| // 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) { |
| 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; |
| } |
| }; |
| private final View mScrimView; |
| private boolean mExtendIntoPadding; |
| public ViewManagerHost(Context context, boolean extendIntoPadding) { |
| super(context); |
| mExtendIntoPadding = extendIntoPadding; |
| setFitsSystemWindows(true); |
| setClipToPadding(!mExtendIntoPadding); |
| setBackground(mBackgroundDrawable); |
| |
| mScrimView = new View(context); |
| mScrimView.setVisibility(View.GONE); |
| mScrimView.setBackgroundColor(0x99000000); |
| addView(mScrimView); |
| } |
| |
| private boolean considerPadding(View child) { |
| return !mExtendIntoPadding || child instanceof KeyguardHostView; |
| } |
| |
| @Override |
| protected void measureChildWithMargins(View child, |
| int parentWidthMeasureSpec, int widthUsed, |
| int parentHeightMeasureSpec, int heightUsed) { |
| if (considerPadding(child)) { |
| // don't extend into padding (default behavior) |
| super.measureChildWithMargins(child, |
| parentWidthMeasureSpec, widthUsed, |
| parentHeightMeasureSpec, heightUsed); |
| } else { |
| // allowed to extend into padding (scrim / camera preview) |
| child.measure(parentWidthMeasureSpec, parentHeightMeasureSpec); |
| } |
| } |
| |
| @Override |
| protected void onLayout(boolean changed, int l, int t, int r, int b) { |
| final int count = getChildCount(); |
| for (int i = 0; i < count; i++) { |
| final View child = getChildAt(i); |
| int cl = l, ct = t, cr = r, cb = b; |
| if (considerPadding(child)) { |
| cl += mPaddingLeft; |
| ct += mPaddingTop; |
| cr -= mPaddingRight; |
| cb -= mPaddingBottom; |
| } |
| child.layout(cl, ct, cr, cb); |
| } |
| } |
| |
| @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, shouldEnableTransparentBars()); |
| |
| 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); |
| } |
| |
| if (force || mKeyguardView == null) { |
| 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)); |
| mKeyguardView.setScrimView(mKeyguardHost.mScrimView); |
| |
| // 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); |
| } |
| |
| 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 (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() { |
| IBinder token = null; |
| if (mKeyguardHost.getVisibility() == View.VISIBLE) { |
| token = mKeyguardHost.getWindowToken(); |
| } |
| try { |
| callback.onShown(token); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Exception calling onShown():", e); |
| } |
| } |
| }); |
| } else { |
| try { |
| callback.onShown(null); |
| } catch (RemoteException e) { |
| Slog.w(TAG, "Exception calling onShown():", e); |
| } |
| } |
| } |
| } else if (callback != null) { |
| try { |
| callback.onShown(null); |
| } 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(); |
| mKeyguardHost.removeView(lastView); |
| } |
| } |
| }, 500); |
| } |
| } |
| } |
| |
| /** |
| * 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); |
| } |
| } |
| } |