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