• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
setExpansion(float fraction)323     public void setExpansion(float fraction) {
324         float alpha = MathUtils.map(KeyguardBouncer.ALPHA_EXPANSION_THRESHOLD, 1, 1, 0, fraction);
325         mView.setAlpha(MathUtils.constrain(alpha, 0f, 1f));
326         mView.setTranslationY(fraction * mView.getHeight());
327     }
328 
329     /**
330      * When bouncer was visible and is starting to become hidden.
331      */
onStartingToHide()332     public void onStartingToHide() {
333         mKeyguardSecurityContainerController.onStartingToHide();
334     }
335 
hasDismissActions()336     public boolean hasDismissActions() {
337         return mDismissAction != null || mCancelAction != null;
338     }
339 
getCurrentSecurityMode()340     public SecurityMode getCurrentSecurityMode() {
341         return mKeyguardSecurityContainerController.getCurrentSecurityMode();
342     }
343 
getTop()344     public int getTop() {
345         int top = mView.getTop();
346         // The password view has an extra top padding that should be ignored.
347         if (getCurrentSecurityMode() == SecurityMode.Password) {
348             View messageArea = mView.findViewById(R.id.keyguard_message_area);
349             top += messageArea.getTop();
350         }
351         return top;
352     }
353 
handleBackKey()354     public boolean handleBackKey() {
355         if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
356                 != SecurityMode.None) {
357             mKeyguardSecurityContainerController.dismiss(
358                     false, KeyguardUpdateMonitor.getCurrentUser());
359             return true;
360         }
361         return false;
362     }
363 
364     /**
365      * In general, we enable unlocking the insecure keyguard with the menu key. However, there are
366      * some cases where we wish to disable it, notably when the menu button placement or technology
367      * is prone to false positives.
368      *
369      * @return true if the menu key should be enabled
370      */
shouldEnableMenuKey()371     public boolean shouldEnableMenuKey() {
372         final Resources res = mView.getResources();
373         final boolean configDisabled = res.getBoolean(R.bool.config_disableMenuKeyInLockScreen);
374         final boolean isTestHarness = ActivityManager.isRunningInTestHarness();
375         final boolean fileOverride = (new File(ENABLE_MENU_KEY_FILE)).exists();
376         return !configDisabled || isTestHarness || fileOverride;
377     }
378 
379     /**
380      * @return true if the current bouncer is password
381      */
dispatchBackKeyEventPreIme()382     public boolean dispatchBackKeyEventPreIme() {
383         if (mKeyguardSecurityContainerController.getCurrentSecurityMode()
384                 == SecurityMode.Password) {
385             return true;
386         }
387         return false;
388     }
389 
390     /**
391      * Allows the media keys to work when the keyguard is showing.
392      * The media keys should be of no interest to the actual keyguard view(s),
393      * so intercepting them here should not be of any harm.
394      * @param event The key event
395      * @return whether the event was consumed as a media key.
396      */
interceptMediaKey(KeyEvent event)397     public boolean interceptMediaKey(KeyEvent event) {
398         int keyCode = event.getKeyCode();
399         if (event.getAction() == KeyEvent.ACTION_DOWN) {
400             switch (keyCode) {
401                 case KeyEvent.KEYCODE_MEDIA_PLAY:
402                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
403                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
404                     /* Suppress PLAY/PAUSE toggle when phone is ringing or
405                      * in-call to avoid music playback */
406                     if (mTelephonyManager != null &&
407                             mTelephonyManager.getCallState() != TelephonyManager.CALL_STATE_IDLE) {
408                         return true;  // suppress key event
409                     }
410                 case KeyEvent.KEYCODE_MUTE:
411                 case KeyEvent.KEYCODE_HEADSETHOOK:
412                 case KeyEvent.KEYCODE_MEDIA_STOP:
413                 case KeyEvent.KEYCODE_MEDIA_NEXT:
414                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
415                 case KeyEvent.KEYCODE_MEDIA_REWIND:
416                 case KeyEvent.KEYCODE_MEDIA_RECORD:
417                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
418                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
419                     handleMediaKeyEvent(event);
420                     return true;
421                 }
422 
423                 case KeyEvent.KEYCODE_VOLUME_UP:
424                 case KeyEvent.KEYCODE_VOLUME_DOWN:
425                 case KeyEvent.KEYCODE_VOLUME_MUTE: {
426                     if (KEYGUARD_MANAGES_VOLUME) {
427                         // Volume buttons should only function for music (local or remote).
428                         // TODO: Actually handle MUTE.
429                         mAudioManager.adjustSuggestedStreamVolume(
430                                 keyCode == KeyEvent.KEYCODE_VOLUME_UP
431                                         ? AudioManager.ADJUST_RAISE
432                                         : AudioManager.ADJUST_LOWER /* direction */,
433                                 AudioManager.STREAM_MUSIC /* stream */, 0 /* flags */);
434                         // Don't execute default volume behavior
435                         return true;
436                     } else {
437                         return false;
438                     }
439                 }
440             }
441         } else if (event.getAction() == KeyEvent.ACTION_UP) {
442             switch (keyCode) {
443                 case KeyEvent.KEYCODE_MUTE:
444                 case KeyEvent.KEYCODE_HEADSETHOOK:
445                 case KeyEvent.KEYCODE_MEDIA_PLAY:
446                 case KeyEvent.KEYCODE_MEDIA_PAUSE:
447                 case KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE:
448                 case KeyEvent.KEYCODE_MEDIA_STOP:
449                 case KeyEvent.KEYCODE_MEDIA_NEXT:
450                 case KeyEvent.KEYCODE_MEDIA_PREVIOUS:
451                 case KeyEvent.KEYCODE_MEDIA_REWIND:
452                 case KeyEvent.KEYCODE_MEDIA_RECORD:
453                 case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
454                 case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
455                     handleMediaKeyEvent(event);
456                     return true;
457                 }
458             }
459         }
460         return false;
461     }
462 
463 
handleMediaKeyEvent(KeyEvent keyEvent)464     private void handleMediaKeyEvent(KeyEvent keyEvent) {
465         mAudioManager.dispatchMediaKeyEvent(keyEvent);
466     }
467 
finish(boolean strongAuth, int currentUser)468     public void finish(boolean strongAuth, int currentUser) {
469         mSecurityCallback.finish(strongAuth, currentUser);
470     }
471 
472     /**
473      * Apply keyguard configuration from the currently active resources. This can be called when the
474      * device configuration changes, to re-apply some resources that are qualified on the device
475      * configuration.
476      */
updateResources()477     public void updateResources() {
478         int gravity;
479 
480         Resources resources = mView.getResources();
481 
482         if (resources.getBoolean(R.bool.can_use_one_handed_bouncer)
483                 && resources.getBoolean(
484                 com.android.internal.R.bool.config_enableDynamicKeyguardPositioning)) {
485             gravity = resources.getInteger(
486                     R.integer.keyguard_host_view_one_handed_gravity);
487         } else {
488             gravity = resources.getInteger(R.integer.keyguard_host_view_gravity);
489         }
490 
491         // Android SysUI uses a FrameLayout as the top-level, but Auto uses RelativeLayout.
492         // We're just changing the gravity here though (which can't be applied to RelativeLayout),
493         // so only attempt the update if mView is inside a FrameLayout.
494         if (mView.getLayoutParams() instanceof FrameLayout.LayoutParams) {
495             FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) mView.getLayoutParams();
496             if (lp.gravity != gravity) {
497                 lp.gravity = gravity;
498                 mView.setLayoutParams(lp);
499             }
500         }
501 
502         if (mKeyguardSecurityContainerController != null) {
503             mKeyguardSecurityContainerController.updateResources();
504         }
505     }
506 
507     /** Update keyguard position based on a tapped X coordinate. */
updateKeyguardPosition(float x)508     public void updateKeyguardPosition(float x) {
509         if (mKeyguardSecurityContainerController != null) {
510             mKeyguardSecurityContainerController.updateKeyguardPosition(x);
511         }
512     }
513 }
514