1 /*
2  * Copyright (C) 2019 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.systemui.statusbar.phone;
18 
19 import static com.android.systemui.statusbar.phone.LockIcon.STATE_BIOMETRICS_ERROR;
20 import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCKED;
21 import static com.android.systemui.statusbar.phone.LockIcon.STATE_LOCK_OPEN;
22 import static com.android.systemui.statusbar.phone.LockIcon.STATE_SCANNING_FACE;
23 
24 import android.content.res.Configuration;
25 import android.content.res.Resources;
26 import android.content.res.TypedArray;
27 import android.graphics.Color;
28 import android.hardware.biometrics.BiometricSourceType;
29 import android.view.View;
30 import android.view.ViewGroup;
31 import android.view.accessibility.AccessibilityNodeInfo;
32 
33 import androidx.annotation.Nullable;
34 
35 import com.android.internal.logging.nano.MetricsProto;
36 import com.android.internal.widget.LockPatternUtils;
37 import com.android.keyguard.KeyguardUpdateMonitor;
38 import com.android.keyguard.KeyguardUpdateMonitorCallback;
39 import com.android.systemui.R;
40 import com.android.systemui.dagger.qualifiers.Main;
41 import com.android.systemui.dock.DockManager;
42 import com.android.systemui.plugins.statusbar.StatusBarStateController;
43 import com.android.systemui.statusbar.CommandQueue;
44 import com.android.systemui.statusbar.KeyguardIndicationController;
45 import com.android.systemui.statusbar.StatusBarState;
46 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator;
47 import com.android.systemui.statusbar.notification.NotificationWakeUpCoordinator.WakeUpListener;
48 import com.android.systemui.statusbar.phone.LockscreenGestureLogger.LockscreenUiEvent;
49 import com.android.systemui.statusbar.policy.AccessibilityController;
50 import com.android.systemui.statusbar.policy.ConfigurationController;
51 import com.android.systemui.statusbar.policy.ConfigurationController.ConfigurationListener;
52 import com.android.systemui.statusbar.policy.KeyguardStateController;
53 
54 import java.util.Optional;
55 
56 import javax.inject.Inject;
57 import javax.inject.Singleton;
58 
59 /** Controls the {@link LockIcon} in the lockscreen. */
60 @Singleton
61 public class LockscreenLockIconController {
62 
63     private final LockscreenGestureLogger mLockscreenGestureLogger;
64     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
65     private final LockPatternUtils mLockPatternUtils;
66     private final ShadeController mShadeController;
67     private final AccessibilityController mAccessibilityController;
68     private final KeyguardIndicationController mKeyguardIndicationController;
69     private final StatusBarStateController mStatusBarStateController;
70     private final ConfigurationController mConfigurationController;
71     private final NotificationWakeUpCoordinator mNotificationWakeUpCoordinator;
72     private final KeyguardBypassController mKeyguardBypassController;
73     private final Optional<DockManager> mDockManager;
74     private final KeyguardStateController mKeyguardStateController;
75     private final Resources mResources;
76     private final HeadsUpManagerPhone mHeadsUpManagerPhone;
77     private boolean mKeyguardShowing;
78     private boolean mKeyguardJustShown;
79     private boolean mBlockUpdates;
80     private boolean mSimLocked;
81     private boolean mTransientBiometricsError;
82     private boolean mDocked;
83     private boolean mWakeAndUnlockRunning;
84     private boolean mShowingLaunchAffordance;
85     private boolean mBouncerShowingScrimmed;
86     private int mStatusBarState = StatusBarState.SHADE;
87     private LockIcon mLockIcon;
88 
89     private View.OnAttachStateChangeListener mOnAttachStateChangeListener =
90             new View.OnAttachStateChangeListener() {
91         @Override
92         public void onViewAttachedToWindow(View v) {
93             mStatusBarStateController.addCallback(mSBStateListener);
94             mConfigurationController.addCallback(mConfigurationListener);
95             mNotificationWakeUpCoordinator.addListener(mWakeUpListener);
96             mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
97             mKeyguardStateController.addCallback(mKeyguardMonitorCallback);
98 
99             mDockManager.ifPresent(dockManager -> dockManager.addListener(mDockEventListener));
100 
101             mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
102             mConfigurationListener.onThemeChanged();
103             update();
104         }
105 
106         @Override
107         public void onViewDetachedFromWindow(View v) {
108             mStatusBarStateController.removeCallback(mSBStateListener);
109             mConfigurationController.removeCallback(mConfigurationListener);
110             mNotificationWakeUpCoordinator.removeListener(mWakeUpListener);
111             mKeyguardUpdateMonitor.removeCallback(mUpdateMonitorCallback);
112             mKeyguardStateController.removeCallback(mKeyguardMonitorCallback);
113 
114             mDockManager.ifPresent(dockManager -> dockManager.removeListener(mDockEventListener));
115         }
116     };
117 
118     private final StatusBarStateController.StateListener mSBStateListener =
119             new StatusBarStateController.StateListener() {
120                 @Override
121                 public void onDozingChanged(boolean isDozing) {
122                     setDozing(isDozing);
123                 }
124 
125                 @Override
126                 public void onPulsingChanged(boolean pulsing) {
127                     setPulsing(pulsing);
128                 }
129 
130                 @Override
131                 public void onDozeAmountChanged(float linear, float eased) {
132                     if (mLockIcon != null) {
133                         mLockIcon.setDozeAmount(eased);
134                     }
135                 }
136 
137                 @Override
138                 public void onStateChanged(int newState) {
139                     setStatusBarState(newState);
140                 }
141             };
142 
143     private final ConfigurationListener mConfigurationListener = new ConfigurationListener() {
144         private int mDensity;
145 
146         @Override
147         public void onThemeChanged() {
148             if (mLockIcon == null) {
149                 return;
150             }
151 
152             TypedArray typedArray = mLockIcon.getContext().getTheme().obtainStyledAttributes(
153                     null, new int[]{ R.attr.wallpaperTextColor }, 0, 0);
154             int iconColor = typedArray.getColor(0, Color.WHITE);
155             typedArray.recycle();
156             mLockIcon.onThemeChange(iconColor);
157         }
158 
159         @Override
160         public void onDensityOrFontScaleChanged() {
161             if (mLockIcon == null) {
162                 return;
163             }
164 
165             ViewGroup.LayoutParams lp = mLockIcon.getLayoutParams();
166             if (lp == null) {
167                 return;
168             }
169             lp.width = mLockIcon.getResources().getDimensionPixelSize(R.dimen.keyguard_lock_width);
170             lp.height = mLockIcon.getResources().getDimensionPixelSize(
171                     R.dimen.keyguard_lock_height);
172             mLockIcon.setLayoutParams(lp);
173             update(true /* force */);
174         }
175 
176         @Override
177         public void onLocaleListChanged() {
178             if (mLockIcon == null) {
179                 return;
180             }
181 
182             mLockIcon.setContentDescription(
183                     mLockIcon.getResources().getText(R.string.accessibility_unlock_button));
184             update(true /* force */);
185         }
186 
187         @Override
188         public void onConfigChanged(Configuration newConfig) {
189             final int density = newConfig.densityDpi;
190             if (density != mDensity) {
191                 mDensity = density;
192                 update();
193             }
194         }
195     };
196 
197     private final WakeUpListener mWakeUpListener = new WakeUpListener() {
198         @Override
199         public void onPulseExpansionChanged(boolean expandingChanged) {
200         }
201 
202         @Override
203         public void onFullyHiddenChanged(boolean isFullyHidden) {
204             if (mKeyguardBypassController.getBypassEnabled()) {
205                 boolean changed = updateIconVisibility();
206                 if (changed) {
207                     update();
208                 }
209             }
210         }
211     };
212 
213     private final KeyguardUpdateMonitorCallback mUpdateMonitorCallback =
214             new KeyguardUpdateMonitorCallback() {
215                 @Override
216                 public void onSimStateChanged(int subId, int slotId, int simState) {
217                     mSimLocked = mKeyguardUpdateMonitor.isSimPinSecure();
218                     update();
219                 }
220 
221                 @Override
222                 public void onKeyguardVisibilityChanged(boolean showing) {
223                     update();
224                 }
225 
226                 @Override
227                 public void onBiometricRunningStateChanged(boolean running,
228                         BiometricSourceType biometricSourceType) {
229                     update();
230                 }
231 
232                 @Override
233                 public void onStrongAuthStateChanged(int userId) {
234                     update();
235                 }
236             };
237 
238     private final DockManager.DockEventListener mDockEventListener =
239             event -> {
240                 boolean docked =
241                         event == DockManager.STATE_DOCKED || event == DockManager.STATE_DOCKED_HIDE;
242                 if (docked != mDocked) {
243                     mDocked = docked;
244                     update();
245                 }
246             };
247 
248     private final KeyguardStateController.Callback mKeyguardMonitorCallback =
249             new KeyguardStateController.Callback() {
250                 @Override
251                 public void onKeyguardShowingChanged() {
252                     boolean force = false;
253                     boolean wasShowing = mKeyguardShowing;
254                     mKeyguardShowing = mKeyguardStateController.isShowing();
255                     if (!wasShowing && mKeyguardShowing && mBlockUpdates) {
256                         mBlockUpdates = false;
257                         force = true;
258                     }
259                     if (!wasShowing && mKeyguardShowing) {
260                         mKeyguardJustShown = true;
261                     }
262                     update(force);
263                 }
264 
265                 @Override
266                 public void onKeyguardFadingAwayChanged() {
267                     if (!mKeyguardStateController.isKeyguardFadingAway()) {
268                         if (mBlockUpdates) {
269                             mBlockUpdates = false;
270                             update(true /* force */);
271                         }
272                     }
273                 }
274 
275                 @Override
276                 public void onUnlockedChanged() {
277                     update();
278                 }
279             };
280 
281     private final View.AccessibilityDelegate mAccessibilityDelegate =
282             new View.AccessibilityDelegate() {
283                 @Override
284                 public void onInitializeAccessibilityNodeInfo(View host,
285                         AccessibilityNodeInfo info) {
286                     super.onInitializeAccessibilityNodeInfo(host, info);
287                     boolean fingerprintRunning =
288                             mKeyguardUpdateMonitor.isFingerprintDetectionRunning();
289                     // Only checking if unlocking with Biometric is allowed (no matter strong or
290                     // non-strong as long as primary auth, i.e. PIN/pattern/password, is not
291                     // required), so it's ok to pass true for isStrongBiometric to
292                     // isUnlockingWithBiometricAllowed() to bypass the check of whether non-strong
293                     // biometric is allowed
294                     boolean unlockingAllowed = mKeyguardUpdateMonitor
295                             .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */);
296                     if (fingerprintRunning && unlockingAllowed) {
297                         AccessibilityNodeInfo.AccessibilityAction unlock =
298                                 new AccessibilityNodeInfo.AccessibilityAction(
299                                 AccessibilityNodeInfo.ACTION_CLICK,
300                                 mResources.getString(
301                                         R.string.accessibility_unlock_without_fingerprint));
302                         info.addAction(unlock);
303                         info.setHintText(mResources.getString(
304                                 R.string.accessibility_waiting_for_fingerprint));
305                     } else if (getState() == STATE_SCANNING_FACE) {
306                         //Avoid 'button' to be spoken for scanning face
307                         info.setClassName(LockIcon.class.getName());
308                         info.setContentDescription(mResources.getString(
309                                 R.string.accessibility_scanning_face));
310                     }
311                 }
312             };
313     private int mLastState;
314 
315     @Inject
LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger, KeyguardUpdateMonitor keyguardUpdateMonitor, LockPatternUtils lockPatternUtils, ShadeController shadeController, AccessibilityController accessibilityController, KeyguardIndicationController keyguardIndicationController, StatusBarStateController statusBarStateController, ConfigurationController configurationController, NotificationWakeUpCoordinator notificationWakeUpCoordinator, KeyguardBypassController keyguardBypassController, @Nullable DockManager dockManager, KeyguardStateController keyguardStateController, @Main Resources resources, HeadsUpManagerPhone headsUpManagerPhone)316     public LockscreenLockIconController(LockscreenGestureLogger lockscreenGestureLogger,
317             KeyguardUpdateMonitor keyguardUpdateMonitor,
318             LockPatternUtils lockPatternUtils,
319             ShadeController shadeController,
320             AccessibilityController accessibilityController,
321             KeyguardIndicationController keyguardIndicationController,
322             StatusBarStateController statusBarStateController,
323             ConfigurationController configurationController,
324             NotificationWakeUpCoordinator notificationWakeUpCoordinator,
325             KeyguardBypassController keyguardBypassController,
326             @Nullable DockManager dockManager,
327             KeyguardStateController keyguardStateController,
328             @Main Resources resources,
329             HeadsUpManagerPhone headsUpManagerPhone) {
330         mLockscreenGestureLogger = lockscreenGestureLogger;
331         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
332         mLockPatternUtils = lockPatternUtils;
333         mShadeController = shadeController;
334         mAccessibilityController = accessibilityController;
335         mKeyguardIndicationController = keyguardIndicationController;
336         mStatusBarStateController = statusBarStateController;
337         mConfigurationController = configurationController;
338         mNotificationWakeUpCoordinator = notificationWakeUpCoordinator;
339         mKeyguardBypassController = keyguardBypassController;
340         mDockManager = dockManager == null ? Optional.empty() : Optional.of(dockManager);
341         mKeyguardStateController = keyguardStateController;
342         mResources = resources;
343         mHeadsUpManagerPhone = headsUpManagerPhone;
344 
345         mKeyguardIndicationController.setLockIconController(this);
346     }
347 
348     /**
349      * Associate the controller with a {@link LockIcon}
350      *
351      * TODO: change to an init method and inject the view.
352      */
attach(LockIcon lockIcon)353     public void attach(LockIcon lockIcon) {
354         mLockIcon = lockIcon;
355 
356         mLockIcon.setOnClickListener(this::handleClick);
357         mLockIcon.setOnLongClickListener(this::handleLongClick);
358         mLockIcon.setAccessibilityDelegate(mAccessibilityDelegate);
359 
360         if (mLockIcon.isAttachedToWindow()) {
361             mOnAttachStateChangeListener.onViewAttachedToWindow(mLockIcon);
362         }
363         mLockIcon.addOnAttachStateChangeListener(mOnAttachStateChangeListener);
364         setStatusBarState(mStatusBarStateController.getState());
365     }
366 
getView()367     public LockIcon getView() {
368         return mLockIcon;
369     }
370 
371     /**
372      * Called whenever the scrims become opaque, transparent or semi-transparent.
373      */
onScrimVisibilityChanged(Integer scrimsVisible)374     public void onScrimVisibilityChanged(Integer scrimsVisible) {
375         if (mWakeAndUnlockRunning
376                 && scrimsVisible == ScrimController.TRANSPARENT) {
377             mWakeAndUnlockRunning = false;
378             update();
379         }
380     }
381 
382     /**
383      * Propagate {@link StatusBar} pulsing state.
384      */
setPulsing(boolean pulsing)385     private void setPulsing(boolean pulsing) {
386         update();
387     }
388 
389     /**
390      * We need to hide the lock whenever there's a fingerprint unlock, otherwise you'll see the
391      * icon on top of the black front scrim.
392      * @param wakeAndUnlock are we wake and unlocking
393      * @param isUnlock are we currently unlocking
394      */
onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock)395     public void onBiometricAuthModeChanged(boolean wakeAndUnlock, boolean isUnlock) {
396         if (wakeAndUnlock) {
397             mWakeAndUnlockRunning = true;
398         }
399         if (isUnlock && mKeyguardBypassController.getBypassEnabled() && canBlockUpdates()) {
400             // We don't want the icon to change while we are unlocking
401             mBlockUpdates = true;
402         }
403         update();
404     }
405 
406     /**
407      * When we're launching an affordance, like double pressing power to open camera.
408      */
onShowingLaunchAffordanceChanged(Boolean showing)409     public void onShowingLaunchAffordanceChanged(Boolean showing) {
410         mShowingLaunchAffordance = showing;
411         update();
412     }
413 
414     /** Sets whether the bouncer is showing. */
setBouncerShowingScrimmed(boolean bouncerShowing)415     public void setBouncerShowingScrimmed(boolean bouncerShowing) {
416         mBouncerShowingScrimmed = bouncerShowing;
417         if (mKeyguardBypassController.getBypassEnabled()) {
418             update();
419         }
420     }
421 
422     /**
423      * Animate padlock opening when bouncer challenge is solved.
424      */
onBouncerPreHideAnimation()425     public void onBouncerPreHideAnimation() {
426         update();
427     }
428 
429     /**
430      * If we're currently presenting an authentication error message.
431      */
setTransientBiometricsError(boolean transientBiometricsError)432     public void setTransientBiometricsError(boolean transientBiometricsError) {
433         mTransientBiometricsError = transientBiometricsError;
434         update();
435     }
436 
handleLongClick(View view)437     private boolean handleLongClick(View view) {
438         mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
439                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
440         mLockscreenGestureLogger.log(LockscreenUiEvent.LOCKSCREEN_LOCK_TAP);
441         mKeyguardIndicationController.showTransientIndication(
442                 R.string.keyguard_indication_trust_disabled);
443         mKeyguardUpdateMonitor.onLockIconPressed();
444         mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
445 
446         return true;
447     }
448 
449 
handleClick(View view)450     private void handleClick(View view) {
451         if (!mAccessibilityController.isAccessibilityEnabled()) {
452             return;
453         }
454         mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
455     }
456 
update()457     private void update() {
458         update(false /* force */);
459     }
460 
update(boolean force)461     private void update(boolean force) {
462         int state = getState();
463         boolean shouldUpdate = mLastState != state || force;
464         if (mBlockUpdates && canBlockUpdates()) {
465             shouldUpdate = false;
466         }
467         if (shouldUpdate && mLockIcon != null) {
468             mLockIcon.update(state, mStatusBarStateController.isPulsing(),
469                     mStatusBarStateController.isDozing(), mKeyguardJustShown);
470         }
471         mLastState = state;
472         mKeyguardJustShown = false;
473         updateIconVisibility();
474         updateClickability();
475     }
476 
getState()477     private int getState() {
478         if ((mKeyguardStateController.canDismissLockScreen()
479                 || !mKeyguardStateController.isShowing()
480                 || mKeyguardStateController.isKeyguardGoingAway()
481                 || mKeyguardStateController.isKeyguardFadingAway()) && !mSimLocked) {
482             return STATE_LOCK_OPEN;
483         } else if (mTransientBiometricsError) {
484             return STATE_BIOMETRICS_ERROR;
485         } else if (mKeyguardUpdateMonitor.isFaceDetectionRunning()
486                 && !mStatusBarStateController.isPulsing()) {
487             return STATE_SCANNING_FACE;
488         } else {
489             return STATE_LOCKED;
490         }
491     }
492 
canBlockUpdates()493     private boolean canBlockUpdates() {
494         return mKeyguardShowing || mKeyguardStateController.isKeyguardFadingAway();
495     }
496 
setDozing(boolean isDozing)497     private void setDozing(boolean isDozing) {
498         update();
499     }
500 
501     /** Set the StatusBarState. */
setStatusBarState(int statusBarState)502     private void setStatusBarState(int statusBarState) {
503         mStatusBarState = statusBarState;
504         updateIconVisibility();
505     }
506 
507     /**
508      * Update the icon visibility
509      * @return true if the visibility changed
510      */
updateIconVisibility()511     private boolean updateIconVisibility() {
512         boolean onAodNotPulsingOrDocked = mStatusBarStateController.isDozing()
513                 && (!mStatusBarStateController.isPulsing() || mDocked);
514         boolean invisible = onAodNotPulsingOrDocked || mWakeAndUnlockRunning
515                 || mShowingLaunchAffordance;
516         if (mKeyguardBypassController.getBypassEnabled() && !mBouncerShowingScrimmed) {
517             if ((mHeadsUpManagerPhone.isHeadsUpGoingAway()
518                     || mHeadsUpManagerPhone.hasPinnedHeadsUp()
519                     || mStatusBarState == StatusBarState.KEYGUARD)
520                     && !mNotificationWakeUpCoordinator.getNotificationsFullyHidden()) {
521                 invisible = true;
522             }
523         }
524 
525         if (mLockIcon == null) {
526             return false;
527         }
528 
529         return mLockIcon.updateIconVisibility(!invisible);
530     }
531 
updateClickability()532     private void updateClickability() {
533         if (mAccessibilityController == null) {
534             return;
535         }
536         boolean canLock = mKeyguardStateController.isMethodSecure()
537                 && mKeyguardStateController.canDismissLockScreen();
538         boolean clickToUnlock = mAccessibilityController.isAccessibilityEnabled();
539         if (mLockIcon != null) {
540             mLockIcon.setClickable(clickToUnlock);
541             mLockIcon.setLongClickable(canLock && !clickToUnlock);
542             mLockIcon.setFocusable(mAccessibilityController.isAccessibilityEnabled());
543         }
544     }
545 
546 }
547