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