1 /* 2 * Copyright (C) 2019 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.cdnr; 18 19 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CARRIER_API; 20 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CARRIER_CONFIG; 21 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CSIM; 22 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_DATA_OPERATOR_SIGNALLING; 23 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_ERI; 24 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_MODEM_CONFIG; 25 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_RUIM; 26 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_SIM; 27 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_USIM; 28 import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_VOICE_OPERATOR_SIGNALLING; 29 30 import android.annotation.NonNull; 31 import android.content.Context; 32 import android.content.res.Resources; 33 import android.os.PersistableBundle; 34 import android.telephony.CarrierConfigManager; 35 import android.telephony.Rlog; 36 import android.telephony.ServiceState; 37 import android.text.TextUtils; 38 import android.util.LocalLog; 39 import android.util.SparseArray; 40 41 import com.android.internal.telephony.GsmCdmaPhone; 42 import com.android.internal.telephony.cdnr.EfData.EFSource; 43 import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; 44 import com.android.internal.telephony.uicc.IccRecords; 45 import com.android.internal.telephony.uicc.IccRecords.CarrierNameDisplayConditionBitmask; 46 import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo; 47 import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName; 48 import com.android.internal.telephony.uicc.RuimRecords; 49 import com.android.internal.telephony.uicc.SIMRecords; 50 import com.android.internal.util.IndentingPrintWriter; 51 52 import java.util.Arrays; 53 import java.util.Collections; 54 import java.util.List; 55 import java.util.Locale; 56 import java.util.Objects; 57 58 /** Carrier display name resolver. */ 59 public class CarrierDisplayNameResolver { 60 private static final boolean DBG = true; 61 private static final String TAG = "CDNR"; 62 63 /** 64 * Only display SPN in home network, and PLMN network name in roaming network. 65 */ 66 @CarrierNameDisplayConditionBitmask 67 private static final int DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK = 0; 68 69 private static final CarrierDisplayNameConditionRule DEFAULT_CARRIER_DISPLAY_NAME_RULE = 70 new CarrierDisplayNameConditionRule(DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK); 71 72 private final SparseArray<EfData> mEf = new SparseArray<>(); 73 74 private final LocalLog mLocalLog; 75 private final Context mContext; 76 private final GsmCdmaPhone mPhone; 77 private final CarrierConfigManager mCCManager; 78 79 private CarrierDisplayNameData mCarrierDisplayNameData; 80 81 /** 82 * The priority of ef source. Lower index means higher priority. 83 */ 84 private static final List<Integer> EF_SOURCE_PRIORITY = 85 Arrays.asList( 86 EF_SOURCE_CARRIER_API, 87 EF_SOURCE_CARRIER_CONFIG, 88 EF_SOURCE_ERI, 89 EF_SOURCE_USIM, 90 EF_SOURCE_SIM, 91 EF_SOURCE_CSIM, 92 EF_SOURCE_RUIM, 93 EF_SOURCE_VOICE_OPERATOR_SIGNALLING, 94 EF_SOURCE_DATA_OPERATOR_SIGNALLING, 95 EF_SOURCE_MODEM_CONFIG); 96 CarrierDisplayNameResolver(GsmCdmaPhone phone)97 public CarrierDisplayNameResolver(GsmCdmaPhone phone) { 98 mLocalLog = new LocalLog(32); 99 mContext = phone.getContext(); 100 mPhone = phone; 101 mCCManager = (CarrierConfigManager) mContext.getSystemService( 102 Context.CARRIER_CONFIG_SERVICE); 103 } 104 105 /** 106 * Update the ef from Ruim. If {@code ruim} is null, the ef records from this source will be 107 * removed. 108 * 109 * @param ruim Ruim records. 110 */ updateEfFromRuim(RuimRecords ruim)111 public void updateEfFromRuim(RuimRecords ruim) { 112 int key = getSourcePriority(EF_SOURCE_RUIM); 113 if (ruim == null) { 114 mEf.remove(key); 115 } else { 116 mEf.put(key, new RuimEfData(ruim)); 117 } 118 } 119 120 /** 121 * Update the ef from Usim. If {@code usim} is null, the ef records from this source will be 122 * removed. 123 * 124 * @param usim Usim records. 125 */ updateEfFromUsim(SIMRecords usim)126 public void updateEfFromUsim(SIMRecords usim) { 127 int key = getSourcePriority(EF_SOURCE_USIM); 128 if (usim == null) { 129 mEf.remove(key); 130 } else { 131 mEf.put(key, new UsimEfData(usim)); 132 } 133 } 134 135 /** 136 * Update the ef from carrier config. If {@code config} is null, the ef records from this source 137 * will be removed. 138 * 139 * @param config carrier config. 140 */ updateEfFromCarrierConfig(PersistableBundle config)141 public void updateEfFromCarrierConfig(PersistableBundle config) { 142 int key = getSourcePriority(EF_SOURCE_CARRIER_CONFIG); 143 if (config == null) { 144 mEf.remove(key); 145 } else { 146 mEf.put(key, new CarrierConfigEfData(config)); 147 } 148 } 149 150 /** 151 * Update the ef for CDMA eri text. The ef records from this source will be set all of the 152 * following situation are satisfied. 153 * 154 * 1. {@code eriText} is neither empty nor null. 155 * 2. Current network is CDMA or CdmaLte 156 * 3. ERI is allowed. 157 * 158 * @param eriText 159 */ updateEfForEri(String eriText)160 public void updateEfForEri(String eriText) { 161 PersistableBundle config = getCarrierConfig(); 162 int key = getSourcePriority(EF_SOURCE_ERI); 163 if (!TextUtils.isEmpty(eriText) && (mPhone.isPhoneTypeCdma() || mPhone.isPhoneTypeCdmaLte()) 164 && config.getBoolean(CarrierConfigManager.KEY_ALLOW_ERI_BOOL)) { 165 mEf.put(key, new EriEfData(eriText)); 166 } else { 167 mEf.remove(key); 168 } 169 } 170 171 /** 172 * Update the ef for brandOverride. If {@code operatorName} is empty or null, the ef records 173 * from this source will be removed. 174 * 175 * @param operatorName operator name from brand override. 176 */ updateEfForBrandOverride(String operatorName)177 public void updateEfForBrandOverride(String operatorName) { 178 int key = getSourcePriority(EF_SOURCE_CARRIER_API); 179 if (TextUtils.isEmpty(operatorName)) { 180 mEf.remove(key); 181 } else { 182 mEf.put(key, 183 new BrandOverrideEfData(operatorName, getServiceState().getOperatorNumeric())); 184 } 185 } 186 187 /** Get the resolved carrier display name. */ getCarrierDisplayNameData()188 public CarrierDisplayNameData getCarrierDisplayNameData() { 189 resolveCarrierDisplayName(); 190 return mCarrierDisplayNameData; 191 } 192 193 @Override toString()194 public String toString() { 195 StringBuilder sb = new StringBuilder(); 196 for (int i = 0; i < mEf.size(); i++) { 197 EfData p = mEf.valueAt(i); 198 sb.append("{spnDisplayCondition = " + p.getServiceProviderNameDisplayCondition() 199 + ", spn = " + p.getServiceProviderName() 200 + ", spdiList = " + p.getServiceProviderDisplayInformation() 201 + ", pnnList = " + p.getPlmnNetworkNameList() 202 + ", oplList = " + p.getOperatorPlmnList() 203 + ", ehplmn = " + p.getEhplmnList() 204 + "}, "); 205 } 206 sb.append(", roamingFromSS = " + getServiceState().getRoaming()); 207 sb.append(", registeredPLMN = " + getServiceState().getOperatorNumeric()); 208 return sb.toString(); 209 } 210 211 /** 212 * Dumps information for carrier display name resolver. 213 * @param pw information printer. 214 */ dump(IndentingPrintWriter pw)215 public void dump(IndentingPrintWriter pw) { 216 pw.println("CDNR:"); 217 pw.increaseIndent(); 218 pw.println("fields = " + toString()); 219 pw.println("carrierDisplayNameData = " + mCarrierDisplayNameData); 220 pw.decreaseIndent(); 221 222 pw.println("CDNR local log:"); 223 pw.increaseIndent(); 224 mLocalLog.dump(pw); 225 pw.decreaseIndent(); 226 } 227 228 @NonNull getCarrierConfig()229 private PersistableBundle getCarrierConfig() { 230 PersistableBundle config = mCCManager.getConfigForSubId(mPhone.getSubId()); 231 if (config == null) config = CarrierConfigManager.getDefaultConfig(); 232 return config; 233 } 234 235 @NonNull getDisplayRule()236 private CarrierDisplayNameConditionRule getDisplayRule() { 237 for (int i = 0; i < mEf.size(); i++) { 238 if (mEf.valueAt(i).getServiceProviderNameDisplayCondition() 239 != IccRecords.INVALID_CARRIER_NAME_DISPLAY_CONDITION_BITMASK) { 240 return new CarrierDisplayNameConditionRule( 241 mEf.valueAt(i).getServiceProviderNameDisplayCondition()); 242 } 243 } 244 return DEFAULT_CARRIER_DISPLAY_NAME_RULE; 245 } 246 247 @NonNull getEfSpdi()248 private List<String> getEfSpdi() { 249 for (int i = 0; i < mEf.size(); i++) { 250 if (mEf.valueAt(i).getServiceProviderDisplayInformation() != null) { 251 return mEf.valueAt(i).getServiceProviderDisplayInformation(); 252 } 253 } 254 return Collections.EMPTY_LIST; 255 } 256 257 @NonNull getEfSpn()258 private String getEfSpn() { 259 for (int i = 0; i < mEf.size(); i++) { 260 if (!TextUtils.isEmpty(mEf.valueAt(i).getServiceProviderName())) { 261 return mEf.valueAt(i).getServiceProviderName(); 262 } 263 } 264 return ""; 265 } 266 267 @NonNull getEfOpl()268 private List<OperatorPlmnInfo> getEfOpl() { 269 for (int i = 0; i < mEf.size(); i++) { 270 if (mEf.valueAt(i).getOperatorPlmnList() != null) { 271 return mEf.valueAt(i).getOperatorPlmnList(); 272 } 273 } 274 return Collections.EMPTY_LIST; 275 } 276 277 @NonNull getEfPnn()278 private List<PlmnNetworkName> getEfPnn() { 279 for (int i = 0; i < mEf.size(); i++) { 280 if (mEf.valueAt(i).getPlmnNetworkNameList() != null) { 281 return mEf.valueAt(i).getPlmnNetworkNameList(); 282 } 283 } 284 return Collections.EMPTY_LIST; 285 } 286 getCarrierDisplayNameFromEf()287 private CarrierDisplayNameData getCarrierDisplayNameFromEf() { 288 CarrierDisplayNameConditionRule displayRule = getDisplayRule(); 289 290 String registeredPlmnNumeric = getServiceState().getOperatorNumeric(); 291 List<String> efSpdi = getEfSpdi(); 292 293 // Currently use the roaming state from ServiceState. 294 // EF_SPDI is only used when determine the service provider name and PLMN network name 295 // display condition rule. 296 // All the PLMNs will be considered HOME PLMNs if there is a brand override. 297 boolean isRoaming = getServiceState().getRoaming() 298 && !efSpdi.contains(registeredPlmnNumeric); 299 boolean showSpn = displayRule.shouldShowSpn(isRoaming); 300 boolean showPlmn = displayRule.shouldShowPnn(isRoaming); 301 String spn = getEfSpn(); 302 303 // Resolve the PLMN network name 304 List<OperatorPlmnInfo> efOpl = getEfOpl(); 305 List<PlmnNetworkName> efPnn = getEfPnn(); 306 307 String plmn = null; 308 if (efOpl.isEmpty()) { 309 // If the EF_OPL is not present, then the first record in EF_PNN is used for the 310 // default network name when registered in the HPLMN or an EHPLMN(if the EHPLMN list 311 // is present). 312 plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0)); 313 } else { 314 // TODO: Check the TAC/LAC & registered PLMN numeric in OPL list to determine which 315 // PLMN name should be used to override the current one. 316 } 317 318 // If no PLMN override is present, then the PLMN should be displayed numerically. 319 if (TextUtils.isEmpty(plmn)) { 320 plmn = registeredPlmnNumeric; 321 } 322 323 return new CarrierDisplayNameData.Builder() 324 .setSpn(spn) 325 .setShowSpn(showSpn) 326 .setPlmn(plmn) 327 .setShowPlmn(showPlmn) 328 .build(); 329 } 330 getCarrierDisplayNameFromWifiCallingOverride( CarrierDisplayNameData rawCarrierDisplayNameData)331 private CarrierDisplayNameData getCarrierDisplayNameFromWifiCallingOverride( 332 CarrierDisplayNameData rawCarrierDisplayNameData) { 333 PersistableBundle config = getCarrierConfig(); 334 boolean useRootLocale = config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE); 335 Resources r = mContext.getResources(); 336 if (useRootLocale) r.getConfiguration().setLocale(Locale.ROOT); 337 String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats); 338 WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats, 339 getServiceState().getVoiceRegState() == ServiceState.STATE_POWER_OFF); 340 341 // Override the spn, data spn, plmn by wifi-calling 342 String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn()); 343 String wfcDataSpn = wfcFormatter.formatDataName(rawCarrierDisplayNameData.getSpn()); 344 String wfcPlmn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getPlmn()); 345 CarrierDisplayNameData result = rawCarrierDisplayNameData; 346 if (!TextUtils.isEmpty(wfcSpn) && !TextUtils.isEmpty(wfcDataSpn)) { 347 result = new CarrierDisplayNameData.Builder() 348 .setSpn(wfcSpn) 349 .setDataSpn(wfcDataSpn) 350 .setShowSpn(true) 351 .build(); 352 } else if (!TextUtils.isEmpty(wfcPlmn)) { 353 result = new CarrierDisplayNameData.Builder() 354 .setPlmn(wfcPlmn) 355 .setShowPlmn(true) 356 .build(); 357 } 358 return result; 359 } 360 361 /** 362 * Override the given carrier display name data {@code data} by out of service rule. 363 * @param data the carrier display name data need to be overridden. 364 * @return overridden carrier display name data. 365 */ getOutOfServiceDisplayName(CarrierDisplayNameData data)366 private CarrierDisplayNameData getOutOfServiceDisplayName(CarrierDisplayNameData data) { 367 // Out of service/Power off/Emergency Only override 368 // 1) In flight mode(service state is ServiceState.STATE_POWER_OFF), or the service 369 // state is ServiceState.STATE_OUT_OF_SERVICE but emergency call is not allowed. 370 // showPlmn = true 371 // Only show "No Service" as PLMN 372 // 373 // 2) Out of service but emergency call is allowed. 374 // showPlmn = true 375 // Only show "Emergency call only" as PLMN 376 String plmn = null; 377 boolean isSimReady = mPhone.getUiccCardApplication() != null 378 && mPhone.getUiccCardApplication().getState() == AppState.APPSTATE_READY; 379 boolean forceDisplayNoService = mContext.getResources().getBoolean( 380 com.android.internal.R.bool.config_display_no_service_when_sim_unready) 381 && !isSimReady; 382 ServiceState ss = getServiceState(); 383 if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF || !ss.isEmergencyOnly() 384 || forceDisplayNoService) { 385 plmn = mContext.getResources().getString( 386 com.android.internal.R.string.lockscreen_carrier_default); 387 } else { 388 plmn = mContext.getResources().getString( 389 com.android.internal.R.string.emergency_calls_only); 390 } 391 return new CarrierDisplayNameData.Builder() 392 .setSpn(data.getSpn()) 393 .setDataSpn(data.getDataSpn()) 394 .setShowSpn(data.shouldShowSpn()) 395 .setPlmn(plmn) 396 .setShowPlmn(true) 397 .build(); 398 } 399 resolveCarrierDisplayName()400 private void resolveCarrierDisplayName() { 401 CarrierDisplayNameData data = getCarrierDisplayNameFromEf(); 402 if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data); 403 if (getCombinedRegState(getServiceState()) == ServiceState.STATE_IN_SERVICE) { 404 if (mPhone.isWifiCallingEnabled()) { 405 data = getCarrierDisplayNameFromWifiCallingOverride(data); 406 if (DBG) { 407 Rlog.d(TAG, "CarrierName override by wifi-calling " + data); 408 } 409 } 410 } else { 411 data = getOutOfServiceDisplayName(data); 412 if (DBG) Rlog.d(TAG, "Out of service carrierName " + data); 413 } 414 415 if (!Objects.equals(mCarrierDisplayNameData, data)) { 416 mLocalLog.log(String.format("ResolveCarrierDisplayName: %s", data.toString())); 417 } 418 419 mCarrierDisplayNameData = data; 420 } 421 422 /** 423 * Get the PLMN network name from the {@link PlmnNetworkName} object. 424 * @param name the {@link PlmnNetworkName} object may contain the full and short version of PLMN 425 * network name. 426 * @return full/short version PLMN network name if one of those is existed, otherwise return an 427 * empty string. 428 */ getPlmnNetworkName(PlmnNetworkName name)429 private static String getPlmnNetworkName(PlmnNetworkName name) { 430 if (name == null) return ""; 431 if (!TextUtils.isEmpty(name.fullName)) return name.fullName; 432 if (!TextUtils.isEmpty(name.shortName)) return name.shortName; 433 return ""; 434 } 435 436 /** 437 * Get the priority of the source of ef object. If {@code source} is not in the priority list, 438 * return {@link Integer#MAX_VALUE}. 439 * @param source source of ef object. 440 * @return the priority of the source of ef object. 441 */ getSourcePriority(@FSource int source)442 private static int getSourcePriority(@EFSource int source) { 443 int priority = EF_SOURCE_PRIORITY.indexOf(source); 444 if (priority == -1) priority = Integer.MAX_VALUE; 445 return priority; 446 } 447 448 private static final class CarrierDisplayNameConditionRule { 449 private int mDisplayConditionBitmask; 450 CarrierDisplayNameConditionRule(int carrierDisplayConditionBitmask)451 CarrierDisplayNameConditionRule(int carrierDisplayConditionBitmask) { 452 mDisplayConditionBitmask = carrierDisplayConditionBitmask; 453 } 454 shouldShowSpn(boolean isRoaming)455 boolean shouldShowSpn(boolean isRoaming) { 456 return !isRoaming || ((mDisplayConditionBitmask 457 & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN) 458 == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN); 459 } 460 shouldShowPnn(boolean isRoaming)461 boolean shouldShowPnn(boolean isRoaming) { 462 return isRoaming || ((mDisplayConditionBitmask 463 & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN) 464 == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN); 465 } 466 467 @Override toString()468 public String toString() { 469 return String.format("{ SPN_bit = %d, PLMN_bit = %d }", 470 mDisplayConditionBitmask 471 & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN, 472 mDisplayConditionBitmask 473 & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN); 474 } 475 } 476 getServiceState()477 private ServiceState getServiceState() { 478 return mPhone.getServiceStateTracker().getServiceState(); 479 } 480 481 /** 482 * WiFi-Calling formatter for carrier name. 483 */ 484 private static final class WfcCarrierNameFormatter { 485 final String mVoiceFormat; 486 final String mDataFormat; 487 WfcCarrierNameFormatter(@onNull PersistableBundle config, @NonNull String[] wfcFormats, boolean inFlightMode)488 WfcCarrierNameFormatter(@NonNull PersistableBundle config, 489 @NonNull String[] wfcFormats, boolean inFlightMode) { 490 int voiceIdx = config.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT); 491 int dataIdx = config.getInt(CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT); 492 int flightModeIdx = config.getInt( 493 CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT); 494 495 if (voiceIdx < 0 || voiceIdx >= wfcFormats.length) { 496 Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: " 497 + voiceIdx); 498 voiceIdx = 0; 499 } 500 501 if (dataIdx < 0 || dataIdx >= wfcFormats.length) { 502 Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: " 503 + dataIdx); 504 dataIdx = 0; 505 } 506 507 if (flightModeIdx < 0 || flightModeIdx >= wfcFormats.length) { 508 // KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT out of bounds. Use the value from 509 // voiceIdx. 510 flightModeIdx = voiceIdx; 511 } 512 513 // flight mode 514 if (inFlightMode) { 515 voiceIdx = flightModeIdx; 516 } 517 518 mVoiceFormat = voiceIdx != -1 ? wfcFormats[voiceIdx] : ""; 519 mDataFormat = dataIdx != -1 ? wfcFormats[dataIdx] : ""; 520 } 521 522 /** 523 * Format the given {@code name} using wifi-calling voice name formatter. 524 * @param name the string need to be formatted. 525 * @return formatted string if {@code name} is not empty, otherwise return {@code name}. 526 */ formatVoiceName(String name)527 public String formatVoiceName(String name) { 528 if (TextUtils.isEmpty(name)) return name; 529 return String.format(mVoiceFormat, name.trim()); 530 } 531 532 /** 533 * Format the given {@code name} using wifi-calling data name formatter. 534 * @param name the string need to be formatted. 535 * @return formatted string if {@code name} is not empty, otherwise return {@code name}. 536 */ formatDataName(String name)537 public String formatDataName(String name) { 538 if (TextUtils.isEmpty(name)) return name; 539 return String.format(mDataFormat, name.trim()); 540 } 541 } 542 543 /** 544 * Consider dataRegState if voiceRegState is OOS to determine SPN to be displayed. 545 * @param ss service state. 546 */ getCombinedRegState(ServiceState ss)547 private static int getCombinedRegState(ServiceState ss) { 548 if (ss.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegState(); 549 return ss.getVoiceRegState(); 550 } 551 } 552