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 registeredPlmnName = getServiceState().getOperatorAlpha();
292         String registeredPlmnNumeric = getServiceState().getOperatorNumeric();
293         List<String> efSpdi = getEfSpdi();
294 
295         // Currently use the roaming state from ServiceState.
296         // EF_SPDI is only used when determine the service provider name and PLMN network name
297         // display condition rule.
298         // All the PLMNs will be considered HOME PLMNs if there is a brand override.
299         boolean isRoaming = getServiceState().getRoaming()
300                 && !efSpdi.contains(registeredPlmnNumeric);
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 (isRoaming) {
309             plmn = registeredPlmnName;
310         } else {
311             if (efOpl.isEmpty()) {
312                 // If the EF_OPL is not present, then the first record in EF_PNN is used for the
313                 // default network name when registered in the HPLMN or an EHPLMN(if the EHPLMN
314                 // list is present).
315                 plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0));
316             } else {
317                 // TODO: Check the TAC/LAC & registered PLMN numeric in OPL list to determine which
318                 // PLMN name should be used to override the current one.
319             }
320         }
321 
322         // If no PLMN override is present, then the PLMN should be displayed numerically.
323         if (TextUtils.isEmpty(plmn)) {
324             plmn = TextUtils.isEmpty(registeredPlmnName) ? registeredPlmnNumeric
325                     : registeredPlmnName;
326         }
327 
328         boolean showSpn = displayRule.shouldShowSpn(isRoaming, spn);
329         boolean showPlmn = TextUtils.isEmpty(spn) || displayRule.shouldShowPlmn(isRoaming, plmn);
330 
331         return new CarrierDisplayNameData.Builder()
332                 .setSpn(spn)
333                 .setShowSpn(showSpn)
334                 .setPlmn(plmn)
335                 .setShowPlmn(showPlmn)
336                 .build();
337     }
338 
getCarrierDisplayNameFromWifiCallingOverride( CarrierDisplayNameData rawCarrierDisplayNameData)339     private CarrierDisplayNameData getCarrierDisplayNameFromWifiCallingOverride(
340             CarrierDisplayNameData rawCarrierDisplayNameData) {
341         PersistableBundle config = getCarrierConfig();
342         boolean useRootLocale = config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE);
343         Resources r = mContext.getResources();
344         if (useRootLocale) r.getConfiguration().setLocale(Locale.ROOT);
345         String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats);
346         WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats,
347                 getServiceState().getState() == ServiceState.STATE_POWER_OFF);
348 
349         // Override the spn, data spn, plmn by wifi-calling
350         String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn());
351         String wfcDataSpn = wfcFormatter.formatDataName(rawCarrierDisplayNameData.getSpn());
352         List<PlmnNetworkName> efPnn = getEfPnn();
353         String plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0));
354         String wfcPlmn = wfcFormatter.formatVoiceName(
355                 TextUtils.isEmpty(plmn) ? rawCarrierDisplayNameData.getPlmn() : plmn);
356 
357         CarrierDisplayNameData result = rawCarrierDisplayNameData;
358         if (!TextUtils.isEmpty(wfcSpn) && !TextUtils.isEmpty(wfcDataSpn)) {
359             result = new CarrierDisplayNameData.Builder()
360                     .setSpn(wfcSpn)
361                     .setDataSpn(wfcDataSpn)
362                     .setShowSpn(true)
363                     .build();
364         } else if (!TextUtils.isEmpty(wfcPlmn)) {
365             result = new CarrierDisplayNameData.Builder()
366                     .setPlmn(wfcPlmn)
367                     .setShowPlmn(true)
368                     .build();
369         }
370         return result;
371     }
372 
373     /**
374      * Override the given carrier display name data {@code data} by out of service rule.
375      * @param data the carrier display name data need to be overridden.
376      * @return overridden carrier display name data.
377      */
getOutOfServiceDisplayName(CarrierDisplayNameData data)378     private CarrierDisplayNameData getOutOfServiceDisplayName(CarrierDisplayNameData data) {
379         // Out of service/Power off/Emergency Only override
380         // 1) In flight mode(service state is ServiceState.STATE_POWER_OFF), or the service
381         //    state is ServiceState.STATE_OUT_OF_SERVICE but emergency call is not allowed.
382         //    showPlmn = true
383         //    Only show "No Service" as PLMN
384         //
385         // 2) Out of service but emergency call is allowed.
386         //    showPlmn = true
387         //    Only show "Emergency call only" as PLMN
388         String plmn = null;
389         boolean isSimReady = mPhone.getUiccCardApplication() != null
390                 && mPhone.getUiccCardApplication().getState() == AppState.APPSTATE_READY;
391         boolean forceDisplayNoService =
392                 mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady;
393         ServiceState ss = getServiceState();
394         if (ss.getState() == ServiceState.STATE_POWER_OFF
395                 || forceDisplayNoService || !Phone.isEmergencyCallOnly()) {
396             plmn = mContext.getResources().getString(
397                     com.android.internal.R.string.lockscreen_carrier_default);
398         } else {
399             plmn = mContext.getResources().getString(
400                     com.android.internal.R.string.emergency_calls_only);
401         }
402         return new CarrierDisplayNameData.Builder()
403                 .setSpn(data.getSpn())
404                 .setDataSpn(data.getDataSpn())
405                 .setShowSpn(data.shouldShowSpn())
406                 .setPlmn(plmn)
407                 .setShowPlmn(true)
408                 .build();
409     }
410 
resolveCarrierDisplayName()411     private void resolveCarrierDisplayName() {
412         CarrierDisplayNameData data = getCarrierDisplayNameFromEf();
413         if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data);
414         if (getCombinedRegState(getServiceState()) == ServiceState.STATE_IN_SERVICE) {
415             if (mPhone.isWifiCallingEnabled()) {
416                 data = getCarrierDisplayNameFromWifiCallingOverride(data);
417                 if (DBG) {
418                     Rlog.d(TAG, "CarrierName override by wifi-calling " + data);
419                 }
420             }
421         } else {
422             data = getOutOfServiceDisplayName(data);
423             if (DBG) Rlog.d(TAG, "Out of service carrierName " + data);
424         }
425 
426         if (!Objects.equals(mCarrierDisplayNameData, data)) {
427             mLocalLog.log(String.format("ResolveCarrierDisplayName: %s", data.toString()));
428         }
429 
430         mCarrierDisplayNameData = data;
431     }
432 
433     /**
434      * Get the PLMN network name from the {@link PlmnNetworkName} object.
435      * @param name the {@link PlmnNetworkName} object may contain the full and short version of PLMN
436      * network name.
437      * @return full/short version PLMN network name if one of those is existed, otherwise return an
438      * empty string.
439      */
getPlmnNetworkName(PlmnNetworkName name)440     private static String getPlmnNetworkName(PlmnNetworkName name) {
441         if (name == null) return "";
442         if (!TextUtils.isEmpty(name.fullName)) return name.fullName;
443         if (!TextUtils.isEmpty(name.shortName)) return name.shortName;
444         return "";
445     }
446 
447     /**
448      * Get the priority of the source of ef object. If {@code source} is not in the priority list,
449      * return {@link Integer#MAX_VALUE}.
450      * @param source source of ef object.
451      * @return the priority of the source of ef object.
452      */
getSourcePriority(@FSource int source)453     private static int getSourcePriority(@EFSource int source) {
454         int priority = EF_SOURCE_PRIORITY.indexOf(source);
455         if (priority == -1) priority = Integer.MAX_VALUE;
456         return priority;
457     }
458 
459     private static final class CarrierDisplayNameConditionRule {
460         private int mDisplayConditionBitmask;
461 
CarrierDisplayNameConditionRule(int carrierDisplayConditionBitmask)462         CarrierDisplayNameConditionRule(int carrierDisplayConditionBitmask) {
463             mDisplayConditionBitmask = carrierDisplayConditionBitmask;
464         }
465 
shouldShowSpn(boolean isRoaming, String spn)466         boolean shouldShowSpn(boolean isRoaming, String spn) {
467             //Check if show SPN is required when roaming.
468             Boolean showSpnInRoaming = ((mDisplayConditionBitmask
469                     & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN)
470                     == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN);
471 
472             return !TextUtils.isEmpty(spn) && (!isRoaming || showSpnInRoaming);
473         }
474 
shouldShowPlmn(boolean isRoaming, String plmn)475         boolean shouldShowPlmn(boolean isRoaming, String plmn) {
476             // Check if show PLMN is required when not roaming.
477             Boolean showPlmnInNotRoaming = ((mDisplayConditionBitmask
478                     & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN)
479                     == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN);
480 
481             return !TextUtils.isEmpty(plmn) && (isRoaming || showPlmnInNotRoaming);
482         }
483 
484         @Override
toString()485         public String toString() {
486             return String.format("{ SPN_bit = %d, PLMN_bit = %d }",
487                     mDisplayConditionBitmask
488                             & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN,
489                     mDisplayConditionBitmask
490                             & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN);
491         }
492     }
493 
getServiceState()494     private ServiceState getServiceState() {
495         return mPhone.getServiceStateTracker().getServiceState();
496     }
497 
498     /**
499      * WiFi-Calling formatter for carrier name.
500      */
501     private static final class WfcCarrierNameFormatter {
502         final String mVoiceFormat;
503         final String mDataFormat;
504 
WfcCarrierNameFormatter(@onNull PersistableBundle config, @NonNull String[] wfcFormats, boolean inFlightMode)505         WfcCarrierNameFormatter(@NonNull PersistableBundle config,
506                 @NonNull String[] wfcFormats, boolean inFlightMode) {
507             int voiceIdx = config.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT);
508             int dataIdx = config.getInt(CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT);
509             int flightModeIdx = config.getInt(
510                     CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT);
511 
512             if (voiceIdx < 0 || voiceIdx >= wfcFormats.length) {
513                 Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: "
514                         + voiceIdx);
515                 voiceIdx = 0;
516             }
517 
518             if (dataIdx < 0 || dataIdx >= wfcFormats.length) {
519                 Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: "
520                         + dataIdx);
521                 dataIdx = 0;
522             }
523 
524             if (flightModeIdx < 0 || flightModeIdx >= wfcFormats.length) {
525                 // KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT out of bounds. Use the value from
526                 // voiceIdx.
527                 flightModeIdx = voiceIdx;
528             }
529 
530             // flight mode
531             if (inFlightMode) {
532                 voiceIdx = flightModeIdx;
533             }
534 
535             mVoiceFormat = voiceIdx != -1 ? wfcFormats[voiceIdx] : "";
536             mDataFormat = dataIdx != -1 ? wfcFormats[dataIdx] : "";
537         }
538 
539         /**
540          * Format the given {@code name} using wifi-calling voice name formatter.
541          * @param name the string need to be formatted.
542          * @return formatted string if {@code name} is not empty, otherwise return {@code name}.
543          */
formatVoiceName(String name)544         public String formatVoiceName(String name) {
545             if (TextUtils.isEmpty(name)) return name;
546             return String.format(mVoiceFormat, name.trim());
547         }
548 
549         /**
550          * Format the given {@code name} using wifi-calling data name formatter.
551          * @param name the string need to be formatted.
552          * @return formatted string if {@code name} is not empty, otherwise return {@code name}.
553          */
formatDataName(String name)554         public String formatDataName(String name) {
555             if (TextUtils.isEmpty(name)) return name;
556             return String.format(mDataFormat, name.trim());
557         }
558     }
559 
560     /**
561      * Consider dataRegState if voiceRegState is OOS to determine SPN to be displayed.
562      * @param ss service state.
563      */
getCombinedRegState(ServiceState ss)564     private static int getCombinedRegState(ServiceState ss) {
565         if (ss.getState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegistrationState();
566         return ss.getState();
567     }
568 }
569