| /* |
| * Copyright (C) 2019 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package com.android.internal.telephony.cdnr; |
| |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CARRIER_API; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CARRIER_CONFIG; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_CSIM; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_DATA_OPERATOR_SIGNALLING; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_ERI; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_MODEM_CONFIG; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_RUIM; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_SIM; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_USIM; |
| import static com.android.internal.telephony.cdnr.EfData.EF_SOURCE_VOICE_OPERATOR_SIGNALLING; |
| |
| import android.annotation.NonNull; |
| import android.content.Context; |
| import android.content.res.Resources; |
| import android.os.PersistableBundle; |
| import android.telephony.CarrierConfigManager; |
| import android.telephony.ServiceState; |
| import android.text.TextUtils; |
| import android.util.LocalLog; |
| import android.util.SparseArray; |
| |
| import com.android.internal.telephony.GsmCdmaPhone; |
| import com.android.internal.telephony.Phone; |
| import com.android.internal.telephony.cdnr.EfData.EFSource; |
| import com.android.internal.telephony.uicc.IccCardApplicationStatus.AppState; |
| import com.android.internal.telephony.uicc.IccRecords; |
| import com.android.internal.telephony.uicc.IccRecords.CarrierNameDisplayConditionBitmask; |
| import com.android.internal.telephony.uicc.IccRecords.OperatorPlmnInfo; |
| import com.android.internal.telephony.uicc.IccRecords.PlmnNetworkName; |
| import com.android.internal.telephony.uicc.RuimRecords; |
| import com.android.internal.telephony.uicc.SIMRecords; |
| import com.android.internal.util.IndentingPrintWriter; |
| import com.android.telephony.Rlog; |
| |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Objects; |
| |
| /** Carrier display name resolver. */ |
| public class CarrierDisplayNameResolver { |
| private static final boolean DBG = true; |
| private static final String TAG = "CDNR"; |
| |
| /** |
| * Only display SPN in home network, and PLMN network name in roaming network. |
| */ |
| @CarrierNameDisplayConditionBitmask |
| private static final int DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK = 0; |
| |
| private static final CarrierDisplayNameConditionRule DEFAULT_CARRIER_DISPLAY_NAME_RULE = |
| new CarrierDisplayNameConditionRule(DEFAULT_CARRIER_NAME_DISPLAY_CONDITION_BITMASK); |
| |
| private final SparseArray<EfData> mEf = new SparseArray<>(); |
| |
| private final LocalLog mLocalLog; |
| private final Context mContext; |
| private final GsmCdmaPhone mPhone; |
| private final CarrierConfigManager mCCManager; |
| |
| private CarrierDisplayNameData mCarrierDisplayNameData; |
| |
| /** |
| * The priority of ef source. Lower index means higher priority. |
| */ |
| private static final List<Integer> EF_SOURCE_PRIORITY = |
| Arrays.asList( |
| EF_SOURCE_CARRIER_API, |
| EF_SOURCE_CARRIER_CONFIG, |
| EF_SOURCE_ERI, |
| EF_SOURCE_USIM, |
| EF_SOURCE_SIM, |
| EF_SOURCE_CSIM, |
| EF_SOURCE_RUIM, |
| EF_SOURCE_VOICE_OPERATOR_SIGNALLING, |
| EF_SOURCE_DATA_OPERATOR_SIGNALLING, |
| EF_SOURCE_MODEM_CONFIG); |
| |
| public CarrierDisplayNameResolver(GsmCdmaPhone phone) { |
| mLocalLog = new LocalLog(32); |
| mContext = phone.getContext(); |
| mPhone = phone; |
| mCCManager = (CarrierConfigManager) mContext.getSystemService( |
| Context.CARRIER_CONFIG_SERVICE); |
| } |
| |
| /** |
| * Update the ef from Ruim. If {@code ruim} is null, the ef records from this source will be |
| * removed. |
| * |
| * @param ruim Ruim records. |
| */ |
| public void updateEfFromRuim(RuimRecords ruim) { |
| int key = getSourcePriority(EF_SOURCE_RUIM); |
| if (ruim == null) { |
| mEf.remove(key); |
| } else { |
| mEf.put(key, new RuimEfData(ruim)); |
| } |
| } |
| |
| /** |
| * Update the ef from Usim. If {@code usim} is null, the ef records from this source will be |
| * removed. |
| * |
| * @param usim Usim records. |
| */ |
| public void updateEfFromUsim(SIMRecords usim) { |
| int key = getSourcePriority(EF_SOURCE_USIM); |
| if (usim == null) { |
| mEf.remove(key); |
| } else { |
| mEf.put(key, new UsimEfData(usim)); |
| } |
| } |
| |
| /** |
| * Update the ef from carrier config. If {@code config} is null, the ef records from this source |
| * will be removed. |
| * |
| * @param config carrier config. |
| */ |
| public void updateEfFromCarrierConfig(PersistableBundle config) { |
| int key = getSourcePriority(EF_SOURCE_CARRIER_CONFIG); |
| if (config == null) { |
| mEf.remove(key); |
| } else { |
| mEf.put(key, new CarrierConfigEfData(config)); |
| } |
| } |
| |
| /** |
| * Update the ef for CDMA eri text. The ef records from this source will be set all of the |
| * following situation are satisfied. |
| * |
| * 1. {@code eriText} is neither empty nor null. |
| * 2. Current network is CDMA or CdmaLte |
| * 3. ERI is allowed. |
| * |
| * @param eriText |
| */ |
| public void updateEfForEri(String eriText) { |
| PersistableBundle config = getCarrierConfig(); |
| int key = getSourcePriority(EF_SOURCE_ERI); |
| if (!TextUtils.isEmpty(eriText) && (mPhone.isPhoneTypeCdma() || mPhone.isPhoneTypeCdmaLte()) |
| && config.getBoolean(CarrierConfigManager.KEY_ALLOW_ERI_BOOL)) { |
| mEf.put(key, new EriEfData(eriText)); |
| } else { |
| mEf.remove(key); |
| } |
| } |
| |
| /** |
| * Update the ef for brandOverride. If {@code operatorName} is empty or null, the ef records |
| * from this source will be removed. |
| * |
| * @param operatorName operator name from brand override. |
| */ |
| public void updateEfForBrandOverride(String operatorName) { |
| int key = getSourcePriority(EF_SOURCE_CARRIER_API); |
| if (TextUtils.isEmpty(operatorName)) { |
| mEf.remove(key); |
| } else { |
| mEf.put(key, |
| new BrandOverrideEfData(operatorName, getServiceState().getOperatorNumeric())); |
| } |
| } |
| |
| /** Get the resolved carrier display name. */ |
| public CarrierDisplayNameData getCarrierDisplayNameData() { |
| resolveCarrierDisplayName(); |
| return mCarrierDisplayNameData; |
| } |
| |
| @Override |
| public String toString() { |
| StringBuilder sb = new StringBuilder(); |
| for (int i = 0; i < mEf.size(); i++) { |
| EfData p = mEf.valueAt(i); |
| sb.append("{spnDisplayCondition = " + p.getServiceProviderNameDisplayCondition() |
| + ", spn = " + p.getServiceProviderName() |
| + ", spdiList = " + p.getServiceProviderDisplayInformation() |
| + ", pnnList = " + p.getPlmnNetworkNameList() |
| + ", oplList = " + p.getOperatorPlmnList() |
| + ", ehplmn = " + p.getEhplmnList() |
| + "}, "); |
| } |
| sb.append(", roamingFromSS = " + getServiceState().getRoaming()); |
| sb.append(", registeredPLMN = " + getServiceState().getOperatorNumeric()); |
| return sb.toString(); |
| } |
| |
| /** |
| * Dumps information for carrier display name resolver. |
| * @param pw information printer. |
| */ |
| public void dump(IndentingPrintWriter pw) { |
| pw.println("CDNR:"); |
| pw.increaseIndent(); |
| pw.println("fields = " + toString()); |
| pw.println("carrierDisplayNameData = " + mCarrierDisplayNameData); |
| pw.decreaseIndent(); |
| |
| pw.println("CDNR local log:"); |
| pw.increaseIndent(); |
| mLocalLog.dump(pw); |
| pw.decreaseIndent(); |
| } |
| |
| @NonNull |
| private PersistableBundle getCarrierConfig() { |
| PersistableBundle config = mCCManager.getConfigForSubId(mPhone.getSubId()); |
| if (config == null) config = CarrierConfigManager.getDefaultConfig(); |
| return config; |
| } |
| |
| @NonNull |
| private CarrierDisplayNameConditionRule getDisplayRule() { |
| for (int i = 0; i < mEf.size(); i++) { |
| if (mEf.valueAt(i).getServiceProviderNameDisplayCondition() |
| != IccRecords.INVALID_CARRIER_NAME_DISPLAY_CONDITION_BITMASK) { |
| return new CarrierDisplayNameConditionRule( |
| mEf.valueAt(i).getServiceProviderNameDisplayCondition()); |
| } |
| } |
| return DEFAULT_CARRIER_DISPLAY_NAME_RULE; |
| } |
| |
| @NonNull |
| private List<String> getEfSpdi() { |
| for (int i = 0; i < mEf.size(); i++) { |
| if (mEf.valueAt(i).getServiceProviderDisplayInformation() != null) { |
| return mEf.valueAt(i).getServiceProviderDisplayInformation(); |
| } |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| @NonNull |
| private String getEfSpn() { |
| for (int i = 0; i < mEf.size(); i++) { |
| if (!TextUtils.isEmpty(mEf.valueAt(i).getServiceProviderName())) { |
| return mEf.valueAt(i).getServiceProviderName(); |
| } |
| } |
| return ""; |
| } |
| |
| @NonNull |
| private List<OperatorPlmnInfo> getEfOpl() { |
| for (int i = 0; i < mEf.size(); i++) { |
| if (mEf.valueAt(i).getOperatorPlmnList() != null) { |
| return mEf.valueAt(i).getOperatorPlmnList(); |
| } |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| @NonNull |
| private List<PlmnNetworkName> getEfPnn() { |
| for (int i = 0; i < mEf.size(); i++) { |
| if (mEf.valueAt(i).getPlmnNetworkNameList() != null) { |
| return mEf.valueAt(i).getPlmnNetworkNameList(); |
| } |
| } |
| return Collections.EMPTY_LIST; |
| } |
| |
| private CarrierDisplayNameData getCarrierDisplayNameFromEf() { |
| CarrierDisplayNameConditionRule displayRule = getDisplayRule(); |
| |
| String registeredPlmnNumeric = getServiceState().getOperatorNumeric(); |
| List<String> efSpdi = getEfSpdi(); |
| |
| // Currently use the roaming state from ServiceState. |
| // EF_SPDI is only used when determine the service provider name and PLMN network name |
| // display condition rule. |
| // All the PLMNs will be considered HOME PLMNs if there is a brand override. |
| boolean isRoaming = getServiceState().getRoaming() |
| && !efSpdi.contains(registeredPlmnNumeric); |
| boolean showSpn = displayRule.shouldShowSpn(isRoaming); |
| boolean showPlmn = displayRule.shouldShowPnn(isRoaming); |
| String spn = getEfSpn(); |
| |
| // Resolve the PLMN network name |
| List<OperatorPlmnInfo> efOpl = getEfOpl(); |
| List<PlmnNetworkName> efPnn = getEfPnn(); |
| |
| String plmn = null; |
| if (efOpl.isEmpty()) { |
| // If the EF_OPL is not present, then the first record in EF_PNN is used for the |
| // default network name when registered in the HPLMN or an EHPLMN(if the EHPLMN list |
| // is present). |
| plmn = efPnn.isEmpty() ? "" : getPlmnNetworkName(efPnn.get(0)); |
| } else { |
| // TODO: Check the TAC/LAC & registered PLMN numeric in OPL list to determine which |
| // PLMN name should be used to override the current one. |
| } |
| |
| // If no PLMN override is present, then the PLMN should be displayed numerically. |
| if (TextUtils.isEmpty(plmn)) { |
| plmn = registeredPlmnNumeric; |
| } |
| |
| return new CarrierDisplayNameData.Builder() |
| .setSpn(spn) |
| .setShowSpn(showSpn) |
| .setPlmn(plmn) |
| .setShowPlmn(showPlmn) |
| .build(); |
| } |
| |
| private CarrierDisplayNameData getCarrierDisplayNameFromWifiCallingOverride( |
| CarrierDisplayNameData rawCarrierDisplayNameData) { |
| PersistableBundle config = getCarrierConfig(); |
| boolean useRootLocale = config.getBoolean(CarrierConfigManager.KEY_WFC_SPN_USE_ROOT_LOCALE); |
| Resources r = mContext.getResources(); |
| if (useRootLocale) r.getConfiguration().setLocale(Locale.ROOT); |
| String[] wfcSpnFormats = r.getStringArray(com.android.internal.R.array.wfcSpnFormats); |
| WfcCarrierNameFormatter wfcFormatter = new WfcCarrierNameFormatter(config, wfcSpnFormats, |
| getServiceState().getState() == ServiceState.STATE_POWER_OFF); |
| |
| // Override the spn, data spn, plmn by wifi-calling |
| String wfcSpn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getSpn()); |
| String wfcDataSpn = wfcFormatter.formatDataName(rawCarrierDisplayNameData.getSpn()); |
| String wfcPlmn = wfcFormatter.formatVoiceName(rawCarrierDisplayNameData.getPlmn()); |
| CarrierDisplayNameData result = rawCarrierDisplayNameData; |
| if (!TextUtils.isEmpty(wfcSpn) && !TextUtils.isEmpty(wfcDataSpn)) { |
| result = new CarrierDisplayNameData.Builder() |
| .setSpn(wfcSpn) |
| .setDataSpn(wfcDataSpn) |
| .setShowSpn(true) |
| .build(); |
| } else if (!TextUtils.isEmpty(wfcPlmn)) { |
| result = new CarrierDisplayNameData.Builder() |
| .setPlmn(wfcPlmn) |
| .setShowPlmn(true) |
| .build(); |
| } |
| return result; |
| } |
| |
| /** |
| * Override the given carrier display name data {@code data} by out of service rule. |
| * @param data the carrier display name data need to be overridden. |
| * @return overridden carrier display name data. |
| */ |
| private CarrierDisplayNameData getOutOfServiceDisplayName(CarrierDisplayNameData data) { |
| // Out of service/Power off/Emergency Only override |
| // 1) In flight mode(service state is ServiceState.STATE_POWER_OFF), or the service |
| // state is ServiceState.STATE_OUT_OF_SERVICE but emergency call is not allowed. |
| // showPlmn = true |
| // Only show "No Service" as PLMN |
| // |
| // 2) Out of service but emergency call is allowed. |
| // showPlmn = true |
| // Only show "Emergency call only" as PLMN |
| String plmn = null; |
| boolean isSimReady = mPhone.getUiccCardApplication() != null |
| && mPhone.getUiccCardApplication().getState() == AppState.APPSTATE_READY; |
| boolean forceDisplayNoService = |
| mPhone.getServiceStateTracker().shouldForceDisplayNoService() && !isSimReady; |
| ServiceState ss = getServiceState(); |
| if (ss.getState() == ServiceState.STATE_POWER_OFF |
| || forceDisplayNoService || !Phone.isEmergencyCallOnly()) { |
| plmn = mContext.getResources().getString( |
| com.android.internal.R.string.lockscreen_carrier_default); |
| } else { |
| plmn = mContext.getResources().getString( |
| com.android.internal.R.string.emergency_calls_only); |
| } |
| return new CarrierDisplayNameData.Builder() |
| .setSpn(data.getSpn()) |
| .setDataSpn(data.getDataSpn()) |
| .setShowSpn(data.shouldShowSpn()) |
| .setPlmn(plmn) |
| .setShowPlmn(true) |
| .build(); |
| } |
| |
| private void resolveCarrierDisplayName() { |
| CarrierDisplayNameData data = getCarrierDisplayNameFromEf(); |
| if (DBG) Rlog.d(TAG, "CarrierName from EF: " + data); |
| if (getCombinedRegState(getServiceState()) == ServiceState.STATE_IN_SERVICE) { |
| if (mPhone.isWifiCallingEnabled()) { |
| data = getCarrierDisplayNameFromWifiCallingOverride(data); |
| if (DBG) { |
| Rlog.d(TAG, "CarrierName override by wifi-calling " + data); |
| } |
| } |
| } else { |
| data = getOutOfServiceDisplayName(data); |
| if (DBG) Rlog.d(TAG, "Out of service carrierName " + data); |
| } |
| |
| if (!Objects.equals(mCarrierDisplayNameData, data)) { |
| mLocalLog.log(String.format("ResolveCarrierDisplayName: %s", data.toString())); |
| } |
| |
| mCarrierDisplayNameData = data; |
| } |
| |
| /** |
| * Get the PLMN network name from the {@link PlmnNetworkName} object. |
| * @param name the {@link PlmnNetworkName} object may contain the full and short version of PLMN |
| * network name. |
| * @return full/short version PLMN network name if one of those is existed, otherwise return an |
| * empty string. |
| */ |
| private static String getPlmnNetworkName(PlmnNetworkName name) { |
| if (name == null) return ""; |
| if (!TextUtils.isEmpty(name.fullName)) return name.fullName; |
| if (!TextUtils.isEmpty(name.shortName)) return name.shortName; |
| return ""; |
| } |
| |
| /** |
| * Get the priority of the source of ef object. If {@code source} is not in the priority list, |
| * return {@link Integer#MAX_VALUE}. |
| * @param source source of ef object. |
| * @return the priority of the source of ef object. |
| */ |
| private static int getSourcePriority(@EFSource int source) { |
| int priority = EF_SOURCE_PRIORITY.indexOf(source); |
| if (priority == -1) priority = Integer.MAX_VALUE; |
| return priority; |
| } |
| |
| private static final class CarrierDisplayNameConditionRule { |
| private int mDisplayConditionBitmask; |
| |
| CarrierDisplayNameConditionRule(int carrierDisplayConditionBitmask) { |
| mDisplayConditionBitmask = carrierDisplayConditionBitmask; |
| } |
| |
| boolean shouldShowSpn(boolean isRoaming) { |
| return !isRoaming || ((mDisplayConditionBitmask |
| & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN) |
| == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN); |
| } |
| |
| boolean shouldShowPnn(boolean isRoaming) { |
| return isRoaming || ((mDisplayConditionBitmask |
| & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN) |
| == IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN); |
| } |
| |
| @Override |
| public String toString() { |
| return String.format("{ SPN_bit = %d, PLMN_bit = %d }", |
| mDisplayConditionBitmask |
| & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_SPN, |
| mDisplayConditionBitmask |
| & IccRecords.CARRIER_NAME_DISPLAY_CONDITION_BITMASK_PLMN); |
| } |
| } |
| |
| private ServiceState getServiceState() { |
| return mPhone.getServiceStateTracker().getServiceState(); |
| } |
| |
| /** |
| * WiFi-Calling formatter for carrier name. |
| */ |
| private static final class WfcCarrierNameFormatter { |
| final String mVoiceFormat; |
| final String mDataFormat; |
| |
| WfcCarrierNameFormatter(@NonNull PersistableBundle config, |
| @NonNull String[] wfcFormats, boolean inFlightMode) { |
| int voiceIdx = config.getInt(CarrierConfigManager.KEY_WFC_SPN_FORMAT_IDX_INT); |
| int dataIdx = config.getInt(CarrierConfigManager.KEY_WFC_DATA_SPN_FORMAT_IDX_INT); |
| int flightModeIdx = config.getInt( |
| CarrierConfigManager.KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT); |
| |
| if (voiceIdx < 0 || voiceIdx >= wfcFormats.length) { |
| Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_SPN_FORMAT_IDX_INT out of bounds: " |
| + voiceIdx); |
| voiceIdx = 0; |
| } |
| |
| if (dataIdx < 0 || dataIdx >= wfcFormats.length) { |
| Rlog.e(TAG, "updateSpnDisplay: KEY_WFC_DATA_SPN_FORMAT_IDX_INT out of bounds: " |
| + dataIdx); |
| dataIdx = 0; |
| } |
| |
| if (flightModeIdx < 0 || flightModeIdx >= wfcFormats.length) { |
| // KEY_WFC_FLIGHT_MODE_SPN_FORMAT_IDX_INT out of bounds. Use the value from |
| // voiceIdx. |
| flightModeIdx = voiceIdx; |
| } |
| |
| // flight mode |
| if (inFlightMode) { |
| voiceIdx = flightModeIdx; |
| } |
| |
| mVoiceFormat = voiceIdx != -1 ? wfcFormats[voiceIdx] : ""; |
| mDataFormat = dataIdx != -1 ? wfcFormats[dataIdx] : ""; |
| } |
| |
| /** |
| * Format the given {@code name} using wifi-calling voice name formatter. |
| * @param name the string need to be formatted. |
| * @return formatted string if {@code name} is not empty, otherwise return {@code name}. |
| */ |
| public String formatVoiceName(String name) { |
| if (TextUtils.isEmpty(name)) return name; |
| return String.format(mVoiceFormat, name.trim()); |
| } |
| |
| /** |
| * Format the given {@code name} using wifi-calling data name formatter. |
| * @param name the string need to be formatted. |
| * @return formatted string if {@code name} is not empty, otherwise return {@code name}. |
| */ |
| public String formatDataName(String name) { |
| if (TextUtils.isEmpty(name)) return name; |
| return String.format(mDataFormat, name.trim()); |
| } |
| } |
| |
| /** |
| * Consider dataRegState if voiceRegState is OOS to determine SPN to be displayed. |
| * @param ss service state. |
| */ |
| private static int getCombinedRegState(ServiceState ss) { |
| if (ss.getState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegistrationState(); |
| return ss.getState(); |
| } |
| } |