1 /*
2  * Copyright (C) 2020 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.res.ColorStateList;
21 import android.content.res.Resources;
22 import android.media.AudioManager;
23 import android.os.SystemClock;
24 import android.service.trust.TrustAgentService;
25 import android.telephony.TelephonyManager;
26 import android.util.Log;
27 import android.util.MathUtils;
28 import android.view.KeyEvent;
29 import android.view.View;
30 import android.view.View.OnKeyListener;
31 import android.view.ViewTreeObserver;
32 import android.widget.FrameLayout;
33 
34 import com.android.keyguard.KeyguardSecurityContainer.SecurityCallback;
35 import com.android.keyguard.KeyguardSecurityModel.SecurityMode;
36 import com.android.keyguard.dagger.KeyguardBouncerScope;
37 import com.android.settingslib.Utils;
38 import com.android.systemui.R;
39 import com.android.systemui.plugins.ActivityStarter;
40 import com.android.systemui.statusbar.phone.KeyguardBouncer;
41 import com.android.systemui.util.ViewController;
42 
43 import java.io.File;
44 
45 import javax.inject.Inject;
46 
47 /** Controller for a {@link KeyguardHostView}. */
48 @KeyguardBouncerScope
49 public class KeyguardHostViewController extends ViewController<KeyguardHostView> {
50     private static final String TAG = "KeyguardViewBase";
51     public static final boolean DEBUG = KeyguardConstants.DEBUG;
52     // Whether the volume keys should be handled by keyguard. If true, then
53     // they will be handled here for specific media types such as music, otherwise
54     // the audio service will bring up the volume dialog.
55     private static final boolean KEYGUARD_MANAGES_VOLUME = false;
56 
57     private static final String ENABLE_MENU_KEY_FILE = "/data/local/enable_menu_key";
58 
59     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
60     private final KeyguardSecurityContainerController mKeyguardSecurityContainerController;
61     private final TelephonyManager mTelephonyManager;
62     private final ViewMediatorCallback mViewMediatorCallback;
63     private final AudioManager mAudioManager;
64 
65     private ActivityStarter.OnDismissAction mDismissAction;
66     private Runnable mCancelAction;
67 
68     private final KeyguardUpdateMonitorCallback mUpdateCallback =
69             new KeyguardUpdateMonitorCallback() {
70                 @Override
71                 public void onUserSwitchComplete(int userId) {
72                     mKeyguardSecurityContainerController.showPrimarySecurityScreen(
73                             false /* turning off */);
74                 }
75 
76                 @Override
77                 public void onTrustGrantedWithFlags(int flags, int userId) {
78                     if (userId != KeyguardUpdateMonitor.getCurrentUser()) return;
79                     boolean bouncerVisible = mView.isVisibleToUser();
80                     boolean initiatedByUser =
81                             (flags & TrustAgentService.FLAG_GRANT_TRUST_INITIATED_BY_USER) != 0;
82                     boolean dismissKeyguard =
83                             (flags & TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD) != 0;
84 
85                     if (initiatedByUser || dismissKeyguard) {
86                         if (mViewMediatorCallback.isScreenOn()
87                                 && (bouncerVisible || dismissKeyguard)) {
88                             if (!bouncerVisible) {
89                                 // The trust agent dismissed the keyguard without the user proving
90                                 // that they are present (by swiping up to show the bouncer). That's
91                                 // fine if the user proved presence via some other way to the trust
92                                 //agent.
93                                 Log.i(TAG, "TrustAgent dismissed Keyguard.");
94                             }
95                             mSecurityCallback.dismiss(false /* authenticated */, userId,
96                                     /* bypassSecondaryLockScreen */ false);
97                         } else {
98                             mViewMediatorCallback.playTrustedSound();
99                         }
100                     }
101                 }
102             };
103 
104     private final SecurityCallback mSecurityCallback = new SecurityCallback() {
105 
106         @Override
107         public boolean dismiss(boolean authenticated, int targetUserId,
108                 boolean bypassSecondaryLockScreen) {
109             return mKeyguardSecurityContainerController.showNextSecurityScreenOrFinish(
110                     authenticated, targetUserId, bypassSecondaryLockScreen);
111         }
112 
113         @Override
114         public void userActivity() {
115             mViewMediatorCallback.userActivity();
116         }
117 
118         @Override
119         public void onSecurityModeChanged(SecurityMode securityMode, boolean needsInput) {
120             mViewMediatorCallback.setNeedsInput(needsInput);
121         }
122 
123         /**
124          * Authentication has happened and it's time to dismiss keyguard. This function
125          * should clean up and inform KeyguardViewMediator.
126          *
127          * @param strongAuth whether the user has authenticated with strong authentication like
128          *                   pattern, password or PIN but not by trust agents or fingerprint
129          * @param targetUserId a user that needs to be the foreground user at the dismissal
130          *                    completion.
131          */
132         @Override
133         public void finish(boolean strongAuth, int targetUserId) {
134             // If there's a pending runnable because the user interacted with a widget
135             // and we're leaving keyguard, then run it.
136             boolean deferKeyguardDone = false;
137             if (mDismissAction != null) {
138                 deferKeyguardDone = mDismissAction.onDismiss();
139                 mDismissAction = null;
140                 mCancelAction = null;
141             }
142             if (mViewMediatorCallback != null) {
143                 if (deferKeyguardDone) {
144                     mViewMediatorCallback.keyguardDonePending(strongAuth, targetUserId);
145                 } else {
146                     mViewMediatorCallback.keyguardDone(strongAuth, targetUserId);
147                 }
148             }
149         }
150 
151         @Override
152         public void reset() {
153             mViewMediatorCallback.resetKeyguard();
154         }
155 
156         @Override
157         public void onCancelClicked() {
158             mViewMediatorCallback.onCancelClicked();
159         }
160     };
161 
162     private OnKeyListener mOnKeyListener = (v, keyCode, event) -> interceptMediaKey(event);
163 
164     @Inject
KeyguardHostViewController(KeyguardHostView view, KeyguardUpdateMonitor keyguardUpdateMonitor, AudioManager audioManager, TelephonyManager telephonyManager, ViewMediatorCallback viewMediatorCallback, KeyguardSecurityContainerController.Factory keyguardSecurityContainerControllerFactory)165     public KeyguardHostViewController(KeyguardHostView view,
166             KeyguardUpdateMonitor keyguardUpdateMonitor,
167             AudioManager audioManager,
168             TelephonyManager telephonyManager,
169             ViewMediatorCallback viewMediatorCallback,
170             KeyguardSecurityContainerController.Factory
171                     keyguardSecurityContainerControllerFactory) {
172         super(view);
173         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
174         mAudioManager = audioManager;
175         mTelephonyManager = telephonyManager;
176         mViewMediatorCallback = viewMediatorCallback;
177         mKeyguardSecurityContainerController = keyguardSecurityContainerControllerFactory.create(
178                 mSecurityCallback);
179     }
180 
181     /** Initialize the Controller. */
onInit()182     public void onInit() {
183         mKeyguardSecurityContainerController.init();
184         updateResources();
185     }
186 
187     @Override
onViewAttached()188     protected void onViewAttached() {
189         mView.setViewMediatorCallback(mViewMediatorCallback);
190         // Update ViewMediator with the current input method requirements
191         mViewMediatorCallback.setNeedsInput(mKeyguardSecurityContainerController.needsInput());
192         mKeyguardUpdateMonitor.registerCallback(mUpdateCallback);
193         mView.setOnKeyListener(mOnKeyListener);
194         mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
195     }
196 
197     @Override
onViewDetached()198     protected void onViewDetached() {
199         mKeyguardUpdateMonitor.removeCallback(mUpdateCallback);
200         mView.setOnKeyListener(null);
201     }
202 
203      /** Called before this view is being removed. */
cleanUp()204     public void cleanUp() {
205         mKeyguardSecurityContainerController.onPause();
206     }
207 
resetSecurityContainer()208     public void resetSecurityContainer() {
209         mKeyguardSecurityContainerController.reset();
210     }
211 
212     /**
213      * Dismisses the keyguard by going to the next screen or making it gone.
214      * @param targetUserId a user that needs to be the foreground user at the dismissal completion.
215      * @return True if the keyguard is done.
216      */
dismiss(int targetUserId)217     public boolean dismiss(int targetUserId) {
218         return mSecurityCallback.dismiss(false, targetUserId, false);
219     }
220 
221     /**
222      * Called when the Keyguard is actively shown on the screen.
223      */
onResume()224     public void onResume() {
225         if (DEBUG) Log.d(TAG, "screen on, instance " + Integer.toHexString(hashCode()));
226         mKeyguardSecurityContainerController.onResume(KeyguardSecurityView.SCREEN_ON);
227         mView.requestFocus();
228     }
229 
getAccessibilityTitleForCurrentMode()230     public CharSequence getAccessibilityTitleForCurrentMode() {
231         return mKeyguardSecurityContainerController.getTitle();
232     }
233 
234     /**
235      * Starts the animation when the Keyguard gets shown.
236      */
appear(int statusBarHeight)237     public void appear(int statusBarHeight) {
238         // We might still be collapsed and the view didn't have time to layout yet or still
239         // be small, let's wait on the predraw to do the animation in that case.
240         if (mView.getHeight() != 0 && mView.getHeight() != statusBarHeight) {
241             mKeyguardSecurityContainerController.startAppearAnimation();
242         } else {
243             mView.getViewTreeObserver().addOnPreDrawListener(
244                     new ViewTreeObserver.OnPreDrawListener() {
245                         @Override
246                         public boolean onPreDraw() {
247                             mView.getViewTreeObserver().removeOnPreDrawListener(this);
248                             mKeyguardSecurityContainerController.startAppearAnimation();
249                             return true;
250                         }
251                     });
252             mView.requestLayout();
253         }
254     }
255 
256     /**
257      * Show a string explaining why the security view needs to be solved.
258      *
259      * @param reason a flag indicating which string should be shown, see
260      *               {@link KeyguardSecurityView#PROMPT_REASON_NONE},
261      *               {@link KeyguardSecurityView#PROMPT_REASON_RESTART},
262      *               {@link KeyguardSecurityView#PROMPT_REASON_TIMEOUT}, and
263      *               {@link KeyguardSecurityView#PROMPT_REASON_PREPARE_FOR_UPDATE}.
264      */
showPromptReason(int reason)265     public void showPromptReason(int reason) {
266         mKeyguardSecurityContainerController.showPromptReason(reason);
267     }
268 
showMessage(CharSequence message, ColorStateList colorState)269     public void showMessage(CharSequence message, ColorStateList colorState) {
270         mKeyguardSecurityContainerController.showMessage(message, colorState);
271     }
272 
showErrorMessage(CharSequence customMessage)273     public void showErrorMessage(CharSequence customMessage) {
274         showMessage(customMessage, Utils.getColorError(mView.getContext()));
275     }
276 
277     /**
278      * Sets an action to run when keyguard finishes.
279      *
280      * @param action
281      */
setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction)282     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
283         if (mCancelAction != null) {
284             mCancelAction.run();
285             mCancelAction = null;
286         }
287         mDismissAction = action;
288         mCancelAction = cancelAction;
289     }
290 
cancelDismissAction()291     public void cancelDismissAction() {
292         setOnDismissAction(null, null);
293     }
294 
startDisappearAnimation(Runnable finishRunnable)295     public void startDisappearAnimation(Runnable finishRunnable) {
296         if (!mKeyguardSecurityContainerController.startDisappearAnimation(finishRunnable)
297                 && finishRunnable != null) {
298             finishRunnable.run();
299         }
300     }
301 
302     /**
303      * Called when the Keyguard is not actively shown anymore on the screen.
304      */
onPause()305     public void onPause() {
306         if (DEBUG) {
307             Log.d(TAG, String.format("screen off, instance %s at %s",
308                     Integer.toHexString(hashCode()), SystemClock.uptimeMillis()));
309         }
310         mKeyguardSecurityContainerController.showPrimarySecurityScreen(true);
311         mKeyguardSecurityContainerController.onPause();
312         mView.clearFocus();
313     }
314 
315     /**
316      * Called when the view needs to be shown.
317      */
showPrimarySecurityScreen()318     public void showPrimarySecurityScreen() {
319         if (DEBUG) Log.d(TAG, "show()");
320         mKeyguardSecurityContainerController.showPrimarySecurityScreen(false);
321     }
322 
323     /**
324      * Fades and translates in/out the security screen.
325      * @param fraction amount of the screen that should show.
326      */
setExpansion(float fraction)327     public void setExpansion(float fraction) {
328         float alpha = MathUtils.map(KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction);
329         mView.setAlpha(MathUtils.constrain(alpha, 0f, 1f));
330         mView.setTranslationY(fraction * mView.getHeight());
331     }
332 
333     /**
334      * When bouncer was visible and is starting to become hidden.
335      */
onStartingToHide()336     public void onStartingToHide() {
337         mKeyguardSecurityContainerController.onStartingToHide();
338     }
339 
hasDismissActions()340     public boolean hasDismissActions() {
341         return mDismissAction != null || mCancelAction != null;
342     }
343 
getCurrentSecurityMode()344     public SecurityMode getCurrentSecurityMode() {
345         return mKeyguardSecurityContainerController.getCurrentSecurityMode();
346     }
347 
getTop()348     public int getTop() {
349         int top = mView.getTop();
350         // The password view has an extra top padding that should be ignored.
351         if (getCurrentSecurityMode() == SecurityMode.Password) {
352             View messageArea = mView.findViewById(R.id.keyguard_message_area);
353             top += messageArea.getTop();
354         }
355         return top;
356     }
357 
handleBackKey()358     public boolean handleBackKey() {
359         if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
360                 != SecurityMode.None) {
361             mKeyguardSecurityContainerController.dismiss(
362                     false, KeyguardUpdateMonitor.getCurrentUser());
363             return true;
364         }
365         return false;
366     }
367 
368     /**
369      * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
370      * some cases where we wish to disable it, notably when the menu button placement or technology
371      * is prone to false positives.
372      *
373      * @return true if the menu key should be enabled
374      */
shouldEnableMenuKey()375     public boolean shouldEnableMenuKey() {
376         final Resources res = mView.getResources();
377         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
378         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
379         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
380         return !configDisabled || isTestHarness || fileOverride;
381     }
382 
383     /**
384      * @return true if the current bouncer is password
385      */
dispatchBackKeyEventPreIme()386     public boolean dispatchBackKeyEventPreIme() {
387         if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
388                 == SecurityMode.Password) {
389             return true;
390         }
391         return false;
392     }
393 
394     /**
395      * Allows the media keys to work when the keyguard is showing.
396      * The media keys should be of no interest to the actual keyguard view(s),
397      * so intercepting them here should not be of any harm.
398      * @param event The key event
399      * @return whether the event was consumed as a media key.
400      */
interceptMediaKey(KeyEvent event)401     public boolean interceptMediaKey(KeyEvent event) {
402         int keyCode = event.getKeyCode();
403         if (event.getAction() == KeyEvent.ACTION_DOWN) {
404             switch (keyCode) {
405                 case KeyEvent.KEYCODE_MEDIA_PLAY:
406                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
407                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
408                     /* Suppress PLAY/PAUSE toggle when phone is ringing or
409                      * in-call to avoid music playback */
410                     if (mTelephonyManager != null &&
411                             mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
412                         return true;  // suppress key event
413                     }
414                 case KeyEvent.KEYCODE_MUTE:
415                 case KeyEvent.KEYCODE_HEADSETHOOK:
416                 case KeyEvent.KEYCODE_MEDIA_STOP:
417                 case KeyEvent.KEYCODE_MEDIA_NEXT:
418                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
419                 case KeyEvent.KEYCODE_MEDIA_REWIND:
420                 case KeyEvent.KEYCODE_MEDIA_RECORD:
421                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
422                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
423                     handleMediaKeyEvent(event);
424                     return true;
425                 }
426 
427                 case KeyEvent.KEYCODE_VOLUME_UP:
428                 case KeyEvent.KEYCODE_VOLUME_DOWN:
429                 case KeyEvent.KEYCODE_VOLUME_MUTE: {
430                     if (KEYGUARD_MANAGES_VOLUME) {
431                         // Volume buttons should only function for music (local or remote).
432                         // TODO: Actually handle MUTE.
433                         mAudioManager.adjustSuggestedStreamVolume(
434                                 keyCode == KeyEvent.KEYCODE_VOLUME_UP
435                                         ? AudioManager.ADJUST_RAISE
436                                         : AudioManager.ADJUST_LOWER /* direction */,
437                                 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
438                         // Don't execute default volume behavior
439                         return true;
440                     } else {
441                         return false;
442                     }
443                 }
444             }
445         } else if (event.getAction() == KeyEvent.ACTION_UP) {
446             switch (keyCode) {
447                 case KeyEvent.KEYCODE_MUTE:
448                 case KeyEvent.KEYCODE_HEADSETHOOK:
449                 case KeyEvent.KEYCODE_MEDIA_PLAY:
450                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
451                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
452                 case KeyEvent.KEYCODE_MEDIA_STOP:
453                 case KeyEvent.KEYCODE_MEDIA_NEXT:
454                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
455                 case KeyEvent.KEYCODE_MEDIA_REWIND:
456                 case KeyEvent.KEYCODE_MEDIA_RECORD:
457                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
458                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
459                     handleMediaKeyEvent(event);
460                     return true;
461                 }
462             }
463         }
464         return false;
465     }
466 
467 
handleMediaKeyEvent(KeyEvent keyEvent)468     private void handleMediaKeyEvent(KeyEvent keyEvent) {
469         mAudioManager.dispatchMediaKeyEvent(keyEvent);
470     }
471 
finish(boolean strongAuth, int currentUser)472     public void finish(boolean strongAuth, int currentUser) {
473         mSecurityCallback.finish(strongAuth, currentUser);
474     }
475 
476     /**
477      * Apply keyguard configuration from the currently active resources. This can be called when the
478      * device configuration changes, to re-apply some resources that are qualified on the device
479      * configuration.
480      */
updateResources()481     public void updateResources() {
482         int gravity;
483 
484         Resources resources = mView.getResources();
485 
486         if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)
487                 && resources.getBoolean(
488                 com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
489             gravity = resources.getInteger(
490                     R.integer.keyguard_host_view_one_handed_gravity);
491         } else {
492             gravity = resources.getInteger(R.integer.keyguard_host_view_gravity);
493         }
494 
495         // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout.
496         // We're just changing the gravity here though (which can't be applied to RelativeLayout),
497         // so only attempt the update if mView is inside a FrameLayout.
498         if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) {
499             FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams();
500             if (lp.gravity != gravity) {
501                 lp.gravity = gravity;
502                 mView.setLayoutParams(lp);
503             }
504         }
505 
506         if (mKeyguardSecurityContainerController != null) {
507             mKeyguardSecurityContainerController.updateResources();
508         }
509     }
510 
511     /** Update keyguard position based on a tapped X coordinate. */
updateKeyguardPosition(float x)512     public void updateKeyguardPosition(float x) {
513         if (mKeyguardSecurityContainerController != null) {
514             mKeyguardSecurityContainerController.updateKeyguardPosition(x);
515         }
516     }
517 }
518