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 android.app.admin.DevicePolicyManager.DEVICE_OWNER_TYPE_FINANCED; 20 import static android.view.View.GONE; 21 import static android.view.View.VISIBLE; 22 23 import static com.android.systemui.DejankUtils.whitelistIpcs; 24 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_ALIGNMENT; 25 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_BATTERY; 26 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_DISCLOSURE; 27 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_LOGOUT; 28 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_OWNER_INFO; 29 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_RESTING; 30 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_TRUST; 31 import static com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController.INDICATION_TYPE_USER_LOCKED; 32 import static com.android.systemui.plugins.FalsingManager.LOW_PENALTY; 33 34 import android.animation.Animator; 35 import android.animation.AnimatorListenerAdapter; 36 import android.app.IActivityManager; 37 import android.app.admin.DevicePolicyManager; 38 import android.content.BroadcastReceiver; 39 import android.content.Context; 40 import android.content.Intent; 41 import android.content.IntentFilter; 42 import android.content.pm.UserInfo; 43 import android.content.res.ColorStateList; 44 import android.content.res.Resources; 45 import android.graphics.Color; 46 import android.hardware.biometrics.BiometricSourceType; 47 import android.hardware.face.FaceManager; 48 import android.hardware.fingerprint.FingerprintManager; 49 import android.os.BatteryManager; 50 import android.os.Handler; 51 import android.os.Message; 52 import android.os.RemoteException; 53 import android.os.UserHandle; 54 import android.os.UserManager; 55 import android.text.TextUtils; 56 import android.text.format.Formatter; 57 import android.util.Log; 58 import android.view.View; 59 import android.view.ViewGroup; 60 61 import androidx.annotation.Nullable; 62 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.app.IBatteryStats; 65 import com.android.internal.widget.LockPatternUtils; 66 import com.android.internal.widget.ViewClippingUtil; 67 import com.android.keyguard.KeyguardUpdateMonitor; 68 import com.android.keyguard.KeyguardUpdateMonitorCallback; 69 import com.android.settingslib.fuelgauge.BatteryStatus; 70 import com.android.systemui.R; 71 import com.android.systemui.animation.Interpolators; 72 import com.android.systemui.broadcast.BroadcastDispatcher; 73 import com.android.systemui.dagger.SysUISingleton; 74 import com.android.systemui.dagger.qualifiers.Main; 75 import com.android.systemui.dock.DockManager; 76 import com.android.systemui.keyguard.KeyguardIndication; 77 import com.android.systemui.keyguard.KeyguardIndicationRotateTextViewController; 78 import com.android.systemui.plugins.FalsingManager; 79 import com.android.systemui.plugins.statusbar.StatusBarStateController; 80 import com.android.systemui.statusbar.phone.KeyguardBypassController; 81 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; 82 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 83 import com.android.systemui.statusbar.policy.KeyguardStateController; 84 import com.android.systemui.util.concurrency.DelayableExecutor; 85 import com.android.systemui.util.wakelock.SettableWakeLock; 86 import com.android.systemui.util.wakelock.WakeLock; 87 88 import java.io.FileDescriptor; 89 import java.io.PrintWriter; 90 import java.text.NumberFormat; 91 92 import javax.inject.Inject; 93 94 /** 95 * Controls the indications and error messages shown on the Keyguard 96 */ 97 @SysUISingleton 98 public class KeyguardIndicationController { 99 100 private static final String TAG = "KeyguardIndication"; 101 private static final boolean DEBUG_CHARGING_SPEED = false; 102 103 private static final int MSG_HIDE_TRANSIENT = 1; 104 private static final int MSG_SHOW_ACTION_TO_UNLOCK = 2; 105 private static final long TRANSIENT_BIOMETRIC_ERROR_TIMEOUT = 1300; 106 private static final float BOUNCE_ANIMATION_FINAL_Y = 0f; 107 108 private final Context mContext; 109 private final BroadcastDispatcher mBroadcastDispatcher; 110 private final KeyguardStateController mKeyguardStateController; 111 private final StatusBarStateController mStatusBarStateController; 112 private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; 113 private ViewGroup mIndicationArea; 114 private KeyguardIndicationTextView mTopIndicationView; 115 private KeyguardIndicationTextView mLockScreenIndicationView; 116 private final IBatteryStats mBatteryInfo; 117 private final SettableWakeLock mWakeLock; 118 private final DockManager mDockManager; 119 private final DevicePolicyManager mDevicePolicyManager; 120 private final UserManager mUserManager; 121 private final @Main DelayableExecutor mExecutor; 122 private final LockPatternUtils mLockPatternUtils; 123 private final IActivityManager mIActivityManager; 124 private final FalsingManager mFalsingManager; 125 private final KeyguardBypassController mKeyguardBypassController; 126 127 protected KeyguardIndicationRotateTextViewController mRotateTextViewController; 128 private BroadcastReceiver mBroadcastReceiver; 129 private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 130 131 private String mRestingIndication; 132 private String mAlignmentIndication; 133 private CharSequence mTransientIndication; 134 protected ColorStateList mInitialTextColorState; 135 private boolean mVisible; 136 private boolean mHideTransientMessageOnScreenOff; 137 138 private boolean mPowerPluggedIn; 139 private boolean mPowerPluggedInWired; 140 private boolean mPowerCharged; 141 private boolean mBatteryOverheated; 142 private boolean mEnableBatteryDefender; 143 private int mChargingSpeed; 144 private int mChargingWattage; 145 private int mBatteryLevel; 146 private boolean mBatteryPresent = true; 147 private long mChargingTimeRemaining; 148 private String mMessageToShowOnScreenOn; 149 protected int mLockScreenMode; 150 private boolean mInited; 151 152 private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; 153 154 private boolean mDozing; 155 private final ViewClippingUtil.ClippingParameters mClippingParams = 156 new ViewClippingUtil.ClippingParameters() { 157 @Override 158 public boolean shouldFinish(View view) { 159 return view == mIndicationArea; 160 } 161 }; 162 163 /** 164 * Creates a new KeyguardIndicationController and registers callbacks. 165 */ 166 @Inject KeyguardIndicationController(Context context, WakeLock.Builder wakeLockBuilder, KeyguardStateController keyguardStateController, StatusBarStateController statusBarStateController, KeyguardUpdateMonitor keyguardUpdateMonitor, DockManager dockManager, BroadcastDispatcher broadcastDispatcher, DevicePolicyManager devicePolicyManager, IBatteryStats iBatteryStats, UserManager userManager, @Main DelayableExecutor executor, FalsingManager falsingManager, LockPatternUtils lockPatternUtils, IActivityManager iActivityManager, KeyguardBypassController keyguardBypassController)167 public KeyguardIndicationController(Context context, 168 WakeLock.Builder wakeLockBuilder, 169 KeyguardStateController keyguardStateController, 170 StatusBarStateController statusBarStateController, 171 KeyguardUpdateMonitor keyguardUpdateMonitor, 172 DockManager dockManager, 173 BroadcastDispatcher broadcastDispatcher, 174 DevicePolicyManager devicePolicyManager, 175 IBatteryStats iBatteryStats, 176 UserManager userManager, 177 @Main DelayableExecutor executor, 178 FalsingManager falsingManager, 179 LockPatternUtils lockPatternUtils, 180 IActivityManager iActivityManager, 181 KeyguardBypassController keyguardBypassController) { 182 mContext = context; 183 mBroadcastDispatcher = broadcastDispatcher; 184 mDevicePolicyManager = devicePolicyManager; 185 mKeyguardStateController = keyguardStateController; 186 mStatusBarStateController = statusBarStateController; 187 mKeyguardUpdateMonitor = keyguardUpdateMonitor; 188 mDockManager = dockManager; 189 mWakeLock = new SettableWakeLock( 190 wakeLockBuilder.setTag("Doze:KeyguardIndication").build(), TAG); 191 mBatteryInfo = iBatteryStats; 192 mUserManager = userManager; 193 mExecutor = executor; 194 mLockPatternUtils = lockPatternUtils; 195 mIActivityManager = iActivityManager; 196 mFalsingManager = falsingManager; 197 mKeyguardBypassController = keyguardBypassController; 198 199 } 200 201 /** Call this after construction to finish setting up the instance. */ init()202 public void init() { 203 if (mInited) { 204 return; 205 } 206 mInited = true; 207 208 mDockManager.addAlignmentStateListener( 209 alignState -> mHandler.post(() -> handleAlignStateChanged(alignState))); 210 mKeyguardUpdateMonitor.registerCallback(getKeyguardCallback()); 211 mKeyguardUpdateMonitor.registerCallback(mTickReceiver); 212 mStatusBarStateController.addCallback(mStatusBarStateListener); 213 mKeyguardStateController.addCallback(mKeyguardStateCallback); 214 215 mStatusBarStateListener.onDozingChanged(mStatusBarStateController.isDozing()); 216 } 217 setIndicationArea(ViewGroup indicationArea)218 public void setIndicationArea(ViewGroup indicationArea) { 219 mIndicationArea = indicationArea; 220 mTopIndicationView = indicationArea.findViewById(R.id.keyguard_indication_text); 221 mLockScreenIndicationView = indicationArea.findViewById( 222 R.id.keyguard_indication_text_bottom); 223 mInitialTextColorState = mTopIndicationView != null 224 ? mTopIndicationView.getTextColors() : ColorStateList.valueOf(Color.WHITE); 225 mRotateTextViewController = new KeyguardIndicationRotateTextViewController( 226 mLockScreenIndicationView, 227 mExecutor, 228 mStatusBarStateController); 229 updateIndication(false /* animate */); 230 updateDisclosure(); 231 if (mBroadcastReceiver == null) { 232 // Update the disclosure proactively to avoid IPC on the critical path. 233 mBroadcastReceiver = new BroadcastReceiver() { 234 @Override 235 public void onReceive(Context context, Intent intent) { 236 updateDisclosure(); 237 } 238 }; 239 IntentFilter intentFilter = new IntentFilter(); 240 intentFilter.addAction(DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED); 241 intentFilter.addAction(Intent.ACTION_USER_REMOVED); 242 mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, intentFilter); 243 } 244 } 245 handleAlignStateChanged(int alignState)246 private void handleAlignStateChanged(int alignState) { 247 String alignmentIndication = ""; 248 if (alignState == DockManager.ALIGN_STATE_POOR) { 249 alignmentIndication = 250 mContext.getResources().getString(R.string.dock_alignment_slow_charging); 251 } else if (alignState == DockManager.ALIGN_STATE_TERRIBLE) { 252 alignmentIndication = 253 mContext.getResources().getString(R.string.dock_alignment_not_charging); 254 } 255 if (!alignmentIndication.equals(mAlignmentIndication)) { 256 mAlignmentIndication = alignmentIndication; 257 updateIndication(false); 258 } 259 } 260 261 /** 262 * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this 263 * {@link KeyguardIndicationController}. 264 * 265 * <p>Subclasses may override this method to extend or change the callback behavior by extending 266 * the {@link BaseKeyguardCallback}. 267 * 268 * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the 269 * same instance. 270 */ getKeyguardCallback()271 protected KeyguardUpdateMonitorCallback getKeyguardCallback() { 272 if (mUpdateMonitorCallback == null) { 273 mUpdateMonitorCallback = new BaseKeyguardCallback(); 274 } 275 return mUpdateMonitorCallback; 276 } 277 278 /** 279 * Doesn't include disclosure which gets triggered separately. 280 */ updateIndications(boolean animate, int userId)281 private void updateIndications(boolean animate, int userId) { 282 updateOwnerInfo(); 283 updateBattery(animate); 284 updateUserLocked(userId); 285 updateTransient(); 286 updateTrust(userId, getTrustGrantedIndication(), getTrustManagedIndication()); 287 updateAlignment(); 288 updateLogoutView(); 289 updateResting(); 290 } 291 updateDisclosure()292 private void updateDisclosure() { 293 // avoid calling this method since it has an IPC 294 if (whitelistIpcs(this::isOrganizationOwnedDevice)) { 295 final CharSequence organizationName = getOrganizationOwnedDeviceOrganizationName(); 296 final CharSequence disclosure = getDisclosureText(organizationName); 297 mRotateTextViewController.updateIndication( 298 INDICATION_TYPE_DISCLOSURE, 299 new KeyguardIndication.Builder() 300 .setMessage(disclosure) 301 .setTextColor(mInitialTextColorState) 302 .build(), 303 /* updateImmediately */ false); 304 } else { 305 mRotateTextViewController.hideIndication(INDICATION_TYPE_DISCLOSURE); 306 } 307 308 updateResting(); 309 } 310 getDisclosureText(@ullable CharSequence organizationName)311 private CharSequence getDisclosureText(@Nullable CharSequence organizationName) { 312 final Resources packageResources = mContext.getResources(); 313 if (organizationName == null) { 314 return packageResources.getText(R.string.do_disclosure_generic); 315 } else if (mDevicePolicyManager.isDeviceManaged() 316 && mDevicePolicyManager.getDeviceOwnerType( 317 mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()) 318 == DEVICE_OWNER_TYPE_FINANCED) { 319 return packageResources.getString(R.string.do_financed_disclosure_with_name, 320 organizationName); 321 } else { 322 return packageResources.getString(R.string.do_disclosure_with_name, 323 organizationName); 324 } 325 } 326 updateOwnerInfo()327 private void updateOwnerInfo() { 328 String info = mLockPatternUtils.getDeviceOwnerInfo(); 329 if (info == null) { 330 // Use the current user owner information if enabled. 331 final boolean ownerInfoEnabled = mLockPatternUtils.isOwnerInfoEnabled( 332 KeyguardUpdateMonitor.getCurrentUser()); 333 if (ownerInfoEnabled) { 334 info = mLockPatternUtils.getOwnerInfo(KeyguardUpdateMonitor.getCurrentUser()); 335 } 336 } 337 if (info != null) { 338 mRotateTextViewController.updateIndication( 339 INDICATION_TYPE_OWNER_INFO, 340 new KeyguardIndication.Builder() 341 .setMessage(info) 342 .setTextColor(mInitialTextColorState) 343 .build(), 344 false); 345 } else { 346 mRotateTextViewController.hideIndication(INDICATION_TYPE_OWNER_INFO); 347 } 348 } 349 updateBattery(boolean animate)350 private void updateBattery(boolean animate) { 351 if (mPowerPluggedIn || mEnableBatteryDefender) { 352 String powerIndication = computePowerIndication(); 353 if (DEBUG_CHARGING_SPEED) { 354 powerIndication += ", " + (mChargingWattage / 1000) + " mW"; 355 } 356 357 mRotateTextViewController.updateIndication( 358 INDICATION_TYPE_BATTERY, 359 new KeyguardIndication.Builder() 360 .setMessage(powerIndication) 361 .setTextColor(mInitialTextColorState) 362 .build(), 363 animate); 364 } else { 365 // don't show the charging information if device isn't plugged in 366 mRotateTextViewController.hideIndication(INDICATION_TYPE_BATTERY); 367 } 368 } 369 updateUserLocked(int userId)370 private void updateUserLocked(int userId) { 371 if (!mKeyguardUpdateMonitor.isUserUnlocked(userId)) { 372 mRotateTextViewController.updateIndication( 373 INDICATION_TYPE_USER_LOCKED, 374 new KeyguardIndication.Builder() 375 .setMessage(mContext.getResources().getText( 376 com.android.internal.R.string.lockscreen_storage_locked)) 377 .setTextColor(mInitialTextColorState) 378 .build(), 379 false); 380 } else { 381 mRotateTextViewController.hideIndication(INDICATION_TYPE_USER_LOCKED); 382 } 383 } 384 updateTransient()385 private void updateTransient() { 386 if (!TextUtils.isEmpty(mTransientIndication)) { 387 mRotateTextViewController.showTransient(mTransientIndication); 388 } else { 389 mRotateTextViewController.hideTransient(); 390 } 391 } 392 updateTrust(int userId, CharSequence trustGrantedIndication, CharSequence trustManagedIndication)393 private void updateTrust(int userId, CharSequence trustGrantedIndication, 394 CharSequence trustManagedIndication) { 395 if (!TextUtils.isEmpty(trustGrantedIndication) 396 && mKeyguardUpdateMonitor.getUserHasTrust(userId)) { 397 mRotateTextViewController.updateIndication( 398 INDICATION_TYPE_TRUST, 399 new KeyguardIndication.Builder() 400 .setMessage(trustGrantedIndication) 401 .setTextColor(mInitialTextColorState) 402 .build(), 403 false); 404 } else if (!TextUtils.isEmpty(trustManagedIndication) 405 && mKeyguardUpdateMonitor.getUserTrustIsManaged(userId) 406 && !mKeyguardUpdateMonitor.getUserHasTrust(userId)) { 407 mRotateTextViewController.updateIndication( 408 INDICATION_TYPE_TRUST, 409 new KeyguardIndication.Builder() 410 .setMessage(trustManagedIndication) 411 .setTextColor(mInitialTextColorState) 412 .build(), 413 false); 414 } else { 415 mRotateTextViewController.hideIndication(INDICATION_TYPE_TRUST); 416 } 417 } 418 updateAlignment()419 private void updateAlignment() { 420 if (!TextUtils.isEmpty(mAlignmentIndication)) { 421 mRotateTextViewController.updateIndication( 422 INDICATION_TYPE_ALIGNMENT, 423 new KeyguardIndication.Builder() 424 .setMessage(mAlignmentIndication) 425 .setTextColor(ColorStateList.valueOf( 426 mContext.getColor(R.color.misalignment_text_color))) 427 .build(), 428 true); 429 } else { 430 mRotateTextViewController.hideIndication(INDICATION_TYPE_ALIGNMENT); 431 } 432 } 433 updateResting()434 private void updateResting() { 435 if (mRestingIndication != null 436 && !mRotateTextViewController.hasIndications()) { 437 mRotateTextViewController.updateIndication( 438 INDICATION_TYPE_RESTING, 439 new KeyguardIndication.Builder() 440 .setMessage(mRestingIndication) 441 .setTextColor(mInitialTextColorState) 442 .build(), 443 false); 444 } else { 445 mRotateTextViewController.hideIndication(INDICATION_TYPE_RESTING); 446 } 447 } 448 updateLogoutView()449 private void updateLogoutView() { 450 final boolean shouldShowLogout = mKeyguardUpdateMonitor.isLogoutEnabled() 451 && KeyguardUpdateMonitor.getCurrentUser() != UserHandle.USER_SYSTEM; 452 if (shouldShowLogout) { 453 mRotateTextViewController.updateIndication( 454 INDICATION_TYPE_LOGOUT, 455 new KeyguardIndication.Builder() 456 .setMessage(mContext.getResources().getString( 457 com.android.internal.R.string.global_action_logout)) 458 .setTextColor(mInitialTextColorState) 459 .setBackground(mContext.getDrawable( 460 com.android.systemui.R.drawable.logout_button_background)) 461 .setClickListener((view) -> { 462 if (mFalsingManager.isFalseTap(LOW_PENALTY)) { 463 return; 464 } 465 int currentUserId = KeyguardUpdateMonitor.getCurrentUser(); 466 try { 467 mIActivityManager.switchUser(UserHandle.USER_SYSTEM); 468 mIActivityManager.stopUser(currentUserId, true /* force */, 469 null); 470 } catch (RemoteException re) { 471 Log.e(TAG, "Failed to logout user", re); 472 } 473 }) 474 .build(), 475 false); 476 } else { 477 mRotateTextViewController.hideIndication(INDICATION_TYPE_LOGOUT); 478 } 479 } 480 isOrganizationOwnedDevice()481 private boolean isOrganizationOwnedDevice() { 482 return mDevicePolicyManager.isDeviceManaged() 483 || mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile(); 484 } 485 486 @Nullable getOrganizationOwnedDeviceOrganizationName()487 private CharSequence getOrganizationOwnedDeviceOrganizationName() { 488 if (mDevicePolicyManager.isDeviceManaged()) { 489 return mDevicePolicyManager.getDeviceOwnerOrganizationName(); 490 } else if (mDevicePolicyManager.isOrganizationOwnedDeviceWithManagedProfile()) { 491 return getWorkProfileOrganizationName(); 492 } 493 return null; 494 } 495 getWorkProfileOrganizationName()496 private CharSequence getWorkProfileOrganizationName() { 497 final int profileId = getWorkProfileUserId(UserHandle.myUserId()); 498 if (profileId == UserHandle.USER_NULL) { 499 return null; 500 } 501 return mDevicePolicyManager.getOrganizationNameForUser(profileId); 502 } 503 getWorkProfileUserId(int userId)504 private int getWorkProfileUserId(int userId) { 505 for (final UserInfo userInfo : mUserManager.getProfiles(userId)) { 506 if (userInfo.isManagedProfile()) { 507 return userInfo.id; 508 } 509 } 510 return UserHandle.USER_NULL; 511 } 512 513 /** 514 * Sets the visibility of keyguard bottom area, and if the indications are updatable. 515 * 516 * @param visible true to make the area visible and update the indication, false otherwise. 517 */ setVisible(boolean visible)518 public void setVisible(boolean visible) { 519 mVisible = visible; 520 mIndicationArea.setVisibility(visible ? VISIBLE : GONE); 521 if (visible) { 522 // If this is called after an error message was already shown, we should not clear it. 523 // Otherwise the error message won't be shown 524 if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) { 525 hideTransientIndication(); 526 } 527 updateIndication(false); 528 } else if (!visible) { 529 // If we unlock and return to keyguard quickly, previous error should not be shown 530 hideTransientIndication(); 531 } 532 } 533 534 /** 535 * Sets the indication that is shown if nothing else is showing. 536 */ setRestingIndication(String restingIndication)537 public void setRestingIndication(String restingIndication) { 538 mRestingIndication = restingIndication; 539 updateIndication(false); 540 } 541 542 /** 543 * Returns the indication text indicating that trust has been granted. 544 * 545 * @return {@code null} or an empty string if a trust indication text should not be shown. 546 */ 547 @VisibleForTesting getTrustGrantedIndication()548 String getTrustGrantedIndication() { 549 return mContext.getString(R.string.keyguard_indication_trust_unlocked); 550 } 551 552 /** 553 * Sets if the device is plugged in 554 */ 555 @VisibleForTesting setPowerPluggedIn(boolean plugged)556 void setPowerPluggedIn(boolean plugged) { 557 mPowerPluggedIn = plugged; 558 } 559 560 /** 561 * Returns the indication text indicating that trust is currently being managed. 562 * 563 * @return {@code null} or an empty string if a trust managed text should not be shown. 564 */ getTrustManagedIndication()565 private String getTrustManagedIndication() { 566 return null; 567 } 568 569 /** 570 * Hides transient indication in {@param delayMs}. 571 */ hideTransientIndicationDelayed(long delayMs)572 public void hideTransientIndicationDelayed(long delayMs) { 573 mHandler.sendMessageDelayed( 574 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs); 575 } 576 577 /** 578 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 579 */ showTransientIndication(int transientIndication)580 public void showTransientIndication(int transientIndication) { 581 showTransientIndication(mContext.getResources().getString(transientIndication)); 582 } 583 584 /** 585 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 586 */ showTransientIndication(CharSequence transientIndication)587 public void showTransientIndication(CharSequence transientIndication) { 588 showTransientIndication(transientIndication, false /* isError */, 589 false /* hideOnScreenOff */); 590 } 591 592 /** 593 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 594 */ showTransientIndication(CharSequence transientIndication, boolean isError, boolean hideOnScreenOff)595 private void showTransientIndication(CharSequence transientIndication, 596 boolean isError, boolean hideOnScreenOff) { 597 mTransientIndication = transientIndication; 598 mHideTransientMessageOnScreenOff = hideOnScreenOff && transientIndication != null; 599 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 600 mHandler.removeMessages(MSG_SHOW_ACTION_TO_UNLOCK); 601 if (mDozing && !TextUtils.isEmpty(mTransientIndication)) { 602 // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared. 603 mWakeLock.setAcquired(true); 604 } 605 hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); 606 607 updateIndication(false); 608 } 609 610 /** 611 * Hides transient indication. 612 */ hideTransientIndication()613 public void hideTransientIndication() { 614 if (mTransientIndication != null) { 615 mTransientIndication = null; 616 mHideTransientMessageOnScreenOff = false; 617 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 618 mRotateTextViewController.hideTransient(); 619 updateIndication(false); 620 } 621 } 622 updateIndication(boolean animate)623 protected final void updateIndication(boolean animate) { 624 if (TextUtils.isEmpty(mTransientIndication)) { 625 mWakeLock.setAcquired(false); 626 } 627 628 if (!mVisible) { 629 return; 630 } 631 632 // A few places might need to hide the indication, so always start by making it visible 633 mIndicationArea.setVisibility(VISIBLE); 634 635 // Walk down a precedence-ordered list of what indication 636 // should be shown based on user or device state 637 // AoD 638 if (mDozing) { 639 mLockScreenIndicationView.setVisibility(View.GONE); 640 mTopIndicationView.setVisibility(VISIBLE); 641 // When dozing we ignore any text color and use white instead, because 642 // colors can be hard to read in low brightness. 643 mTopIndicationView.setTextColor(Color.WHITE); 644 if (!TextUtils.isEmpty(mTransientIndication)) { 645 mTopIndicationView.switchIndication(mTransientIndication, null); 646 } else if (!mBatteryPresent) { 647 // If there is no battery detected, hide the indication and bail 648 mIndicationArea.setVisibility(GONE); 649 } else if (!TextUtils.isEmpty(mAlignmentIndication)) { 650 mTopIndicationView.switchIndication(mAlignmentIndication, null); 651 mTopIndicationView.setTextColor(mContext.getColor(R.color.misalignment_text_color)); 652 } else if (mPowerPluggedIn || mEnableBatteryDefender) { 653 String indication = computePowerIndication(); 654 if (animate) { 655 animateText(mTopIndicationView, indication); 656 } else { 657 mTopIndicationView.switchIndication(indication, null); 658 } 659 } else { 660 String percentage = NumberFormat.getPercentInstance() 661 .format(mBatteryLevel / 100f); 662 mTopIndicationView.switchIndication(percentage, null); 663 } 664 return; 665 } 666 667 // LOCK SCREEN 668 mTopIndicationView.setVisibility(GONE); 669 mTopIndicationView.setText(null); 670 mLockScreenIndicationView.setVisibility(View.VISIBLE); 671 updateIndications(animate, KeyguardUpdateMonitor.getCurrentUser()); 672 } 673 674 // animates textView - textView moves up and bounces down animateText(KeyguardIndicationTextView textView, String indication)675 private void animateText(KeyguardIndicationTextView textView, String indication) { 676 int yTranslation = mContext.getResources().getInteger( 677 R.integer.wired_charging_keyguard_text_animation_distance); 678 int animateUpDuration = mContext.getResources().getInteger( 679 R.integer.wired_charging_keyguard_text_animation_duration_up); 680 int animateDownDuration = mContext.getResources().getInteger( 681 R.integer.wired_charging_keyguard_text_animation_duration_down); 682 textView.animate().cancel(); 683 ViewClippingUtil.setClippingDeactivated(textView, true, mClippingParams); 684 textView.animate() 685 .translationYBy(yTranslation) 686 .setInterpolator(Interpolators.LINEAR) 687 .setDuration(animateUpDuration) 688 .setListener(new AnimatorListenerAdapter() { 689 private boolean mCancelled; 690 691 @Override 692 public void onAnimationStart(Animator animation) { 693 textView.switchIndication(indication, null); 694 } 695 696 @Override 697 public void onAnimationCancel(Animator animation) { 698 textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); 699 mCancelled = true; 700 } 701 702 @Override 703 public void onAnimationEnd(Animator animation) { 704 if (mCancelled) { 705 ViewClippingUtil.setClippingDeactivated(textView, false, 706 mClippingParams); 707 return; 708 } 709 textView.animate() 710 .setDuration(animateDownDuration) 711 .setInterpolator(Interpolators.BOUNCE) 712 .translationY(BOUNCE_ANIMATION_FINAL_Y) 713 .setListener(new AnimatorListenerAdapter() { 714 @Override 715 public void onAnimationEnd(Animator animation) { 716 textView.setTranslationY(BOUNCE_ANIMATION_FINAL_Y); 717 ViewClippingUtil.setClippingDeactivated(textView, false, 718 mClippingParams); 719 } 720 }); 721 } 722 }); 723 } 724 computePowerIndication()725 protected String computePowerIndication() { 726 if (mPowerCharged) { 727 return mContext.getResources().getString(R.string.keyguard_charged); 728 } 729 730 int chargingId; 731 String percentage = NumberFormat.getPercentInstance().format(mBatteryLevel / 100f); 732 if (mBatteryOverheated) { 733 chargingId = R.string.keyguard_plugged_in_charging_limited; 734 return mContext.getResources().getString(chargingId, percentage); 735 } 736 737 final boolean hasChargingTime = mChargingTimeRemaining > 0; 738 if (mPowerPluggedInWired) { 739 switch (mChargingSpeed) { 740 case BatteryStatus.CHARGING_FAST: 741 chargingId = hasChargingTime 742 ? R.string.keyguard_indication_charging_time_fast 743 : R.string.keyguard_plugged_in_charging_fast; 744 break; 745 case BatteryStatus.CHARGING_SLOWLY: 746 chargingId = hasChargingTime 747 ? R.string.keyguard_indication_charging_time_slowly 748 : R.string.keyguard_plugged_in_charging_slowly; 749 break; 750 default: 751 chargingId = hasChargingTime 752 ? R.string.keyguard_indication_charging_time 753 : R.string.keyguard_plugged_in; 754 break; 755 } 756 } else { 757 chargingId = hasChargingTime 758 ? R.string.keyguard_indication_charging_time_wireless 759 : R.string.keyguard_plugged_in_wireless; 760 } 761 762 if (hasChargingTime) { 763 String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes( 764 mContext, mChargingTimeRemaining); 765 return mContext.getResources().getString(chargingId, chargingTimeFormatted, 766 percentage); 767 } else { 768 return mContext.getResources().getString(chargingId, percentage); 769 } 770 } 771 setStatusBarKeyguardViewManager( StatusBarKeyguardViewManager statusBarKeyguardViewManager)772 public void setStatusBarKeyguardViewManager( 773 StatusBarKeyguardViewManager statusBarKeyguardViewManager) { 774 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 775 } 776 777 private final KeyguardUpdateMonitorCallback mTickReceiver = 778 new KeyguardUpdateMonitorCallback() { 779 @Override 780 public void onTimeChanged() { 781 if (mVisible) { 782 updateIndication(false /* animate */); 783 } 784 } 785 }; 786 787 private final Handler mHandler = new Handler() { 788 @Override 789 public void handleMessage(Message msg) { 790 if (msg.what == MSG_HIDE_TRANSIENT) { 791 hideTransientIndication(); 792 } else if (msg.what == MSG_SHOW_ACTION_TO_UNLOCK) { 793 showActionToUnlock(); 794 } 795 } 796 }; 797 798 /** 799 * Show message on the keyguard for how the user can unlock/enter their device. 800 */ showActionToUnlock()801 public void showActionToUnlock() { 802 if (mDozing) { 803 return; 804 } 805 806 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 807 if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { 808 return; // udfps affordance is highlighted, no need to show action to unlock 809 } else if (mKeyguardUpdateMonitor.isFaceEnrolled()) { 810 String message = mContext.getString(R.string.keyguard_retry); 811 mStatusBarKeyguardViewManager.showBouncerMessage(message, mInitialTextColorState); 812 } 813 } else if (mKeyguardUpdateMonitor.isScreenOn()) { 814 if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { 815 showTransientIndication(mContext.getString(R.string.keyguard_unlock_press), 816 false /* isError */, true /* hideOnScreenOff */); 817 } else { 818 showTransientIndication(mContext.getString(R.string.keyguard_unlock), 819 false /* isError */, true /* hideOnScreenOff */); 820 } 821 } 822 } 823 showTryFingerprintMsg()824 private void showTryFingerprintMsg() { 825 if (mKeyguardUpdateMonitor.isUdfpsAvailable()) { 826 // if udfps available, there will always be a tappable affordance to unlock 827 // For example, the lock icon 828 if (mKeyguardBypassController.getUserHasDeviceEntryIntent()) { 829 showTransientIndication(R.string.keyguard_unlock_press); 830 } else { 831 showTransientIndication(R.string.keyguard_face_failed_use_fp); 832 } 833 } else { 834 showTransientIndication(R.string.keyguard_try_fingerprint); 835 } 836 } 837 dump(FileDescriptor fd, PrintWriter pw, String[] args)838 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 839 pw.println("KeyguardIndicationController:"); 840 pw.println(" mInitialTextColorState: " + mInitialTextColorState); 841 pw.println(" mPowerPluggedInWired: " + mPowerPluggedInWired); 842 pw.println(" mPowerPluggedIn: " + mPowerPluggedIn); 843 pw.println(" mPowerCharged: " + mPowerCharged); 844 pw.println(" mChargingSpeed: " + mChargingSpeed); 845 pw.println(" mChargingWattage: " + mChargingWattage); 846 pw.println(" mMessageToShowOnScreenOn: " + mMessageToShowOnScreenOn); 847 pw.println(" mDozing: " + mDozing); 848 pw.println(" mBatteryLevel: " + mBatteryLevel); 849 pw.println(" mBatteryPresent: " + mBatteryPresent); 850 pw.println(" mTextView.getText(): " + ( 851 mTopIndicationView == null ? null : mTopIndicationView.getText())); 852 pw.println(" computePowerIndication(): " + computePowerIndication()); 853 mRotateTextViewController.dump(fd, pw, args); 854 } 855 856 protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { 857 public static final int HIDE_DELAY_MS = 5000; 858 859 @Override onLockScreenModeChanged(int mode)860 public void onLockScreenModeChanged(int mode) { 861 mLockScreenMode = mode; 862 } 863 864 @Override onRefreshBatteryInfo(BatteryStatus status)865 public void onRefreshBatteryInfo(BatteryStatus status) { 866 boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING 867 || status.status == BatteryManager.BATTERY_STATUS_FULL; 868 boolean wasPluggedIn = mPowerPluggedIn; 869 mPowerPluggedInWired = status.isPluggedInWired() && isChargingOrFull; 870 mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; 871 mPowerCharged = status.isCharged(); 872 mChargingWattage = status.maxChargingWattage; 873 mChargingSpeed = status.getChargingSpeed(mContext); 874 mBatteryLevel = status.level; 875 mBatteryPresent = status.present; 876 mBatteryOverheated = status.isOverheated(); 877 mEnableBatteryDefender = mBatteryOverheated && status.isPluggedIn(); 878 try { 879 mChargingTimeRemaining = mPowerPluggedIn 880 ? mBatteryInfo.computeChargeTimeRemaining() : -1; 881 } catch (RemoteException e) { 882 Log.e(TAG, "Error calling IBatteryStats: ", e); 883 mChargingTimeRemaining = -1; 884 } 885 updateIndication(!wasPluggedIn && mPowerPluggedInWired); 886 if (mDozing) { 887 if (!wasPluggedIn && mPowerPluggedIn) { 888 showTransientIndication(computePowerIndication()); 889 } else if (wasPluggedIn && !mPowerPluggedIn) { 890 hideTransientIndication(); 891 } 892 } 893 } 894 895 @Override onBiometricHelp(int msgId, String helpString, BiometricSourceType biometricSourceType)896 public void onBiometricHelp(int msgId, String helpString, 897 BiometricSourceType biometricSourceType) { 898 // TODO(b/141025588): refactor to reduce repetition of code/comments 899 // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong 900 // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to 901 // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the 902 // check of whether non-strong biometric is allowed 903 if (!mKeyguardUpdateMonitor 904 .isUnlockingWithBiometricAllowed(true /* isStrongBiometric */)) { 905 return; 906 } 907 908 boolean showActionToUnlock = 909 msgId == KeyguardUpdateMonitor.BIOMETRIC_HELP_FACE_NOT_RECOGNIZED; 910 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 911 mStatusBarKeyguardViewManager.showBouncerMessage(helpString, 912 mInitialTextColorState); 913 } else if (mKeyguardUpdateMonitor.isScreenOn()) { 914 if (biometricSourceType == BiometricSourceType.FACE 915 && shouldSuppressFaceMsgAndShowTryFingerprintMsg()) { 916 showTryFingerprintMsg(); 917 return; 918 } 919 showTransientIndication(helpString, false /* isError */, showActionToUnlock); 920 } else if (showActionToUnlock) { 921 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_SHOW_ACTION_TO_UNLOCK), 922 TRANSIENT_BIOMETRIC_ERROR_TIMEOUT); 923 } 924 } 925 926 @Override onBiometricError(int msgId, String errString, BiometricSourceType biometricSourceType)927 public void onBiometricError(int msgId, String errString, 928 BiometricSourceType biometricSourceType) { 929 if (shouldSuppressBiometricError(msgId, biometricSourceType, mKeyguardUpdateMonitor)) { 930 return; 931 } 932 if (biometricSourceType == BiometricSourceType.FACE 933 && shouldSuppressFaceMsgAndShowTryFingerprintMsg() 934 && !mStatusBarKeyguardViewManager.isBouncerShowing() 935 && mKeyguardUpdateMonitor.isScreenOn()) { 936 showTryFingerprintMsg(); 937 return; 938 } 939 if (msgId == FaceManager.FACE_ERROR_TIMEOUT) { 940 // The face timeout message is not very actionable, let's ask the user to 941 // manually retry. 942 if (!mStatusBarKeyguardViewManager.isBouncerShowing() 943 && mKeyguardUpdateMonitor.isUdfpsEnrolled() 944 && mKeyguardUpdateMonitor.isFingerprintDetectionRunning()) { 945 showTryFingerprintMsg(); 946 } else if (mStatusBarKeyguardViewManager.isShowingAlternateAuth()) { 947 mStatusBarKeyguardViewManager.showBouncerMessage( 948 mContext.getResources().getString(R.string.keyguard_unlock_press), 949 mInitialTextColorState 950 ); 951 } else { 952 // suggest swiping up to unlock (try face auth again or swipe up to bouncer) 953 showActionToUnlock(); 954 } 955 } else if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 956 mStatusBarKeyguardViewManager.showBouncerMessage(errString, mInitialTextColorState); 957 } else if (mKeyguardUpdateMonitor.isScreenOn()) { 958 showTransientIndication(errString, /* isError */ true, 959 /* hideOnScreenOff */ true); 960 } else { 961 mMessageToShowOnScreenOn = errString; 962 } 963 } 964 shouldSuppressBiometricError(int msgId, BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor)965 private boolean shouldSuppressBiometricError(int msgId, 966 BiometricSourceType biometricSourceType, KeyguardUpdateMonitor updateMonitor) { 967 if (biometricSourceType == BiometricSourceType.FINGERPRINT) 968 return shouldSuppressFingerprintError(msgId, updateMonitor); 969 if (biometricSourceType == BiometricSourceType.FACE) 970 return shouldSuppressFaceError(msgId, updateMonitor); 971 return false; 972 } 973 shouldSuppressFingerprintError(int msgId, KeyguardUpdateMonitor updateMonitor)974 private boolean shouldSuppressFingerprintError(int msgId, 975 KeyguardUpdateMonitor updateMonitor) { 976 // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong 977 // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to 978 // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the 979 // check of whether non-strong biometric is allowed 980 return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) 981 && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) 982 || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED 983 || msgId == FingerprintManager.FINGERPRINT_ERROR_USER_CANCELED); 984 } 985 shouldSuppressFaceMsgAndShowTryFingerprintMsg()986 private boolean shouldSuppressFaceMsgAndShowTryFingerprintMsg() { 987 // For dual biometric, don't show face auth messages 988 return mKeyguardUpdateMonitor.isFingerprintDetectionRunning() 989 && mKeyguardUpdateMonitor.isUnlockingWithBiometricAllowed( 990 true /* isStrongBiometric */); 991 } 992 shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor)993 private boolean shouldSuppressFaceError(int msgId, KeyguardUpdateMonitor updateMonitor) { 994 // Only checking if unlocking with Biometric is allowed (no matter strong or non-strong 995 // as long as primary auth, i.e. PIN/pattern/password, is not required), so it's ok to 996 // pass true for isStrongBiometric to isUnlockingWithBiometricAllowed() to bypass the 997 // check of whether non-strong biometric is allowed 998 return ((!updateMonitor.isUnlockingWithBiometricAllowed(true /* isStrongBiometric */) 999 && msgId != FaceManager.FACE_ERROR_LOCKOUT_PERMANENT) 1000 || msgId == FaceManager.FACE_ERROR_CANCELED); 1001 } 1002 1003 @Override onTrustAgentErrorMessage(CharSequence message)1004 public void onTrustAgentErrorMessage(CharSequence message) { 1005 showTransientIndication(message, true /* isError */, false /* hideOnScreenOff */); 1006 } 1007 1008 @Override onScreenTurnedOn()1009 public void onScreenTurnedOn() { 1010 if (mMessageToShowOnScreenOn != null) { 1011 showTransientIndication(mMessageToShowOnScreenOn, true /* isError */, 1012 false /* hideOnScreenOff */); 1013 // We want to keep this message around in case the screen was off 1014 hideTransientIndicationDelayed(HIDE_DELAY_MS); 1015 mMessageToShowOnScreenOn = null; 1016 } 1017 } 1018 1019 @Override onBiometricRunningStateChanged(boolean running, BiometricSourceType biometricSourceType)1020 public void onBiometricRunningStateChanged(boolean running, 1021 BiometricSourceType biometricSourceType) { 1022 if (running && biometricSourceType == BiometricSourceType.FACE) { 1023 // Let's hide any previous messages when authentication starts, otherwise 1024 // multiple auth attempts would overlap. 1025 hideTransientIndication(); 1026 mMessageToShowOnScreenOn = null; 1027 } 1028 } 1029 1030 @Override onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, boolean isStrongBiometric)1031 public void onBiometricAuthenticated(int userId, BiometricSourceType biometricSourceType, 1032 boolean isStrongBiometric) { 1033 super.onBiometricAuthenticated(userId, biometricSourceType, isStrongBiometric); 1034 mHandler.sendEmptyMessage(MSG_HIDE_TRANSIENT); 1035 1036 if (biometricSourceType == BiometricSourceType.FACE 1037 && !mKeyguardBypassController.canBypass()) { 1038 mHandler.sendEmptyMessage(MSG_SHOW_ACTION_TO_UNLOCK); 1039 } 1040 } 1041 1042 @Override onUserSwitchComplete(int userId)1043 public void onUserSwitchComplete(int userId) { 1044 if (mVisible) { 1045 updateIndication(false); 1046 } 1047 } 1048 1049 @Override onUserUnlocked()1050 public void onUserUnlocked() { 1051 if (mVisible) { 1052 updateIndication(false); 1053 } 1054 } 1055 1056 @Override onLogoutEnabledChanged()1057 public void onLogoutEnabledChanged() { 1058 if (mVisible) { 1059 updateIndication(false); 1060 } 1061 } 1062 1063 @Override onRequireUnlockForNfc()1064 public void onRequireUnlockForNfc() { 1065 showTransientIndication(mContext.getString(R.string.require_unlock_for_nfc), 1066 false /* isError */, false /* hideOnScreenOff */); 1067 hideTransientIndicationDelayed(HIDE_DELAY_MS); 1068 } 1069 } 1070 1071 private StatusBarStateController.StateListener mStatusBarStateListener = 1072 new StatusBarStateController.StateListener() { 1073 @Override 1074 public void onStateChanged(int newState) { 1075 setVisible(newState == StatusBarState.KEYGUARD); 1076 } 1077 1078 @Override 1079 public void onDozingChanged(boolean dozing) { 1080 if (mDozing == dozing) { 1081 return; 1082 } 1083 mDozing = dozing; 1084 1085 if (mHideTransientMessageOnScreenOff && mDozing) { 1086 hideTransientIndication(); 1087 } 1088 updateIndication(false); 1089 } 1090 }; 1091 1092 private KeyguardStateController.Callback mKeyguardStateCallback = 1093 new KeyguardStateController.Callback() { 1094 @Override 1095 public void onUnlockedChanged() { 1096 updateIndication(false); 1097 } 1098 1099 @Override 1100 public void onKeyguardShowingChanged() { 1101 if (!mKeyguardStateController.isShowing()) { 1102 mTopIndicationView.clearMessages(); 1103 mLockScreenIndicationView.clearMessages(); 1104 } 1105 } 1106 }; 1107 } 1108