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