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