Home | History | Annotate | Download | only in keyguard
      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 
     17 package com.android.keyguard;
     18 
     19 import android.app.PendingIntent;
     20 import android.graphics.Bitmap;
     21 import android.graphics.drawable.BitmapDrawable;
     22 import com.android.internal.policy.IKeyguardShowCallback;
     23 import com.android.internal.widget.LockPatternUtils;
     24 
     25 import android.app.Activity;
     26 import android.app.ActivityManager;
     27 import android.appwidget.AppWidgetManager;
     28 import android.content.Context;
     29 import android.content.pm.ActivityInfo;
     30 import android.content.res.Configuration;
     31 import android.content.res.Resources;
     32 import android.graphics.Canvas;
     33 import android.graphics.ColorFilter;
     34 import android.graphics.PixelFormat;
     35 import android.graphics.PorterDuff;
     36 import android.graphics.Rect;
     37 import android.graphics.drawable.Drawable;
     38 import android.os.Bundle;
     39 import android.os.IBinder;
     40 import android.os.Parcelable;
     41 import android.os.RemoteException;
     42 import android.os.SystemProperties;
     43 import android.util.Log;
     44 import android.util.Slog;
     45 import android.util.SparseArray;
     46 import android.view.KeyEvent;
     47 import android.view.LayoutInflater;
     48 import android.view.MotionEvent;
     49 import android.view.View;
     50 import android.view.ViewGroup;
     51 import android.view.ViewManager;
     52 import android.view.WindowManager;
     53 import 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  */
     61 public 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