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.ServiceState; 36 import android.text.TextUtils; 37 import android.util.LocalLog; 38 import android.util.SparseArray; 39 40 import com.android.internal.telephony.GsmCdmaPhone; 41 import com.android.internal.telephony.Phone; 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 import com.android.telephony.Rlog; 52 53 import java.util.Arrays; 54 import java.util.Collections; 55 import java.util.List; 56 import java.util.Locale; 57 import java.util.Objects; 58 59 /** Carrier display name resolver. */ 60 public class CarrierDisplayNameResolver { 61 private static final boolean DBG = true; 62 private static final String TAG = "CDNR"; 63 64 /** 65 * Only display SPN in home network, and PLMN network name in roaming network. 66 */ 67 @CarrierNameDisplayConditionBitmask 68 private static final int DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK = 0; 69 70 private static final CarrierDisplayNameConditionRule DEFAULT_CARRIER_DISPLAY_NAME_RULE = 71 new CarrierDisplayNameConditionRule(DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK); 72 73 private final SparseArray<EfData> mEf = new SparseArray<>(); 74 75 private final LocalLog mLocalLog; 76 private final Context mContext; 77 private final GsmCdmaPhone mPhone; 78 private final CarrierConfigManager mCCManager; 79 80 private CarrierDisplayNameData mCarrierDisplayNameData; 81 82 /** 83 * The priority of ef source. Lower index means higher priority. 84 */ 85 private static final List<Integer> EF_SOURCE_PRIORITY = 86 Arrays.asList( 87 EF_SOURCE_CARRIER_API, 88 EF_SOURCE_CARRIER_CONFIG, 89 EF_SOURCE_ERI, 90 EF_SOURCE_USIM, 91 EF_SOURCE_SIM, 92 EF_SOURCE_CSIM, 93 EF_SOURCE_RUIM, 94 EF_SOURCE_VOICE_OPERATOR_SIGNALLING, 95 EF_SOURCE_DATA_OPERATOR_SIGNALLING, 96 EF_SOURCE_MODEM_CONFIG); 97 CarrierDisplayNameResolver(GsmCdmaPhone phone)98 public CarrierDisplayNameResolver(GsmCdmaPhone phone) { 99 mLocalLog = new LocalLog(32); 100 mContext = phone.getContext(); 101 mPhone = phone; 102 mCCManager = (CarrierConfigManager) mContext.getSystemService( 103 Context.CARRIER_CONFIG_SERVICE); 104 } 105 106 /** 107 * Update the ef from Ruim. If {@code ruim} is null, the ef records from this source will be 108 * removed. 109 * 110 * @param ruim Ruim records. 111 */ updateEfFromRuim(RuimRecords ruim)112 public void updateEfFromRuim(RuimRecords ruim) { 113 int key = getSourcePriority(EF_SOURCE_RUIM); 114 if (ruim == null) { 115 mEf.remove(key); 116 } else { 117 mEf.put(key, new RuimEfData(ruim)); 118 } 119 } 120 121 /** 122 * Update the ef from Usim. If {@code usim} is null, the ef records from this source will be 123 * removed. 124 * 125 * @param usim Usim records. 126 */ updateEfFromUsim(SIMRecords usim)127 public void updateEfFromUsim(SIMRecords usim) { 128 int key = getSourcePriority(EF_SOURCE_USIM); 129 if (usim == null) { 130 mEf.remove(key); 131 } else { 132 mEf.put(key, new UsimEfData(usim)); 133 } 134 } 135 136 /** 137 * Update the ef from carrier config. If {@code config} is null, the ef records from this source 138 * will be removed. 139 * 140 * @param config carrier config. 141 */ updateEfFromCarrierConfig(PersistableBundle config)142 public void updateEfFromCarrierConfig(PersistableBundle config) { 143 int key = getSourcePriority(EF_SOURCE_CARRIER_CONFIG); 144 if (config == null) { 145 mEf.remove(key); 146 } else { 147 mEf.put(key, new CarrierConfigEfData(config)); 148 } 149 } 150 151 /** 152 * Update the ef for CDMA eri text. The ef records from this source will be set all of the 153 * following situation are satisfied. 154 * 155 * 1. {@code eriText} is neither empty nor null. 156 * 2. Current network is CDMA or CdmaLte 157 * 3. ERI is allowed. 158 * 159 * @param eriText 160 */ updateEfForEri(String eriText)161 public void updateEfForEri(String eriText) { 162 PersistableBundle config = getCarrierConfig(); 163 int key = getSourcePriority(EF_SOURCE_ERI); 164 if (!TextUtils.isEmpty(eriText) && (mPhone.isPhoneTypeCdma() || mPhone.isPhoneTypeCdmaLte()) 165 && config.getBoolean(CarrierConfigManager.KEY_ALLOW_ERI_BOOL)) { 166 mEf.put(key, new EriEfData(eriText)); 167 } else { 168 mEf.remove(key); 169 } 170 } 171 172 /** 173 * Update the ef for brandOverride. If {@code operatorName} is empty or null, the ef records 174 * from this source will be removed. 175 * 176 * @param operatorName operator name from brand override. 177 */ updateEfForBrandOverride(String operatorName)178 public void updateEfForBrandOverride(String operatorName) { 179 int key = getSourcePriority(EF_SOURCE_CARRIER_API); 180 if (TextUtils.isEmpty(operatorName)) { 181 mEf.remove(key); 182 } else { 183 mEf.put(key, 184 new BrandOverrideEfData(operatorName, getServiceState().getOperatorNumeric())); 185 } 186 } 187 188 /** Get the resolved carrier display name. */ getCarrierDisplayNameData()189 public CarrierDisplayNameData getCarrierDisplayNameData() { 190 resolveCarrierDisplayName(); 191 return mCarrierDisplayNameData; 192 } 193 194 @Override toString()195 public String toString() { 196 StringBuilder sb = new StringBuilder(); 197 for (int i = 0; i < mEf.size(); i++) { 198 EfData p = mEf.valueAt(i); 199 sb.append("{spnDisplayCondition = " + p.getServiceProviderNameDisplayCondition() 200 + ", spn = " + p.getServiceProviderName() 201 + ", spdiList = " + p.getServiceProviderDisplayInformation() 202 + ", pnnList = " + p.getPlmnNetworkNameList() 203 + ", oplList = " + p.getOperatorPlmnList() 204 + ", ehplmn = " + p.getEhplmnList() 205 + "}, "); 206 } 207 sb.append(", roamingFromSS = " + getServiceState().getRoaming()); 208 sb.append(", registeredPLMN = " + getServiceState().getOperatorNumeric()); 209 return sb.toString(); 210 } 211 212 /** 213 * Dumps information for carrier display name resolver. 214 * @param pw information printer. 215 */ dump(IndentingPrintWriter pw)216 public void dump(IndentingPrintWriter pw) { 217 pw.println("CDNR:"); 218 pw.increaseIndent(); 219 pw.println("fields = " + toString()); 220 pw.println("carrierDisplayNameData = " + mCarrierDisplayNameData); 221 pw.decreaseIndent(); 222 223 pw.println("CDNR local log:"); 224 pw.increaseIndent(); 225 mLocalLog.dump(pw); 226 pw.decreaseIndent(); 227 } 228 229 @NonNull getCarrierConfig()230 private PersistableBundle getCarrierConfig() { 231 PersistableBundle config = mCCManager.getConfigForSubId(mPhone.getSubId()); 232 if (config == null) config = CarrierConfigManager.getDefaultConfig(); 233 return config; 234 } 235 236 @NonNull getDisplayRule()237 private CarrierDisplayNameConditionRule getDisplayRule() { 238 for (int i = 0; i < mEf.size(); i++) { 239 if (mEf.valueAt(i).getServiceProviderNameDisplayCondition() 240 != IccRecords.INVALID_CARRIER_NAME_DISPLAY_CONDITION_BITMASK) { 241 return new CarrierDisplayNameConditionRule( 242 mEf.valueAt(i).getServiceProviderNameDisplayCondition()); 243 } 244 } 245 return DEFAULT_CARRIER_DISPLAY_NAME_RULE; 246 } 247 248 @NonNull getEfSpdi()249 private List<String> getEfSpdi() { 250 for (int i = 0; i < mEf.size(); i++) { 251 if (mEf.valueAt(i).getServiceProviderDisplayInformation() != null) { 252 return mEf.valueAt(i).getServiceProviderDisplayInformation(); 253 } 254 } 255 return Collections.EMPTY_LIST; 256 } 257 258 @NonNull getEfSpn()259 private String getEfSpn() { 260 for (int i = 0; i < mEf.size(); i++) { 261 if (!TextUtils.isEmpty(mEf.valueAt(i).getServiceProviderName())) { 262 return mEf.valueAt(i).getServiceProviderName(); 263 } 264 } 265 return ""; 266 } 267 268 @NonNull getEfOpl()269 private List<OperatorPlmnInfo> getEfOpl() { 270 for (int i = 0; i < mEf.size(); i++) { 271 if (mEf.valueAt(i).getOperatorPlmnList() != null) { 272 return mEf.valueAt(i).getOperatorPlmnList(); 273 } 274 } 275 return Collections.EMPTY_LIST; 276 } 277 278 @NonNull getEfPnn()279 private List<PlmnNetworkName> getEfPnn() { 280 for (int i = 0; i < mEf.size(); i++) { 281 if (mEf.valueAt(i).getPlmnNetworkNameList() != null) { 282 return mEf.valueAt(i).getPlmnNetworkNameList(); 283 } 284 } 285 return Collections.EMPTY_LIST; 286 } 287 getCarrierDisplayNameFromEf()288 private CarrierDisplayNameData getCarrierDisplayNameFromEf() { 289 CarrierDisplayNameConditionRule displayRule = getDisplayRule(); 290 291 String registeredPlmnNumeric = getServiceState().getOperatorNumeric(); 292 List<String> efSpdi = getEfSpdi(); 293 294 // Currently use the roaming state from ServiceState. 295 // EF_SPDI is only used when determine the service provider name and PLMN network name 296 // display condition rule. 297 // All the PLMNs will be considered HOME PLMNs if there is a brand override. 298 boolean isRoaming = getServiceState().getRoaming() 299 && !efSpdi.contains(registeredPlmnNumeric); 300 boolean showSpn = displayRule.shouldShowSpn(isRoaming); 301 boolean showPlmn = displayRule.shouldShowPnn(isRoaming); 302 String spn = getEfSpn(); 303 304 // Resolve the PLMN network name 305 List<OperatorPlmnInfo> efOpl = getEfOpl(); 306 List<PlmnNetworkName> efPnn = getEfPnn(); 307 308 String plmn = null; 309 if (efOpl.isEmpty()) { 310 // If the EF_OPL is not present, then the first record in EF_PNN is used for the 311 // default network name when registered in the HPLMN or an EHPLMN(if the EHPLMN list 312 // is present). 313 plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0)); 314 } else { 315 // TODO: Check the TAC/LAC & registered PLMN numeric in OPL list to determine which 316 // PLMN name should be used to override the current one. 317 } 318 319 // If no PLMN override is present, then the PLMN should be displayed numerically. 320 if (TextUtils.isEmpty(plmn)) { 321 plmn = registeredPlmnNumeric; 322 } 323 324 return new CarrierDisplayNameData.Builder() 325 .setSpn(spn) 326 .setShowSpn(showSpn) 327 .setPlmn(plmn) 328 .setShowPlmn(showPlmn) 329 .build(); 330 } 331 getCarrierDisplayNameFromWifiCallingOverride( CarrierDisplayNameData rawCarrierDisplayNameData)332 private CarrierDisplayNameData getCarrierDisplayNameFromWifiCallingOverride( 333 CarrierDisplayNameData rawCarrierDisplayNameData) { 334 PersistableBundle config = getCarrierConfig(); 335 boolean useRootLocale = config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE); 336 Resources r = mContext.getResources(); 337 if (useRootLocale) r.getConfiguration().setLocale(Locale.ROOT); 338 String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats); 339 WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats, 340 getServiceState().getState() == ServiceState.STATE_POWER_OFF); 341 342 // Override the spn, data spn, plmn by wifi-calling 343 String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn()); 344 String wfcDataSpn = wfcFormatter.formatDataName(rawCarrierDisplayNameData.getSpn()); 345 String wfcPlmn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getPlmn()); 346 CarrierDisplayNameData result = rawCarrierDisplayNameData; 347 if (!TextUtils.isEmpty(wfcSpn) && !TextUtils.isEmpty(wfcDataSpn)) { 348 result = new CarrierDisplayNameData.Builder() 349 .setSpn(wfcSpn) 350 .setDataSpn(wfcDataSpn) 351 .setShowSpn(true) 352 .build(); 353 } else if (!TextUtils.isEmpty(wfcPlmn)) { 354 result = new CarrierDisplayNameData.Builder() 355 .setPlmn(wfcPlmn) 356 .setShowPlmn(true) 357 .build(); 358 } 359 return result; 360 } 361 362 /** 363 * Override the given carrier display name data {@code data} by out of service rule. 364 * @param data the carrier display name data need to be overridden. 365 * @return overridden carrier display name data. 366 */ getOutOfServiceDisplayName(CarrierDisplayNameData data)367 private CarrierDisplayNameData getOutOfServiceDisplayName(CarrierDisplayNameData data) { 368 // Out of service/Power off/Emergency Only override 369 // 1) In flight mode(service state is ServiceState.STATE_POWER_OFF), or the service 370 // state is ServiceState.STATE_OUT_OF_SERVICE but emergency call is not allowed. 371 // showPlmn = true 372 // Only show "No Service" as PLMN 373 // 374 // 2) Out of service but emergency call is allowed. 375 // showPlmn = true 376 // Only show "Emergency call only" as PLMN 377 String plmn = null; 378 boolean isSimReady = mPhone.getUiccCardApplication() != null 379 && mPhone.getUiccCardApplication().getState() == AppState.APPSTATE_READY; 380 boolean forceDisplayNoService = 381 mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady; 382 ServiceState ss = getServiceState(); 383 if (ss.getState() == ServiceState.STATE_POWER_OFF 384 || forceDisplayNoService || !Phone.isEmergencyCallOnly()) { 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.getState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegistrationState(); 549 return ss.getState(); 550 } 551 } 552