1 /* 2 * Copyright (C) 2014 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License 15 */ 16 17 package com.android.systemui.statusbar; 18 19 import android.app.admin.DevicePolicyManager; 20 import android.content.BroadcastReceiver; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.IntentFilter; 24 import android.content.res.Resources; 25 import android.graphics.Color; 26 import android.hardware.fingerprint.FingerprintManager; 27 import android.os.BatteryManager; 28 import android.os.BatteryStats; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.os.ServiceManager; 33 import android.os.UserHandle; 34 import android.os.UserManager; 35 import android.text.TextUtils; 36 import android.text.format.Formatter; 37 import android.util.Log; 38 import android.view.View; 39 import android.view.ViewGroup; 40 41 import com.android.internal.annotations.VisibleForTesting; 42 import com.android.internal.app.IBatteryStats; 43 import com.android.keyguard.KeyguardUpdateMonitor; 44 import com.android.keyguard.KeyguardUpdateMonitorCallback; 45 import com.android.settingslib.Utils; 46 import com.android.systemui.Dependency; 47 import com.android.systemui.R; 48 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; 49 import com.android.systemui.statusbar.phone.LockIcon; 50 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 51 import com.android.systemui.statusbar.policy.UserInfoController; 52 import com.android.systemui.util.wakelock.SettableWakeLock; 53 import com.android.systemui.util.wakelock.WakeLock; 54 55 /** 56 * Controls the indications and error messages shown on the Keyguard 57 */ 58 public class KeyguardIndicationController { 59 60 private static final String TAG = "KeyguardIndication"; 61 private static final boolean DEBUG_CHARGING_SPEED = false; 62 63 private static final int MSG_HIDE_TRANSIENT = 1; 64 private static final int MSG_CLEAR_FP_MSG = 2; 65 private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300; 66 67 private final Context mContext; 68 private ViewGroup mIndicationArea; 69 private KeyguardIndicationTextView mTextView; 70 private KeyguardIndicationTextView mDisclosure; 71 private final UserManager mUserManager; 72 private final IBatteryStats mBatteryInfo; 73 private final SettableWakeLock mWakeLock; 74 75 private final int mSlowThreshold; 76 private final int mFastThreshold; 77 private LockIcon mLockIcon; 78 private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 79 80 private String mRestingIndication; 81 private String mTransientIndication; 82 private int mTransientTextColor; 83 private int mInitialTextColor; 84 private boolean mVisible; 85 86 private boolean mPowerPluggedIn; 87 private boolean mPowerCharged; 88 private int mChargingSpeed; 89 private int mChargingWattage; 90 private String mMessageToShowOnScreenOn; 91 92 private KeyguardUpdateMonitorCallback mUpdateMonitorCallback; 93 94 private final DevicePolicyManager mDevicePolicyManager; 95 private boolean mDozing; 96 97 /** 98 * Creates a new KeyguardIndicationController and registers callbacks. 99 */ 100 public KeyguardIndicationController(Context context, ViewGroup indicationArea, 101 LockIcon lockIcon) { 102 this(context, indicationArea, lockIcon, 103 WakeLock.createPartial(context, "Doze:KeyguardIndication")); 104 105 registerCallbacks(KeyguardUpdateMonitor.getInstance(context)); 106 } 107 108 /** 109 * Creates a new KeyguardIndicationController for testing. Does *not* register callbacks. 110 */ 111 @VisibleForTesting 112 KeyguardIndicationController(Context context, ViewGroup indicationArea, LockIcon lockIcon, 113 WakeLock wakeLock) { 114 mContext = context; 115 mIndicationArea = indicationArea; 116 mTextView = (KeyguardIndicationTextView) indicationArea.findViewById( 117 R.id.keyguard_indication_text); 118 mInitialTextColor = mTextView != null ? mTextView.getCurrentTextColor() : Color.WHITE; 119 mDisclosure = (KeyguardIndicationTextView) indicationArea.findViewById( 120 R.id.keyguard_indication_enterprise_disclosure); 121 mLockIcon = lockIcon; 122 mWakeLock = new SettableWakeLock(wakeLock); 123 124 Resources res = context.getResources(); 125 mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold); 126 mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold); 127 128 mUserManager = context.getSystemService(UserManager.class); 129 mBatteryInfo = IBatteryStats.Stub.asInterface( 130 ServiceManager.getService(BatteryStats.SERVICE_NAME)); 131 132 mDevicePolicyManager = (DevicePolicyManager) context.getSystemService( 133 Context.DEVICE_POLICY_SERVICE); 134 135 updateDisclosure(); 136 } 137 138 private void registerCallbacks(KeyguardUpdateMonitor monitor) { 139 monitor.registerCallback(getKeyguardCallback()); 140 141 mContext.registerReceiverAsUser(mTickReceiver, UserHandle.SYSTEM, 142 new IntentFilter(Intent.ACTION_TIME_TICK), null, 143 Dependency.get(Dependency.TIME_TICK_HANDLER)); 144 } 145 146 /** 147 * Gets the {@link KeyguardUpdateMonitorCallback} instance associated with this 148 * {@link KeyguardIndicationController}. 149 * 150 * <p>Subclasses may override this method to extend or change the callback behavior by extending 151 * the {@link BaseKeyguardCallback}. 152 * 153 * @return A KeyguardUpdateMonitorCallback. Multiple calls to this method <b>must</b> return the 154 * same instance. 155 */ 156 protected KeyguardUpdateMonitorCallback getKeyguardCallback() { 157 if (mUpdateMonitorCallback == null) { 158 mUpdateMonitorCallback = new BaseKeyguardCallback(); 159 } 160 return mUpdateMonitorCallback; 161 } 162 163 private void updateDisclosure() { 164 if (mDevicePolicyManager == null) { 165 return; 166 } 167 168 if (!mDozing && mDevicePolicyManager.isDeviceManaged()) { 169 final CharSequence organizationName = 170 mDevicePolicyManager.getDeviceOwnerOrganizationName(); 171 if (organizationName != null) { 172 mDisclosure.switchIndication(mContext.getResources().getString( 173 R.string.do_disclosure_with_name, organizationName)); 174 } else { 175 mDisclosure.switchIndication(R.string.do_disclosure_generic); 176 } 177 mDisclosure.setVisibility(View.VISIBLE); 178 } else { 179 mDisclosure.setVisibility(View.GONE); 180 } 181 } 182 183 public void setVisible(boolean visible) { 184 mVisible = visible; 185 mIndicationArea.setVisibility(visible ? View.VISIBLE : View.GONE); 186 if (visible) { 187 // If this is called after an error message was already shown, we should not clear it. 188 // Otherwise the error message won't be shown 189 if (!mHandler.hasMessages(MSG_HIDE_TRANSIENT)) { 190 hideTransientIndication(); 191 } 192 updateIndication(); 193 } else if (!visible) { 194 // If we unlock and return to keyguard quickly, previous error should not be shown 195 hideTransientIndication(); 196 } 197 } 198 199 /** 200 * Sets the indication that is shown if nothing else is showing. 201 */ 202 public void setRestingIndication(String restingIndication) { 203 mRestingIndication = restingIndication; 204 updateIndication(); 205 } 206 207 /** 208 * Sets the active controller managing changes and callbacks to user information. 209 */ 210 public void setUserInfoController(UserInfoController userInfoController) { 211 } 212 213 /** 214 * Returns the indication text indicating that trust has been granted. 215 * 216 * @return {@code null} or an empty string if a trust indication text should not be shown. 217 */ 218 protected String getTrustGrantedIndication() { 219 return null; 220 } 221 222 /** 223 * Returns the indication text indicating that trust is currently being managed. 224 * 225 * @return {@code null} or an empty string if a trust managed text should not be shown. 226 */ 227 protected String getTrustManagedIndication() { 228 return null; 229 } 230 231 /** 232 * Hides transient indication in {@param delayMs}. 233 */ 234 public void hideTransientIndicationDelayed(long delayMs) { 235 mHandler.sendMessageDelayed( 236 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs); 237 } 238 239 /** 240 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 241 */ 242 public void showTransientIndication(int transientIndication) { 243 showTransientIndication(mContext.getResources().getString(transientIndication)); 244 } 245 246 /** 247 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 248 */ 249 public void showTransientIndication(String transientIndication) { 250 showTransientIndication(transientIndication, mInitialTextColor); 251 } 252 253 /** 254 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 255 */ 256 public void showTransientIndication(String transientIndication, int textColor) { 257 mTransientIndication = transientIndication; 258 mTransientTextColor = textColor; 259 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 260 if (mDozing && !TextUtils.isEmpty(mTransientIndication)) { 261 // Make sure this doesn't get stuck and burns in. Acquire wakelock until its cleared. 262 mWakeLock.setAcquired(true); 263 hideTransientIndicationDelayed(BaseKeyguardCallback.HIDE_DELAY_MS); 264 } 265 updateIndication(); 266 } 267 268 /** 269 * Hides transient indication. 270 */ 271 public void hideTransientIndication() { 272 if (mTransientIndication != null) { 273 mTransientIndication = null; 274 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 275 updateIndication(); 276 } 277 } 278 279 protected final void updateIndication() { 280 if (TextUtils.isEmpty(mTransientIndication)) { 281 mWakeLock.setAcquired(false); 282 } 283 284 if (mVisible) { 285 // Walk down a precedence-ordered list of what indication 286 // should be shown based on user or device state 287 if (mDozing) { 288 // If we're dozing, never show a persistent indication. 289 if (!TextUtils.isEmpty(mTransientIndication)) { 290 // When dozing we ignore any text color and use white instead, because 291 // colors can be hard to read in low brightness. 292 mTextView.setTextColor(Color.WHITE); 293 mTextView.switchIndication(mTransientIndication); 294 } else { 295 mTextView.switchIndication(null); 296 } 297 return; 298 } 299 300 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 301 int userId = KeyguardUpdateMonitor.getCurrentUser(); 302 String trustGrantedIndication = getTrustGrantedIndication(); 303 String trustManagedIndication = getTrustManagedIndication(); 304 if (!mUserManager.isUserUnlocked(userId)) { 305 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked); 306 mTextView.setTextColor(mInitialTextColor); 307 } else if (!TextUtils.isEmpty(mTransientIndication)) { 308 mTextView.switchIndication(mTransientIndication); 309 mTextView.setTextColor(mTransientTextColor); 310 } else if (!TextUtils.isEmpty(trustGrantedIndication) 311 && updateMonitor.getUserHasTrust(userId)) { 312 mTextView.switchIndication(trustGrantedIndication); 313 mTextView.setTextColor(mInitialTextColor); 314 } else if (mPowerPluggedIn) { 315 String indication = computePowerIndication(); 316 if (DEBUG_CHARGING_SPEED) { 317 indication += ", " + (mChargingWattage / 1000) + " mW"; 318 } 319 mTextView.switchIndication(indication); 320 mTextView.setTextColor(mInitialTextColor); 321 } else if (!TextUtils.isEmpty(trustManagedIndication) 322 && updateMonitor.getUserTrustIsManaged(userId) 323 && !updateMonitor.getUserHasTrust(userId)) { 324 mTextView.switchIndication(trustManagedIndication); 325 mTextView.setTextColor(mInitialTextColor); 326 } else { 327 mTextView.switchIndication(mRestingIndication); 328 mTextView.setTextColor(mInitialTextColor); 329 } 330 } 331 } 332 333 private String computePowerIndication() { 334 if (mPowerCharged) { 335 return mContext.getResources().getString(R.string.keyguard_charged); 336 } 337 338 // Try fetching charging time from battery stats. 339 long chargingTimeRemaining = 0; 340 try { 341 chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining(); 342 343 } catch (RemoteException e) { 344 Log.e(TAG, "Error calling IBatteryStats: ", e); 345 } 346 final boolean hasChargingTime = chargingTimeRemaining > 0; 347 348 int chargingId; 349 switch (mChargingSpeed) { 350 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: 351 chargingId = hasChargingTime 352 ? R.string.keyguard_indication_charging_time_fast 353 : R.string.keyguard_plugged_in_charging_fast; 354 break; 355 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: 356 chargingId = hasChargingTime 357 ? R.string.keyguard_indication_charging_time_slowly 358 : R.string.keyguard_plugged_in_charging_slowly; 359 break; 360 default: 361 chargingId = hasChargingTime 362 ? R.string.keyguard_indication_charging_time 363 : R.string.keyguard_plugged_in; 364 break; 365 } 366 367 if (hasChargingTime) { 368 String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes( 369 mContext, chargingTimeRemaining); 370 return mContext.getResources().getString(chargingId, chargingTimeFormatted); 371 } else { 372 return mContext.getResources().getString(chargingId); 373 } 374 } 375 376 public void setStatusBarKeyguardViewManager( 377 StatusBarKeyguardViewManager statusBarKeyguardViewManager) { 378 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 379 } 380 381 private final BroadcastReceiver mTickReceiver = new BroadcastReceiver() { 382 @Override 383 public void onReceive(Context context, Intent intent) { 384 mHandler.post(() -> { 385 if (mVisible) { 386 updateIndication(); 387 } 388 }); 389 } 390 }; 391 392 private final Handler mHandler = new Handler() { 393 @Override 394 public void handleMessage(Message msg) { 395 if (msg.what == MSG_HIDE_TRANSIENT) { 396 hideTransientIndication(); 397 } else if (msg.what == MSG_CLEAR_FP_MSG) { 398 mLockIcon.setTransientFpError(false); 399 } 400 } 401 }; 402 403 public void setDozing(boolean dozing) { 404 if (mDozing == dozing) { 405 return; 406 } 407 mDozing = dozing; 408 updateIndication(); 409 updateDisclosure(); 410 } 411 412 protected class BaseKeyguardCallback extends KeyguardUpdateMonitorCallback { 413 public static final int HIDE_DELAY_MS = 5000; 414 private int mLastSuccessiveErrorMessage = -1; 415 416 @Override 417 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { 418 boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING 419 || status.status == BatteryManager.BATTERY_STATUS_FULL; 420 boolean wasPluggedIn = mPowerPluggedIn; 421 mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; 422 mPowerCharged = status.isCharged(); 423 mChargingWattage = status.maxChargingWattage; 424 mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); 425 updateIndication(); 426 if (mDozing) { 427 if (!wasPluggedIn && mPowerPluggedIn) { 428 showTransientIndication(computePowerIndication()); 429 hideTransientIndicationDelayed(HIDE_DELAY_MS); 430 } else if (wasPluggedIn && !mPowerPluggedIn) { 431 hideTransientIndication(); 432 } 433 } 434 } 435 436 @Override 437 public void onKeyguardVisibilityChanged(boolean showing) { 438 if (showing) { 439 updateDisclosure(); 440 } 441 } 442 443 @Override 444 public void onFingerprintHelp(int msgId, String helpString) { 445 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 446 if (!updateMonitor.isUnlockingWithFingerprintAllowed()) { 447 return; 448 } 449 int errorColor = Utils.getColorError(mContext); 450 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 451 mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor); 452 } else if (updateMonitor.isScreenOn()) { 453 mLockIcon.setTransientFpError(true); 454 showTransientIndication(helpString, errorColor); 455 hideTransientIndicationDelayed(TRANSIENT_FP_ERROR_TIMEOUT); 456 mHandler.removeMessages(MSG_CLEAR_FP_MSG); 457 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG), 458 TRANSIENT_FP_ERROR_TIMEOUT); 459 } 460 // Help messages indicate that there was actually a try since the last error, so those 461 // are not two successive error messages anymore. 462 mLastSuccessiveErrorMessage = -1; 463 } 464 465 @Override 466 public void onFingerprintError(int msgId, String errString) { 467 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 468 if ((!updateMonitor.isUnlockingWithFingerprintAllowed() 469 && msgId != FingerprintManager.FINGERPRINT_ERROR_LOCKOUT_PERMANENT) 470 || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 471 return; 472 } 473 int errorColor = Utils.getColorError(mContext); 474 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 475 // When swiping up right after receiving a fingerprint error, the bouncer calls 476 // authenticate leading to the same message being shown again on the bouncer. 477 // We want to avoid this, as it may confuse the user when the message is too 478 // generic. 479 if (mLastSuccessiveErrorMessage != msgId) { 480 mStatusBarKeyguardViewManager.showBouncerMessage(errString, errorColor); 481 } 482 } else if (updateMonitor.isScreenOn()) { 483 showTransientIndication(errString, errorColor); 484 // We want to keep this message around in case the screen was off 485 hideTransientIndicationDelayed(HIDE_DELAY_MS); 486 } else { 487 mMessageToShowOnScreenOn = errString; 488 } 489 mLastSuccessiveErrorMessage = msgId; 490 } 491 492 @Override 493 public void onScreenTurnedOn() { 494 if (mMessageToShowOnScreenOn != null) { 495 int errorColor = Utils.getColorError(mContext); 496 showTransientIndication(mMessageToShowOnScreenOn, errorColor); 497 // We want to keep this message around in case the screen was off 498 hideTransientIndicationDelayed(HIDE_DELAY_MS); 499 mMessageToShowOnScreenOn = null; 500 } 501 } 502 503 @Override 504 public void onFingerprintRunningStateChanged(boolean running) { 505 if (running) { 506 mMessageToShowOnScreenOn = null; 507 } 508 } 509 510 @Override 511 public void onFingerprintAuthenticated(int userId) { 512 super.onFingerprintAuthenticated(userId); 513 mLastSuccessiveErrorMessage = -1; 514 } 515 516 @Override 517 public void onFingerprintAuthFailed() { 518 super.onFingerprintAuthFailed(); 519 mLastSuccessiveErrorMessage = -1; 520 } 521 522 @Override 523 public void onUserUnlocked() { 524 if (mVisible) { 525 updateIndication(); 526 } 527 } 528 }; 529 } 530