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.Activity;
     20 import android.app.ActivityManager;
     21 import android.content.Context;
     22 import android.content.res.ColorStateList;
     23 import android.content.res.Resources;
     24 import android.graphics.Canvas;
     25 import android.media.AudioManager;
     26 import android.os.SystemClock;
     27 import android.service.trust.TrustAgentService;
     28 import android.telephony.TelephonyManager;
     29 import android.util.AttributeSet;
     30 import android.util.Log;
     31 import android.view.KeyEvent;
     32 import android.widget.FrameLayout;
     33 
     34 import com.android.internal.widget.LockPatternUtils;
     35 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
     36 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
     37 import com.android.settingslib.Utils;
     38 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
     39 
     40 import java.io.File;
     41 
     42 /**
     43  * Base class for keyguard view.  {@link #reset} is where you should
     44  * reset the state of your view.  Use the {@link KeyguardViewCallback} via
     45  * {@link #getCallback()} to send information back (such as poking the wake lock,
     46  * or finishing the keyguard).
     47  *
     48  * Handles intercepting of media keys that still work when the keyguard is
     49  * showing.
     50  */
     51 public class KeyguardHostView extends FrameLayout implements SecurityCallback {
     52 
     53     private AudioManager mAudioManager;
     54     private TelephonyManager mTelephonyManager = null;
     55     protected ViewMediatorCallback mViewMediatorCallback;
     56     protected LockPatternUtils mLockPatternUtils;
     57     private OnDismissAction mDismissAction;
     58     private Runnable mCancelAction;
     59 
     60     private final KeyguardUpdateMonitorCallback mUpdateCallback =
     61             new KeyguardUpdateMonitorCallback() {
     62 
     63         @Override
     64         public void onUserSwitchComplete(int userId) {
     65             getSecurityContainer().showPrimarySecurityScreen(false /* turning off */);
     66         }
     67 
     68         @Override
     69         public void onTrustGrantedWithFlags(int flags, int userId) {
     70             if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
     71             if (!isAttachedToWindow()) return;
     72             boolean bouncerVisible = isVisibleToUser();
     73             boolean initiatedByUser =
     74                     (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
     75             boolean dismissKeyguard =
     76                     (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
     77 
     78             if (initiatedByUser || dismissKeyguard) {
     79                 if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) {
     80                     if (!bouncerVisible) {
     81                         // The trust agent dismissed the keyguard without the user proving
     82                         // that they are present (by swiping up to show the bouncer). That's fine if
     83                         // the user proved presence via some other way to the trust agent.
     84                         Log.i(TAG, "TrustAgent dismissed Keyguard.");
     85                     }
     86                     dismiss(false /* authenticated */, userId);
     87                 } else {
     88                     mViewMediatorCallback.playTrustedSound();
     89                 }
     90             }
     91         }
     92     };
     93 
     94     // Whether the volume keys should be handled by keyguard. If true, then
     95     // they will be handled here for specific media types such as music, otherwise
     96     // the audio service will bring up the volume dialog.
     97     private static final boolean KEYGUARD_MANAGES_VOLUME = false;
     98     public static final boolean DEBUG = KeyguardConstants.DEBUG;
     99     private static final String TAG = "KeyguardViewBase";
    100 
    101     private KeyguardSecurityContainer mSecurityContainer;
    102 
    103     public KeyguardHostView(Context context) {
    104         this(context, null);
    105     }
    106 
    107     public KeyguardHostView(Context context, AttributeSet attrs) {
    108         super(context, attrs);
    109         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
    110     }
    111 
    112     @Override
    113     protected void dispatchDraw(Canvas canvas) {
    114         super.dispatchDraw(canvas);
    115         if (mViewMediatorCallback != null) {
    116             mViewMediatorCallback.keyguardDoneDrawing();
    117         }
    118     }
    119 
    120     /**
    121      * Sets an action to run when keyguard finishes.
    122      *
    123      * @param action
    124      */
    125     public void setOnDismissAction(OnDismissAction action, Runnable cancelAction) {
    126         if (mCancelAction != null) {
    127             mCancelAction.run();
    128             mCancelAction = null;
    129         }
    130         mDismissAction = action;
    131         mCancelAction = cancelAction;
    132     }
    133 
    134     public boolean hasDismissActions() {
    135         return mDismissAction != null || mCancelAction != null;
    136     }
    137 
    138     public void cancelDismissAction() {
    139         setOnDismissAction(null, null);
    140     }
    141 
    142     @Override
    143     protected void onFinishInflate() {
    144         mSecurityContainer =
    145                 findViewById(R.id.keyguard_security_container);
    146         mLockPatternUtils = new LockPatternUtils(mContext);
    147         mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
    148         mSecurityContainer.setSecurityCallback(this);
    149         mSecurityContainer.showPrimarySecurityScreen(false);
    150     }
    151 
    152     /**
    153      * Called when the view needs to be shown.
    154      */
    155     public void showPrimarySecurityScreen() {
    156         if (DEBUG) Log.d(TAG, "show()");
    157         mSecurityContainer.showPrimarySecurityScreen(false);
    158     }
    159 
    160     public KeyguardSecurityView getCurrentSecurityView() {
    161         return mSecurityContainer != null ? mSecurityContainer.getCurrentSecurityView() : null;
    162     }
    163 
    164     /**
    165      * Show a string explaining why the security view needs to be solved.
    166      *
    167      * @param reason a flag indicating which string should be shown, see
    168      *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
    169      *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
    170      *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
    171      */
    172     public void showPromptReason(int reason) {
    173         mSecurityContainer.showPromptReason(reason);
    174     }
    175 
    176     public void showMessage(CharSequence message, ColorStateList colorState) {
    177         mSecurityContainer.showMessage(message, colorState);
    178     }
    179 
    180     public void showErrorMessage(CharSequence message) {
    181         showMessage(message, Utils.getColorError(mContext));
    182     }
    183 
    184     /**
    185      * Dismisses the keyguard by going to the next screen or making it gone.
    186      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
    187      * @return True if the keyguard is done.
    188      */
    189     public boolean dismiss(int targetUserId) {
    190         return dismiss(false, targetUserId);
    191     }
    192 
    193     public boolean handleBackKey() {
    194         if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
    195             mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser());
    196             return true;
    197         }
    198         return false;
    199     }
    200 
    201     protected KeyguardSecurityContainer getSecurityContainer() {
    202         return mSecurityContainer;
    203     }
    204 
    205     @Override
    206     public boolean dismiss(boolean authenticated, int targetUserId) {
    207         return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId);
    208     }
    209 
    210     /**
    211      * Authentication has happened and it's time to dismiss keyguard. This function
    212      * should clean up and inform KeyguardViewMediator.
    213      *
    214      * @param strongAuth whether the user has authenticated with strong authentication like
    215      *                   pattern, password or PIN but not by trust agents or fingerprint
    216      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
    217      */
    218     @Override
    219     public void finish(boolean strongAuth, int targetUserId) {
    220         // If there's a pending runnable because the user interacted with a widget
    221         // and we're leaving keyguard, then run it.
    222         boolean deferKeyguardDone = false;
    223         if (mDismissAction != null) {
    224             deferKeyguardDone = mDismissAction.onDismiss();
    225             mDismissAction = null;
    226             mCancelAction = null;
    227         }
    228         if (mViewMediatorCallback != null) {
    229             if (deferKeyguardDone) {
    230                 mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
    231             } else {
    232                 mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
    233             }
    234         }
    235     }
    236 
    237     @Override
    238     public void reset() {
    239         mViewMediatorCallback.resetKeyguard();
    240     }
    241 
    242     @Override
    243     public void onCancelClicked() {
    244         mViewMediatorCallback.onCancelClicked();
    245     }
    246 
    247     public void resetSecurityContainer() {
    248         mSecurityContainer.reset();
    249     }
    250 
    251     @Override
    252     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
    253         if (mViewMediatorCallback != null) {
    254             mViewMediatorCallback.setNeedsInput(needsInput);
    255         }
    256     }
    257 
    258     public CharSequence getAccessibilityTitleForCurrentMode() {
    259         return mSecurityContainer.getTitle();
    260     }
    261 
    262     public void userActivity() {
    263         if (mViewMediatorCallback != null) {
    264             mViewMediatorCallback.userActivity();
    265         }
    266     }
    267 
    268     /**
    269      * Called when the Keyguard is not actively shown anymore on the screen.
    270      */
    271     public void onPause() {
    272         if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
    273                 Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
    274         mSecurityContainer.showPrimarySecurityScreen(true);
    275         mSecurityContainer.onPause();
    276         clearFocus();
    277     }
    278 
    279     /**
    280      * Called when the Keyguard is actively shown on the screen.
    281      */
    282     public void onResume() {
    283         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
    284         mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
    285         requestFocus();
    286     }
    287 
    288     /**
    289      * Starts the animation when the Keyguard gets shown.
    290      */
    291     public void startAppearAnimation() {
    292         mSecurityContainer.startAppearAnimation();
    293     }
    294 
    295     public void startDisappearAnimation(Runnable finishRunnable) {
    296         if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
    297             finishRunnable.run();
    298         }
    299     }
    300 
    301     /**
    302      * Called before this view is being removed.
    303      */
    304     public void cleanUp() {
    305         getSecurityContainer().onPause();
    306     }
    307 
    308     @Override
    309     public boolean dispatchKeyEvent(KeyEvent event) {
    310         if (interceptMediaKey(event)) {
    311             return true;
    312         }
    313         return super.dispatchKeyEvent(event);
    314     }
    315 
    316     /**
    317      * Allows the media keys to work when the keyguard is showing.
    318      * The media keys should be of no interest to the actual keyguard view(s),
    319      * so intercepting them here should not be of any harm.
    320      * @param event The key event
    321      * @return whether the event was consumed as a media key.
    322      */
    323     public boolean interceptMediaKey(KeyEvent event) {
    324         final int keyCode = event.getKeyCode();
    325         if (event.getAction() == KeyEvent.ACTION_DOWN) {
    326             switch (keyCode) {
    327                 case KeyEvent.KEYCODE_MEDIA_PLAY:
    328                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
    329                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    330                     /* Suppress PLAY/PAUSE toggle when phone is ringing or
    331                      * in-call to avoid music playback */
    332                     if (mTelephonyManager == null) {
    333                         mTelephonyManager = (TelephonyManager) getContext().getSystemService(
    334                                 Context.TELEPHONY_SERVICE);
    335                     }
    336                     if (mTelephonyManager != null &&
    337                             mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
    338                         return true;  // suppress key event
    339                     }
    340                 case KeyEvent.KEYCODE_MUTE:
    341                 case KeyEvent.KEYCODE_HEADSETHOOK:
    342                 case KeyEvent.KEYCODE_MEDIA_STOP:
    343                 case KeyEvent.KEYCODE_MEDIA_NEXT:
    344                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    345                 case KeyEvent.KEYCODE_MEDIA_REWIND:
    346                 case KeyEvent.KEYCODE_MEDIA_RECORD:
    347                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    348                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
    349                     handleMediaKeyEvent(event);
    350                     return true;
    351                 }
    352 
    353                 case KeyEvent.KEYCODE_VOLUME_UP:
    354                 case KeyEvent.KEYCODE_VOLUME_DOWN:
    355                 case KeyEvent.KEYCODE_VOLUME_MUTE: {
    356                     if (KEYGUARD_MANAGES_VOLUME) {
    357                         synchronized (this) {
    358                             if (mAudioManager == null) {
    359                                 mAudioManager = (AudioManager) getContext().getSystemService(
    360                                         Context.AUDIO_SERVICE);
    361                             }
    362                         }
    363                         // Volume buttons should only function for music (local or remote).
    364                         // TODO: Actually handle MUTE.
    365                         mAudioManager.adjustSuggestedStreamVolume(
    366                                 keyCode == KeyEvent.KEYCODE_VOLUME_UP
    367                                         ? AudioManager.ADJUST_RAISE
    368                                         : AudioManager.ADJUST_LOWER /* direction */,
    369                                 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
    370                         // Don't execute default volume behavior
    371                         return true;
    372                     } else {
    373                         return false;
    374                     }
    375                 }
    376             }
    377         } else if (event.getAction() == KeyEvent.ACTION_UP) {
    378             switch (keyCode) {
    379                 case KeyEvent.KEYCODE_MUTE:
    380                 case KeyEvent.KEYCODE_HEADSETHOOK:
    381                 case KeyEvent.KEYCODE_MEDIA_PLAY:
    382                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
    383                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    384                 case KeyEvent.KEYCODE_MEDIA_STOP:
    385                 case KeyEvent.KEYCODE_MEDIA_NEXT:
    386                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    387                 case KeyEvent.KEYCODE_MEDIA_REWIND:
    388                 case KeyEvent.KEYCODE_MEDIA_RECORD:
    389                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    390                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
    391                     handleMediaKeyEvent(event);
    392                     return true;
    393                 }
    394             }
    395         }
    396         return false;
    397     }
    398 
    399     private void handleMediaKeyEvent(KeyEvent keyEvent) {
    400         synchronized (this) {
    401             if (mAudioManager == null) {
    402                 mAudioManager = (AudioManager) getContext().getSystemService(
    403                         Context.AUDIO_SERVICE);
    404             }
    405         }
    406         mAudioManager.dispatchMediaKeyEvent(keyEvent);
    407     }
    408 
    409     @Override
    410     public void dispatchSystemUiVisibilityChanged(int visibility) {
    411         super.dispatchSystemUiVisibilityChanged(visibility);
    412 
    413         if (!(mContext instanceof Activity)) {
    414             setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
    415         }
    416     }
    417 
    418     /**
    419      * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
    420      * some cases where we wish to disable it, notably when the menu button placement or technology
    421      * is prone to false positives.
    422      *
    423      * @return true if the menu key should be enabled
    424      */
    425     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
    426     public boolean shouldEnableMenuKey() {
    427         final Resources res = getResources();
    428         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
    429         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
    430         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
    431         return !configDisabled || isTestHarness || fileOverride;
    432     }
    433 
    434     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
    435         mViewMediatorCallback = viewMediatorCallback;
    436         // Update ViewMediator with the current input method requirements
    437         mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
    438     }
    439 
    440     public void setLockPatternUtils(LockPatternUtils utils) {
    441         mLockPatternUtils = utils;
    442         mSecurityContainer.setLockPatternUtils(utils);
    443     }
    444 
    445     public SecurityMode getSecurityMode() {
    446         return mSecurityContainer.getSecurityMode();
    447     }
    448 
    449     public SecurityMode getCurrentSecurityMode() {
    450         return mSecurityContainer.getCurrentSecurityMode();
    451     }
    452 }
    453