• 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.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 */, 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 
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                 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      *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART} and
168      *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}.
169      */
showPromptReason(int reason)170     public void showPromptReason(int reason) {
171         mSecurityContainer.showPromptReason(reason);
172     }
173 
showMessage(String message, int color)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      */
dismiss(int targetUserId)183     public boolean dismiss(int targetUserId) {
184         return dismiss(false, targetUserId);
185     }
186 
handleBackKey()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
dispatchPopulateAccessibilityEvent(AccessibilityEvent event)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 
getSecurityContainer()205     protected KeyguardSecurityContainer getSecurityContainer() {
206         return mSecurityContainer;
207     }
208 
209     @Override
dismiss(boolean authenticated, int targetUserId)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
finish(boolean strongAuth, int targetUserId)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
reset()242     public void reset() {
243         mViewMediatorCallback.resetKeyguard();
244     }
245 
246     @Override
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)247     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
248         if (mViewMediatorCallback != null) {
249             mViewMediatorCallback.setNeedsInput(needsInput);
250         }
251     }
252 
userActivity()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      */
onPause()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      */
onResume()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      */
startAppearAnimation()282     public void startAppearAnimation() {
283         mSecurityContainer.startAppearAnimation();
284     }
285 
startDisappearAnimation(Runnable finishRunnable)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      */
cleanUp()295     public void cleanUp() {
296         getSecurityContainer().onPause();
297     }
298 
299     @Override
dispatchKeyEvent(KeyEvent event)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      */
interceptMediaKey(KeyEvent event)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 
handleMediaKeyEvent(KeyEvent keyEvent)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
dispatchSystemUiVisibilityChanged(int visibility)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";
shouldEnableMenuKey()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 
setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)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 
setLockPatternUtils(LockPatternUtils utils)431     public void setLockPatternUtils(LockPatternUtils utils) {
432         mLockPatternUtils = utils;
433         mSecurityContainer.setLockPatternUtils(utils);
434     }
435 
getSecurityMode()436     public SecurityMode getSecurityMode() {
437         return mSecurityContainer.getSecurityMode();
438     }
439 
getCurrentSecurityMode()440     public SecurityMode getCurrentSecurityMode() {
441         return mSecurityContainer.getCurrentSecurityMode();
442     }
443 }
444