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