KeyguardStatusViewManager.java revision ebcd6bb1b9ac5f898621ba25c37f2e3ccd2ff33b
1/* 2 * Copyright (C) 2011 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 17package com.android.internal.policy.impl; 18 19import com.android.internal.R; 20import com.android.internal.telephony.IccCard; 21import com.android.internal.telephony.IccCard.State; 22import com.android.internal.widget.LockPatternUtils; 23import com.android.internal.widget.TransportControlView; 24import com.android.internal.policy.impl.KeyguardUpdateMonitor.SimStateCallback; 25 26import java.util.ArrayList; 27import java.util.Date; 28 29import libcore.util.MutableInt; 30 31import android.content.ContentResolver; 32import android.content.Context; 33import android.provider.Settings; 34import android.text.TextUtils; 35import android.text.format.DateFormat; 36import android.util.Log; 37import android.view.View; 38import android.view.View.OnClickListener; 39import android.widget.Button; 40import android.widget.TextView; 41 42/*** 43 * Manages a number of views inside of LockScreen layouts. See below for a list of widgets 44 * 45 */ 46class KeyguardStatusViewManager implements OnClickListener { 47 private static final boolean DEBUG = false; 48 private static final String TAG = "KeyguardStatusView"; 49 50 public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock; 51 public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm; 52 public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; 53 public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; 54 private static final long INSTRUCTION_RESET_DELAY = 2000; // time until instruction text resets 55 56 private static final int INSTRUCTION_TEXT = 10; 57 private static final int CARRIER_TEXT = 11; 58 private static final int CARRIER_HELP_TEXT = 12; 59 private static final int HELP_MESSAGE_TEXT = 13; 60 private static final int OWNER_INFO = 14; 61 62 private StatusMode mStatus; 63 private String mDateFormatString; 64 private TransientTextManager mTransientTextManager; 65 66 // Views that this class controls. 67 // NOTE: These may be null in some LockScreen screens and should protect from NPE 68 private TextView mCarrierView; 69 private TextView mDateView; 70 private TextView mStatus1View; 71 private TextView mOwnerInfoView; 72 private TextView mAlarmStatusView; 73 private TransportControlView mTransportView; 74 75 // Top-level container view for above views 76 private View mContainer; 77 78 // are we showing battery information? 79 private boolean mShowingBatteryInfo = false; 80 81 // last known plugged in state 82 private boolean mPluggedIn = false; 83 84 // last known battery level 85 private int mBatteryLevel = 100; 86 87 private LockPatternUtils mLockPatternUtils; 88 private KeyguardUpdateMonitor mUpdateMonitor; 89 private Button mEmergencyCallButton; 90 private boolean mUnlockDisabledDueToSimState; 91 92 // Shadowed text values 93 private CharSequence mCarrierText; 94 private CharSequence mCarrierHelpText; 95 private String mHelpMessageText; 96 private String mInstructionText; 97 private CharSequence mOwnerInfoText; 98 private boolean mShowingStatus; 99 private KeyguardScreenCallback mCallback; 100 private final boolean mShowEmergencyButtonByDefault; 101 102 private class TransientTextManager { 103 private TextView mTextView; 104 private class Data { 105 final int icon; 106 final CharSequence text; 107 Data(CharSequence t, int i) { 108 text = t; 109 icon = i; 110 } 111 }; 112 private ArrayList<Data> mMessages = new ArrayList<Data>(5); 113 114 TransientTextManager(TextView textView) { 115 mTextView = textView; 116 } 117 118 /* Show given message with icon for up to duration ms. Newer messages override older ones. 119 * The most recent message with the longest duration is shown as messages expire until 120 * nothing is left, in which case the text/icon is defined by a call to 121 * getAltTextMessage() */ 122 void post(final CharSequence message, final int icon, long duration) { 123 if (mTextView == null) { 124 return; 125 } 126 mTextView.setText(message); 127 mTextView.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); 128 final Data data = new Data(message, icon); 129 mContainer.postDelayed(new Runnable() { 130 public void run() { 131 mMessages.remove(data); 132 int last = mMessages.size() - 1; 133 final CharSequence lastText; 134 final int lastIcon; 135 if (last > 0) { 136 final Data oldData = mMessages.get(last); 137 lastText = oldData.text; 138 lastIcon = oldData.icon; 139 } else { 140 final MutableInt tmpIcon = new MutableInt(0); 141 lastText = getAltTextMessage(tmpIcon); 142 lastIcon = tmpIcon.value; 143 } 144 mTextView.setText(lastText); 145 mTextView.setCompoundDrawablesWithIntrinsicBounds(lastIcon, 0, 0, 0); 146 } 147 }, duration); 148 } 149 }; 150 151 public KeyguardStatusViewManager(View view, KeyguardUpdateMonitor updateMonitor, 152 LockPatternUtils lockPatternUtils, KeyguardScreenCallback callback, 153 boolean showEmergencyButtonByDefault) { 154 mContainer = view; 155 mDateFormatString = getContext().getString(R.string.full_wday_month_day_no_year); 156 mLockPatternUtils = lockPatternUtils; 157 mUpdateMonitor = updateMonitor; 158 mCallback = callback; 159 160 mCarrierView = (TextView) findViewById(R.id.carrier); 161 mDateView = (TextView) findViewById(R.id.date); 162 mStatus1View = (TextView) findViewById(R.id.status1); 163 mAlarmStatusView = (TextView) findViewById(R.id.alarm_status); 164 mOwnerInfoView = (TextView) findViewById(R.id.propertyOf); 165 mTransportView = (TransportControlView) findViewById(R.id.transport); 166 mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); 167 mShowEmergencyButtonByDefault = showEmergencyButtonByDefault; 168 if (mEmergencyCallButton != null) { 169 mEmergencyCallButton.setText(R.string.lockscreen_emergency_call); 170 mEmergencyCallButton.setOnClickListener(this); 171 mEmergencyCallButton.setFocusable(false); // touch only! 172 } 173 174 mTransientTextManager = new TransientTextManager(mCarrierView); 175 176 updateEmergencyCallButtonState(); 177 178 resetStatusInfo(); 179 refreshDate(); 180 updateOwnerInfo(); 181 182 // Required to get Marquee to work. 183 final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView, 184 mAlarmStatusView }; 185 for (View v : scrollableViews) { 186 if (v != null) { 187 v.setSelected(true); 188 } 189 } 190 191 // until we get an update... 192 setCarrierText(LockPatternUtils.getCarrierString( 193 mUpdateMonitor.getTelephonyPlmn(), mUpdateMonitor.getTelephonySpn())); 194 } 195 196 private boolean inWidgetMode() { 197 return mTransportView != null && mTransportView.getVisibility() == View.VISIBLE; 198 } 199 200 void setInstructionText(String string) { 201 mInstructionText = string; 202 update(INSTRUCTION_TEXT, string); 203 } 204 205 void setCarrierText(CharSequence string) { 206 mCarrierText = string; 207 update(CARRIER_TEXT, string); 208 } 209 210 void setOwnerInfo(CharSequence string) { 211 mOwnerInfoText = string; 212 update(OWNER_INFO, string); 213 } 214 215 /** 216 * Sets the carrier help text message, if view is present. Carrier help text messages are 217 * typically for help dealing with SIMS and connectivity. 218 * 219 * @param resId resource id of the message 220 */ 221 public void setCarrierHelpText(int resId) { 222 mCarrierHelpText = getText(resId); 223 update(CARRIER_HELP_TEXT, mCarrierHelpText); 224 } 225 226 private CharSequence getText(int resId) { 227 return resId == 0 ? null : getContext().getText(resId); 228 } 229 230 /** 231 * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password" 232 * or "try again." 233 * 234 * @param textResId 235 * @param lockIcon 236 */ 237 public void setHelpMessage(int textResId, int lockIcon) { 238 final CharSequence tmp = getText(textResId); 239 mHelpMessageText = tmp == null ? null : tmp.toString(); 240 update(HELP_MESSAGE_TEXT, mHelpMessageText); 241 } 242 243 private void update(int what, CharSequence string) { 244 if (inWidgetMode()) { 245 if (DEBUG) Log.v(TAG, "inWidgetMode() is true"); 246 // Use Transient text for messages shown while widget is shown. 247 switch (what) { 248 case INSTRUCTION_TEXT: 249 case CARRIER_HELP_TEXT: 250 case HELP_MESSAGE_TEXT: 251 mTransientTextManager.post(string, 0, INSTRUCTION_RESET_DELAY); 252 break; 253 254 case OWNER_INFO: 255 case CARRIER_TEXT: 256 default: 257 if (DEBUG) Log.w(TAG, "Not showing message id " + what + ", str=" + string); 258 } 259 } else { 260 updateStatusLines(mShowingStatus); 261 } 262 } 263 264 public void onPause() { 265 mUpdateMonitor.removeCallback(mInfoCallback); 266 mUpdateMonitor.removeCallback(mSimStateCallback); 267 } 268 269 /** {@inheritDoc} */ 270 public void onResume() { 271 mUpdateMonitor.registerInfoCallback(mInfoCallback); 272 mUpdateMonitor.registerSimStateCallback(mSimStateCallback); 273 updateEmergencyCallButtonState(); 274 resetStatusInfo(); 275 } 276 277 void resetStatusInfo() { 278 mInstructionText = null; 279 mShowingBatteryInfo = mUpdateMonitor.shouldShowBatteryInfo(); 280 mPluggedIn = mUpdateMonitor.isDevicePluggedIn(); 281 mBatteryLevel = mUpdateMonitor.getBatteryLevel(); 282 updateStatusLines(true); 283 } 284 285 /** 286 * Update the status lines based on these rules: 287 * AlarmStatus: Alarm state always gets it's own line. 288 * Status1 is shared between help, battery status and generic unlock instructions, 289 * prioritized in that order. 290 * @param showStatusLines status lines are shown if true 291 */ 292 void updateStatusLines(boolean showStatusLines) { 293 if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")"); 294 mShowingStatus = showStatusLines; 295 updateAlarmInfo(); 296 updateOwnerInfo(); 297 updateStatus1(); 298 updateCarrierText(); 299 } 300 301 private void updateAlarmInfo() { 302 if (mAlarmStatusView != null) { 303 String nextAlarm = mLockPatternUtils.getNextAlarm(); 304 boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm); 305 mAlarmStatusView.setText(nextAlarm); 306 mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0); 307 mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE); 308 } 309 } 310 311 private void updateOwnerInfo() { 312 final ContentResolver res = getContext().getContentResolver(); 313 final boolean ownerInfoEnabled = Settings.Secure.getInt(res, 314 Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; 315 mOwnerInfoText = ownerInfoEnabled ? 316 Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null; 317 if (mOwnerInfoView != null) { 318 mOwnerInfoView.setText(mOwnerInfoText); 319 mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE); 320 } 321 } 322 323 private void updateStatus1() { 324 if (mStatus1View != null) { 325 MutableInt icon = new MutableInt(0); 326 CharSequence string = getPriorityTextMessage(icon); 327 mStatus1View.setText(string); 328 mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); 329 mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE); 330 } 331 } 332 333 private void updateCarrierText() { 334 if (!inWidgetMode() && mCarrierView != null) { 335 mCarrierView.setText(mCarrierText); 336 } 337 } 338 339 private CharSequence getAltTextMessage(MutableInt icon) { 340 // If we have replaced the status area with a single widget, then this code 341 // prioritizes what to show in that space when all transient messages are gone. 342 CharSequence string = null; 343 if (mShowingBatteryInfo) { 344 // Battery status 345 if (mPluggedIn) { 346 // Charging or charged 347 if (mUpdateMonitor.isDeviceCharged()) { 348 string = getContext().getString(R.string.lockscreen_charged); 349 } else { 350 string = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel); 351 } 352 icon.value = CHARGING_ICON; 353 } else if (mBatteryLevel < KeyguardUpdateMonitor.LOW_BATTERY_THRESHOLD) { 354 // Battery is low 355 string = getContext().getString(R.string.lockscreen_low_battery); 356 icon.value = BATTERY_LOW_ICON; 357 } 358 } else { 359 string = mCarrierText; 360 } 361 return string; 362 } 363 364 private CharSequence getPriorityTextMessage(MutableInt icon) { 365 CharSequence string = null; 366 if (!TextUtils.isEmpty(mInstructionText)) { 367 // Instructions only 368 string = mInstructionText; 369 icon.value = LOCK_ICON; 370 } else if (mShowingBatteryInfo) { 371 // Battery status 372 if (mPluggedIn) { 373 // Charging or charged 374 if (mUpdateMonitor.isDeviceCharged()) { 375 string = getContext().getString(R.string.lockscreen_charged); 376 } else { 377 string = getContext().getString(R.string.lockscreen_plugged_in, mBatteryLevel); 378 } 379 icon.value = CHARGING_ICON; 380 } else if (mBatteryLevel < KeyguardUpdateMonitor.LOW_BATTERY_THRESHOLD) { 381 // Battery is low 382 string = getContext().getString(R.string.lockscreen_low_battery); 383 icon.value = BATTERY_LOW_ICON; 384 } 385 } else if (!inWidgetMode() && mOwnerInfoView == null && mOwnerInfoText != null) { 386 // OwnerInfo shows in status if we don't have a dedicated widget 387 string = mOwnerInfoText; 388 } 389 return string; 390 } 391 392 void refreshDate() { 393 if (mDateView != null) { 394 mDateView.setText(DateFormat.format(mDateFormatString, new Date())); 395 } 396 } 397 398 /** 399 * Determine the current status of the lock screen given the sim state and other stuff. 400 */ 401 public StatusMode getStatusForIccState(IccCard.State simState) { 402 boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned() 403 && (simState == IccCard.State.ABSENT || simState == IccCard.State.PERM_DISABLED)); 404 405 // Assume we're NETWORK_LOCKED if not provisioned 406 simState = missingAndNotProvisioned ? State.NETWORK_LOCKED : simState; 407 switch (simState) { 408 case ABSENT: 409 return StatusMode.SimMissing; 410 case NETWORK_LOCKED: 411 return StatusMode.SimMissingLocked; 412 case NOT_READY: 413 return StatusMode.SimMissing; 414 case PIN_REQUIRED: 415 return StatusMode.SimLocked; 416 case PUK_REQUIRED: 417 return StatusMode.SimPukLocked; 418 case READY: 419 return StatusMode.Normal; 420 case PERM_DISABLED: 421 return StatusMode.SimPermDisabled; 422 case UNKNOWN: 423 return StatusMode.SimMissing; 424 } 425 return StatusMode.SimMissing; 426 } 427 428 private Context getContext() { 429 return mContainer.getContext(); 430 } 431 432 /** 433 * Update carrier text, carrier help and emergency button to match the current status based 434 * on SIM state. 435 * 436 * @param simState 437 */ 438 private void updateWithSimStatus(State simState) { 439 // The emergency call button no longer appears on this screen. 440 if (DEBUG) Log.d(TAG, "updateLayout: status=" + mStatus); 441 442 CharSequence carrierText = null; 443 int carrierHelpTextId = 0; 444 mUnlockDisabledDueToSimState = false; 445 mStatus = getStatusForIccState(simState); 446 switch (mStatus) { 447 case Normal: 448 carrierText = LockPatternUtils.getCarrierString(mUpdateMonitor.getTelephonyPlmn(), 449 mUpdateMonitor.getTelephonySpn()); 450 break; 451 452 case NetworkLocked: 453 carrierText = LockPatternUtils.getCarrierString(mUpdateMonitor.getTelephonyPlmn(), 454 getContext().getText(R.string.lockscreen_network_locked_message)); 455 carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled; 456 break; 457 458 case SimMissing: 459 carrierText = getContext().getText(R.string.lockscreen_missing_sim_message_short); 460 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long; 461 break; 462 463 case SimPermDisabled: 464 carrierText = getContext().getText(R.string.lockscreen_missing_sim_message_short); 465 carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions; 466 mUnlockDisabledDueToSimState = true; 467 break; 468 469 case SimMissingLocked: 470 carrierText = LockPatternUtils.getCarrierString(mUpdateMonitor.getTelephonyPlmn(), 471 getContext().getText(R.string.lockscreen_missing_sim_message_short)); 472 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions; 473 mUnlockDisabledDueToSimState = true; 474 break; 475 476 case SimLocked: 477 carrierText = LockPatternUtils.getCarrierString(mUpdateMonitor.getTelephonyPlmn(), 478 getContext().getText(R.string.lockscreen_sim_locked_message)); 479 break; 480 481 case SimPukLocked: 482 carrierText = LockPatternUtils.getCarrierString(mUpdateMonitor.getTelephonyPlmn(), 483 getContext().getText(R.string.lockscreen_sim_puk_locked_message)); 484 if (!mLockPatternUtils.isPukUnlockScreenEnable()) { 485 mUnlockDisabledDueToSimState = true; 486 } 487 break; 488 } 489 490 setCarrierText(carrierText); 491 setCarrierHelpText(carrierHelpTextId); 492 updateEmergencyCallButtonState(); 493 } 494 495 private View findViewById(int id) { 496 return mContainer.findViewById(id); 497 } 498 499 /** 500 * The status of this lock screen. Primarily used for widgets on LockScreen. 501 */ 502 enum StatusMode { 503 /** 504 * Normal case (sim card present, it's not locked) 505 */ 506 Normal(true), 507 508 /** 509 * The sim card is 'network locked'. 510 */ 511 NetworkLocked(true), 512 513 /** 514 * The sim card is missing. 515 */ 516 SimMissing(false), 517 518 /** 519 * The sim card is missing, and this is the device isn't provisioned, so we don't let 520 * them get past the screen. 521 */ 522 SimMissingLocked(false), 523 524 /** 525 * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many 526 * times. 527 */ 528 SimPukLocked(false), 529 530 /** 531 * The sim card is locked. 532 */ 533 SimLocked(true), 534 535 /** 536 * The sim card is permanently disabled due to puk unlock failure 537 */ 538 SimPermDisabled(false); 539 540 private final boolean mShowStatusLines; 541 542 StatusMode(boolean mShowStatusLines) { 543 this.mShowStatusLines = mShowStatusLines; 544 } 545 546 /** 547 * @return Whether the status lines (battery level and / or next alarm) are shown while 548 * in this state. Mostly dictated by whether this is room for them. 549 */ 550 public boolean shouldShowStatusLines() { 551 return mShowStatusLines; 552 } 553 } 554 555 private void updateEmergencyCallButtonState() { 556 if (mEmergencyCallButton != null) { 557 boolean showIfCapable = mShowEmergencyButtonByDefault || mUnlockDisabledDueToSimState; 558 mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, showIfCapable); 559 } 560 } 561 562 private KeyguardUpdateMonitor.InfoCallback mInfoCallback 563 = new KeyguardUpdateMonitor.InfoCallback() { 564 565 public void onRefreshBatteryInfo(boolean showBatteryInfo, boolean pluggedIn, 566 int batteryLevel) { 567 mShowingBatteryInfo = showBatteryInfo; 568 mPluggedIn = pluggedIn; 569 mBatteryLevel = batteryLevel; 570 updateStatusLines(true); 571 } 572 573 public void onTimeChanged() { 574 refreshDate(); 575 } 576 577 public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { 578 setCarrierText(LockPatternUtils.getCarrierString(plmn, spn)); 579 } 580 581 public void onRingerModeChanged(int state) { 582 583 } 584 585 public void onPhoneStateChanged(String newState) { 586 updateEmergencyCallButtonState(); 587 } 588 589 /** {@inheritDoc} */ 590 public void onClockVisibilityChanged() { 591 // ignored 592 } 593 }; 594 595 private SimStateCallback mSimStateCallback = new SimStateCallback() { 596 597 public void onSimStateChanged(State simState) { 598 updateWithSimStatus(simState); 599 } 600 }; 601 602 public void onClick(View v) { 603 if (v == mEmergencyCallButton) { 604 mCallback.takeEmergencyCallAction(); 605 } 606 } 607} 608