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.ActivityManager;
20 import android.content.Context;
21 import android.content.res.ColorStateList;
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.widget.FrameLayout;
32 
33 import androidx.annotation.VisibleForTesting;
34 
35 import com.android.internal.widget.LockPatternUtils;
36 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
37 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
38 import com.android.settingslib.Utils;
39 import com.android.systemui.Dependency;
40 import com.android.systemui.R;
41 import com.android.systemui.plugins.ActivityStarter.OnDismissAction;
42 
43 import java.io.File;
44 
45 /**
46  * Base class for keyguard view.  {@link #reset} is where you should
47  * reset the state of your view.  Use the {@link KeyguardViewCallback} via
48  * {@link #getCallback()} to send information back (such as poking the wake lock,
49  * or finishing the keyguard).
50  *
51  * Handles intercepting of media keys that still work when the keyguard is
52  * showing.
53  */
54 public class KeyguardHostView extends FrameLayout implements SecurityCallback {
55 
56     private AudioManager mAudioManager;
57     private TelephonyManager mTelephonyManager = null;
58     protected ViewMediatorCallback mViewMediatorCallback;
59     protected LockPatternUtils mLockPatternUtils;
60     private OnDismissAction mDismissAction;
61     private Runnable mCancelAction;
62 
63     private final KeyguardUpdateMonitorCallback mUpdateCallback =
64             new KeyguardUpdateMonitorCallback() {
65 
66         @Override
67         public void onUserSwitchComplete(int userId) {
68             getSecurityContainer().showPrimarySecurityScreen(false /* turning off */);
69         }
70 
71         @Override
72         public void onTrustGrantedWithFlags(int flags, int userId) {
73             if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
74             if (!isAttachedToWindow()) return;
75             boolean bouncerVisible = isVisibleToUser();
76             boolean initiatedByUser =
77                     (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
78             boolean dismissKeyguard =
79                     (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
80 
81             if (initiatedByUser || dismissKeyguard) {
82                 if (mViewMediatorCallback.isScreenOn() && (bouncerVisible || dismissKeyguard)) {
83                     if (!bouncerVisible) {
84                         // The trust agent dismissed the keyguard without the user proving
85                         // that they are present (by swiping up to show the bouncer). That's fine if
86                         // the user proved presence via some other way to the trust agent.
87                         Log.i(TAG, "TrustAgent dismissed Keyguard.");
88                     }
89                     dismiss(false /* authenticated */, userId,
90                             /* bypassSecondaryLockScreen */ false);
91                 } else {
92                     mViewMediatorCallback.playTrustedSound();
93                 }
94             }
95         }
96     };
97 
98     // Whether the volume keys should be handled by keyguard. If true, then
99     // they will be handled here for specific media types such as music, otherwise
100     // the audio service will bring up the volume dialog.
101     private static final boolean KEYGUARD_MANAGES_VOLUME = false;
102     public static final boolean DEBUG = KeyguardConstants.DEBUG;
103     private static final String TAG = "KeyguardViewBase";
104 
105     @VisibleForTesting
106     protected 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         Dependency.get(KeyguardUpdateMonitor.class).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 
hasDismissActions()139     public boolean hasDismissActions() {
140         return mDismissAction != null || mCancelAction != null;
141     }
142 
cancelDismissAction()143     public void cancelDismissAction() {
144         setOnDismissAction(null, null);
145     }
146 
147     @Override
onFinishInflate()148     protected void onFinishInflate() {
149         mSecurityContainer =
150                 findViewById(R.id.keyguard_security_container);
151         mLockPatternUtils = new LockPatternUtils(mContext);
152         mSecurityContainer.setLockPatternUtils(mLockPatternUtils);
153         mSecurityContainer.setSecurityCallback(this);
154         mSecurityContainer.showPrimarySecurityScreen(false);
155     }
156 
157     /**
158      * Called when the view needs to be shown.
159      */
showPrimarySecurityScreen()160     public void showPrimarySecurityScreen() {
161         if (DEBUG) Log.d(TAG, "show()");
162         mSecurityContainer.showPrimarySecurityScreen(false);
163     }
164 
getCurrentSecurityView()165     public KeyguardSecurityView getCurrentSecurityView() {
166         return mSecurityContainer != null ? mSecurityContainer.getCurrentSecurityView() : null;
167     }
168 
169     /**
170      * Show a string explaining why the security view needs to be solved.
171      *
172      * @param reason a flag indicating which string should be shown, see
173      *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
174      *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
175      *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
176      *               {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
177      */
showPromptReason(int reason)178     public void showPromptReason(int reason) {
179         mSecurityContainer.showPromptReason(reason);
180     }
181 
showMessage(CharSequence message, ColorStateList colorState)182     public void showMessage(CharSequence message, ColorStateList colorState) {
183         mSecurityContainer.showMessage(message, colorState);
184     }
185 
showErrorMessage(CharSequence message)186     public void showErrorMessage(CharSequence message) {
187         showMessage(message, Utils.getColorError(mContext));
188     }
189 
190     /**
191      * Dismisses the keyguard by going to the next screen or making it gone.
192      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
193      * @return True if the keyguard is done.
194      */
dismiss(int targetUserId)195     public boolean dismiss(int targetUserId) {
196         return dismiss(false, targetUserId, false);
197     }
198 
handleBackKey()199     public boolean handleBackKey() {
200         if (mSecurityContainer.getCurrentSecuritySelection() != SecurityMode.None) {
201             mSecurityContainer.dismiss(false, KeyguardUpdateMonitor.getCurrentUser());
202             return true;
203         }
204         return false;
205     }
206 
getSecurityContainer()207     protected KeyguardSecurityContainer getSecurityContainer() {
208         return mSecurityContainer;
209     }
210 
211     @Override
dismiss(boolean authenticated, int targetUserId, boolean bypassSecondaryLockScreen)212     public boolean dismiss(boolean authenticated, int targetUserId,
213             boolean bypassSecondaryLockScreen) {
214         return mSecurityContainer.showNextSecurityScreenOrFinish(authenticated, targetUserId,
215                 bypassSecondaryLockScreen);
216     }
217 
218     /**
219      * Authentication has happened and it's time to dismiss keyguard. This function
220      * should clean up and inform KeyguardViewMediator.
221      *
222      * @param strongAuth whether the user has authenticated with strong authentication like
223      *                   pattern, password or PIN but not by trust agents or fingerprint
224      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
225      */
226     @Override
finish(boolean strongAuth, int targetUserId)227     public void finish(boolean strongAuth, int targetUserId) {
228         // If there's a pending runnable because the user interacted with a widget
229         // and we're leaving keyguard, then run it.
230         boolean deferKeyguardDone = false;
231         if (mDismissAction != null) {
232             deferKeyguardDone = mDismissAction.onDismiss();
233             mDismissAction = null;
234             mCancelAction = null;
235         }
236         if (mViewMediatorCallback != null) {
237             if (deferKeyguardDone) {
238                 mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
239             } else {
240                 mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
241             }
242         }
243     }
244 
245     @Override
reset()246     public void reset() {
247         mViewMediatorCallback.resetKeyguard();
248     }
249 
250     @Override
onCancelClicked()251     public void onCancelClicked() {
252         mViewMediatorCallback.onCancelClicked();
253     }
254 
resetSecurityContainer()255     public void resetSecurityContainer() {
256         mSecurityContainer.reset();
257     }
258 
259     @Override
onSecurityModeChanged(SecurityMode securityMode, boolean needsInput)260     public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
261         if (mViewMediatorCallback != null) {
262             mViewMediatorCallback.setNeedsInput(needsInput);
263         }
264     }
265 
getAccessibilityTitleForCurrentMode()266     public CharSequence getAccessibilityTitleForCurrentMode() {
267         return mSecurityContainer.getTitle();
268     }
269 
userActivity()270     public void userActivity() {
271         if (mViewMediatorCallback != null) {
272             mViewMediatorCallback.userActivity();
273         }
274     }
275 
276     /**
277      * Called when the Keyguard is not actively shown anymore on the screen.
278      */
onPause()279     public void onPause() {
280         if (DEBUG) Log.d(TAG, String.format("screen off, instance %s at %s",
281                 Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
282         mSecurityContainer.showPrimarySecurityScreen(true);
283         mSecurityContainer.onPause();
284         clearFocus();
285     }
286 
287     /**
288      * Called when the Keyguard is actively shown on the screen.
289      */
onResume()290     public void onResume() {
291         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
292         mSecurityContainer.onResume(KeyguardSecurityView.SCREEN_ON);
293         requestFocus();
294     }
295 
296     /**
297      * Starts the animation when the Keyguard gets shown.
298      */
startAppearAnimation()299     public void startAppearAnimation() {
300         mSecurityContainer.startAppearAnimation();
301     }
302 
startDisappearAnimation(Runnable finishRunnable)303     public void startDisappearAnimation(Runnable finishRunnable) {
304         if (!mSecurityContainer.startDisappearAnimation(finishRunnable) && finishRunnable != null) {
305             finishRunnable.run();
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     /**
418      * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
419      * some cases where we wish to disable it, notably when the menu button placement or technology
420      * is prone to false positives.
421      *
422      * @return true if the menu key should be enabled
423      */
424     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
shouldEnableMenuKey()425     public boolean shouldEnableMenuKey() {
426         final Resources res = getResources();
427         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
428         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
429         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
430         return !configDisabled || isTestHarness || fileOverride;
431     }
432 
setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback)433     public void setViewMediatorCallback(ViewMediatorCallback viewMediatorCallback) {
434         mViewMediatorCallback = viewMediatorCallback;
435         // Update ViewMediator with the current input method requirements
436         mViewMediatorCallback.setNeedsInput(mSecurityContainer.needsInput());
437     }
438 
setLockPatternUtils(LockPatternUtils utils)439     public void setLockPatternUtils(LockPatternUtils utils) {
440         mLockPatternUtils = utils;
441         mSecurityContainer.setLockPatternUtils(utils);
442     }
443 
getSecurityMode()444     public SecurityMode getSecurityMode() {
445         return mSecurityContainer.getSecurityMode();
446     }
447 
getCurrentSecurityMode()448     public SecurityMode getCurrentSecurityMode() {
449         return mSecurityContainer.getCurrentSecurityMode();
450     }
451 
452     /**
453      * When bouncer was visible and is starting to become hidden.
454      */
onStartingToHide()455     public void onStartingToHide() {
456         mSecurityContainer.onStartingToHide();
457     }
458 }
459