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.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 40 import com.android.internal.app.IBatteryStats; 41 import com.android.keyguard.KeyguardUpdateMonitor; 42 import com.android.keyguard.KeyguardUpdateMonitorCallback; 43 import com.android.systemui.R; 44 import com.android.systemui.statusbar.phone.KeyguardIndicationTextView; 45 import com.android.systemui.statusbar.phone.LockIcon; 46 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager; 47 48 /** 49 * Controls the indications and error messages shown on the Keyguard 50 */ 51 public class KeyguardIndicationController { 52 53 private static final String TAG = "KeyguardIndication"; 54 private static final boolean DEBUG_CHARGING_SPEED = false; 55 56 private static final int MSG_HIDE_TRANSIENT = 1; 57 private static final int MSG_CLEAR_FP_MSG = 2; 58 private static final long TRANSIENT_FP_ERROR_TIMEOUT = 1300; 59 60 private final Context mContext; 61 private final KeyguardIndicationTextView mTextView; 62 private final UserManager mUserManager; 63 private final IBatteryStats mBatteryInfo; 64 65 private final int mSlowThreshold; 66 private final int mFastThreshold; 67 private final LockIcon mLockIcon; 68 private StatusBarKeyguardViewManager mStatusBarKeyguardViewManager; 69 70 private String mRestingIndication; 71 private String mTransientIndication; 72 private int mTransientTextColor; 73 private boolean mVisible; 74 75 private boolean mPowerPluggedIn; 76 private boolean mPowerCharged; 77 private int mChargingSpeed; 78 private int mChargingWattage; 79 private String mMessageToShowOnScreenOn; 80 81 public KeyguardIndicationController(Context context, KeyguardIndicationTextView textView, 82 LockIcon lockIcon) { 83 mContext = context; 84 mTextView = textView; 85 mLockIcon = lockIcon; 86 87 Resources res = context.getResources(); 88 mSlowThreshold = res.getInteger(R.integer.config_chargingSlowlyThreshold); 89 mFastThreshold = res.getInteger(R.integer.config_chargingFastThreshold); 90 91 mUserManager = context.getSystemService(UserManager.class); 92 mBatteryInfo = IBatteryStats.Stub.asInterface( 93 ServiceManager.getService(BatteryStats.SERVICE_NAME)); 94 95 KeyguardUpdateMonitor.getInstance(context).registerCallback(mUpdateMonitor); 96 context.registerReceiverAsUser(mReceiver, UserHandle.SYSTEM, 97 new IntentFilter(Intent.ACTION_TIME_TICK), null, null); 98 } 99 100 public void setVisible(boolean visible) { 101 mVisible = visible; 102 mTextView.setVisibility(visible ? View.VISIBLE : View.GONE); 103 if (visible) { 104 hideTransientIndication(); 105 updateIndication(); 106 } 107 } 108 109 /** 110 * Sets the indication that is shown if nothing else is showing. 111 */ 112 public void setRestingIndication(String restingIndication) { 113 mRestingIndication = restingIndication; 114 updateIndication(); 115 } 116 117 /** 118 * Hides transient indication in {@param delayMs}. 119 */ 120 public void hideTransientIndicationDelayed(long delayMs) { 121 mHandler.sendMessageDelayed( 122 mHandler.obtainMessage(MSG_HIDE_TRANSIENT), delayMs); 123 } 124 125 /** 126 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 127 */ 128 public void showTransientIndication(int transientIndication) { 129 showTransientIndication(mContext.getResources().getString(transientIndication)); 130 } 131 132 /** 133 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 134 */ 135 public void showTransientIndication(String transientIndication) { 136 showTransientIndication(transientIndication, Color.WHITE); 137 } 138 139 /** 140 * Shows {@param transientIndication} until it is hidden by {@link #hideTransientIndication}. 141 */ 142 public void showTransientIndication(String transientIndication, int textColor) { 143 mTransientIndication = transientIndication; 144 mTransientTextColor = textColor; 145 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 146 updateIndication(); 147 } 148 149 /** 150 * Hides transient indication. 151 */ 152 public void hideTransientIndication() { 153 if (mTransientIndication != null) { 154 mTransientIndication = null; 155 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 156 updateIndication(); 157 } 158 } 159 160 private void updateIndication() { 161 if (mVisible) { 162 // Walk down a precedence-ordered list of what should indication 163 // should be shown based on user or device state 164 if (!mUserManager.isUserUnlocked(ActivityManager.getCurrentUser())) { 165 mTextView.switchIndication(com.android.internal.R.string.lockscreen_storage_locked); 166 mTextView.setTextColor(Color.WHITE); 167 168 } else if (!TextUtils.isEmpty(mTransientIndication)) { 169 mTextView.switchIndication(mTransientIndication); 170 mTextView.setTextColor(mTransientTextColor); 171 172 } else if (mPowerPluggedIn) { 173 String indication = computePowerIndication(); 174 if (DEBUG_CHARGING_SPEED) { 175 indication += ", " + (mChargingWattage / 1000) + " mW"; 176 } 177 mTextView.switchIndication(indication); 178 mTextView.setTextColor(Color.WHITE); 179 180 } else { 181 mTextView.switchIndication(mRestingIndication); 182 mTextView.setTextColor(Color.WHITE); 183 } 184 } 185 } 186 187 private String computePowerIndication() { 188 if (mPowerCharged) { 189 return mContext.getResources().getString(R.string.keyguard_charged); 190 } 191 192 // Try fetching charging time from battery stats. 193 long chargingTimeRemaining = 0; 194 try { 195 chargingTimeRemaining = mBatteryInfo.computeChargeTimeRemaining(); 196 197 } catch (RemoteException e) { 198 Log.e(TAG, "Error calling IBatteryStats: ", e); 199 } 200 final boolean hasChargingTime = chargingTimeRemaining > 0; 201 202 int chargingId; 203 switch (mChargingSpeed) { 204 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_FAST: 205 chargingId = hasChargingTime 206 ? R.string.keyguard_indication_charging_time_fast 207 : R.string.keyguard_plugged_in_charging_fast; 208 break; 209 case KeyguardUpdateMonitor.BatteryStatus.CHARGING_SLOWLY: 210 chargingId = hasChargingTime 211 ? R.string.keyguard_indication_charging_time_slowly 212 : R.string.keyguard_plugged_in_charging_slowly; 213 break; 214 default: 215 chargingId = hasChargingTime 216 ? R.string.keyguard_indication_charging_time 217 : R.string.keyguard_plugged_in; 218 break; 219 } 220 221 if (hasChargingTime) { 222 String chargingTimeFormatted = Formatter.formatShortElapsedTimeRoundingUpToMinutes( 223 mContext, chargingTimeRemaining); 224 return mContext.getResources().getString(chargingId, chargingTimeFormatted); 225 } else { 226 return mContext.getResources().getString(chargingId); 227 } 228 } 229 230 KeyguardUpdateMonitorCallback mUpdateMonitor = new KeyguardUpdateMonitorCallback() { 231 public int mLastSuccessiveErrorMessage = -1; 232 233 @Override 234 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { 235 boolean isChargingOrFull = status.status == BatteryManager.BATTERY_STATUS_CHARGING 236 || status.status == BatteryManager.BATTERY_STATUS_FULL; 237 mPowerPluggedIn = status.isPluggedIn() && isChargingOrFull; 238 mPowerCharged = status.isCharged(); 239 mChargingWattage = status.maxChargingWattage; 240 mChargingSpeed = status.getChargingSpeed(mSlowThreshold, mFastThreshold); 241 updateIndication(); 242 } 243 244 @Override 245 public void onFingerprintHelp(int msgId, String helpString) { 246 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 247 if (!updateMonitor.isUnlockingWithFingerprintAllowed()) { 248 return; 249 } 250 int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null); 251 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 252 mStatusBarKeyguardViewManager.showBouncerMessage(helpString, errorColor); 253 } else if (updateMonitor.isDeviceInteractive()) { 254 mLockIcon.setTransientFpError(true); 255 showTransientIndication(helpString, errorColor); 256 mHandler.removeMessages(MSG_CLEAR_FP_MSG); 257 mHandler.sendMessageDelayed(mHandler.obtainMessage(MSG_CLEAR_FP_MSG), 258 TRANSIENT_FP_ERROR_TIMEOUT); 259 } 260 // Help messages indicate that there was actually a try since the last error, so those 261 // are not two successive error messages anymore. 262 mLastSuccessiveErrorMessage = -1; 263 } 264 265 @Override 266 public void onFingerprintError(int msgId, String errString) { 267 KeyguardUpdateMonitor updateMonitor = KeyguardUpdateMonitor.getInstance(mContext); 268 if (!updateMonitor.isUnlockingWithFingerprintAllowed() 269 || msgId == FingerprintManager.FINGERPRINT_ERROR_CANCELED) { 270 return; 271 } 272 int errorColor = mContext.getResources().getColor(R.color.system_warning_color, null); 273 if (mStatusBarKeyguardViewManager.isBouncerShowing()) { 274 // When swiping up right after receiving a fingerprint error, the bouncer calls 275 // authenticate leading to the same message being shown again on the bouncer. 276 // We want to avoid this, as it may confuse the user when the message is too 277 // generic. 278 if (mLastSuccessiveErrorMessage != msgId) { 279 mStatusBarKeyguardViewManager.showBouncerMessage(errString, errorColor); 280 } 281 } else if (updateMonitor.isDeviceInteractive()) { 282 showTransientIndication(errString, errorColor); 283 // We want to keep this message around in case the screen was off 284 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 285 hideTransientIndicationDelayed(5000); 286 } else { 287 mMessageToShowOnScreenOn = errString; 288 } 289 mLastSuccessiveErrorMessage = msgId; 290 } 291 292 @Override 293 public void onScreenTurnedOn() { 294 if (mMessageToShowOnScreenOn != null) { 295 int errorColor = mContext.getResources().getColor(R.color.system_warning_color, 296 null); 297 showTransientIndication(mMessageToShowOnScreenOn, errorColor); 298 // We want to keep this message around in case the screen was off 299 mHandler.removeMessages(MSG_HIDE_TRANSIENT); 300 hideTransientIndicationDelayed(5000); 301 mMessageToShowOnScreenOn = null; 302 } 303 } 304 305 @Override 306 public void onFingerprintRunningStateChanged(boolean running) { 307 if (running) { 308 mMessageToShowOnScreenOn = null; 309 } 310 } 311 312 @Override 313 public void onFingerprintAuthenticated(int userId) { 314 super.onFingerprintAuthenticated(userId); 315 mLastSuccessiveErrorMessage = -1; 316 } 317 318 @Override 319 public void onFingerprintAuthFailed() { 320 super.onFingerprintAuthFailed(); 321 mLastSuccessiveErrorMessage = -1; 322 } 323 }; 324 325 BroadcastReceiver mReceiver = new BroadcastReceiver() { 326 @Override 327 public void onReceive(Context context, Intent intent) { 328 if (mVisible) { 329 updateIndication(); 330 } 331 } 332 }; 333 334 private final Handler mHandler = new Handler() { 335 @Override 336 public void handleMessage(Message msg) { 337 if (msg.what == MSG_HIDE_TRANSIENT && mTransientIndication != null) { 338 mTransientIndication = null; 339 updateIndication(); 340 } else if (msg.what == MSG_CLEAR_FP_MSG) { 341 mLockIcon.setTransientFpError(false); 342 hideTransientIndication(); 343 } 344 } 345 }; 346 347 public void setStatusBarKeyguardViewManager( 348 StatusBarKeyguardViewManager statusBarKeyguardViewManager) { 349 mStatusBarKeyguardViewManager = statusBarKeyguardViewManager; 350 } 351 } 352