1 /* 2 * Copyright (C) 2012 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.internal.telephony.cdma; 18 19 import com.android.internal.telephony.TelephonyProperties; 20 import com.android.internal.telephony.MccTable; 21 import com.android.internal.telephony.EventLogTags; 22 import com.android.internal.telephony.uicc.RuimRecords; 23 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 24 25 import android.telephony.CellInfo; 26 import android.telephony.CellInfoLte; 27 import android.telephony.CellSignalStrengthLte; 28 import android.telephony.CellIdentityLte; 29 import android.telephony.SignalStrength; 30 import android.telephony.ServiceState; 31 import android.telephony.cdma.CdmaCellLocation; 32 import android.text.TextUtils; 33 import android.os.AsyncResult; 34 import android.os.Message; 35 import android.os.SystemClock; 36 import android.os.SystemProperties; 37 38 import android.telephony.Rlog; 39 import android.util.EventLog; 40 41 import java.io.FileDescriptor; 42 import java.io.PrintWriter; 43 import java.util.ArrayList; 44 import java.util.List; 45 46 public class CdmaLteServiceStateTracker extends CdmaServiceStateTracker { 47 private CDMALTEPhone mCdmaLtePhone; 48 private final CellInfoLte mCellInfoLte; 49 50 private CellIdentityLte mNewCellIdentityLte = new CellIdentityLte(); 51 private CellIdentityLte mLasteCellIdentityLte = new CellIdentityLte(); 52 53 public CdmaLteServiceStateTracker(CDMALTEPhone phone) { 54 super(phone, new CellInfoLte()); 55 mCdmaLtePhone = phone; 56 mCellInfoLte = (CellInfoLte) mCellInfo; 57 58 ((CellInfoLte)mCellInfo).setCellSignalStrength(new CellSignalStrengthLte()); 59 ((CellInfoLte)mCellInfo).setCellIdentity(new CellIdentityLte()); 60 61 if (DBG) log("CdmaLteServiceStateTracker Constructors"); 62 } 63 64 @Override 65 public void handleMessage(Message msg) { 66 AsyncResult ar; 67 int[] ints; 68 String[] strings; 69 70 if (!mPhone.mIsTheCurrentActivePhone) { 71 loge("Received message " + msg + "[" + msg.what + "]" + 72 " while being destroyed. Ignoring."); 73 return; 74 } 75 76 switch (msg.what) { 77 case EVENT_POLL_STATE_GPRS: 78 if (DBG) log("handleMessage EVENT_POLL_STATE_GPRS"); 79 ar = (AsyncResult)msg.obj; 80 handlePollStateResult(msg.what, ar); 81 break; 82 case EVENT_RUIM_RECORDS_LOADED: 83 updatePhoneObject(); 84 RuimRecords ruim = (RuimRecords)mIccRecords; 85 if ((ruim != null) && ruim.isProvisioned()) { 86 mMdn = ruim.getMdn(); 87 mMin = ruim.getMin(); 88 parseSidNid(ruim.getSid(), ruim.getNid()); 89 mPrlVersion = ruim.getPrlVersion(); 90 mIsMinInfoReady = true; 91 updateOtaspState(); 92 } 93 // SID/NID/PRL is loaded. Poll service state 94 // again to update to the roaming state with 95 // the latest variables. 96 pollState(); 97 break; 98 default: 99 super.handleMessage(msg); 100 } 101 } 102 103 /** 104 * Handle the result of one of the pollState()-related requests 105 */ 106 @Override 107 protected void handlePollStateResultMessage(int what, AsyncResult ar) { 108 if (what == EVENT_POLL_STATE_GPRS) { 109 String states[] = (String[])ar.result; 110 if (DBG) { 111 log("handlePollStateResultMessage: EVENT_POLL_STATE_GPRS states.length=" + 112 states.length + " states=" + states); 113 } 114 115 int type = 0; 116 int regState = -1; 117 if (states.length > 0) { 118 try { 119 regState = Integer.parseInt(states[0]); 120 121 // states[3] (if present) is the current radio technology 122 if (states.length >= 4 && states[3] != null) { 123 type = Integer.parseInt(states[3]); 124 } 125 } catch (NumberFormatException ex) { 126 loge("handlePollStateResultMessage: error parsing GprsRegistrationState: " 127 + ex); 128 } 129 if (states.length >= 10) { 130 int mcc; 131 int mnc; 132 int tac; 133 int pci; 134 int eci; 135 int csgid; 136 String operatorNumeric = null; 137 138 try { 139 operatorNumeric = mNewSS.getOperatorNumeric(); 140 mcc = Integer.parseInt(operatorNumeric.substring(0,3)); 141 } catch (Exception e) { 142 try { 143 operatorNumeric = mSS.getOperatorNumeric(); 144 mcc = Integer.parseInt(operatorNumeric.substring(0,3)); 145 } catch (Exception ex) { 146 loge("handlePollStateResultMessage: bad mcc operatorNumeric=" + 147 operatorNumeric + " ex=" + ex); 148 operatorNumeric = ""; 149 mcc = Integer.MAX_VALUE; 150 } 151 } 152 try { 153 mnc = Integer.parseInt(operatorNumeric.substring(3)); 154 } catch (Exception e) { 155 loge("handlePollStateResultMessage: bad mnc operatorNumeric=" + 156 operatorNumeric + " e=" + e); 157 mnc = Integer.MAX_VALUE; 158 } 159 160 // Use Integer#decode to be generous in what we receive and allow 161 // decimal, hex or octal values. 162 try { 163 tac = Integer.decode(states[6]); 164 } catch (Exception e) { 165 loge("handlePollStateResultMessage: bad tac states[6]=" + 166 states[6] + " e=" + e); 167 tac = Integer.MAX_VALUE; 168 } 169 try { 170 pci = Integer.decode(states[7]); 171 } catch (Exception e) { 172 loge("handlePollStateResultMessage: bad pci states[7]=" + 173 states[7] + " e=" + e); 174 pci = Integer.MAX_VALUE; 175 } 176 try { 177 eci = Integer.decode(states[8]); 178 } catch (Exception e) { 179 loge("handlePollStateResultMessage: bad eci states[8]=" + 180 states[8] + " e=" + e); 181 eci = Integer.MAX_VALUE; 182 } 183 try { 184 csgid = Integer.decode(states[9]); 185 } catch (Exception e) { 186 // FIX: Always bad so don't pollute the logs 187 // loge("handlePollStateResultMessage: bad csgid states[9]=" + 188 // states[9] + " e=" + e); 189 csgid = Integer.MAX_VALUE; 190 } 191 mNewCellIdentityLte = new CellIdentityLte(mcc, mnc, eci, pci, tac); 192 if (DBG) { 193 log("handlePollStateResultMessage: mNewLteCellIdentity=" + 194 mNewCellIdentityLte); 195 } 196 } 197 } 198 199 mNewSS.setRilDataRadioTechnology(type); 200 int dataRegState = regCodeToServiceState(regState); 201 mNewSS.setDataRegState(dataRegState); 202 if (DBG) { 203 log("handlPollStateResultMessage: CdmaLteSST setDataRegState=" + dataRegState 204 + " regState=" + regState 205 + " dataRadioTechnology=" + type); 206 } 207 } else { 208 super.handlePollStateResultMessage(what, ar); 209 } 210 } 211 212 @Override 213 protected void pollState() { 214 mPollingContext = new int[1]; 215 mPollingContext[0] = 0; 216 217 switch (mCi.getRadioState()) { 218 case RADIO_UNAVAILABLE: 219 mNewSS.setStateOutOfService(); 220 mNewCellLoc.setStateInvalid(); 221 setSignalStrengthDefaultValues(); 222 mGotCountryCode = false; 223 224 pollStateDone(); 225 break; 226 227 case RADIO_OFF: 228 mNewSS.setStateOff(); 229 mNewCellLoc.setStateInvalid(); 230 setSignalStrengthDefaultValues(); 231 mGotCountryCode = false; 232 233 pollStateDone(); 234 break; 235 236 default: 237 // Issue all poll-related commands at once, then count 238 // down the responses which are allowed to arrive 239 // out-of-order. 240 241 mPollingContext[0]++; 242 // RIL_REQUEST_OPERATOR is necessary for CDMA 243 mCi.getOperator(obtainMessage(EVENT_POLL_STATE_OPERATOR_CDMA, mPollingContext)); 244 245 mPollingContext[0]++; 246 // RIL_REQUEST_VOICE_REGISTRATION_STATE is necessary for CDMA 247 mCi.getVoiceRegistrationState(obtainMessage(EVENT_POLL_STATE_REGISTRATION_CDMA, 248 mPollingContext)); 249 250 mPollingContext[0]++; 251 // RIL_REQUEST_DATA_REGISTRATION_STATE 252 mCi.getDataRegistrationState(obtainMessage(EVENT_POLL_STATE_GPRS, 253 mPollingContext)); 254 break; 255 } 256 } 257 258 @Override 259 protected void pollStateDone() { 260 log("pollStateDone: lte 1 ss=[" + mSS + "] newSS=[" + mNewSS + "]"); 261 262 useDataRegStateForDataOnlyDevices(); 263 264 boolean hasRegistered = mSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE 265 && mNewSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE; 266 267 boolean hasDeregistered = mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE 268 && mNewSS.getVoiceRegState() != ServiceState.STATE_IN_SERVICE; 269 270 boolean hasCdmaDataConnectionAttached = 271 mSS.getDataRegState() != ServiceState.STATE_IN_SERVICE 272 && mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE; 273 274 boolean hasCdmaDataConnectionDetached = 275 mSS.getDataRegState() == ServiceState.STATE_IN_SERVICE 276 && mNewSS.getDataRegState() != ServiceState.STATE_IN_SERVICE; 277 278 boolean hasCdmaDataConnectionChanged = 279 mSS.getDataRegState() != mNewSS.getDataRegState(); 280 281 boolean hasVoiceRadioTechnologyChanged = mSS.getRilVoiceRadioTechnology() 282 != mNewSS.getRilVoiceRadioTechnology(); 283 284 boolean hasDataRadioTechnologyChanged = mSS.getRilDataRadioTechnology() 285 != mNewSS.getRilDataRadioTechnology(); 286 287 boolean hasChanged = !mNewSS.equals(mSS); 288 289 boolean hasRoamingOn = !mSS.getRoaming() && mNewSS.getRoaming(); 290 291 boolean hasRoamingOff = mSS.getRoaming() && !mNewSS.getRoaming(); 292 293 boolean hasLocationChanged = !mNewCellLoc.equals(mCellLoc); 294 295 boolean has4gHandoff = 296 mNewSS.getDataRegState() == ServiceState.STATE_IN_SERVICE && 297 (((mSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && 298 (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)) || 299 ((mSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD) && 300 (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_LTE))); 301 302 boolean hasMultiApnSupport = 303 (((mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) || 304 (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD)) && 305 ((mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_LTE) && 306 (mSS.getRilDataRadioTechnology() != ServiceState.RIL_RADIO_TECHNOLOGY_EHRPD))); 307 308 boolean hasLostMultiApnSupport = 309 ((mNewSS.getRilDataRadioTechnology() >= ServiceState.RIL_RADIO_TECHNOLOGY_IS95A) && 310 (mNewSS.getRilDataRadioTechnology() <= ServiceState.RIL_RADIO_TECHNOLOGY_EVDO_A)); 311 312 if (DBG) { 313 log("pollStateDone:" 314 + " hasRegistered=" + hasRegistered 315 + " hasDeegistered=" + hasDeregistered 316 + " hasCdmaDataConnectionAttached=" + hasCdmaDataConnectionAttached 317 + " hasCdmaDataConnectionDetached=" + hasCdmaDataConnectionDetached 318 + " hasCdmaDataConnectionChanged=" + hasCdmaDataConnectionChanged 319 + " hasVoiceRadioTechnologyChanged= " + hasVoiceRadioTechnologyChanged 320 + " hasDataRadioTechnologyChanged=" + hasDataRadioTechnologyChanged 321 + " hasChanged=" + hasChanged 322 + " hasRoamingOn=" + hasRoamingOn 323 + " hasRoamingOff=" + hasRoamingOff 324 + " hasLocationChanged=" + hasLocationChanged 325 + " has4gHandoff = " + has4gHandoff 326 + " hasMultiApnSupport=" + hasMultiApnSupport 327 + " hasLostMultiApnSupport=" + hasLostMultiApnSupport); 328 } 329 // Add an event log when connection state changes 330 if (mSS.getVoiceRegState() != mNewSS.getVoiceRegState() 331 || mSS.getDataRegState() != mNewSS.getDataRegState()) { 332 EventLog.writeEvent(EventLogTags.CDMA_SERVICE_STATE_CHANGE, mSS.getVoiceRegState(), 333 mSS.getDataRegState(), mNewSS.getVoiceRegState(), mNewSS.getDataRegState()); 334 } 335 336 ServiceState tss; 337 tss = mSS; 338 mSS = mNewSS; 339 mNewSS = tss; 340 // clean slate for next time 341 mNewSS.setStateOutOfService(); 342 343 CdmaCellLocation tcl = mCellLoc; 344 mCellLoc = mNewCellLoc; 345 mNewCellLoc = tcl; 346 347 mNewSS.setStateOutOfService(); // clean slate for next time 348 349 if (hasVoiceRadioTechnologyChanged) { 350 updatePhoneObject(); 351 } 352 353 if (hasDataRadioTechnologyChanged) { 354 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_DATA_NETWORK_TYPE, 355 ServiceState.rilRadioTechnologyToString(mSS.getRilDataRadioTechnology())); 356 } 357 358 if (hasRegistered) { 359 mNetworkAttachedRegistrants.notifyRegistrants(); 360 } 361 362 if (hasChanged) { 363 if (mPhone.isEriFileLoaded()) { 364 String eriText; 365 // Now the CDMAPhone sees the new ServiceState so it can get the 366 // new ERI text 367 if (mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE) { 368 eriText = mPhone.getCdmaEriText(); 369 } else if (mSS.getVoiceRegState() == ServiceState.STATE_POWER_OFF) { 370 eriText = (mIccRecords != null) ? mIccRecords.getServiceProviderName() : null; 371 if (TextUtils.isEmpty(eriText)) { 372 // Sets operator alpha property by retrieving from 373 // build-time system property 374 eriText = SystemProperties.get("ro.cdma.home.operator.alpha"); 375 } 376 } else { 377 // Note that ServiceState.STATE_OUT_OF_SERVICE is valid used 378 // for mRegistrationState 0,2,3 and 4 379 eriText = mPhone.getContext() 380 .getText(com.android.internal.R.string.roamingTextSearching).toString(); 381 } 382 mSS.setOperatorAlphaLong(eriText); 383 } 384 385 if (mUiccApplcation != null && mUiccApplcation.getState() == AppState.APPSTATE_READY && 386 mIccRecords != null) { 387 // SIM is found on the device. If ERI roaming is OFF, and SID/NID matches 388 // one configured in SIM, use operator name from CSIM record. 389 boolean showSpn = 390 ((RuimRecords)mIccRecords).getCsimSpnDisplayCondition(); 391 int iconIndex = mSS.getCdmaEriIconIndex(); 392 393 if (showSpn && (iconIndex == EriInfo.ROAMING_INDICATOR_OFF) && 394 isInHomeSidNid(mSS.getSystemId(), mSS.getNetworkId()) && 395 mIccRecords != null) { 396 mSS.setOperatorAlphaLong(mIccRecords.getServiceProviderName()); 397 } 398 } 399 400 String operatorNumeric; 401 402 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ALPHA, 403 mSS.getOperatorAlphaLong()); 404 405 String prevOperatorNumeric = 406 SystemProperties.get(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, ""); 407 operatorNumeric = mSS.getOperatorNumeric(); 408 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_NUMERIC, operatorNumeric); 409 410 if (operatorNumeric == null) { 411 if (DBG) log("operatorNumeric is null"); 412 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, ""); 413 mGotCountryCode = false; 414 } else { 415 String isoCountryCode = ""; 416 String mcc = operatorNumeric.substring(0, 3); 417 try { 418 isoCountryCode = MccTable.countryCodeForMcc(Integer.parseInt(operatorNumeric 419 .substring(0, 3))); 420 } catch (NumberFormatException ex) { 421 loge("countryCodeForMcc error" + ex); 422 } catch (StringIndexOutOfBoundsException ex) { 423 loge("countryCodeForMcc error" + ex); 424 } 425 426 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISO_COUNTRY, 427 isoCountryCode); 428 mGotCountryCode = true; 429 430 if (shouldFixTimeZoneNow(mPhone, operatorNumeric, prevOperatorNumeric, 431 mNeedFixZone)) { 432 fixTimeZone(isoCountryCode); 433 } 434 } 435 436 mPhone.setSystemProperty(TelephonyProperties.PROPERTY_OPERATOR_ISROAMING, 437 mSS.getRoaming() ? "true" : "false"); 438 439 updateSpnDisplay(); 440 mPhone.notifyServiceStateChanged(mSS); 441 } 442 443 if (hasCdmaDataConnectionAttached || has4gHandoff) { 444 mAttachedRegistrants.notifyRegistrants(); 445 } 446 447 if (hasCdmaDataConnectionDetached) { 448 mDetachedRegistrants.notifyRegistrants(); 449 } 450 451 if ((hasCdmaDataConnectionChanged || hasDataRadioTechnologyChanged)) { 452 notifyDataRegStateRilRadioTechnologyChanged(); 453 mPhone.notifyDataConnection(null); 454 } 455 456 if (hasRoamingOn) { 457 mRoamingOnRegistrants.notifyRegistrants(); 458 } 459 460 if (hasRoamingOff) { 461 mRoamingOffRegistrants.notifyRegistrants(); 462 } 463 464 if (hasLocationChanged) { 465 mPhone.notifyLocationChanged(); 466 } 467 468 ArrayList<CellInfo> arrayCi = new ArrayList<CellInfo>(); 469 synchronized(mCellInfo) { 470 CellInfoLte cil = (CellInfoLte)mCellInfo; 471 472 boolean cidChanged = ! mNewCellIdentityLte.equals(mLasteCellIdentityLte); 473 if (hasRegistered || hasDeregistered || cidChanged) { 474 // TODO: Handle the absence of LteCellIdentity 475 long timeStamp = SystemClock.elapsedRealtime() * 1000; 476 boolean registered = mSS.getVoiceRegState() == ServiceState.STATE_IN_SERVICE; 477 mLasteCellIdentityLte = mNewCellIdentityLte; 478 479 cil.setRegisterd(registered); 480 cil.setCellIdentity(mLasteCellIdentityLte); 481 if (DBG) { 482 log("pollStateDone: hasRegistered=" + hasRegistered + 483 " hasDeregistered=" + hasDeregistered + 484 " cidChanged=" + cidChanged + 485 " mCellInfo=" + mCellInfo); 486 } 487 arrayCi.add(mCellInfo); 488 } 489 mPhoneBase.notifyCellInfo(arrayCi); 490 } 491 } 492 493 @Override 494 protected boolean onSignalStrengthResult(AsyncResult ar, boolean isGsm) { 495 if (mSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) { 496 isGsm = true; 497 } 498 boolean ssChanged = super.onSignalStrengthResult(ar, isGsm); 499 500 synchronized (mCellInfo) { 501 if (mSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_LTE) { 502 mCellInfoLte.setTimeStamp(SystemClock.elapsedRealtime() * 1000); 503 mCellInfoLte.setTimeStampType(CellInfo.TIMESTAMP_TYPE_JAVA_RIL); 504 mCellInfoLte.getCellSignalStrength() 505 .initialize(mSignalStrength,SignalStrength.INVALID); 506 } 507 if (mCellInfoLte.getCellIdentity() != null) { 508 ArrayList<CellInfo> arrayCi = new ArrayList<CellInfo>(); 509 arrayCi.add(mCellInfoLte); 510 mPhoneBase.notifyCellInfo(arrayCi); 511 } 512 } 513 return ssChanged; 514 } 515 516 @Override 517 public boolean isConcurrentVoiceAndDataAllowed() { 518 // Using the Conncurrent Service Supported flag for CdmaLte devices. 519 return mSS.getCssIndicator() == 1; 520 } 521 522 /** 523 * Check whether the specified SID and NID pair appears in the HOME SID/NID list 524 * read from NV or SIM. 525 * 526 * @return true if provided sid/nid pair belongs to operator's home network. 527 */ 528 private boolean isInHomeSidNid(int sid, int nid) { 529 // if SID/NID is not available, assume this is home network. 530 if (isSidsAllZeros()) return true; 531 532 // length of SID/NID shold be same 533 if (mHomeSystemId.length != mHomeNetworkId.length) return true; 534 535 if (sid == 0) return true; 536 537 for (int i = 0; i < mHomeSystemId.length; i++) { 538 // Use SID only if NID is a reserved value. 539 // SID 0 and NID 0 and 65535 are reserved. (C.0005 2.6.5.2) 540 if ((mHomeSystemId[i] == sid) && 541 ((mHomeNetworkId[i] == 0) || (mHomeNetworkId[i] == 65535) || 542 (nid == 0) || (nid == 65535) || (mHomeNetworkId[i] == nid))) { 543 return true; 544 } 545 } 546 // SID/NID are not in the list. So device is not in home network 547 return false; 548 } 549 550 /** 551 * TODO: Remove when we get new ril/modem for Galaxy Nexus. 552 * 553 * @return all available cell information, the returned List maybe empty but never null. 554 */ 555 @Override 556 public List<CellInfo> getAllCellInfo() { 557 if (mCi.getRilVersion() >= 8) { 558 return super.getAllCellInfo(); 559 } else { 560 ArrayList<CellInfo> arrayList = new ArrayList<CellInfo>(); 561 CellInfo ci; 562 synchronized(mCellInfo) { 563 arrayList.add(mCellInfoLte); 564 } 565 if (DBG) log ("getAllCellInfo: arrayList=" + arrayList); 566 return arrayList; 567 } 568 } 569 570 @Override 571 protected void log(String s) { 572 Rlog.d(LOG_TAG, "[CdmaLteSST] " + s); 573 } 574 575 @Override 576 protected void loge(String s) { 577 Rlog.e(LOG_TAG, "[CdmaLteSST] " + s); 578 } 579 580 @Override 581 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { 582 pw.println("CdmaLteServiceStateTracker extends:"); 583 super.dump(fd, pw, args); 584 pw.println(" mCdmaLtePhone=" + mCdmaLtePhone); 585 } 586 } 587