• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 static com.android.systemui.DejankUtils.whitelistIpcs;
20 
21 import android.animation.Animator;
22 import android.animation.AnimatorListenerAdapter;
23 import android.app.admin.DevicePolicyManager;
24 import android.content.BroadcastReceiver;
25 import android.content.Context;
26 import android.content.Intent;
27 import android.content.IntentFilter;
28 import android.content.pm.UserInfo;
29 import android.content.res.ColorStateList;
30 import android.graphics.Color;
31 import android.hardware.biometrics.BiometricSourceType;
32 import android.hardware.face.FaceManager;
33 import android.hardware.fingerprint.FingerprintManager;
34 import android.os.BatteryManager;
35 import android.os.Handler;
36 import android.os.Message;
37 import android.os.RemoteException;
38 import android.os.UserHandle;
39 import android.os.UserManager;
40 import android.text.TextUtils;
41 import android.text.format.Formatter;
42 import android.util.Log;
43 import android.view.View;
44 import android.view.ViewGroup;
45 
46 import androidx.annotation.Nullable;
47 
48 import com.android.internal.annotations.VisibleForTesting;
49 import com.android.internal.app.IBatteryStats;
50 import com.android.internal.widget.ViewClippingUtil;
51 import com.android.keyguard.KeyguardUpdateMonitor;
52 import com.android.keyguard.KeyguardUpdateMonitorCallback;
53 import com.android.settingslib.Utils;
54 import com.android.settingslib.fuelgauge.BatteryStatus;
55 import com.android.systemui.Interpolators;
56 import com.android.systemui.R;
57 import com.android.systemui.broadcast.BroadcastDispatcher;
58 import com.android.systemui.dock.DockManager;
59 import com.android.systemui.plugins.statusbar.StatusBarStateController;
60 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
61 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView;
62 import com.android.systemui.statusbar.phone.LockscreenLockIconController;
63 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
64 import com.android.systemui.statusbar.policy.KeyguardStateController;
65 import com.android.systemui.util.wakelock.SettableWakeLock;
66 import com.android.systemui.util.wakelock.WakeLock;
67 
68 import java.io.FileDescriptor;
69 import java.io.PrintWriter;
70 import java.text.NumberFormat;
71 import java.util.IllegalFormatConversionException;
72 
73 import javax.inject.Inject;
74 import javax.inject.Singleton;
75 
76 /**
77  * Controls the indications and error messages shown on the Keyguard
78  */
79 @Singleton
80 public class KeyguardIndicationController implements StateListener,
81         KeyguardStateController.Callback {
82 
83     private static final String TAG = "KeyguardIndication";
84     private static final boolean DEBUG_CHARGING_SPEED = false;
85 
86     private static final int MSG_HIDE_TRANSIENT = 1;
87     private static final int MSG_CLEAR_BIOMETRIC_MSG = 2;
88     private static final int MSG_SWIPE_UP_TO_UNLOCK = 3;
89     private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300;
90     private static final float BOUNCE_ANIMATION_FINAL_Y = 0f;
91 
92     private final Context mContext;
93     private final BroadcastDispatcher mBroadcastDispatcher;
94     private final KeyguardStateController mKeyguardStateController;
95     private final StatusBarStateController mStatusBarStateController;
96     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
97     private ViewGroup mIndicationArea;
98     private KeyguardIndicationTextView mTextView;
99     private KeyguardIndicationTextView mDisclosure;
100     private final IBatteryStats mBatteryInfo;
101     private final SettableWakeLock mWakeLock;
102     private final DockManager mDockManager;
103     private final DevicePolicyManager mDevicePolicyManager;
104     private final UserManager mUserManager;
105 
106     private BroadcastReceiver mBroadcastReceiver;
107     private LockscreenLockIconController mLockIconController;
108     private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
109 
110     private String mRestingIndication;
111     private String mAlignmentIndication;
112     private CharSequence mTransientIndication;
113     private boolean mTransientTextIsError;
114     private ColorStateList mInitialTextColorState;
115     private boolean mVisible;
116     private boolean mHideTransientMessageOnScreenOff;
117 
118     private boolean mPowerPluggedIn;
119     private boolean mPowerPluggedInWired;
120     private boolean mPowerCharged;
121     private boolean mBatteryOverheated;
122     private boolean mEnableBatteryDefender;
123     private int mChargingSpeed;
124     private int mChargingWattage;
125     private int mBatteryLevel;
126     private boolean mBatteryPresent = true;
127     private long mChargingTimeRemaining;
128     private float mDisclosureMaxAlpha;
129     private String mMessageToShowOnScreenOn;
130 
131     private KeyguardUpdateMonitorCallback mUpdateMonitorCallback;
132 
133     private boolean mDozing;
134     private final ViewClippingUtil.ClippingParameters mClippingParams =
135             new ViewClippingUtil.ClippingParameters() {
136                 @Override
137                 public boolean shouldFinish(View view) {
138                     return view == mIndicationArea;
139                 }
140             };
141 
142     /**
143      * Creates a new KeyguardIndicationController and registers callbacks.
144      */
145     @Inject
KeyguardIndicationController(Context context, WakeLock.Builder wakeLockBuilder, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, BroadcastDispatcher broadcastDispatcher, DevicePolicyManager devicePolicyManager, IBatteryStats iBatteryStats, UserManager userManager)146     public KeyguardIndicationController(Context context,
147             WakeLock.Builder wakeLockBuilder,
148             KeyguardStateController keyguardStateController,
149             StatusBarStateController statusBarStateController,
150             KeyguardUpdateMonitor keyguardUpdateMonitor,
151             DockManager dockManager,
152             BroadcastDispatcher broadcastDispatcher,
153             DevicePolicyManager devicePolicyManager,
154             IBatteryStats iBatteryStats,
155             UserManager userManager) {
156         mContext = context;
157         mBroadcastDispatcher = broadcastDispatcher;
158         mDevicePolicyManager = devicePolicyManager;
159         mKeyguardStateController = keyguardStateController;
160         mStatusBarStateController = statusBarStateController;
161         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
162         mDockManager = dockManager;
163         mDockManager.addAlignmentStateListener(
164                 alignState -> mHandler.post(() -> handleAlignStateChanged(alignState)));
165         mWakeLock = new SettableWakeLock(
166                 wakeLockBuilder.setTag("Doze:KeyguardIndication").build(), TAG);
167         mBatteryInfo = iBatteryStats;
168         mUserManager = userManager;
169 
170         mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback());
171         mKeyguardUpdateMonitor.registerCallback(mTickReceiver);
172         mStatusBarStateController.addCallback(this);
173         mKeyguardStateController.addCallback(this);
174     }
175 
setIndicationArea(ViewGroup indicationArea)176     public void setIndicationArea(ViewGroup indicationArea) {
177         mIndicationArea = indicationArea;
178         mTextView = indicationArea.findViewById(R.id.keyguard_indication_text);
179         mInitialTextColorState = mTextView != null ?
180                 mTextView.getTextColors() : ColorStateList.valueOf(Color.WHITE);
181         mDisclosure = indicationArea.findViewById(R.id.keyguard_indication_enterprise_disclosure);
182         mDisclosureMaxAlpha = mDisclosure.getAlpha();
183         updateIndication(false /* animate */);
184         updateDisclosure();
185 
186         if (mBroadcastReceiver == null) {
187             // Update the disclosure proactively to avoid IPC on the critical path.
188             mBroadcastReceiver = new BroadcastReceiver() {
189                 @Override
190                 public void onReceive(Context context, Intent intent) {
191                     updateDisclosure();
192                 }
193             };
194             IntentFilter intentFilter = new IntentFilter();
195             intentFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED);
196             intentFilter.addAction(Intent.ACTION_USER_REMOVED);
197             mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter);
198         }
199     }
200 
setLockIconController(LockscreenLockIconController lockIconController)201     public void setLockIconController(LockscreenLockIconController lockIconController) {
202         mLockIconController = lockIconController;
203     }
204 
handleAlignStateChanged(int alignState)205     private void handleAlignStateChanged(int alignState) {
206         String alignmentIndication = "";
207         if (alignState == DockManager.ALIGN_STATE_POOR) {
208             alignmentIndication =
209                     mContext.getResources().getString(R.string.dock_alignment_slow_charging);
210         } else if (alignState == DockManager.ALIGN_STATE_TERRIBLE) {
211             alignmentIndication =
212                     mContext.getResources().getString(R.string.dock_alignment_not_charging);
213         }
214         if (!alignmentIndication.equals(mAlignmentIndication)) {
215             mAlignmentIndication = alignmentIndication;
216             updateIndication(false);
217         }
218     }
219 
220     /**
221      * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this
222      * {@link KeyguardIndicationController}.
223      *
224      * <p>Subclasses may override this method to extend or change the callback behavior by extending
225      * the {@link BaseKeyguardCallback}.
226      *
227      * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the
228      * same instance.
229      */
getKeyguardCallback()230     protected KeyguardUpdateMonitorCallback getKeyguardCallback() {
231         if (mUpdateMonitorCallback == null) {
232             mUpdateMonitorCallback = new BaseKeyguardCallback();
233         }
234         return mUpdateMonitorCallback;
235     }
236 
updateDisclosure()237     private void updateDisclosure() {
238         // NOTE: Because this uses IPC, avoid calling updateDisclosure() on a critical path.
239         if (whitelistIpcs(this::isOrganizationOwnedDevice)) {
240             CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName();
241             if (organizationName != null) {
242                 mDisclosure.switchIndication(mContext.getResources().getString(
243                         R.string.do_disclosure_with_name, organizationName));
244             } else {
245                 mDisclosure.switchIndication(R.string.do_disclosure_generic);
246             }
247             mDisclosure.setVisibility(View.VISIBLE);
248         } else {
249             mDisclosure.setVisibility(View.GONE);
250         }
251     }
252 
isOrganizationOwnedDevice()253     private boolean isOrganizationOwnedDevice() {
254         return mDevicePolicyManager.isDeviceManaged()
255                 || mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile();
256     }
257 
258     @Nullable
getOrganizationOwnedDeviceOrganizationName()259     private CharSequence getOrganizationOwnedDeviceOrganizationName() {
260         if (mDevicePolicyManager.isDeviceManaged()) {
261             return mDevicePolicyManager.getDeviceOwnerOrganizationName();
262         } else if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) {
263             return getWorkProfileOrganizationName();
264         }
265         return null;
266     }
267 
getWorkProfileOrganizationName()268     private CharSequence getWorkProfileOrganizationName() {
269         final int profileId = getWorkProfileUserId(UserHandle.myUserId());
270         if (profileId == UserHandle.USER_NULL) {
271             return null;
272         }
273         return mDevicePolicyManager.getOrganizationNameForUser(profileId);
274     }
275 
getWorkProfileUserId(int userId)276     private int getWorkProfileUserId(int userId) {
277         for (final UserInfo userInfo : mUserManager.getProfiles(userId)) {
278             if (userInfo.isManagedProfile()) {
279                 return userInfo.id;
280             }
281         }
282         return UserHandle.USER_NULL;
283     }
284 
setVisible(boolean visible)285     public void setVisible(boolean visible) {
286         mVisible = visible;
287         mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE);
288         if (visible) {
289             // If this is called after an error message was already shown, we should not clear it.
290             // Otherwise the error message won't be shown
291             if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) {
292                 hideTransientIndication();
293             }
294             updateIndication(false);
295         } else if (!visible) {
296             // If we unlock and return to keyguard quickly, previous error should not be shown
297             hideTransientIndication();
298         }
299     }
300 
301     /**
302      * Sets the indication that is shown if nothing else is showing.
303      */
setRestingIndication(String restingIndication)304     public void setRestingIndication(String restingIndication) {
305         mRestingIndication = restingIndication;
306         updateIndication(false);
307     }
308 
309     /**
310      * Returns the indication text indicating that trust has been granted.
311      *
312      * @return {@code null} or an empty string if a trust indication text should not be shown.
313      */
314     @VisibleForTesting
getTrustGrantedIndication()315     String getTrustGrantedIndication() {
316         return mContext.getString(R.string.keyguard_indication_trust_unlocked);
317     }
318 
319     /**
320      * Sets if the device is plugged in
321      */
322     @VisibleForTesting
setPowerPluggedIn(boolean plugged)323     void setPowerPluggedIn(boolean plugged) {
324         mPowerPluggedIn = plugged;
325     }
326 
327     /**
328      * Returns the indication text indicating that trust is currently being managed.
329      *
330      * @return {@code null} or an empty string if a trust managed text should not be shown.
331      */
getTrustManagedIndication()332     private String getTrustManagedIndication() {
333         return null;
334     }
335 
336     /**
337      * Hides transient indication in {@param delayMs}.
338      */
hideTransientIndicationDelayed(long delayMs)339     public void hideTransientIndicationDelayed(long delayMs) {
340         mHandler.sendMessageDelayed(
341                 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs);
342     }
343 
344     /**
345      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
346      */
showTransientIndication(int transientIndication)347     public void showTransientIndication(int transientIndication) {
348         showTransientIndication(mContext.getResources().getString(transientIndication));
349     }
350 
351     /**
352      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
353      */
showTransientIndication(CharSequence transientIndication)354     public void showTransientIndication(CharSequence transientIndication) {
355         showTransientIndication(transientIndication, false /* isError */,
356                 false /* hideOnScreenOff */);
357     }
358 
359     /**
360      * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}.
361      */
showTransientIndication(CharSequence transientIndication, boolean isError, boolean hideOnScreenOff)362     private void showTransientIndication(CharSequence transientIndication,
363             boolean isError, boolean hideOnScreenOff) {
364         mTransientIndication = transientIndication;
365         mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null;
366         mTransientTextIsError = isError;
367         mHandler.removeMessages(MSG_HIDE_TRANSIENT);
368         mHandler.removeMessages(MSG_SWIPE_UP_TO_UNLOCK);
369         if (mDozing && !TextUtils.isEmpty(mTransientIndication)) {
370             // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared.
371             mWakeLock.setAcquired(true);
372             hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
373         }
374 
375         updateIndication(false);
376     }
377 
378     /**
379      * Hides transient indication.
380      */
hideTransientIndication()381     public void hideTransientIndication() {
382         if (mTransientIndication != null) {
383             mTransientIndication = null;
384             mHideTransientMessageOnScreenOff = false;
385             mHandler.removeMessages(MSG_HIDE_TRANSIENT);
386             updateIndication(false);
387         }
388     }
389 
updateIndication(boolean animate)390     protected final void updateIndication(boolean animate) {
391         if (TextUtils.isEmpty(mTransientIndication)) {
392             mWakeLock.setAcquired(false);
393         }
394 
395         if (!mVisible) {
396             return;
397         }
398 
399         // A few places might need to hide the indication, so always start by making it visible
400         mIndicationArea.setVisibility(View.VISIBLE);
401 
402         // Walk down a precedence-ordered list of what indication
403         // should be shown based on user or device state
404         if (mDozing) {
405             // When dozing we ignore any text color and use white instead, because
406             // colors can be hard to read in low brightness.
407             mTextView.setTextColor(Color.WHITE);
408             if (!TextUtils.isEmpty(mTransientIndication)) {
409                 mTextView.switchIndication(mTransientIndication);
410             } else if (!mBatteryPresent) {
411                 // If there is no battery detected, hide the indication and bail
412                 mIndicationArea.setVisibility(View.GONE);
413             } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
414                 mTextView.switchIndication(mAlignmentIndication);
415                 mTextView.setTextColor(mContext.getColor(R.color.misalignment_text_color));
416             } else if (mPowerPluggedIn || mEnableBatteryDefender) {
417                 String indication = computePowerIndication();
418                 if (animate) {
419                     animateText(mTextView, indication);
420                 } else {
421                     mTextView.switchIndication(indication);
422                 }
423             } else {
424                 String percentage = NumberFormat.getPercentInstance()
425                         .format(mBatteryLevel / 100f);
426                 mTextView.switchIndication(percentage);
427             }
428             return;
429         }
430 
431         int userId = KeyguardUpdateMonitor.getCurrentUser();
432         String trustGrantedIndication = getTrustGrantedIndication();
433         String trustManagedIndication = getTrustManagedIndication();
434 
435         String powerIndication = null;
436         if (mPowerPluggedIn || mEnableBatteryDefender) {
437             powerIndication = computePowerIndication();
438         }
439 
440         // Some cases here might need to hide the indication (if the battery is not present)
441         boolean hideIndication = false;
442         boolean isError = false;
443         if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) {
444             mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked);
445         } else if (!TextUtils.isEmpty(mTransientIndication)) {
446             if (powerIndication != null && !mTransientIndication.equals(powerIndication)) {
447                 String indication = mContext.getResources().getString(
448                                 R.string.keyguard_indication_trust_unlocked_plugged_in,
449                                 mTransientIndication, powerIndication);
450                 mTextView.switchIndication(indication);
451                 hideIndication = !mBatteryPresent;
452             } else {
453                 mTextView.switchIndication(mTransientIndication);
454             }
455             isError = mTransientTextIsError;
456         } else if (!TextUtils.isEmpty(trustGrantedIndication)
457                 && mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
458             if (powerIndication != null) {
459                 String indication = mContext.getResources().getString(
460                                 R.string.keyguard_indication_trust_unlocked_plugged_in,
461                                 trustGrantedIndication, powerIndication);
462                 mTextView.switchIndication(indication);
463                 hideIndication = !mBatteryPresent;
464             } else {
465                 mTextView.switchIndication(trustGrantedIndication);
466             }
467         } else if (!TextUtils.isEmpty(mAlignmentIndication)) {
468             mTextView.switchIndication(mAlignmentIndication);
469             isError = true;
470             hideIndication = !mBatteryPresent;
471         } else if (mPowerPluggedIn || mEnableBatteryDefender) {
472             if (DEBUG_CHARGING_SPEED) {
473                 powerIndication += ",  " + (mChargingWattage / 1000) + " mW";
474             }
475             if (animate) {
476                 animateText(mTextView, powerIndication);
477             } else {
478                 mTextView.switchIndication(powerIndication);
479             }
480             hideIndication = !mBatteryPresent;
481         } else if (!TextUtils.isEmpty(trustManagedIndication)
482                 && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId)
483                 && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) {
484             mTextView.switchIndication(trustManagedIndication);
485         } else {
486             mTextView.switchIndication(mRestingIndication);
487         }
488         mTextView.setTextColor(isError ? Utils.getColorError(mContext)
489                 : mInitialTextColorState);
490         if (hideIndication) {
491             mIndicationArea.setVisibility(View.GONE);
492         }
493     }
494 
495     // animates textView - textView moves up and bounces down
animateText(KeyguardIndicationTextView textView, String indication)496     private void animateText(KeyguardIndicationTextView textView, String indication) {
497         int yTranslation = mContext.getResources().getInteger(
498                 R.integer.wired_charging_keyguard_text_animation_distance);
499         int animateUpDuration = mContext.getResources().getInteger(
500                 R.integer.wired_charging_keyguard_text_animation_duration_up);
501         int animateDownDuration = mContext.getResources().getInteger(
502                 R.integer.wired_charging_keyguard_text_animation_duration_down);
503         textView.animate().cancel();
504         ViewClippingUtil.setClippingDeactivated(textView, true, mClippingParams);
505         textView.animate()
506                 .translationYBy(yTranslation)
507                 .setInterpolator(Interpolators.LINEAR)
508                 .setDuration(animateUpDuration)
509                 .setListener(new AnimatorListenerAdapter() {
510                     private boolean mCancelled;
511 
512                     @Override
513                     public void onAnimationStart(Animator animation) {
514                         textView.switchIndication(indication);
515                     }
516 
517                     @Override
518                     public void onAnimationCancel(Animator animation) {
519                         textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y);
520                         mCancelled = true;
521                     }
522 
523                     @Override
524                     public void onAnimationEnd(Animator animation) {
525                         if (mCancelled) {
526                             ViewClippingUtil.setClippingDeactivated(textView, false,
527                                     mClippingParams);
528                             return;
529                         }
530                         textView.animate()
531                                 .setDuration(animateDownDuration)
532                                 .setInterpolator(Interpolators.BOUNCE)
533                                 .translationY(BOUNCE_ANIMATION_FINAL_Y)
534                                 .setListener(new AnimatorListenerAdapter() {
535                                     @Override
536                                     public void onAnimationEnd(Animator animation) {
537                                         textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y);
538                                         ViewClippingUtil.setClippingDeactivated(textView, false,
539                                                 mClippingParams);
540                                     }
541                                 });
542                     }
543                 });
544     }
545 
computePowerIndication()546     protected String computePowerIndication() {
547         if (mPowerCharged) {
548             return mContext.getResources().getString(R.string.keyguard_charged);
549         }
550 
551         int chargingId;
552         String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f);
553 
554         if (mBatteryOverheated) {
555             chargingId = R.string.keyguard_plugged_in_charging_limited;
556             return mContext.getResources().getString(chargingId, percentage);
557         }
558 
559         final boolean hasChargingTime = mChargingTimeRemaining > 0;
560         if (mPowerPluggedInWired) {
561             switch (mChargingSpeed) {
562                 case BatteryStatus.CHARGING_FAST:
563                     chargingId = hasChargingTime
564                             ? R.string.keyguard_indication_charging_time_fast
565                             : R.string.keyguard_plugged_in_charging_fast;
566                     break;
567                 case BatteryStatus.CHARGING_SLOWLY:
568                     chargingId = hasChargingTime
569                             ? R.string.keyguard_indication_charging_time_slowly
570                             : R.string.keyguard_plugged_in_charging_slowly;
571                     break;
572                 default:
573                     chargingId = hasChargingTime
574                             ? R.string.keyguard_indication_charging_time
575                             : R.string.keyguard_plugged_in;
576                     break;
577             }
578         } else {
579             chargingId = hasChargingTime
580                     ? R.string.keyguard_indication_charging_time_wireless
581                     : R.string.keyguard_plugged_in_wireless;
582         }
583 
584         if (hasChargingTime) {
585             // We now have battery percentage in these strings and it's expected that all
586             // locales will also have it in the future. For now, we still have to support the old
587             // format until all languages get the new translations.
588             String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes(
589                     mContext, mChargingTimeRemaining);
590             try {
591                 return mContext.getResources().getString(chargingId, chargingTimeFormatted,
592                         percentage);
593             } catch (IllegalFormatConversionException e) {
594                 return mContext.getResources().getString(chargingId, chargingTimeFormatted);
595             }
596         } else {
597             // Same as above
598             try {
599                 return mContext.getResources().getString(chargingId, percentage);
600             } catch (IllegalFormatConversionException e) {
601                 return mContext.getResources().getString(chargingId);
602             }
603         }
604     }
605 
setStatusBarKeyguardViewManager( StatusBarKeyguardViewManager statusBarKeyguardViewManager)606     public void setStatusBarKeyguardViewManager(
607             StatusBarKeyguardViewManager statusBarKeyguardViewManager) {
608         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
609     }
610 
611     private final KeyguardUpdateMonitorCallback mTickReceiver =
612             new KeyguardUpdateMonitorCallback() {
613                 @Override
614                 public void onTimeChanged() {
615                     if (mVisible) {
616                         updateIndication(false /* animate */);
617                     }
618                 }
619             };
620 
621     private final Handler mHandler = new Handler() {
622         @Override
623         public void handleMessage(Message msg) {
624             if (msg.what == MSG_HIDE_TRANSIENT) {
625                 hideTransientIndication();
626             } else if (msg.what == MSG_CLEAR_BIOMETRIC_MSG) {
627                 if (mLockIconController != null) {
628                     mLockIconController.setTransientBiometricsError(false);
629                 }
630             } else if (msg.what == MSG_SWIPE_UP_TO_UNLOCK) {
631                 showSwipeUpToUnlock();
632             }
633         }
634     };
635 
showSwipeUpToUnlock()636     private void showSwipeUpToUnlock() {
637         if (mDozing) {
638             return;
639         }
640 
641         if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
642             String message = mContext.getString(R.string.keyguard_retry);
643             mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState);
644         } else if (mKeyguardUpdateMonitor.isScreenOn()) {
645             showTransientIndication(mContext.getString(R.string.keyguard_unlock),
646                     false /* isError */, true /* hideOnScreenOff */);
647             hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS);
648         }
649     }
650 
setDozing(boolean dozing)651     public void setDozing(boolean dozing) {
652         if (mDozing == dozing) {
653             return;
654         }
655         mDozing = dozing;
656         if (mHideTransientMessageOnScreenOff && mDozing) {
657             hideTransientIndication();
658         } else {
659             updateIndication(false);
660         }
661     }
662 
dump(FileDescriptor fd, PrintWriter pw, String[] args)663     public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
664         pw.println("KeyguardIndicationController:");
665         pw.println("  mTransientTextIsError: " + mTransientTextIsError);
666         pw.println("  mInitialTextColorState: " + mInitialTextColorState);
667         pw.println("  mPowerPluggedInWired: " + mPowerPluggedInWired);
668         pw.println("  mPowerPluggedIn: " + mPowerPluggedIn);
669         pw.println("  mPowerCharged: " + mPowerCharged);
670         pw.println("  mChargingSpeed: " + mChargingSpeed);
671         pw.println("  mChargingWattage: " + mChargingWattage);
672         pw.println("  mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn);
673         pw.println("  mDozing: " + mDozing);
674         pw.println("  mBatteryLevel: " + mBatteryLevel);
675         pw.println("  mBatteryPresent: " + mBatteryPresent);
676         pw.println("  mTextView.getText(): " + (mTextView == null ? null : mTextView.getText()));
677         pw.println("  computePowerIndication(): " + computePowerIndication());
678     }
679 
680     @Override
onStateChanged(int newState)681     public void onStateChanged(int newState) {
682         // don't care
683     }
684 
685     @Override
onDozingChanged(boolean isDozing)686     public void onDozingChanged(boolean isDozing) {
687         setDozing(isDozing);
688     }
689 
690     @Override
onDozeAmountChanged(float linear, float eased)691     public void onDozeAmountChanged(float linear, float eased) {
692         mDisclosure.setAlpha((1 - linear) * mDisclosureMaxAlpha);
693     }
694 
695     @Override
onUnlockedChanged()696     public void onUnlockedChanged() {
697         updateIndication(!mDozing);
698     }
699 
700     protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback {
701         public static final int HIDE_DELAY_MS = 5000;
702 
703         @Override
onRefreshBatteryInfo(BatteryStatus status)704         public void onRefreshBatteryInfo(BatteryStatus status) {
705             boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING
706                     || status.status == BatteryManager.BATTERY_STATUS_FULL;
707             boolean wasPluggedIn = mPowerPluggedIn;
708             mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull;
709             mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull;
710             mPowerCharged = status.isCharged();
711             mChargingWattage = status.maxChargingWattage;
712             mChargingSpeed = status.getChargingSpeed(mContext);
713             mBatteryLevel = status.level;
714             mBatteryOverheated = status.isOverheated();
715             mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn();
716             mBatteryPresent = status.present;
717             try {
718                 mChargingTimeRemaining = mPowerPluggedIn
719                         ? mBatteryInfo.computeChargeTimeRemaining() : -1;
720             } catch (RemoteException e) {
721                 Log.e(TAG, "Error calling IBatteryStats: ", e);
722                 mChargingTimeRemaining = -1;
723             }
724             updateIndication(!wasPluggedIn && mPowerPluggedInWired);
725             if (mDozing) {
726                 if (!wasPluggedIn && mPowerPluggedIn) {
727                     showTransientIndication(computePowerIndication());
728                     hideTransientIndicationDelayed(HIDE_DELAY_MS);
729                 } else if (wasPluggedIn && !mPowerPluggedIn) {
730                     hideTransientIndication();
731                 }
732             }
733         }
734 
735         @Override
onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType)736         public void onBiometricHelp(int msgId, String helpString,
737                 BiometricSourceType biometricSourceType) {
738             // TODO(b/141025588): refactor to reduce repetition of code/comments
739             // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
740             // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
741             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
742             // check of whether non-strong biometric is allowed
743             if (!mKeyguardUpdateMonitor
744                     .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) {
745                 return;
746             }
747             boolean showSwipeToUnlock =
748                     msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED;
749             if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
750                 mStatusBarKeyguardViewManager.showBouncerMessage(helpString,
751                         mInitialTextColorState);
752             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
753                 showTransientIndication(helpString, false /* isError */, showSwipeToUnlock);
754                 if (!showSwipeToUnlock) {
755                     hideTransientIndicationDelayed(TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
756                 }
757             }
758             if (showSwipeToUnlock) {
759                 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SWIPE_UP_TO_UNLOCK),
760                         TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
761             }
762         }
763 
764         @Override
onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType)765         public void onBiometricError(int msgId, String errString,
766                 BiometricSourceType biometricSourceType) {
767             if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) {
768                 return;
769             }
770             animatePadlockError();
771             if (msgId == FaceManager.FACE_ERROR_TIMEOUT) {
772                 // The face timeout message is not very actionable, let's ask the user to
773                 // manually retry.
774                 showSwipeUpToUnlock();
775             } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) {
776                 mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState);
777             } else if (mKeyguardUpdateMonitor.isScreenOn()) {
778                 showTransientIndication(errString);
779                 // We want to keep this message around in case the screen was off
780                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
781             } else {
782                 mMessageToShowOnScreenOn = errString;
783             }
784         }
785 
animatePadlockError()786         private void animatePadlockError() {
787             if (mLockIconController != null) {
788                 mLockIconController.setTransientBiometricsError(true);
789             }
790             mHandler.removeMessages(MSG_CLEAR_BIOMETRIC_MSG);
791             mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_BIOMETRIC_MSG),
792                     TRANSIENT_BIOMETRIC_ERROR_TIMEOUT);
793         }
794 
shouldSuppressBiometricError(int msgId, BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor)795         private boolean shouldSuppressBiometricError(int msgId,
796                 BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) {
797             if (biometricSourceType == BiometricSourceType.FINGERPRINT)
798                 return shouldSuppressFingerprintError(msgId, updateMonitor);
799             if (biometricSourceType == BiometricSourceType.FACE)
800                 return shouldSuppressFaceError(msgId, updateMonitor);
801             return false;
802         }
803 
shouldSuppressFingerprintError(int msgId, KeyguardUpdateMonitor updateMonitor)804         private boolean shouldSuppressFingerprintError(int msgId,
805                 KeyguardUpdateMonitor updateMonitor) {
806             // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
807             // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
808             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
809             // check of whether non-strong biometric is allowed
810             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
811                     && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT)
812                     || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED);
813         }
814 
shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor)815         private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) {
816             // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong
817             // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to
818             // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the
819             // check of whether non-strong biometric is allowed
820             return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)
821                     && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT)
822                     || msgId == FaceManager.FACE_ERROR_CANCELED);
823         }
824 
825         @Override
onTrustAgentErrorMessage(CharSequence message)826         public void onTrustAgentErrorMessage(CharSequence message) {
827             showTransientIndication(message, true /* isError */, false /* hideOnScreenOff */);
828         }
829 
830         @Override
onScreenTurnedOn()831         public void onScreenTurnedOn() {
832             if (mMessageToShowOnScreenOn != null) {
833                 showTransientIndication(mMessageToShowOnScreenOn, true /* isError */,
834                         false /* hideOnScreenOff */);
835                 // We want to keep this message around in case the screen was off
836                 hideTransientIndicationDelayed(HIDE_DELAY_MS);
837                 mMessageToShowOnScreenOn = null;
838             }
839         }
840 
841         @Override
onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType)842         public void onBiometricRunningStateChanged(boolean running,
843                 BiometricSourceType biometricSourceType) {
844             if (running) {
845                 // Let's hide any previous messages when authentication starts, otherwise
846                 // multiple auth attempts would overlap.
847                 hideTransientIndication();
848                 mMessageToShowOnScreenOn = null;
849             }
850         }
851 
852         @Override
onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric)853         public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType,
854                 boolean isStrongBiometric) {
855             super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric);
856             mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT);
857         }
858 
859         @Override
onUserSwitchComplete(int userId)860         public void onUserSwitchComplete(int userId) {
861             if (mVisible) {
862                 updateIndication(false);
863             }
864         }
865 
866         @Override
onUserUnlocked()867         public void onUserUnlocked() {
868             if (mVisible) {
869                 updateIndication(false);
870             }
871         }
872     }
873 }
874