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