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