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