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      *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
    168      *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
    169      */
    170     public void showPromptReason(int reason) {
    171         mSecurityContainer.showPromptReason(reason);
    172     }
    173 
    174     public void showMessage(String message, int color) {
    175         mSecurityContainer.showMessage(message, color);
    176     }
    177 
    178     /**
    179      *  Dismisses the keyguard by going to the next screen or making it gone.
    180      *
    181      *  @return True if the keyguard is done.
    182      */
    183     public boolean dismiss() {
    184         return dismiss(false);
    185     }
    186 
    187     public boolean handleBackKey() {
    188         if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
    189             mSecurityContainer.dismiss(false);
    190             return true;
    191         }
    192         return false;
    193     }
    194 
    195     @Override
    196     public boolean dispatchPopulateAccessibilityEvent(AccessibilityEvent event) {
    197         if (event.getEventType() == AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED) {
    198             event.getText().add(mSecurityContainer.getCurrentSecurityModeContentDescription());
    199             return true;
    200         } else {
    201             return super.dispatchPopulateAccessibilityEvent(event);
    202         }
    203     }
    204 
    205     protected KeyguardSecurityContainer getSecurityContainer() {
    206         return mSecurityContainer;
    207     }
    208 
    209     @Override
    210     public boolean dismiss(boolean authenticated) {
    211         return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated);
    212     }
    213 
    214     /**
    215      * Authentication has happened and it's time to dismiss keyguard. This function
    216      * should clean up and inform KeyguardViewMediator.
    217      *
    218      * @param strongAuth whether the user has authenticated with strong authentication like
    219      *                   pattern, password or PIN but not by trust agents or fingerprint
    220      */
    221     @Override
    222     public void finish(boolean strongAuth) {
    223         // If there's a pending runnable because the user interacted with a widget
    224         // and we're leaving keyguard, then run it.
    225         boolean deferKeyguardDone = false;
    226         if (mDismissAction != null) {
    227             deferKeyguardDone = mDismissAction.onDismiss();
    228             mDismissAction = null;
    229             mCancelAction = null;
    230         }
    231         if (mViewMediatorCallback != null) {
    232             if (deferKeyguardDone) {
    233                 mViewMediatorCallback.keyguardDonePending(strongAuth);
    234             } else {
    235                 mViewMediatorCallback.keyguardDone(strongAuth);
    236             }
    237         }
    238     }
    239 
    240     @Override
    241     public void reset() {
    242         mViewMediatorCallback.resetKeyguard();
    243     }
    244 
    245     @Override
    246     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
    247         if (mViewMediatorCallback != null) {
    248             mViewMediatorCallback.setNeedsInput(needsInput);
    249         }
    250     }
    251 
    252     public void userActivity() {
    253         if (mViewMediatorCallback != null) {
    254             mViewMediatorCallback.userActivity();
    255         }
    256     }
    257 
    258     /**
    259      * Called when the Keyguard is not actively shown anymore on the screen.
    260      */
    261     public void onPause() {
    262         if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
    263                 Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
    264         mSecurityContainer.showPrimarySecurityScreen(true);
    265         mSecurityContainer.onPause();
    266         clearFocus();
    267     }
    268 
    269     /**
    270      * Called when the Keyguard is actively shown on the screen.
    271      */
    272     public void onResume() {
    273         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
    274         mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
    275         requestFocus();
    276     }
    277 
    278     /**
    279      * Starts the animation when the Keyguard gets shown.
    280      */
    281     public void startAppearAnimation() {
    282         mSecurityContainer.startAppearAnimation();
    283     }
    284 
    285     public void startDisappearAnimation(Runnable finishRunnable) {
    286         if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
    287             finishRunnable.run();
    288         }
    289     }
    290 
    291     /**
    292      * Called before this view is being removed.
    293      */
    294     public void cleanUp() {
    295         getSecurityContainer().onPause();
    296     }
    297 
    298     @Override
    299     public boolean dispatchKeyEvent(KeyEvent event) {
    300         if (interceptMediaKey(event)) {
    301             return true;
    302         }
    303         return super.dispatchKeyEvent(event);
    304     }
    305 
    306     /**
    307      * Allows the media keys to work when the keyguard is showing.
    308      * The media keys should be of no interest to the actual keyguard view(s),
    309      * so intercepting them here should not be of any harm.
    310      * @param event The key event
    311      * @return whether the event was consumed as a media key.
    312      */
    313     public boolean interceptMediaKey(KeyEvent event) {
    314         final int keyCode = event.getKeyCode();
    315         if (event.getAction() == KeyEvent.ACTION_DOWN) {
    316             switch (keyCode) {
    317                 case KeyEvent.KEYCODE_MEDIA_PLAY:
    318                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
    319                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    320                     /* Suppress PLAY/PAUSE toggle when phone is ringing or
    321                      * in-call to avoid music playback */
    322                     if (mTelephonyManager == null) {
    323                         mTelephonyManager = (TelephonyManager) getContext().getSystemService(
    324                                 Context.TELEPHONY_SERVICE);
    325                     }
    326                     if (mTelephonyManager != null &&
    327                             mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
    328                         return true;  // suppress key event
    329                     }
    330                 case KeyEvent.KEYCODE_MUTE:
    331                 case KeyEvent.KEYCODE_HEADSETHOOK:
    332                 case KeyEvent.KEYCODE_MEDIA_STOP:
    333                 case KeyEvent.KEYCODE_MEDIA_NEXT:
    334                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    335                 case KeyEvent.KEYCODE_MEDIA_REWIND:
    336                 case KeyEvent.KEYCODE_MEDIA_RECORD:
    337                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    338                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
    339                     handleMediaKeyEvent(event);
    340                     return true;
    341                 }
    342 
    343                 case KeyEvent.KEYCODE_VOLUME_UP:
    344                 case KeyEvent.KEYCODE_VOLUME_DOWN:
    345                 case KeyEvent.KEYCODE_VOLUME_MUTE: {
    346                     if (KEYGUARD_MANAGES_VOLUME) {
    347                         synchronized (this) {
    348                             if (mAudioManager == null) {
    349                                 mAudioManager = (AudioManager) getContext().getSystemService(
    350                                         Context.AUDIO_SERVICE);
    351                             }
    352                         }
    353                         // Volume buttons should only function for music (local or remote).
    354                         // TODO: Actually handle MUTE.
    355                         mAudioManager.adjustSuggestedStreamVolume(
    356                                 keyCode == KeyEvent.KEYCODE_VOLUME_UP
    357                                         ? AudioManager.ADJUST_RAISE
    358                                         : AudioManager.ADJUST_LOWER /* direction */,
    359                                 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
    360                         // Don't execute default volume behavior
    361                         return true;
    362                     } else {
    363                         return false;
    364                     }
    365                 }
    366             }
    367         } else if (event.getAction() == KeyEvent.ACTION_UP) {
    368             switch (keyCode) {
    369                 case KeyEvent.KEYCODE_MUTE:
    370                 case KeyEvent.KEYCODE_HEADSETHOOK:
    371                 case KeyEvent.KEYCODE_MEDIA_PLAY:
    372                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
    373                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
    374                 case KeyEvent.KEYCODE_MEDIA_STOP:
    375                 case KeyEvent.KEYCODE_MEDIA_NEXT:
    376                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
    377                 case KeyEvent.KEYCODE_MEDIA_REWIND:
    378                 case KeyEvent.KEYCODE_MEDIA_RECORD:
    379                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
    380                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
    381                     handleMediaKeyEvent(event);
    382                     return true;
    383                 }
    384             }
    385         }
    386         return false;
    387     }
    388 
    389     private void handleMediaKeyEvent(KeyEvent keyEvent) {
    390         synchronized (this) {
    391             if (mAudioManager == null) {
    392                 mAudioManager = (AudioManager) getContext().getSystemService(
    393                         Context.AUDIO_SERVICE);
    394             }
    395         }
    396         mAudioManager.dispatchMediaKeyEvent(keyEvent);
    397     }
    398 
    399     @Override
    400     public void dispatchSystemUiVisibilityChanged(int visibility) {
    401         super.dispatchSystemUiVisibilityChanged(visibility);
    402 
    403         if (!(mContext instanceof Activity)) {
    404             setSystemUiVisibility(STATUS_BAR_DISABLE_BACK);
    405         }
    406     }
    407 
    408     /**
    409      * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
    410      * some cases where we wish to disable it, notably when the menu button placement or technology
    411      * is prone to false positives.
    412      *
    413      * @return true if the menu key should be enabled
    414      */
    415     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
    416     public boolean shouldEnableMenuKey() {
    417         final Resources res = getResources();
    418         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
    419         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
    420         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
    421         return !configDisabled || isTestHarness || fileOverride;
    422     }
    423 
    424     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
    425         mViewMediatorCallback = viewMediatorCallback;
    426         // Update ViewMediator with the current input method requirements
    427         mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
    428     }
    429 
    430     public void setLockPatternUtils(LockPatternUtils utils) {
    431         mLockPatternUtils = utils;
    432         mSecurityContainer.setLockPatternUtils(utils);
    433     }
    434 
    435     public SecurityMode getSecurityMode() {
    436         return mSecurityContainer.getSecurityMode();
    437     }
    438 
    439     public SecurityMode getCurrentSecurityMode() {
    440         return mSecurityContainer.getCurrentSecurityMode();
    441     }
    442 }
    443