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