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