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          */
onDismiss()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 
KeyguardHostView(Context context)108     public KeyguardHostView(Context context) {
109         this(context, null);
110     }
111 
KeyguardHostView(Context context, AttributeSet attrs)112     public KeyguardHostView(Context context, AttributeSet attrs) {
113         super(context, attrs);
114         KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateCallback);
115     }
116 
117     @Override
dispatchDraw(Canvas canvas)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      */
setOnDismissAction(OnDismissAction action, Runnable cancelAction)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 
cancelDismissAction()139     public void cancelDismissAction() {
140         setOnDismissAction(null, null);
141     }
142 
143     @Override
onFinishInflate()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      */
showPrimarySecurityScreen()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      */
showPromptReason(int reason)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      */
dismiss()178     public boolean dismiss() {
179         return dismiss(false);
180     }
181 
handleBackKey()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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)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 
getSecurityContainer()200     protected KeyguardSecurityContainer getSecurityContainer() {
201         return mSecurityContainer;
202     }
203 
204     @Override
dismiss(boolean authenticated)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
finish()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
reset()233     public void reset() {
234         mViewMediatorCallback.resetKeyguard();
235     }
236 
237     @Override
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)238     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
239         if (mViewMediatorCallback != null) {
240             mViewMediatorCallback.setNeedsInput(needsInput);
241         }
242     }
243 
userActivity()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      */
onPause()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      */
onResume()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      */
startAppearAnimation()273     public void startAppearAnimation() {
274         mSecurityContainer.startAppearAnimation();
275     }
276 
startDisappearAnimation(Runnable finishRunnable)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      */
verifyUnlock()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      */
cleanUp()312     public void cleanUp() {
313         getSecurityContainer().onPause();
314     }
315 
316     @Override
dispatchKeyEvent(KeyEvent event)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      */
interceptMediaKey(KeyEvent event)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 
handleMediaKeyEvent(KeyEvent keyEvent)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
dispatchSystemUiVisibilityChanged(int visibility)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";
shouldEnableMenuKey()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 
handleMenuKey()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 
setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)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 
setLockPatternUtils(LockPatternUtils utils)457     public void setLockPatternUtils(LockPatternUtils utils) {
458         mLockPatternUtils = utils;
459         mSecurityContainer.setLockPatternUtils(utils);
460     }
461 
getSecurityMode()462     public SecurityMode getSecurityMode() {
463         return mSecurityContainer.getSecurityMode();
464     }
465 
getCurrentSecurityMode()466     public SecurityMode getCurrentSecurityMode() {
467         return mSecurityContainer.getCurrentSecurityMode();
468     }
469 }
470