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