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.keyguard_obsolete; 18 19import com.android.internal.R; 20import com.android.internal.telephony.IccCardConstants; 21import com.android.internal.widget.DigitalClock; 22import com.android.internal.widget.LockPatternUtils; 23import com.android.internal.widget.TransportControlView; 24 25import java.util.ArrayList; 26import java.util.Date; 27 28import libcore.util.MutableInt; 29 30import android.content.ContentResolver; 31import android.content.Context; 32import android.provider.Settings; 33import android.text.TextUtils; 34import android.text.format.DateFormat; 35import android.util.Log; 36import android.view.View; 37import android.view.View.OnClickListener; 38import android.widget.Button; 39import android.widget.TextView; 40 41/*** 42 * Manages a number of views inside of LockScreen layouts. See below for a list of widgets 43 * 44 */ 45class KeyguardStatusViewManager implements OnClickListener { 46 private static final boolean DEBUG = false; 47 private static final String TAG = "KeyguardStatusView"; 48 49 public static final int LOCK_ICON = 0; // R.drawable.ic_lock_idle_lock; 50 public static final int ALARM_ICON = R.drawable.ic_lock_idle_alarm; 51 public static final int CHARGING_ICON = 0; //R.drawable.ic_lock_idle_charging; 52 public static final int BATTERY_LOW_ICON = 0; //R.drawable.ic_lock_idle_low_battery; 53 private static final long INSTRUCTION_RESET_DELAY = 2000; // time until instruction text resets 54 55 private static final int INSTRUCTION_TEXT = 10; 56 private static final int CARRIER_TEXT = 11; 57 private static final int CARRIER_HELP_TEXT = 12; 58 private static final int HELP_MESSAGE_TEXT = 13; 59 private static final int OWNER_INFO = 14; 60 private static final int BATTERY_INFO = 15; 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 // last known SIM state 88 protected IccCardConstants.State mSimState; 89 90 private LockPatternUtils mLockPatternUtils; 91 private KeyguardUpdateMonitor mUpdateMonitor; 92 private Button mEmergencyCallButton; 93 private boolean mEmergencyButtonEnabledBecauseSimLocked; 94 95 // Shadowed text values 96 private CharSequence mCarrierText; 97 private CharSequence mCarrierHelpText; 98 private String mHelpMessageText; 99 private String mInstructionText; 100 private CharSequence mOwnerInfoText; 101 private boolean mShowingStatus; 102 private KeyguardScreenCallback mCallback; 103 private final boolean mEmergencyCallButtonEnabledInScreen; 104 private CharSequence mPlmn; 105 private CharSequence mSpn; 106 protected int mPhoneState; 107 private DigitalClock mDigitalClock; 108 protected boolean mBatteryCharged; 109 protected boolean mBatteryIsLow; 110 111 private class TransientTextManager { 112 private TextView mTextView; 113 private class Data { 114 final int icon; 115 final CharSequence text; 116 Data(CharSequence t, int i) { 117 text = t; 118 icon = i; 119 } 120 }; 121 private ArrayList<Data> mMessages = new ArrayList<Data>(5); 122 123 TransientTextManager(TextView textView) { 124 mTextView = textView; 125 } 126 127 /* Show given message with icon for up to duration ms. Newer messages override older ones. 128 * The most recent message with the longest duration is shown as messages expire until 129 * nothing is left, in which case the text/icon is defined by a call to 130 * getAltTextMessage() */ 131 void post(final CharSequence message, final int icon, long duration) { 132 if (mTextView == null) { 133 return; 134 } 135 mTextView.setText(message); 136 mTextView.setCompoundDrawablesWithIntrinsicBounds(icon, 0, 0, 0); 137 final Data data = new Data(message, icon); 138 mContainer.postDelayed(new Runnable() { 139 public void run() { 140 mMessages.remove(data); 141 int last = mMessages.size() - 1; 142 final CharSequence lastText; 143 final int lastIcon; 144 if (last > 0) { 145 final Data oldData = mMessages.get(last); 146 lastText = oldData.text; 147 lastIcon = oldData.icon; 148 } else { 149 final MutableInt tmpIcon = new MutableInt(0); 150 lastText = getAltTextMessage(tmpIcon); 151 lastIcon = tmpIcon.value; 152 } 153 mTextView.setText(lastText); 154 mTextView.setCompoundDrawablesWithIntrinsicBounds(lastIcon, 0, 0, 0); 155 } 156 }, duration); 157 } 158 }; 159 160 /** 161 * 162 * @param view the containing view of all widgets 163 * @param updateMonitor the update monitor to use 164 * @param lockPatternUtils lock pattern util object 165 * @param callback used to invoke emergency dialer 166 * @param emergencyButtonEnabledInScreen whether emergency button is enabled by default 167 */ 168 public KeyguardStatusViewManager(View view, KeyguardUpdateMonitor updateMonitor, 169 LockPatternUtils lockPatternUtils, KeyguardScreenCallback callback, 170 boolean emergencyButtonEnabledInScreen) { 171 if (DEBUG) Log.v(TAG, "KeyguardStatusViewManager()"); 172 mContainer = view; 173 mDateFormatString = getContext().getString(R.string.abbrev_wday_month_day_no_year); 174 mLockPatternUtils = lockPatternUtils; 175 mUpdateMonitor = updateMonitor; 176 mCallback = callback; 177 178 mCarrierView = (TextView) findViewById(R.id.carrier); 179 mDateView = (TextView) findViewById(R.id.date); 180 mStatus1View = (TextView) findViewById(R.id.status1); 181 mAlarmStatusView = (TextView) findViewById(R.id.alarm_status); 182 mOwnerInfoView = (TextView) findViewById(R.id.owner_info); 183 mTransportView = (TransportControlView) findViewById(R.id.transport); 184 mEmergencyCallButton = (Button) findViewById(R.id.emergencyCallButton); 185 mEmergencyCallButtonEnabledInScreen = emergencyButtonEnabledInScreen; 186 mDigitalClock = (DigitalClock) findViewById(R.id.time); 187 188 // Hide transport control view until we know we need to show it. 189 if (mTransportView != null) { 190 mTransportView.setVisibility(View.GONE); 191 } 192 193 if (mEmergencyCallButton != null) { 194 mEmergencyCallButton.setText(R.string.lockscreen_emergency_call); 195 mEmergencyCallButton.setOnClickListener(this); 196 mEmergencyCallButton.setFocusable(false); // touch only! 197 } 198 199 mTransientTextManager = new TransientTextManager(mCarrierView); 200 201 // Registering this callback immediately updates the battery state, among other things. 202 mUpdateMonitor.registerCallback(mInfoCallback); 203 204 resetStatusInfo(); 205 refreshDate(); 206 updateOwnerInfo(); 207 208 // Required to get Marquee to work. 209 final View scrollableViews[] = { mCarrierView, mDateView, mStatus1View, mOwnerInfoView, 210 mAlarmStatusView }; 211 for (View v : scrollableViews) { 212 if (v != null) { 213 v.setSelected(true); 214 } 215 } 216 } 217 218 private boolean inWidgetMode() { 219 return mTransportView != null && mTransportView.getVisibility() == View.VISIBLE; 220 } 221 222 void setInstructionText(String string) { 223 mInstructionText = string; 224 update(INSTRUCTION_TEXT, string); 225 } 226 227 void setCarrierText(CharSequence string) { 228 mCarrierText = string; 229 update(CARRIER_TEXT, string); 230 } 231 232 void setOwnerInfo(CharSequence string) { 233 mOwnerInfoText = string; 234 update(OWNER_INFO, string); 235 } 236 237 /** 238 * Sets the carrier help text message, if view is present. Carrier help text messages are 239 * typically for help dealing with SIMS and connectivity. 240 * 241 * @param resId resource id of the message 242 */ 243 public void setCarrierHelpText(int resId) { 244 mCarrierHelpText = getText(resId); 245 update(CARRIER_HELP_TEXT, mCarrierHelpText); 246 } 247 248 private CharSequence getText(int resId) { 249 return resId == 0 ? null : getContext().getText(resId); 250 } 251 252 /** 253 * Unlock help message. This is typically for help with unlock widgets, e.g. "wrong password" 254 * or "try again." 255 * 256 * @param textResId 257 * @param lockIcon 258 */ 259 public void setHelpMessage(int textResId, int lockIcon) { 260 final CharSequence tmp = getText(textResId); 261 mHelpMessageText = tmp == null ? null : tmp.toString(); 262 update(HELP_MESSAGE_TEXT, mHelpMessageText); 263 } 264 265 private void update(int what, CharSequence string) { 266 if (inWidgetMode()) { 267 if (DEBUG) Log.v(TAG, "inWidgetMode() is true"); 268 // Use Transient text for messages shown while widget is shown. 269 switch (what) { 270 case INSTRUCTION_TEXT: 271 case CARRIER_HELP_TEXT: 272 case HELP_MESSAGE_TEXT: 273 case BATTERY_INFO: 274 mTransientTextManager.post(string, 0, INSTRUCTION_RESET_DELAY); 275 break; 276 277 case OWNER_INFO: 278 case CARRIER_TEXT: 279 default: 280 if (DEBUG) Log.w(TAG, "Not showing message id " + what + ", str=" + string); 281 } 282 } else { 283 updateStatusLines(mShowingStatus); 284 } 285 } 286 287 public void onPause() { 288 if (DEBUG) Log.v(TAG, "onPause()"); 289 mUpdateMonitor.removeCallback(mInfoCallback); 290 } 291 292 /** {@inheritDoc} */ 293 public void onResume() { 294 if (DEBUG) Log.v(TAG, "onResume()"); 295 296 // First update the clock, if present. 297 if (mDigitalClock != null) { 298 mDigitalClock.updateTime(); 299 } 300 301 mUpdateMonitor.registerCallback(mInfoCallback); 302 resetStatusInfo(); 303 // Issue the biometric unlock failure message in a centralized place 304 // TODO: we either need to make the Face Unlock multiple failures string a more general 305 // 'biometric unlock' or have each biometric unlock handle this on their own. 306 if (mUpdateMonitor.getMaxBiometricUnlockAttemptsReached()) { 307 setInstructionText(getContext().getString(R.string.faceunlock_multiple_failures)); 308 } 309 } 310 311 void resetStatusInfo() { 312 mInstructionText = null; 313 updateStatusLines(true); 314 } 315 316 /** 317 * Update the status lines based on these rules: 318 * AlarmStatus: Alarm state always gets it's own line. 319 * Status1 is shared between help, battery status and generic unlock instructions, 320 * prioritized in that order. 321 * @param showStatusLines status lines are shown if true 322 */ 323 void updateStatusLines(boolean showStatusLines) { 324 if (DEBUG) Log.v(TAG, "updateStatusLines(" + showStatusLines + ")"); 325 mShowingStatus = showStatusLines; 326 updateAlarmInfo(); 327 updateOwnerInfo(); 328 updateStatus1(); 329 updateCarrierText(); 330 } 331 332 private void updateAlarmInfo() { 333 if (mAlarmStatusView != null) { 334 String nextAlarm = mLockPatternUtils.getNextAlarm(); 335 boolean showAlarm = mShowingStatus && !TextUtils.isEmpty(nextAlarm); 336 mAlarmStatusView.setText(nextAlarm); 337 mAlarmStatusView.setCompoundDrawablesWithIntrinsicBounds(ALARM_ICON, 0, 0, 0); 338 mAlarmStatusView.setVisibility(showAlarm ? View.VISIBLE : View.GONE); 339 } 340 } 341 342 private void updateOwnerInfo() { 343 final ContentResolver res = getContext().getContentResolver(); 344 final boolean ownerInfoEnabled = Settings.Secure.getInt(res, 345 Settings.Secure.LOCK_SCREEN_OWNER_INFO_ENABLED, 1) != 0; 346 mOwnerInfoText = ownerInfoEnabled ? 347 Settings.Secure.getString(res, Settings.Secure.LOCK_SCREEN_OWNER_INFO) : null; 348 if (mOwnerInfoView != null) { 349 mOwnerInfoView.setText(mOwnerInfoText); 350 mOwnerInfoView.setVisibility(TextUtils.isEmpty(mOwnerInfoText) ? View.GONE:View.VISIBLE); 351 } 352 } 353 354 private void updateStatus1() { 355 if (mStatus1View != null) { 356 MutableInt icon = new MutableInt(0); 357 CharSequence string = getPriorityTextMessage(icon); 358 mStatus1View.setText(string); 359 mStatus1View.setCompoundDrawablesWithIntrinsicBounds(icon.value, 0, 0, 0); 360 mStatus1View.setVisibility(mShowingStatus ? View.VISIBLE : View.INVISIBLE); 361 } 362 } 363 364 private void updateCarrierText() { 365 if (!inWidgetMode() && mCarrierView != null) { 366 mCarrierView.setText(mCarrierText); 367 } 368 } 369 370 private CharSequence getAltTextMessage(MutableInt icon) { 371 // If we have replaced the status area with a single widget, then this code 372 // prioritizes what to show in that space when all transient messages are gone. 373 CharSequence string = null; 374 if (mShowingBatteryInfo) { 375 // Battery status 376 if (mPluggedIn) { 377 // Charging, charged or waiting to charge. 378 string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged 379 :R.string.lockscreen_plugged_in, mBatteryLevel); 380 icon.value = CHARGING_ICON; 381 } else if (mBatteryIsLow) { 382 // Battery is low 383 string = getContext().getString(R.string.lockscreen_low_battery); 384 icon.value = BATTERY_LOW_ICON; 385 } 386 } else { 387 string = mCarrierText; 388 } 389 return string; 390 } 391 392 private CharSequence getPriorityTextMessage(MutableInt icon) { 393 CharSequence string = null; 394 if (!TextUtils.isEmpty(mInstructionText)) { 395 // Instructions only 396 string = mInstructionText; 397 icon.value = LOCK_ICON; 398 } else if (mShowingBatteryInfo) { 399 // Battery status 400 if (mPluggedIn) { 401 // Charging, charged or waiting to charge. 402 string = getContext().getString(mBatteryCharged ? R.string.lockscreen_charged 403 :R.string.lockscreen_plugged_in, mBatteryLevel); 404 icon.value = CHARGING_ICON; 405 } else if (mBatteryIsLow) { 406 // Battery is low 407 string = getContext().getString(R.string.lockscreen_low_battery); 408 icon.value = BATTERY_LOW_ICON; 409 } 410 } else if (!inWidgetMode() && mOwnerInfoView == null && mOwnerInfoText != null) { 411 // OwnerInfo shows in status if we don't have a dedicated widget 412 string = mOwnerInfoText; 413 } 414 return string; 415 } 416 417 void refreshDate() { 418 if (mDateView != null) { 419 mDateView.setText(DateFormat.format(mDateFormatString, new Date())); 420 } 421 } 422 423 /** 424 * Determine the current status of the lock screen given the sim state and other stuff. 425 */ 426 public StatusMode getStatusForIccState(IccCardConstants.State simState) { 427 // Since reading the SIM may take a while, we assume it is present until told otherwise. 428 if (simState == null) { 429 return StatusMode.Normal; 430 } 431 432 final boolean missingAndNotProvisioned = (!mUpdateMonitor.isDeviceProvisioned() 433 && (simState == IccCardConstants.State.ABSENT || 434 simState == IccCardConstants.State.PERM_DISABLED)); 435 436 // Assume we're NETWORK_LOCKED if not provisioned 437 simState = missingAndNotProvisioned ? IccCardConstants.State.NETWORK_LOCKED : simState; 438 switch (simState) { 439 case ABSENT: 440 return StatusMode.SimMissing; 441 case NETWORK_LOCKED: 442 return StatusMode.SimMissingLocked; 443 case NOT_READY: 444 return StatusMode.SimMissing; 445 case PIN_REQUIRED: 446 return StatusMode.SimLocked; 447 case PUK_REQUIRED: 448 return StatusMode.SimPukLocked; 449 case READY: 450 return StatusMode.Normal; 451 case PERM_DISABLED: 452 return StatusMode.SimPermDisabled; 453 case UNKNOWN: 454 return StatusMode.SimMissing; 455 } 456 return StatusMode.SimMissing; 457 } 458 459 private Context getContext() { 460 return mContainer.getContext(); 461 } 462 463 /** 464 * Update carrier text, carrier help and emergency button to match the current status based 465 * on SIM state. 466 * 467 * @param simState 468 */ 469 private void updateCarrierStateWithSimStatus(IccCardConstants.State simState) { 470 if (DEBUG) Log.d(TAG, "updateCarrierTextWithSimStatus(), simState = " + simState); 471 472 CharSequence carrierText = null; 473 int carrierHelpTextId = 0; 474 mEmergencyButtonEnabledBecauseSimLocked = false; 475 mStatus = getStatusForIccState(simState); 476 mSimState = simState; 477 switch (mStatus) { 478 case Normal: 479 carrierText = makeCarierString(mPlmn, mSpn); 480 break; 481 482 case NetworkLocked: 483 carrierText = makeCarrierStringOnEmergencyCapable( 484 getContext().getText(R.string.lockscreen_network_locked_message), 485 mPlmn); 486 carrierHelpTextId = R.string.lockscreen_instructions_when_pattern_disabled; 487 break; 488 489 case SimMissing: 490 // Shows "No SIM card | Emergency calls only" on devices that are voice-capable. 491 // This depends on mPlmn containing the text "Emergency calls only" when the radio 492 // has some connectivity. Otherwise, it should be null or empty and just show 493 // "No SIM card" 494 carrierText = makeCarrierStringOnEmergencyCapable( 495 getContext().getText(R.string.lockscreen_missing_sim_message_short), 496 mPlmn); 497 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions_long; 498 break; 499 500 case SimPermDisabled: 501 carrierText = getContext().getText( 502 R.string.lockscreen_permanent_disabled_sim_message_short); 503 carrierHelpTextId = R.string.lockscreen_permanent_disabled_sim_instructions; 504 mEmergencyButtonEnabledBecauseSimLocked = true; 505 break; 506 507 case SimMissingLocked: 508 carrierText = makeCarrierStringOnEmergencyCapable( 509 getContext().getText(R.string.lockscreen_missing_sim_message_short), 510 mPlmn); 511 carrierHelpTextId = R.string.lockscreen_missing_sim_instructions; 512 mEmergencyButtonEnabledBecauseSimLocked = true; 513 break; 514 515 case SimLocked: 516 carrierText = makeCarrierStringOnEmergencyCapable( 517 getContext().getText(R.string.lockscreen_sim_locked_message), 518 mPlmn); 519 mEmergencyButtonEnabledBecauseSimLocked = true; 520 break; 521 522 case SimPukLocked: 523 carrierText = makeCarrierStringOnEmergencyCapable( 524 getContext().getText(R.string.lockscreen_sim_puk_locked_message), 525 mPlmn); 526 if (!mLockPatternUtils.isPukUnlockScreenEnable()) { 527 // This means we're showing the PUK unlock screen 528 mEmergencyButtonEnabledBecauseSimLocked = true; 529 } 530 break; 531 } 532 533 setCarrierText(carrierText); 534 setCarrierHelpText(carrierHelpTextId); 535 updateEmergencyCallButtonState(mPhoneState); 536 } 537 538 539 /* 540 * Add emergencyCallMessage to carrier string only if phone supports emergency calls. 541 */ 542 private CharSequence makeCarrierStringOnEmergencyCapable( 543 CharSequence simMessage, CharSequence emergencyCallMessage) { 544 if (mLockPatternUtils.isEmergencyCallCapable()) { 545 return makeCarierString(simMessage, emergencyCallMessage); 546 } 547 return simMessage; 548 } 549 550 private View findViewById(int id) { 551 return mContainer.findViewById(id); 552 } 553 554 /** 555 * The status of this lock screen. Primarily used for widgets on LockScreen. 556 */ 557 enum StatusMode { 558 /** 559 * Normal case (sim card present, it's not locked) 560 */ 561 Normal(true), 562 563 /** 564 * The sim card is 'network locked'. 565 */ 566 NetworkLocked(true), 567 568 /** 569 * The sim card is missing. 570 */ 571 SimMissing(false), 572 573 /** 574 * The sim card is missing, and this is the device isn't provisioned, so we don't let 575 * them get past the screen. 576 */ 577 SimMissingLocked(false), 578 579 /** 580 * The sim card is PUK locked, meaning they've entered the wrong sim unlock code too many 581 * times. 582 */ 583 SimPukLocked(false), 584 585 /** 586 * The sim card is locked. 587 */ 588 SimLocked(true), 589 590 /** 591 * The sim card is permanently disabled due to puk unlock failure 592 */ 593 SimPermDisabled(false); 594 595 private final boolean mShowStatusLines; 596 597 StatusMode(boolean mShowStatusLines) { 598 this.mShowStatusLines = mShowStatusLines; 599 } 600 601 /** 602 * @return Whether the status lines (battery level and / or next alarm) are shown while 603 * in this state. Mostly dictated by whether this is room for them. 604 */ 605 public boolean shouldShowStatusLines() { 606 return mShowStatusLines; 607 } 608 } 609 610 private void updateEmergencyCallButtonState(int phoneState) { 611 if (mEmergencyCallButton != null) { 612 boolean enabledBecauseSimLocked = 613 mLockPatternUtils.isEmergencyCallEnabledWhileSimLocked() 614 && mEmergencyButtonEnabledBecauseSimLocked; 615 boolean shown = mEmergencyCallButtonEnabledInScreen || enabledBecauseSimLocked; 616 mLockPatternUtils.updateEmergencyCallButtonState(mEmergencyCallButton, 617 phoneState, shown); 618 } 619 } 620 621 private KeyguardUpdateMonitorCallback mInfoCallback = new KeyguardUpdateMonitorCallback() { 622 623 public void onRefreshBatteryInfo(KeyguardUpdateMonitor.BatteryStatus status) { 624 mShowingBatteryInfo = status.isPluggedIn() || status.isBatteryLow(); 625 mPluggedIn = status.isPluggedIn(); 626 mBatteryLevel = status.level; 627 mBatteryCharged = status.isCharged(); 628 mBatteryIsLow = status.isBatteryLow(); 629 final MutableInt tmpIcon = new MutableInt(0); 630 update(BATTERY_INFO, getAltTextMessage(tmpIcon)); 631 } 632 633 @Override 634 public void onTimeChanged() { 635 refreshDate(); 636 } 637 638 @Override 639 public void onRefreshCarrierInfo(CharSequence plmn, CharSequence spn) { 640 mPlmn = plmn; 641 mSpn = spn; 642 updateCarrierStateWithSimStatus(mSimState); 643 } 644 645 @Override 646 public void onPhoneStateChanged(int phoneState) { 647 mPhoneState = phoneState; 648 updateEmergencyCallButtonState(phoneState); 649 } 650 651 @Override 652 public void onSimStateChanged(IccCardConstants.State simState) { 653 updateCarrierStateWithSimStatus(simState); 654 } 655 }; 656 657 public void onClick(View v) { 658 if (v == mEmergencyCallButton) { 659 mCallback.takeEmergencyCallAction(); 660 } 661 } 662 663 /** 664 * Performs concentenation of PLMN/SPN 665 * @param plmn 666 * @param spn 667 * @return 668 */ 669 private static CharSequence makeCarierString(CharSequence plmn, CharSequence spn) { 670 final boolean plmnValid = !TextUtils.isEmpty(plmn); 671 final boolean spnValid = !TextUtils.isEmpty(spn); 672 if (plmnValid && spnValid) { 673 return plmn + "|" + spn; 674 } else if (plmnValid) { 675 return plmn; 676 } else if (spnValid) { 677 return spn; 678 } else { 679 return ""; 680 } 681 } 682} 683