blob: 63986ad10d5aac291338090c9338fc9f2b67c4f2 [file] [log] [blame]
/*
* 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.Rlog;
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.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 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().getVoiceRegState() == 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 = mContext.getResources().getBoolean(
com.android.internal.R.bool.config_display_no_service_when_sim_unready)
&& !isSimReady;
ServiceState ss = getServiceState();
if (ss.getVoiceRegState() == ServiceState.STATE_POWER_OFF || !ss.isEmergencyOnly()
|| forceDisplayNoService) {
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.getVoiceRegState() != ServiceState.STATE_IN_SERVICE) return ss.getDataRegState();
return ss.getVoiceRegState();
}
}