1 /*
2  * Copyright (C) 2014 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;
18 
19 import android.animation.Animator;
20 import android.animation.AnimatorListenerAdapter;
21 import android.app.admin.DevicePolicyManager;
22 import android.content.Context;
23 import android.content.res.ColorStateList;
24 import android.content.res.Resources;
25 import android.graphics.Color;
26 import android.hardware.biometrics.BiometricSourceType;
27 import android.hardware.face.FaceManager;
28 import android.hardware.fingerprint.FingerprintManager;
29 import android.os.BatteryManager;
30 import android.os.BatteryStats;
31 import android.os.Handler;
32 import android.os.Message;
33 import android.os.RemoteException;
34 import android.os.ServiceManager;
35 import android.os.UserManager;
36 import android.text.TextUtils;
37 import android.text.format.Formatter;
38 import android.util.Log;
39 import android.view.View;
40 import android.view.ViewGroup;
41 
42 import com.android.internal.annotations.VisibleForTesting;
43 import com.android.internal.app.IBatteryStats;
44 import com.android.internal.logging.nano.MetricsProto;
45 import com.android.internal.widget.LockPatternUtils;
46 import com.android.internal.widget.ViewClippingUtil;
47 import com.android.keyguard.KeyguardUpdateMonitor;
48 import com.android.keyguard.KeyguardUpdateMonitorCallback;
49 import com.android.settingslib.Utils;
50 import com.android.systemui.Dependency;
51 import com.android.systemui.Interpolators;
52 import com.android.systemui.R;
53 import com.android.systemui.plugins.statusbar.StatusBarStateController;
54 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
55 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
56 import com.android.systemui.statusbar.phone.LockIcon;
57 import com.android.systemui.statusbar.phone.LockscreenGestureLogger;
58 import com.android.systemui.statusbar.phone.ShadeController;
59 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
60 import com.android.systemui.statusbar.phone.UnlockMethodCache;
61 import com.android.systemui.statusbar.policy.AccessibilityController;
62 import com.android.systemui.statusbar.policy.UserInfoController;
63 import com.android.systemui.util.wakelock.SettableWakeLock;
64 import com.android.systemui.util.wakelock.WakeLock;
65 
66 import java.io.FileDescriptor;
67 import java.io.PrintWriter;
68 import java.text.NumberFormat;
69 import java.util.IllegalFormatConversionException;
70 
71 /**
72  * Controls the indications and error messages shown on the Keyguard
73  */
74 public class KeyguardIndicationController implements StateListener,
75         UnlockMethodCache.OnUnlockMethodChangedListener {
76 
77     private static final String TAG = "KeyguardIndication";
78     private static final boolean DEBUG_CHARGING_SPEED = false;
79 
80     private static final int MSG_HIDE_TRANSIENT = 1;
81     private static final int MSG_CLEAR_BIOMETRIC_MSG = 2;
82     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
83 
84     private final Context mContext;
85     private final ShadeController mShadeController;
86     private final AccessibilityController mAccessibilityController;
87     private final UnlockMethodCache mUnlockMethodCache;
88     private final StatusBarStateController mStatusBarStateController;
89     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
90     private ViewGroup mIndicationArea;
91     private KeyguardIndicationTextView mTextView;
92     private KeyguardIndicationTextView mDisclosure;
93     private final UserManager mUserManager;
94     private final IBatteryStats mBatteryInfo;
95     private final SettableWakeLock mWakeLock;
96     private final LockPatternUtils mLockPatternUtils;
97 
98     private final int mSlowThreshold;
99     private final int mFastThreshold;
100     private final LockIcon mLockIcon;
101     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
102     private LockscreenGestureLogger mLockscreenGestureLogger = new LockscreenGestureLogger();
103 
104     private String mRestingIndication;
105     private CharSequence mTransientIndication;
106     private ColorStateList mTransientTextColorState;
107     private ColorStateList mInitialTextColorState;
108     private boolean mVisible;
109 
110     private boolean mPowerPluggedIn;
111     private boolean mPowerPluggedInWired;
112     private boolean mPowerCharged;
113     private int mChargingSpeed;
114     private int mChargingWattage;
115     private int mBatteryLevel;
116     private String mMessageToShowOnScreenOn;
117 
118     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
119 
120     private final DevicePolicyManager mDevicePolicyManager;
121     private boolean mDozing;
122     private final ViewClippingUtil.ClippingParameters mClippingParams =
123             new ViewClippingUtil.ClippingParameters() {
124                 @Override
125                 public boolean shouldFinish(View view) {
126                     return view == mIndicationArea;
127                 }
128             };
129 
130     /**
131      * Creates a new KeyguardIndicationController and registers callbacks.
132      */
KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon)133     public KeyguardIndicationController(Context context, ViewGroup indicationArea,
134             LockIcon lockIcon) {
135         this(context, indicationArea, lockIcon, new LockPatternUtils(context),
136                 WakeLock.createPartial(context, "Doze:KeyguardIndication"),
137                 Dependency.get(ShadeController.class),
138                 Dependency.get(AccessibilityController.class),
139                 UnlockMethodCache.getInstance(context),
140                 Dependency.get(StatusBarStateController.class),
141                 KeyguardUpdateMonitor.getInstance(context));
142     }
143 
144     /**
145      * Creates a new KeyguardIndicationController for testing.
146      */
147     @VisibleForTesting
KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon, LockPatternUtils lockPatternUtils, WakeLock wakeLock, ShadeController shadeController, AccessibilityController accessibilityController, UnlockMethodCache unlockMethodCache, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor)148     KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon,
149             LockPatternUtils lockPatternUtils, WakeLock wakeLock, ShadeController shadeController,
150             AccessibilityController accessibilityController, UnlockMethodCache unlockMethodCache,
151             StatusBarStateController statusBarStateController,
152             KeyguardUpdateMonitor keyguardUpdateMonitor) {
153         mContext = context;
154         mLockIcon = lockIcon;
155         mShadeController = shadeController;
156         mAccessibilityController = accessibilityController;
157         mUnlockMethodCache = unlockMethodCache;
158         mStatusBarStateController = statusBarStateController;
159         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
160         // lock icon is not used on all form factors.
161         if (mLockIcon != null) {
162             mLockIcon.setOnLongClickListener(this::handleLockLongClick);
163             mLockIcon.setOnClickListener(this::handleLockClick);
164         }
165         mWakeLock = new SettableWakeLock(wakeLock, TAG);
166         mLockPatternUtils = lockPatternUtils;
167 
168         Resources res = context.getResources();
169         mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold);
170         mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold);
171 
172         mUserManager = context.getSystemService(UserManager.class);
173         mBatteryInfo = IBatteryStats.Stub.asInterface(
174                 ServiceManager.getService(BatteryStats.SERVICE_NAME));
175 
176         mDevicePolicyManager = (DevicePolicyManager) context.getSystemService(
177                 Context.DEVICE_POLICY_SERVICE);
178         setIndicationArea(indicationArea);
179         updateDisclosure();
180 
181         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
182         mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
183         mStatusBarStateController.addCallback(this);
184         mUnlockMethodCache.addListener(this);
185     }
186 
setIndicationArea(ViewGroup indicationArea)187     public void setIndicationArea(ViewGroup indicationArea) {
188         mIndicationArea = indicationArea;
189         mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
190         mInitialTextColorState = mTextView != null ?
191                 mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
192         mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
193         updateIndication(false /* animate */);
194     }
195 
handleLockLongClick(View view)196     private boolean handleLockLongClick(View view) {
197         mLockscreenGestureLogger.write(MetricsProto.MetricsEvent.ACTION_LS_LOCK,
198                 0 /* lengthDp - N/A */, 0 /* velocityDp - N/A */);
199         showTransientIndication(R.string.keyguard_indication_trust_disabled);
200         mKeyguardUpdateMonitor.onLockIconPressed();
201         mLockPatternUtils.requireCredentialEntry(KeyguardUpdateMonitor.getCurrentUser());
202 
203         return true;
204     }
205 
handleLockClick(View view)206     private void handleLockClick(View view) {
207         if (!mAccessibilityController.isAccessibilityEnabled()) {
208             return;
209         }
210         mShadeController.animateCollapsePanels(CommandQueue.FLAG_EXCLUDE_NONE, true /* force */);
211     }
212 
213     /**
214      * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
215      * {@link KeyguardIndicationController}.
216      *
217      * <p>Subclasses may override this method to extend or change the callback behavior by extending
218      * the {@link BaseKeyguardCallback}.
219      *
220      * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
221      * same instance.
222      */
getKeyguardCallback()223     protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
224         if (mUpdateMonitorCallback == null) {
225             mUpdateMonitorCallback = new BaseKeyguardCallback();
226         }
227         return mUpdateMonitorCallback;
228     }
229 
updateDisclosure()230     private void updateDisclosure() {
231         if (mDevicePolicyManager == null) {
232             return;
233         }
234 
235         if (!mDozing && mDevicePolicyManager.isDeviceManaged()) {
236             final CharSequence organizationName =
237                     mDevicePolicyManager.getDeviceOwnerOrganizationName();
238             if (organizationName != null) {
239                 mDisclosure.switchIndication(mContext.getResources().getString(
240                         R.string.do_disclosure_with_name, organizationName));
241             } else {
242                 mDisclosure.switchIndication(R.string.do_disclosure_generic);
243             }
244             mDisclosure.setVisibility(View.VISIBLE);
245         } else {
246             mDisclosure.setVisibility(View.GONE);
247         }
248     }
249 
setVisible(boolean visible)250     public void setVisible(boolean visible) {
251         mVisible = visible;
252         mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE);
253         if (visible) {
254             // If this is called after an error message was already shown, we should not clear it.
255             // Otherwise the error message won't be shown
256             if  (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
257                 hideTransientIndication();
258             }
259             updateIndication(false);
260         } else if (!visible) {
261             // If we unlock and return to keyguard quickly, previous error should not be shown
262             hideTransientIndication();
263         }
264     }
265 
266     /**
267      * Sets the indication that is shown if nothing else is showing.
268      */
setRestingIndication(String restingIndication)269     public void setRestingIndication(String restingIndication) {
270         mRestingIndication = restingIndication;
271         updateIndication(false);
272     }
273 
274     /**
275      * Sets the active controller managing changes and callbacks to user information.
276      */
setUserInfoController(UserInfoController userInfoController)277     public void setUserInfoController(UserInfoController userInfoController) {
278     }
279 
280     /**
281      * Returns the indication text indicating that trust has been granted.
282      *
283      * @return {@code null} or an empty string if a trust indication text should not be shown.
284      */
285     @VisibleForTesting
getTrustGrantedIndication()286     String getTrustGrantedIndication() {
287         return mContext.getString(R.string.keyguard_indication_trust_unlocked);
288     }
289 
290     /**
291      * Returns the indication text indicating that trust is currently being managed.
292      *
293      * @return {@code null} or an empty string if a trust managed text should not be shown.
294      */
getTrustManagedIndication()295     private String getTrustManagedIndication() {
296         return null;
297     }
298 
299     /**
300      * Hides transient indication in {@param delayMs}.
301      */
hideTransientIndicationDelayed(long delayMs)302     public void hideTransientIndicationDelayed(long delayMs) {
303         mHandler.sendMessageDelayed(
304                 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs);
305     }
306 
307     /**
308      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
309      */
showTransientIndication(int transientIndication)310     public void showTransientIndication(int transientIndication) {
311         showTransientIndication(mContext.getResources().getString(transientIndication));
312     }
313 
314     /**
315      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
316      */
showTransientIndication(CharSequence transientIndication)317     public void showTransientIndication(CharSequence transientIndication) {
318         showTransientIndication(transientIndication, mInitialTextColorState);
319     }
320 
321     /**
322      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
323      */
showTransientIndication(CharSequence transientIndication, ColorStateList textColorState)324     public void showTransientIndication(CharSequence transientIndication,
325             ColorStateList textColorState) {
326         mTransientIndication = transientIndication;
327         mTransientTextColorState = textColorState;
328         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
329         if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
330             // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
331             mWakeLock.setAcquired(true);
332             hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
333         }
334 
335         updateIndication(false);
336     }
337 
338     /**
339      * Hides transient indication.
340      */
hideTransientIndication()341     public void hideTransientIndication() {
342         if (mTransientIndication != null) {
343             mTransientIndication = null;
344             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
345             updateIndication(false);
346         }
347     }
348 
updateIndication(boolean animate)349     protected final void updateIndication(boolean animate) {
350         if (TextUtils.isEmpty(mTransientIndication)) {
351             mWakeLock.setAcquired(false);
352         }
353 
354         if (mVisible) {
355             // Walk down a precedence-ordered list of what indication
356             // should be shown based on user or device state
357             if (mDozing) {
358                 // When dozing we ignore any text color and use white instead, because
359                 // colors can be hard to read in low brightness.
360                 mTextView.setTextColor(Color.WHITE);
361                 if (!TextUtils.isEmpty(mTransientIndication)) {
362                     mTextView.switchIndication(mTransientIndication);
363                 } else if (mPowerPluggedIn) {
364                     String indication = computePowerIndication();
365                     if (animate) {
366                         animateText(mTextView, indication);
367                     } else {
368                         mTextView.switchIndication(indication);
369                     }
370                 } else {
371                     String percentage = NumberFormat.getPercentInstance()
372                             .format(mBatteryLevel / 100f);
373                     mTextView.switchIndication(percentage);
374                 }
375                 return;
376             }
377 
378             int userId = KeyguardUpdateMonitor.getCurrentUser();
379             String trustGrantedIndication = getTrustGrantedIndication();
380             String trustManagedIndication = getTrustManagedIndication();
381             if (!mUserManager.isUserUnlocked(userId)) {
382                 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
383                 mTextView.setTextColor(mInitialTextColorState);
384             } else if (!TextUtils.isEmpty(mTransientIndication)) {
385                 mTextView.switchIndication(mTransientIndication);
386                 mTextView.setTextColor(mTransientTextColorState);
387             } else if (!TextUtils.isEmpty(trustGrantedIndication)
388                     && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
389                 mTextView.switchIndication(trustGrantedIndication);
390                 mTextView.setTextColor(mInitialTextColorState);
391             } else if (mPowerPluggedIn) {
392                 String indication = computePowerIndication();
393                 if (DEBUG_CHARGING_SPEED) {
394                     indication += ",  " + (mChargingWattage / 1000) + " mW";
395                 }
396                 mTextView.setTextColor(mInitialTextColorState);
397                 if (animate) {
398                     animateText(mTextView, indication);
399                 } else {
400                     mTextView.switchIndication(indication);
401                 }
402             } else if (!TextUtils.isEmpty(trustManagedIndication)
403                     && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
404                     && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
405                 mTextView.switchIndication(trustManagedIndication);
406                 mTextView.setTextColor(mInitialTextColorState);
407             } else {
408                 mTextView.switchIndication(mRestingIndication);
409                 mTextView.setTextColor(mInitialTextColorState);
410             }
411         }
412     }
413 
414     // animates textView - textView moves up and bounces down
animateText(KeyguardIndicationTextView textView, String indication)415     private void animateText(KeyguardIndicationTextView textView, String indication) {
416         int yTranslation = mContext.getResources().getInteger(
417                 R.integer.wired_charging_keyguard_text_animation_distance);
418         int animateUpDuration = mContext.getResources().getInteger(
419                 R.integer.wired_charging_keyguard_text_animation_duration_up);
420         int animateDownDuration = mContext.getResources().getInteger(
421                 R.integer.wired_charging_keyguard_text_animation_duration_down);
422         textView.animate().cancel();
423         float translation = textView.getTranslationY();
424         ViewClippingUtil.setClippingDeactivated(textView, true, mClippingParams);
425         textView.animate()
426                 .translationYBy(yTranslation)
427                 .setInterpolator(Interpolators.LINEAR)
428                 .setDuration(animateUpDuration)
429                 .setListener(new AnimatorListenerAdapter() {
430                     private boolean mCancelled;
431 
432                     @Override
433                     public void onAnimationStart(Animator animation) {
434                         textView.switchIndication(indication);
435                     }
436 
437                     @Override
438                     public void onAnimationCancel(Animator animation) {
439                         textView.setTranslationY(translation);
440                         mCancelled = true;
441                     }
442 
443                     @Override
444                     public void onAnimationEnd(Animator animation) {
445                         if (mCancelled) {
446                             ViewClippingUtil.setClippingDeactivated(textView, false,
447                                     mClippingParams);
448                             return;
449                         }
450                         textView.animate()
451                                 .setDuration(animateDownDuration)
452                                 .setInterpolator(Interpolators.BOUNCE)
453                                 .translationY(translation)
454                                 .setListener(new AnimatorListenerAdapter() {
455                                     @Override
456                                     public void onAnimationCancel(Animator animation) {
457                                         textView.setTranslationY(translation);
458                                     }
459 
460                                     @Override
461                                     public void onAnimationEnd(Animator animation) {
462                                         ViewClippingUtil.setClippingDeactivated(textView, false,
463                                                 mClippingParams);
464                                     }
465                                 });
466                     }
467                 });
468     }
469 
computePowerIndication()470     private String computePowerIndication() {
471         if (mPowerCharged) {
472             return mContext.getResources().getString(R.string.keyguard_charged);
473         }
474 
475         // Try fetching charging time from battery stats.
476         long chargingTimeRemaining = 0;
477         try {
478             chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining();
479 
480         } catch (RemoteException e) {
481             Log.e(TAG, "Error calling IBatteryStats: ", e);
482         }
483         final boolean hasChargingTime = chargingTimeRemaining > 0;
484 
485         int chargingId;
486         if (mPowerPluggedInWired) {
487             switch (mChargingSpeed) {
488                 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST:
489                     chargingId = hasChargingTime
490                             ? R.string.keyguard_indication_charging_time_fast
491                             : R.string.keyguard_plugged_in_charging_fast;
492                     break;
493                 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY:
494                     chargingId = hasChargingTime
495                             ? R.string.keyguard_indication_charging_time_slowly
496                             : R.string.keyguard_plugged_in_charging_slowly;
497                     break;
498                 default:
499                     chargingId = hasChargingTime
500                             ? R.string.keyguard_indication_charging_time
501                             : R.string.keyguard_plugged_in;
502                     break;
503             }
504         } else {
505             chargingId = hasChargingTime
506                     ? R.string.keyguard_indication_charging_time_wireless
507                     : R.string.keyguard_plugged_in_wireless;
508         }
509 
510         String percentage = NumberFormat.getPercentInstance()
511                 .format(mBatteryLevel / 100f);
512         if (hasChargingTime) {
513             // We now have battery percentage in these strings and it's expected that all
514             // locales will also have it in the future. For now, we still have to support the old
515             // format until all languages get the new translations.
516             String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
517                     mContext, chargingTimeRemaining);
518             try {
519                 return mContext.getResources().getString(chargingId, chargingTimeFormatted,
520                         percentage);
521             } catch (IllegalFormatConversionException e) {
522                 return mContext.getResources().getString(chargingId, chargingTimeFormatted);
523             }
524         } else {
525             // Same as above
526             try {
527                 return mContext.getResources().getString(chargingId, percentage);
528             } catch (IllegalFormatConversionException e) {
529                 return mContext.getResources().getString(chargingId);
530             }
531         }
532     }
533 
setStatusBarKeyguardViewManager( StatusBarKeyguardViewManager statusBarKeyguardViewManager)534     public void setStatusBarKeyguardViewManager(
535             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
536         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
537     }
538 
539     private final KeyguardUpdateMonitorCallback mTickReceiver =
540             new KeyguardUpdateMonitorCallback() {
541                 @Override
542                 public void onTimeChanged() {
543                     if (mVisible) {
544                         updateIndication(false /* animate */);
545                     }
546                 }
547             };
548 
549     private final Handler mHandler = new Handler() {
550         @Override
551         public void handleMessage(Message msg) {
552             if (msg.what == MSG_HIDE_TRANSIENT) {
553                 hideTransientIndication();
554             } else if (msg.what == MSG_CLEAR_BIOMETRIC_MSG) {
555                 mLockIcon.setTransientBiometricsError(false);
556             }
557         }
558     };
559 
setDozing(boolean dozing)560     public void setDozing(boolean dozing) {
561         if (mDozing == dozing) {
562             return;
563         }
564         mDozing = dozing;
565         updateIndication(false);
566         updateDisclosure();
567     }
568 
dump(FileDescriptor fd, PrintWriter pw, String[] args)569     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
570         pw.println("KeyguardIndicationController:");
571         pw.println("  mTransientTextColorState: " + mTransientTextColorState);
572         pw.println("  mInitialTextColorState: " + mInitialTextColorState);
573         pw.println("  mPowerPluggedInWired: " + mPowerPluggedInWired);
574         pw.println("  mPowerPluggedIn: " + mPowerPluggedIn);
575         pw.println("  mPowerCharged: " + mPowerCharged);
576         pw.println("  mChargingSpeed: " + mChargingSpeed);
577         pw.println("  mChargingWattage: " + mChargingWattage);
578         pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
579         pw.println("  mDozing: " + mDozing);
580         pw.println("  mBatteryLevel: " + mBatteryLevel);
581         pw.println("  mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
582         pw.println("  computePowerIndication(): " + computePowerIndication());
583     }
584 
585     @Override
onStateChanged(int newState)586     public void onStateChanged(int newState) {
587         // don't care
588     }
589 
590     @Override
onDozingChanged(boolean isDozing)591     public void onDozingChanged(boolean isDozing) {
592         setDozing(isDozing);
593     }
594 
595     @Override
onUnlockMethodStateChanged()596     public void onUnlockMethodStateChanged() {
597         updateIndication(!mDozing);
598     }
599 
600     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
601         public static final int HIDE_DELAY_MS = 5000;
602 
603         @Override
onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status)604         public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) {
605             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
606                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
607             boolean wasPluggedIn = mPowerPluggedIn;
608             mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull;
609             mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
610             mPowerCharged = status.isCharged();
611             mChargingWattage = status.maxChargingWattage;
612             mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold);
613             mBatteryLevel = status.level;
614             updateIndication(!wasPluggedIn && mPowerPluggedInWired);
615             if (mDozing) {
616                 if (!wasPluggedIn && mPowerPluggedIn) {
617                     showTransientIndication(computePowerIndication());
618                     hideTransientIndicationDelayed(HIDE_DELAY_MS);
619                 } else if (wasPluggedIn && !mPowerPluggedIn) {
620                     hideTransientIndication();
621                 }
622             }
623         }
624 
625         @Override
onKeyguardVisibilityChanged(boolean showing)626         public void onKeyguardVisibilityChanged(boolean showing) {
627             if (showing) {
628                 updateDisclosure();
629             }
630         }
631 
632         @Override
onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType)633         public void onBiometricHelp(int msgId, String helpString,
634                 BiometricSourceType biometricSourceType) {
635             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
636             if (!updateMonitor.isUnlockingWithBiometricAllowed()) {
637                 return;
638             }
639             animatePadlockError();
640             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
641                 mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
642                         mInitialTextColorState);
643             } else if (updateMonitor.isScreenOn()) {
644                 showTransientIndication(helpString);
645                 hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
646             }
647         }
648 
649         @Override
onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType)650         public void onBiometricError(int msgId, String errString,
651                 BiometricSourceType biometricSourceType) {
652             KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext);
653             if (shouldSuppressBiometricError(msgId, biometricSourceType, updateMonitor)) {
654                 return;
655             }
656             animatePadlockError();
657             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
658                 mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
659             } else if (updateMonitor.isScreenOn()) {
660                 showTransientIndication(errString);
661                 // We want to keep this message around in case the screen was off
662                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
663             } else {
664                 mMessageToShowOnScreenOn = errString;
665             }
666         }
667 
animatePadlockError()668         private void animatePadlockError() {
669             mLockIcon.setTransientBiometricsError(true);
670             mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
671             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
672                     TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
673         }
674 
shouldSuppressBiometricError(int msgId, BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor)675         private boolean shouldSuppressBiometricError(int msgId,
676                 BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
677             if (biometricSourceType == BiometricSourceType.FINGERPRINT)
678                 return shouldSuppressFingerprintError(msgId, updateMonitor);
679             if (biometricSourceType == BiometricSourceType.FACE)
680                 return shouldSuppressFaceError(msgId, updateMonitor);
681             return false;
682         }
683 
shouldSuppressFingerprintError(int msgId, KeyguardUpdateMonitor updateMonitor)684         private boolean shouldSuppressFingerprintError(int msgId,
685                 KeyguardUpdateMonitor updateMonitor) {
686             return ((!updateMonitor.isUnlockingWithBiometricAllowed()
687                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
688                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
689         }
690 
shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor)691         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
692             return ((!updateMonitor.isUnlockingWithBiometricAllowed()
693                     && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
694                     || msgId == FaceManager.FACE_ERROR_CANCELED);
695         }
696 
697         @Override
onTrustAgentErrorMessage(CharSequence message)698         public void onTrustAgentErrorMessage(CharSequence message) {
699             showTransientIndication(message, Utils.getColorError(mContext));
700         }
701 
702         @Override
onScreenTurnedOn()703         public void onScreenTurnedOn() {
704             if (mMessageToShowOnScreenOn != null) {
705                 showTransientIndication(mMessageToShowOnScreenOn, Utils.getColorError(mContext));
706                 // We want to keep this message around in case the screen was off
707                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
708                 mMessageToShowOnScreenOn = null;
709             }
710         }
711 
712         @Override
onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType)713         public void onBiometricRunningStateChanged(boolean running,
714                 BiometricSourceType biometricSourceType) {
715             if (running) {
716                 // Let's hide any previous messages when authentication starts, otherwise
717                 // multiple auth attempts would overlap.
718                 hideTransientIndication();
719                 mMessageToShowOnScreenOn = null;
720             }
721         }
722 
723         @Override
onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType)724         public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType) {
725             super.onBiometricAuthenticated(userId, biometricSourceType);
726             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
727         }
728 
729         @Override
onUserUnlocked()730         public void onUserUnlocked() {
731             if (mVisible) {
732                 updateIndication(false);
733             }
734         }
735 
736         @Override
onKeyguardBouncerChanged(boolean bouncer)737         public void onKeyguardBouncerChanged(boolean bouncer) {
738             if (mLockIcon == null) {
739                 return;
740             }
741             mLockIcon.setBouncerVisible(bouncer);
742         }
743     };
744 }
745