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