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