Merge "Modify tests in accordance with API changes"
diff --git a/src/java/com/android/internal/telephony/PhoneFactory.java b/src/java/com/android/internal/telephony/PhoneFactory.java
index 6f3121b..8683406 100644
--- a/src/java/com/android/internal/telephony/PhoneFactory.java
+++ b/src/java/com/android/internal/telephony/PhoneFactory.java
@@ -280,7 +280,6 @@
sNotificationChannelController = new NotificationChannelController(context);
- sTelephonyNetworkFactories = new TelephonyNetworkFactory[numPhones];
for (int i = 0; i < numPhones; i++) {
sTelephonyNetworkFactories[i] = new TelephonyNetworkFactory(
sSubscriptionMonitor, Looper.myLooper(), sPhones[i]);
diff --git a/src/java/com/android/internal/telephony/ServiceStateTracker.java b/src/java/com/android/internal/telephony/ServiceStateTracker.java
index 7e8bd80..839210f 100755
--- a/src/java/com/android/internal/telephony/ServiceStateTracker.java
+++ b/src/java/com/android/internal/telephony/ServiceStateTracker.java
@@ -2385,21 +2385,16 @@
* is set to roaming when either is true.
*
* There are exceptions for the above rule.
- * The new SS is not set as roaming while gsm service reports
- * roaming but indeed it is same operator.
- * And the operator is considered non roaming.
+ * The new SS is not set as roaming while gsm service or
+ * data service reports roaming but indeed it is same
+ * operator. And the operator is considered non roaming.
*
* The test for the operators is to handle special roaming
* agreements and MVNO's.
*/
boolean roaming = (mGsmRoaming || mDataRoaming);
- // for IWLAN case, data is home. Only check voice roaming.
- if (mNewSS.getRilDataRadioTechnology() == ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN) {
- roaming = mGsmRoaming;
- }
-
- if (mGsmRoaming && !isOperatorConsideredRoaming(mNewSS)
+ if (roaming && !isOperatorConsideredRoaming(mNewSS)
&& (isSameNamedOperators(mNewSS) || isOperatorConsideredNonRoaming(mNewSS))) {
log("updateRoamingState: resource override set non roaming.isSameNamedOperators="
+ isSameNamedOperators(mNewSS) + ",isOperatorConsideredNonRoaming="
@@ -3106,6 +3101,11 @@
}
}
+ // Filter out per transport data RAT changes, only want to track changes based on
+ // transport preference changes (WWAN to WLAN, for example).
+ boolean hasDataTransportPreferenceChanged = !anyDataRatChanged
+ && (mSS.getRilDataRadioTechnology() != mNewSS.getRilDataRadioTechnology());
+
boolean hasVoiceRegStateChanged =
mSS.getVoiceRegState() != mNewSS.getVoiceRegState();
@@ -3185,6 +3185,7 @@
+ " hasDataRegStateChanged = " + hasDataRegStateChanged
+ " hasRilVoiceRadioTechnologyChanged = " + hasRilVoiceRadioTechnologyChanged
+ " hasRilDataRadioTechnologyChanged = " + hasRilDataRadioTechnologyChanged
+ + " hasDataTransportPreferenceChanged = " + hasDataTransportPreferenceChanged
+ " hasChanged = " + hasChanged
+ " hasVoiceRoamingOn = " + hasVoiceRoamingOn
+ " hasVoiceRoamingOff = " + hasVoiceRoamingOff
@@ -3360,7 +3361,10 @@
}
if (hasDataRegStateChanged.get(transport)
- || hasRilDataRadioTechnologyChanged.get(transport)) {
+ || hasRilDataRadioTechnologyChanged.get(transport)
+ // Update all transports if preference changed so that consumers can be notified
+ // that ServiceState#getRilDataRadioTechnology has changed.
+ || hasDataTransportPreferenceChanged) {
notifyDataRegStateRilRadioTechnologyChanged(transport);
mPhone.notifyDataConnection();
}
diff --git a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
index 7606073..78f7779 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DataConnection.java
@@ -22,6 +22,7 @@
import android.annotation.IntDef;
import android.annotation.Nullable;
import android.app.PendingIntent;
+import android.content.Context;
import android.net.ConnectivityManager;
import android.net.KeepalivePacketData;
import android.net.LinkAddress;
@@ -38,11 +39,13 @@
import android.net.StringNetworkSpecifier;
import android.os.AsyncResult;
import android.os.Message;
+import android.os.PersistableBundle;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.provider.Telephony;
import android.telephony.AccessNetworkConstants;
import android.telephony.AccessNetworkConstants.TransportType;
+import android.telephony.CarrierConfigManager;
import android.telephony.DataFailCause;
import android.telephony.NetworkRegistrationInfo;
import android.telephony.Rlog;
@@ -50,11 +53,13 @@
import android.telephony.SubscriptionManager;
import android.telephony.TelephonyManager;
import android.telephony.data.ApnSetting;
+import android.telephony.data.ApnSetting.ApnType;
import android.telephony.data.DataCallResponse;
import android.telephony.data.DataProfile;
import android.telephony.data.DataService;
import android.telephony.data.DataServiceCallback;
import android.text.TextUtils;
+import android.util.LocalLog;
import android.util.Pair;
import android.util.StatsLog;
import android.util.TimeUtils;
@@ -174,6 +179,8 @@
private final String mTagSuffix;
+ private final LocalLog mHandoverLocalLog = new LocalLog(100);
+
/**
* Used internally for saving connecting parameters.
*/
@@ -680,6 +687,7 @@
linkProperties = dc.getLinkProperties();
// Preserve the potential network agent from the source data connection. The ownership
// is not transferred at this moment.
+ mHandoverLocalLog.log("Handover started. Preserved the agent.");
mHandoverSourceNetworkAgent = dc.getNetworkAgent();
log("Get the handover source network agent: " + mHandoverSourceNetworkAgent);
dc.setHandoverState(HANDOVER_STATE_BEING_TRANSFERRED);
@@ -1131,14 +1139,28 @@
* @return True if this data connection should only be used for unmetered purposes.
*/
private boolean isUnmeteredUseOnly() {
- // The data connection can only be unmetered used only if all requests' reasons are
- // unmetered.
+ // If this data connection is on IWLAN, then it's unmetered and can be used by everyone.
+ // Should not be for unmetered used only.
+ if (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WLAN) {
+ return false;
+ }
+
+ // If data is enabled, this data connection can't be for unmetered used only because
+ // everyone should be able to use it.
+ if (mPhone.getDataEnabledSettings().isDataEnabled()) {
+ return false;
+ }
+
+ // If the device is roaming and data roaming it turned on, then this data connection can't
+ // be for unmetered use only.
+ if (mDct.getDataRoamingEnabled() && mPhone.getServiceState().getDataRoaming()) {
+ return false;
+ }
+
+ // The data connection can only be unmetered used only if all attached APN contexts
+ // attached to this data connection are unmetered.
for (ApnContext apnContext : mApnContexts.keySet()) {
- DataConnectionReasons dataConnectionReasons = new DataConnectionReasons();
- boolean isDataAllowed = mDct.isDataAllowed(apnContext, DcTracker.REQUEST_TYPE_NORMAL,
- dataConnectionReasons);
- if (!isDataAllowed || !dataConnectionReasons.contains(
- DataConnectionReasons.DataAllowedReasonType.UNMETERED_APN)) {
+ if (ApnSettingUtils.isMeteredApnType(apnContext.getApnTypeBitmask(), mPhone)) {
return false;
}
}
@@ -1667,6 +1689,31 @@
mHandoverState = HANDOVER_STATE_COMPLETED;
}
+ // Check for dangling agent. Ideally the handover source agent should be null if
+ // handover process is smooth. When it's not null, that means handover failed. The
+ // agent was not successfully transferred to the new data connection. We should
+ // gracefully notify connectivity service the network was disconnected.
+ if (mHandoverSourceNetworkAgent != null) {
+ DataConnection sourceDc = mHandoverSourceNetworkAgent.getDataConnection();
+ if (sourceDc != null) {
+ // If the source data connection still owns this agent, then just reset the
+ // handover state back to idle because handover is already failed.
+ mHandoverLocalLog.log(
+ "Handover failed. Reset the source dc state to idle");
+ sourceDc.setHandoverState(HANDOVER_STATE_IDLE);
+ } else {
+ // The agent is now a dangling agent. No data connection owns this agent.
+ // Gracefully notify connectivity service disconnected.
+ mHandoverLocalLog.log(
+ "Handover failed and dangling agent found.");
+ mHandoverSourceNetworkAgent.acquireOwnership(
+ DataConnection.this, mTransportType);
+ mHandoverSourceNetworkAgent.sendNetworkInfo(mNetworkInfo, DataConnection.this);
+ mHandoverSourceNetworkAgent.releaseOwnership(DataConnection.this);
+ }
+ mHandoverSourceNetworkAgent = null;
+ }
+
if (mConnectionParams != null) {
if (DBG) {
log("DcInactiveState: enter notifyConnectCompleted +ALL failCause="
@@ -1941,15 +1988,25 @@
}
if (mHandoverSourceNetworkAgent != null) {
- log("Transfer network agent successfully.");
+ String logStr = "Transfer network agent successfully.";
+ log(logStr);
+ mHandoverLocalLog.log(logStr);
mNetworkAgent = mHandoverSourceNetworkAgent;
mNetworkAgent.acquireOwnership(DataConnection.this, mTransportType);
+
+ // TODO: Should evaluate mDisabledApnTypeBitMask again after handover. We don't
+ // do it now because connectivity service does not support dynamically removing
+ // immutable capabilities.
+
+ // Update the capability after handover
mNetworkAgent.sendNetworkCapabilities(getNetworkCapabilities(),
DataConnection.this);
mNetworkAgent.sendLinkProperties(mLinkProperties, DataConnection.this);
mHandoverSourceNetworkAgent = null;
} else {
- loge("Failed to get network agent from original data connection");
+ String logStr = "Failed to get network agent from original data connection";
+ loge(logStr);
+ mHandoverLocalLog.log(logStr);
return;
}
} else {
@@ -1958,6 +2015,9 @@
mPhone.getPhoneId());
final int factorySerialNumber = (null == factory)
? NetworkFactory.SerialNumber.NONE : factory.getSerialNumber();
+
+ mDisabledApnTypeBitMask |= getDisallowedApnTypes();
+
mNetworkAgent = DcNetworkAgent.createDcNetworkAgent(DataConnection.this,
mPhone, mNetworkInfo, mScore, misc, factorySerialNumber, mTransportType);
}
@@ -2562,6 +2622,8 @@
}
void setHandoverState(@HandoverState int state) {
+ mHandoverLocalLog.log("State changed from " + handoverStateToString(mHandoverState)
+ + " to " + handoverStateToString(state));
mHandoverState = state;
}
@@ -2713,6 +2775,33 @@
== NetworkRegistrationInfo.NR_STATE_CONNECTED;
}
+ /**
+ * @return The disallowed APN types bitmask
+ */
+ private @ApnType int getDisallowedApnTypes() {
+ CarrierConfigManager configManager = (CarrierConfigManager)
+ mPhone.getContext().getSystemService(Context.CARRIER_CONFIG_SERVICE);
+ int apnTypesBitmask = 0;
+ if (configManager != null) {
+ PersistableBundle bundle = configManager.getConfigForSubId(mSubId);
+ if (bundle != null) {
+ String key = (mTransportType == AccessNetworkConstants.TRANSPORT_TYPE_WWAN)
+ ? CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY
+ : CarrierConfigManager.KEY_CARRIER_WLAN_DISALLOWED_APN_TYPES_STRING_ARRAY;
+ if (bundle.getStringArray(key) != null) {
+ String disallowedApnTypesString =
+ TextUtils.join(",", bundle.getStringArray(key));
+ if (!TextUtils.isEmpty(disallowedApnTypesString)) {
+ apnTypesBitmask = ApnSetting.getApnTypesBitmaskFromString(
+ disallowedApnTypesString);
+ }
+ }
+ }
+ }
+
+ return apnTypesBitmask;
+ }
+
private void dumpToLog() {
dump(null, new PrintWriter(new StringWriter(0)) {
@Override
@@ -2758,6 +2847,15 @@
return score;
}
+ private String handoverStateToString(@HandoverState int state) {
+ switch (state) {
+ case HANDOVER_STATE_IDLE: return "IDLE";
+ case HANDOVER_STATE_BEING_TRANSFERRED: return "BEING_TRANSFERRED";
+ case HANDOVER_STATE_COMPLETED: return "COMPLETED";
+ default: return "UNKNOWN";
+ }
+ }
+
/**
* Dump the current state.
*
@@ -2787,7 +2885,7 @@
pw.println("mLinkProperties=" + mLinkProperties);
pw.flush();
pw.println("mDataRegState=" + mDataRegState);
- pw.println("mHandoverState=" + mHandoverState);
+ pw.println("mHandoverState=" + handoverStateToString(mHandoverState));
pw.println("mRilRat=" + mRilRat);
pw.println("mNetworkCapabilities=" + getNetworkCapabilities());
pw.println("mCreateTime=" + TimeUtils.logTimeOfDay(mCreateTime));
@@ -2797,12 +2895,18 @@
pw.println("mSubscriptionOverride=" + Integer.toHexString(mSubscriptionOverride));
pw.println("mRestrictedNetworkOverride=" + mRestrictedNetworkOverride);
pw.println("mUnmeteredUseOnly=" + mUnmeteredUseOnly);
+ pw.println("disallowedApnTypes="
+ + ApnSetting.getApnTypesStringFromBitmask(getDisallowedApnTypes()));
pw.println("mInstanceNumber=" + mInstanceNumber);
pw.println("mAc=" + mAc);
pw.println("mScore=" + mScore);
if (mNetworkAgent != null) {
mNetworkAgent.dump(fd, pw, args);
}
+ pw.println("handover local log:");
+ pw.increaseIndent();
+ mHandoverLocalLog.dump(fd, pw, args);
+ pw.decreaseIndent();
pw.decreaseIndent();
pw.println();
pw.flush();
diff --git a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
index b36a490..d9edd28 100644
--- a/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
+++ b/src/java/com/android/internal/telephony/dataconnection/DcNetworkAgent.java
@@ -132,6 +132,13 @@
mDataConnection = null;
}
+ /**
+ * @return The data connection that owns this agent
+ */
+ public synchronized DataConnection getDataConnection() {
+ return mDataConnection;
+ }
+
@Override
protected synchronized void unwanted() {
if (mDataConnection == null) {
diff --git a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
index ea11223..43275ec 100644
--- a/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
+++ b/src/java/com/android/internal/telephony/dataconnection/TransportManager.java
@@ -60,7 +60,7 @@
*
* The device can operate in the following modes, which is stored in the system properties
* ro.telephony.iwlan_operation_mode. If the system properties is missing, then it's tied to
- * IRadio version. For 1.4 or above, it's legacy mode. For 1.3 or below, it's
+ * IRadio version. For 1.4 or above, it's AP-assisted mdoe. For 1.3 or below, it's legacy mode.
*
* Legacy mode:
* Frameworks send all data requests to the default data service, which is the cellular data
@@ -75,13 +75,13 @@
* frameworks to bind.
*
* Package name of data service:
- * The resource overlay 'config_wwan_data_service_package' or,
+ * The resource overlay 'config_wlan_data_service_package' or,
* the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_DATA_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
* The carrier config takes precedence over the resource overlay if both exist.
*
* Package name of network service
- * The resource overlay 'config_wwan_network_service_package' or
+ * The resource overlay 'config_wlan_network_service_package' or
* the carrier config
* {@link CarrierConfigManager#KEY_CARRIER_NETWORK_SERVICE_WLAN_PACKAGE_OVERRIDE_STRING}.
* The carrier config takes precedence over the resource overlay if both exist.
@@ -129,8 +129,8 @@
public @interface IwlanOperationMode {}
/**
- * IWLAN default mode. On device that has IRadio 1.3 or above, it means
- * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.2 or below, it means
+ * IWLAN default mode. On device that has IRadio 1.4 or above, it means
+ * {@link #IWLAN_OPERATION_MODE_AP_ASSISTED}. On device that has IRadio 1.3 or below, it means
* {@link #IWLAN_OPERATION_MODE_LEGACY}.
*/
public static final String IWLAN_OPERATION_MODE_DEFAULT = "default";
diff --git a/src/java/com/android/internal/telephony/ims/ImsResolver.java b/src/java/com/android/internal/telephony/ims/ImsResolver.java
index da005fb..3c0320c 100644
--- a/src/java/com/android/internal/telephony/ims/ImsResolver.java
+++ b/src/java/com/android/internal/telephony/ims/ImsResolver.java
@@ -34,6 +34,7 @@
import android.os.UserHandle;
import android.telephony.CarrierConfigManager;
import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyManager;
import android.telephony.ims.ImsService;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IImsMmTelFeature;
@@ -217,7 +218,19 @@
SubscriptionManager.INVALID_SIM_SLOT_INDEX);
if (slotId == SubscriptionManager.INVALID_SIM_SLOT_INDEX) {
- Log.i(TAG, "Received SIM change for invalid slot id.");
+ Log.i(TAG, "Received CCC for invalid slot id.");
+ return;
+ }
+
+ int subId = intent.getIntExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX,
+ SubscriptionManager.INVALID_SUBSCRIPTION_ID);
+ int slotSimState = mTelephonyManagerProxy.getSimState(mContext, slotId);
+ if (subId == SubscriptionManager.INVALID_SUBSCRIPTION_ID
+ && slotSimState != TelephonyManager.SIM_STATE_ABSENT) {
+ // We only care about carrier config updates that happen when a slot is known to be
+ // absent or populated and the carrier config has been loaded.
+ Log.i(TAG, "Received CCC for slot " + slotId + " and sim state "
+ + slotSimState + ", ignoring.");
return;
}
@@ -254,6 +267,28 @@
int getSlotIndex(int subId);
}
+ /**
+ * Testing interface used to stub out TelephonyManager dependencies.
+ */
+ @VisibleForTesting
+ public interface TelephonyManagerProxy {
+ /**
+ * @return the SIM state for the slot ID specified.
+ */
+ int getSimState(Context context, int slotId);
+ }
+
+ private TelephonyManagerProxy mTelephonyManagerProxy = new TelephonyManagerProxy() {
+ @Override
+ public int getSimState(Context context, int slotId) {
+ TelephonyManager tm = context.getSystemService(TelephonyManager.class);
+ if (tm == null) {
+ return TelephonyManager.SIM_STATE_UNKNOWN;
+ }
+ return tm.getSimState(slotId);
+ }
+ };
+
private SubscriptionManagerProxy mSubscriptionManagerProxy = new SubscriptionManagerProxy() {
@Override
public int getSubId(int slotId) {
@@ -446,6 +481,13 @@
Log.w(TAG, "onError: " + name + "returned with an error result");
scheduleQueryForFeatures(name, DELAY_DYNAMIC_QUERY_MS);
}
+
+ @Override
+ public void onPermanentError(ComponentName name) {
+ Log.w(TAG, "onPermanentError: component=" + name);
+ mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE,
+ name.getPackageName()).sendToTarget();
+ }
};
// Array index corresponds to slot Id associated with the service package name.
@@ -497,6 +539,11 @@
}
@VisibleForTesting
+ public void setTelephonyManagerProxy(TelephonyManagerProxy proxy) {
+ mTelephonyManagerProxy = proxy;
+ }
+
+ @VisibleForTesting
public void setSubscriptionManagerProxy(SubscriptionManagerProxy proxy) {
mSubscriptionManagerProxy = proxy;
}
@@ -520,13 +567,32 @@
* Needs to be called after the constructor to kick off the process of binding to ImsServices.
*/
public void initialize() {
- Log.i(TAG, "Initializing cache and binding.");
+ Log.i(TAG, "Initializing cache.");
mFeatureQueryManager = mDynamicQueryManagerFactory.create(mContext, mDynamicQueryListener);
- // Populates the CarrierConfig override package names for each slot
- mHandler.obtainMessage(HANDLER_CONFIG_CHANGED,
- SubscriptionManager.INVALID_SIM_SLOT_INDEX).sendToTarget();
- // Starts first bind to the system.
- mHandler.obtainMessage(HANDLER_ADD_PACKAGE, null).sendToTarget();
+
+ // This will get all services with the correct intent filter from PackageManager
+ List<ImsServiceInfo> infos = getImsServiceInfo(null);
+ for (ImsServiceInfo info : infos) {
+ if (!mInstalledServicesCache.containsKey(info.name)) {
+ mInstalledServicesCache.put(info.name, info);
+ }
+ }
+ // Update the package names of the carrier ImsServices if they do not exist already and
+ // possibly bind if carrier configs exist. Otherwise wait for CarrierConfigChanged
+ // indication.
+ for (int i = 0; i < mNumSlots; i++) {
+ int subId = mSubscriptionManagerProxy.getSubId(i);
+ PersistableBundle config = mCarrierConfigManager.getConfigForSubId(subId);
+ if (config != null && mCarrierServices[i] == null) {
+ String newPackageName = config.getString(
+ CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, null);
+ if (!TextUtils.isEmpty(newPackageName)) {
+ updateBoundCarrierServices(i, newPackageName);
+ Log.i(TAG, "Initializing, found package " + newPackageName + " on slot "
+ + i);
+ }
+ }
+ }
}
/**
@@ -781,8 +847,8 @@
}
}
- // Remove the ImsService from the cache. At this point, the ImsService will have already been
- // killed.
+ // Remove the ImsService from the cache. This may have been due to the ImsService being removed
+ // from the device or was returning permanent errors when bound.
// Called from the handler ONLY
private boolean maybeRemovedImsService(String packageName) {
ImsServiceInfo match = getInfoByPackageName(mInstalledServicesCache, packageName);
@@ -860,7 +926,7 @@
Log.d(TAG, "Updating Features - New Features: " + features);
controller.changeImsServiceFeatures(features);
} else {
- Log.i(TAG, "updateImsServiceFeatures: unbound with active features, rebinding");
+ Log.i(TAG, "updateImsServiceFeatures: unbound with active features, binding");
bindImsServiceWithFeatures(newInfo, features);
}
// If the carrier service features have changed, the device features will also
@@ -1007,6 +1073,15 @@
handleFeaturesChanged(controller.getComponentName(), config.getServiceFeatures());
}
+ @Override
+ public void imsServiceBindPermanentError(ComponentName name) {
+ if (name == null) {
+ return;
+ }
+ Log.w(TAG, "imsServiceBindPermanentError: component=" + name);
+ mHandler.obtainMessage(HANDLER_REMOVE_PACKAGE, name.getPackageName()).sendToTarget();
+ }
+
/**
* Determines if the features specified should cause a bind or keep a binding active to an
* ImsService.
@@ -1068,18 +1143,18 @@
// ImsService is retrieved from the cache. If the cache hasn't been populated yet,
// the calls to unbind/bind will fail (intended during initial start up).
unbindImsService(getImsServiceInfoFromCache(oldPackageName));
- ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
- // if there is no carrier ImsService, newInfo is null. This we still want to update
- // bindings for device ImsService to pick up the missing features.
- if (newInfo == null || newInfo.featureFromMetadata) {
- bindImsService(newInfo);
- // Recalculate the device ImsService features to reflect changes.
- updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
- } else {
- // ImsServiceInfo that has not had features queried yet. Start async
- // bind and query features.
- scheduleQueryForFeatures(newInfo);
- }
+ }
+ ImsServiceInfo newInfo = getImsServiceInfoFromCache(newPackageName);
+ // if there is no carrier ImsService, newInfo is null. This we still want to update
+ // bindings for device ImsService to pick up the missing features.
+ if (newInfo == null || newInfo.featureFromMetadata) {
+ bindImsService(newInfo);
+ // Recalculate the device ImsService features to reflect changes.
+ updateImsServiceFeatures(getImsServiceInfoFromCache(mDeviceService));
+ } else {
+ // ImsServiceInfo that has not had features queried yet. Start async
+ // bind and query features.
+ scheduleQueryForFeatures(newInfo);
}
}
}
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceController.java b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
index 4b1295c..997dac0 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceController.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceController.java
@@ -71,25 +71,23 @@
synchronized (mLock) {
mIsBound = true;
mIsBinding = false;
- Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
- + service);
- if (service != null) {
- try {
- setServiceController(service);
- notifyImsServiceReady();
- // create all associated features in the ImsService
- for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
- addImsServiceFeature(i);
- }
- } catch (RemoteException e) {
- mIsBound = false;
- mIsBinding = false;
- // Remote exception means that the binder already died.
- cleanupConnection();
- startDelayedRebindToService();
- Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
- + e.getMessage());
+ try {
+ Log.d(LOG_TAG, "ImsService(" + name + "): onServiceConnected with binder: "
+ + service);
+ setServiceController(service);
+ notifyImsServiceReady();
+ // create all associated features in the ImsService
+ for (ImsFeatureConfiguration.FeatureSlotPair i : mImsFeatures) {
+ addImsServiceFeature(i);
}
+ } catch (RemoteException e) {
+ mIsBound = false;
+ mIsBinding = false;
+ // Remote exception means that the binder already died.
+ cleanupConnection();
+ startDelayedRebindToService();
+ Log.e(LOG_TAG, "ImsService(" + name + ") RemoteException:"
+ + e.getMessage());
}
}
}
@@ -111,10 +109,27 @@
mIsBound = false;
}
cleanupConnection();
+ // according to the docs, we should fully unbind before rebinding again.
+ mContext.unbindService(mImsServiceConnection);
Log.w(LOG_TAG, "ImsService(" + name + "): onBindingDied. Starting rebind...");
startDelayedRebindToService();
}
+ @Override
+ public void onNullBinding(ComponentName name) {
+ Log.w(LOG_TAG, "ImsService(" + name + "): onNullBinding. Removing.");
+ synchronized (mLock) {
+ mIsBinding = false;
+ mIsBound = false;
+ }
+ cleanupConnection();
+ if (mCallbacks != null) {
+ // Will trigger an unbind.
+ mCallbacks.imsServiceBindPermanentError(getComponentName());
+ }
+ }
+
+ // Does not clear features, just removes all active features.
private void cleanupConnection() {
cleanupAllFeatures();
cleanUpService();
@@ -151,6 +166,12 @@
*/
void imsServiceFeaturesChanged(ImsFeatureConfiguration config,
ImsServiceController controller);
+
+ /**
+ * Called by the ImsServiceController when there has been an error binding that is
+ * not recoverable, such as the ImsService returning a null binder.
+ */
+ void imsServiceBindPermanentError(ComponentName name);
}
/**
@@ -385,6 +406,8 @@
removeImsServiceFeatureCallbacks();
Log.i(LOG_TAG, "Unbinding ImsService: " + mComponentName);
mContext.unbindService(mImsServiceConnection);
+ mIsBound = false;
+ mIsBinding = false;
cleanUpService();
}
}
@@ -397,6 +420,9 @@
HashSet<ImsFeatureConfiguration.FeatureSlotPair> newImsFeatures)
throws RemoteException {
synchronized (mLock) {
+ if (mImsFeatures.equals(newImsFeatures)) {
+ return;
+ }
Log.i(LOG_TAG, "Features changed (" + mImsFeatures + "->" + newImsFeatures + ") for "
+ "ImsService: " + mComponentName);
HashSet<ImsFeatureConfiguration.FeatureSlotPair> oldImsFeatures =
diff --git a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
index cbeeead..5280ff3 100644
--- a/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
+++ b/src/java/com/android/internal/telephony/ims/ImsServiceFeatureQueryManager.java
@@ -25,6 +25,7 @@
import android.telephony.ims.stub.ImsFeatureConfiguration;
import android.util.Log;
+import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@@ -71,8 +72,9 @@
if (service != null) {
queryImsFeatures(IImsServiceController.Stub.asInterface(service));
} else {
- Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null, cleaning up.");
+ Log.w(LOG_TAG, "onServiceConnected: " + name + " binder null.");
cleanup();
+ mListener.onPermanentError(name);
}
}
@@ -91,7 +93,14 @@
mListener.onError(mName);
return;
}
- Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs = config.getServiceFeatures();
+ Set<ImsFeatureConfiguration.FeatureSlotPair> servicePairs;
+ if (config == null) {
+ // ensure that if the ImsService sent a null config, we return an empty feature
+ // set to the ImsResolver.
+ servicePairs = Collections.emptySet();
+ } else {
+ servicePairs = config.getServiceFeatures();
+ }
// Complete, remove from active queries and notify.
cleanup();
mListener.onComplete(mName, servicePairs);
@@ -117,6 +126,11 @@
* Called when a query has failed and should be retried.
*/
void onError(ComponentName name);
+
+ /**
+ * Called when a query has failed due to a permanent error and should not be retried.
+ */
+ void onPermanentError(ComponentName name);
}
// Maps an active ImsService query (by Package Name String) its query.
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
index e4c7c11..cfd36c2 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ServiceStateTrackerTest.java
@@ -1756,6 +1756,44 @@
waitForMs(200);
}
+ private void changeRegStateWithIwlan(int state, CellIdentity cid, int voiceRat, int dataRat,
+ int iwlanState, int iwlanDataRat) {
+ LteVopsSupportInfo lteVopsSupportInfo =
+ new LteVopsSupportInfo(LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE,
+ LteVopsSupportInfo.LTE_STATUS_NOT_AVAILABLE);
+ sst.mPollingContext[0] = 3;
+
+ // PS WWAN
+ NetworkRegistrationInfo dataResult = new NetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ state, dataRat, 0, false,
+ null, cid, 1, false, false, false, lteVopsSupportInfo, false);
+ sst.sendMessage(sst.obtainMessage(
+ ServiceStateTracker.EVENT_POLL_STATE_PS_CELLULAR_REGISTRATION,
+ new AsyncResult(sst.mPollingContext, dataResult, null)));
+ waitForMs(200);
+
+ // CS WWAN
+ NetworkRegistrationInfo voiceResult = new NetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_CS, AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ state, voiceRat, 0, false,
+ null, cid, false, 0, 0, 0);
+ sst.sendMessage(sst.obtainMessage(
+ ServiceStateTracker.EVENT_POLL_STATE_CS_CELLULAR_REGISTRATION,
+ new AsyncResult(sst.mPollingContext, voiceResult, null)));
+ waitForMs(200);
+
+ // PS WLAN
+ NetworkRegistrationInfo dataIwlanResult = new NetworkRegistrationInfo(
+ NetworkRegistrationInfo.DOMAIN_PS, AccessNetworkConstants.TRANSPORT_TYPE_WLAN,
+ iwlanState, iwlanDataRat, 0, false,
+ null, null, 1, false, false, false, lteVopsSupportInfo, false);
+ sst.sendMessage(sst.obtainMessage(
+ ServiceStateTracker.EVENT_POLL_STATE_PS_IWLAN_REGISTRATION,
+ new AsyncResult(sst.mPollingContext, dataIwlanResult, null)));
+ waitForMs(200);
+ }
+
// Edge and GPRS are grouped under the same family and Edge has higher rate than GPRS.
// Expect no rat update when move from E to G.
@Test
@@ -1928,6 +1966,49 @@
assertTrue(Arrays.equals(new int[0], sst.mSS.getCellBandwidths()));
}
+ /**
+ * Ensure that TransportManager changes due to transport preference changes are picked up in the
+ * new ServiceState when a poll event occurs. This causes ServiceState#getRilDataRadioTechnology
+ * to change even though the underlying transports have not changed state.
+ */
+ @SmallTest
+ @Test
+ public void testRilDataTechnologyChangeTransportPreference() {
+ when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(false);
+
+ // Start state: Cell data only LTE + IWLAN
+ CellIdentityLte cellIdentity =
+ new CellIdentityLte(1, 1, 5, 1, 5000, "001", "01", "test", "tst");
+ changeRegStateWithIwlan(
+ // WWAN
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+ // WLAN
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+ TelephonyManager.NETWORK_TYPE_IWLAN);
+ assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_LTE, sst.mSS.getRilDataRadioTechnology());
+
+ sst.registerForDataRegStateOrRatChanged(AccessNetworkConstants.TRANSPORT_TYPE_WWAN,
+ mTestHandler, EVENT_DATA_RAT_CHANGED, null);
+ // transport preference change for a PDN for IWLAN occurred, no registration change, but
+ // trigger unrelated poll to pick up transport preference.
+ when(mTransportManager.isAnyApnPreferredOnIwlan()).thenReturn(true);
+ changeRegStateWithIwlan(
+ // WWAN
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME, cellIdentity,
+ TelephonyManager.NETWORK_TYPE_UNKNOWN, TelephonyManager.NETWORK_TYPE_LTE,
+ // WLAN
+ NetworkRegistrationInfo.REGISTRATION_STATE_HOME,
+ TelephonyManager.NETWORK_TYPE_IWLAN);
+ // Now check to make sure a transport independent notification occurred for the registrants.
+ // There will be two, one when the registration happened and another when the transport
+ // preference changed.
+ ArgumentCaptor<Message> messageArgumentCaptor = ArgumentCaptor.forClass(Message.class);
+ verify(mTestHandler, times(2)).sendMessageAtTime(messageArgumentCaptor.capture(),
+ anyLong());
+ assertEquals(ServiceState.RIL_RADIO_TECHNOLOGY_IWLAN, sst.mSS.getRilDataRadioTechnology());
+ }
+
@Test
public void testGetServiceProviderNameWithBrandOverride() {
String brandOverride = "spn from brand override";
diff --git a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
index b7fe4ca..018747e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/dataconnection/DataConnectionTest.java
@@ -343,6 +343,12 @@
return (long) method.invoke(mDc, response);
}
+ private boolean isUnmeteredUseOnly() throws Exception {
+ Method method = DataConnection.class.getDeclaredMethod("isUnmeteredUseOnly");
+ method.setAccessible(true);
+ return (boolean) method.invoke(mDc);
+ }
+
private SetupResult setLinkProperties(DataCallResponse response,
LinkProperties linkProperties)
throws Exception {
@@ -500,6 +506,12 @@
return (NetworkCapabilities) method.invoke(mDc);
}
+ private int getDisallowedApnTypes() throws Exception {
+ Method method = DataConnection.class.getDeclaredMethod("getDisallowedApnTypes");
+ method.setAccessible(true);
+ return (int) method.invoke(mDc);
+ }
+
@Test
@SmallTest
public void testNetworkCapability() throws Exception {
@@ -516,6 +528,10 @@
assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_MMS));
+ mContextFixture.getCarrierConfigBundle().putStringArray(
+ CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[] {"supl"});
+
mDc.sendMessage(DataConnection.EVENT_DISCONNECT, mDcp);
waitForMs(100);
doReturn(mApn1).when(mApnContext).getApnSetting();
@@ -526,7 +542,7 @@
.hasCapability(NetworkCapabilities.NET_CAPABILITY_DUN));
assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET));
- assertTrue("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
+ assertFalse("capabilities: " + getNetworkCapabilities(), getNetworkCapabilities()
.hasCapability(NetworkCapabilities.NET_CAPABILITY_SUPL));
}
@@ -902,4 +918,40 @@
public void testStartNattKeepaliveFailCondensed() throws Exception {
checkStartNattKeepaliveFail(true);
}
+
+ @Test
+ @SmallTest
+ public void testIsUnmeteredUseOnly() throws Exception {
+ Field field = DataConnection.class.getDeclaredField("mTransportType");
+ field.setAccessible(true);
+ field.setInt(mDc, AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
+
+ assertFalse(isUnmeteredUseOnly());
+
+ field = DataConnection.class.getDeclaredField("mTransportType");
+ field.setAccessible(true);
+ field.setInt(mDc, AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+ doReturn(false).when(mDataEnabledSettings).isDataEnabled();
+ doReturn(false).when(mServiceState).getDataRoaming();
+ doReturn(ApnSetting.TYPE_MMS).when(mApnContext).getApnTypeBitmask();
+
+ mContextFixture.getCarrierConfigBundle().putStringArray(
+ CarrierConfigManager.KEY_CARRIER_METERED_APN_TYPES_STRINGS,
+ new String[] { "default" });
+
+ assertTrue(isUnmeteredUseOnly());
+ }
+
+ @Test
+ @SmallTest
+ public void testGetDisallowedApnTypes() throws Exception {
+ mContextFixture.getCarrierConfigBundle().putStringArray(
+ CarrierConfigManager.KEY_CARRIER_WWAN_DISALLOWED_APN_TYPES_STRING_ARRAY,
+ new String[] { "mms", "supl", "fota" });
+ testConnectEvent();
+
+ assertEquals(ApnSetting.TYPE_MMS | ApnSetting.TYPE_SUPL | ApnSetting.TYPE_FOTA,
+ getDisallowedApnTypes());
+ }
}
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
index 4062b31..db7cf8e 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsResolverTest.java
@@ -46,6 +46,7 @@
import android.os.PersistableBundle;
import android.os.RemoteException;
import android.telephony.CarrierConfigManager;
+import android.telephony.TelephonyManager;
import android.telephony.ims.ImsService;
import android.telephony.ims.feature.ImsFeature;
import android.telephony.ims.stub.ImsFeatureConfiguration;
@@ -88,6 +89,8 @@
@Mock
ImsResolver.SubscriptionManagerProxy mTestSubscriptionManagerProxy;
@Mock
+ ImsResolver.TelephonyManagerProxy mTestTelephonyManagerProxy;
+ @Mock
CarrierConfigManager mMockCarrierConfigManager;
@Mock
ImsResolver.ImsDynamicQueryManagerFactory mMockQueryManagerFactory;
@@ -129,7 +132,7 @@
setupController();
// Complete package manager lookup and cache.
- startBind();
+ startBindCarrierConfigAlreadySet();
ImsResolver.ImsServiceInfo testCachedService =
mTestImsResolver.getImsServiceInfoFromCache(
@@ -155,7 +158,7 @@
setupController();
// Complete package manager lookup and cache.
- startBind();
+ startBindCarrierConfigAlreadySet();
ImsResolver.ImsServiceInfo testCachedService =
mTestImsResolver.getImsServiceInfoFromCache(
@@ -185,7 +188,7 @@
ImsServiceController controller = setupController();
// Start bind to carrier service
- startBind();
+ startBindCarrierConfigAlreadySet();
// setup features response
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
@@ -210,7 +213,7 @@
setupPackageQuery(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true);
ImsServiceController controller = setupController();
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
verify(controller).bind(features);
@@ -234,7 +237,7 @@
ImsServiceController controller = setupController();
// Bind without emergency calling
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, features, 1);
verify(controller).bind(features);
verify(controller, never()).unbind();
@@ -266,7 +269,7 @@
// Set the CarrierConfig string to null so that ImsResolver will not bind to the available
// Services
setConfigCarrierString(0, null);
- startBind();
+ startBindCarrierConfigAlreadySet();
waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
verify(mMockQueryManager, never()).startQuery(any(), any());
@@ -293,7 +296,7 @@
ImsServiceController controller = setupController();
- startBind();
+ startBindNoCarrierConfig(1);
// Wait to make sure that there are no dynamic queries that are being completed.
waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
@@ -333,7 +336,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
// Verify that all features that have been defined for the carrier override are bound
@@ -344,6 +347,7 @@
// device controller (including emergency voice for slot 0)
HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
convertToHashSet(deviceFeatures, 0);
+ deviceFeatureSet.removeAll(carrierFeatures);
verify(deviceController).bind(deviceFeatureSet);
verify(deviceController, never()).unbind();
assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -394,7 +398,7 @@
setupPackageQuery(info);
ImsServiceController controller = setupController();
// Bind using default features
- startBind();
+ startBindNoCarrierConfig(2);
HashSet<ImsFeatureConfiguration.FeatureSlotPair> featureSet =
convertToHashSet(features, 0);
featureSet.addAll(convertToHashSet(features, 1));
@@ -442,7 +446,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
// Verify that all features that have been defined for the carrier override are bound
@@ -455,9 +459,7 @@
convertToHashSet(deviceFeatures, 1);
deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
deviceFeatureSet.removeAll(carrierFeatures);
- // we will first have bound to device and then the features will change once the dynamic
- // returns. So, instead of checking the bind parameters, we will check the change parameters
- verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+ verify(deviceController).bind(deviceFeatureSet);
verify(deviceController, never()).unbind();
assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -508,7 +510,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
// Verify that all features that have been defined for the carrier override are bound
verify(carrierController).bind(carrierFeatures);
@@ -520,9 +522,7 @@
convertToHashSet(deviceFeatures, 1);
deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
deviceFeatureSet.removeAll(carrierFeatures);
- // device ImsService will bind with all of its defined features first and then when the
- // carrier query comes back, it will change. So, checking change instead of bind here.
- verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+ verify(deviceController).bind(deviceFeatureSet);
verify(deviceController, never()).unbind();
assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -566,7 +566,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
// Verify that all features that have been defined for the carrier override are bound
verify(carrierController).bind(carrierFeatures);
@@ -578,9 +578,7 @@
convertToHashSet(deviceFeatures, 1);
deviceFeatureSet.addAll(convertToHashSet(deviceFeatures, 0));
deviceFeatureSet.removeAll(carrierFeatures);
- // we will first have bound to device and then the features will change once the dynamic
- // returns. So, instead of checking the bind parameters, we will check the change parameters
- verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
+ verify(deviceController).bind(deviceFeatureSet);
verify(deviceController, never()).unbind();
assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
@@ -624,7 +622,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
// Carrier service doesn't support the voice feature.
@@ -670,7 +668,7 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
// Tell the package manager that carrier app is uninstalled
@@ -714,14 +712,11 @@
ImsServiceController carrierController = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
setConfigCarrierString(0, null);
- Intent carrierConfigIntent = new Intent();
- carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
- mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
- waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ sendCarrierConfigChanged(0, 0);
// Verify that the carrier controller is unbound
verify(carrierController).unbind();
@@ -768,14 +763,11 @@
ImsServiceController carrierController2 = mock(ImsServiceController.class);
setImsServiceControllerFactory(deviceController, carrierController1, carrierController2);
- startBind();
+ startBindCarrierConfigAlreadySet();
setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures1, 1);
setConfigCarrierString(0, TEST_CARRIER_2_DEFAULT_NAME.getPackageName());
- Intent carrierConfigIntent = new Intent();
- carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, 0);
- mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
- waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ sendCarrierConfigChanged(0, 0);
setupDynamicQueryFeatures(TEST_CARRIER_2_DEFAULT_NAME, carrierFeatures2, 1);
// Verify that carrier 1 is unbound
@@ -816,7 +808,7 @@
setImsServiceControllerFactory(deviceController, carrierController);
// Bind with device ImsService
- startBind();
+ startBindCarrierConfigAlreadySet();
// Boot complete happens and the Carrier ImsService is now available.
HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
@@ -838,6 +830,97 @@
verify(deviceController).changeImsServiceFeatures(deviceFeatureSet);
}
+ /**
+ * If a misbehaving ImsService returns null for the Binder connection when we perform a dynamic
+ * feature query, verify we never perform a full bind for any features.
+ */
+ @Test
+ @SmallTest
+ public void testPermanentBindFailureDuringFeatureQuery() throws RemoteException {
+ setupResolver(1/*numSlots*/);
+ List<ResolveInfo> info = new ArrayList<>();
+ Set<String> deviceFeatures = new HashSet<>();
+ deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+ deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+ // Set the carrier override package for slot 0
+ setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+ // Carrier service doesn't support the voice feature.
+ carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+ // Use device default package, which will load the ImsService that the device provides
+ info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+ info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+ setupPackageQuery(info);
+ ImsServiceController deviceController = mock(ImsServiceController.class);
+ ImsServiceController carrierController = mock(ImsServiceController.class);
+ setImsServiceControllerFactory(deviceController, carrierController);
+
+ startBindCarrierConfigAlreadySet();
+ // dynamic query results in a failure.
+ setupDynamicQueryFeaturesFailure(TEST_CARRIER_DEFAULT_NAME, 1);
+
+ // Verify that a bind never occurs for the carrier controller.
+ verify(carrierController, never()).bind(any());
+ verify(carrierController, never()).unbind();
+ // Verify that all features are used to bind to the device ImsService since the carrier
+ // ImsService failed to bind properly.
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+ convertToHashSet(deviceFeatures, 0);
+ verify(deviceController).bind(deviceFeatureSet);
+ verify(deviceController, never()).unbind();
+ assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+ }
+
+ /**
+ * If a misbehaving ImsService returns null for the Binder connection when we perform bind,
+ * verify the service is disconnected.
+ */
+ @Test
+ @SmallTest
+ public void testPermanentBindFailureDuringBind() throws RemoteException {
+ setupResolver(1/*numSlots*/);
+ List<ResolveInfo> info = new ArrayList<>();
+ Set<String> deviceFeatures = new HashSet<>();
+ deviceFeatures.add(ImsResolver.METADATA_MMTEL_FEATURE);
+ deviceFeatures.add(ImsResolver.METADATA_RCS_FEATURE);
+ // Set the carrier override package for slot 0
+ setConfigCarrierString(0, TEST_CARRIER_DEFAULT_NAME.getPackageName());
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> carrierFeatures = new HashSet<>();
+ // Carrier service doesn't support the voice feature.
+ carrierFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(0, ImsFeature.FEATURE_RCS));
+ // Use device default package, which will load the ImsService that the device provides
+ info.add(getResolveInfo(TEST_DEVICE_DEFAULT_NAME, deviceFeatures, true));
+ info.add(getResolveInfo(TEST_CARRIER_DEFAULT_NAME, new HashSet<>(), true));
+ setupPackageQuery(info);
+ ImsServiceController deviceController = mock(ImsServiceController.class);
+ ImsServiceController carrierController = mock(ImsServiceController.class);
+ setImsServiceControllerFactory(deviceController, carrierController);
+
+ startBindCarrierConfigAlreadySet();
+ setupDynamicQueryFeatures(TEST_CARRIER_DEFAULT_NAME, carrierFeatures, 1);
+
+ // Verify that a bind never occurs for the carrier controller.
+ verify(carrierController).bind(carrierFeatures);
+ verify(carrierController, never()).unbind();
+ // Verify that all features that are not defined in the carrier override are bound in the
+ // device controller (including emergency voice for slot 0)
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> deviceFeatureSet =
+ convertToHashSet(deviceFeatures, 0);
+ deviceFeatureSet.removeAll(carrierFeatures);
+ verify(deviceController).bind(deviceFeatureSet);
+ verify(deviceController, never()).unbind();
+ assertEquals(TEST_DEVICE_DEFAULT_NAME, deviceController.getComponentName());
+
+ mTestImsResolver.imsServiceBindPermanentError(TEST_CARRIER_DEFAULT_NAME);
+ waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ verify(carrierController).unbind();
+ // Verify that the device ImsService features are changed to include the ones previously
+ // taken by the carrier app.
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> originalDeviceFeatureSet =
+ convertToHashSet(deviceFeatures, 0);
+ verify(deviceController).changeImsServiceFeatures(originalDeviceFeatureSet);
+ }
+
private void setupResolver(int numSlots) {
when(mMockContext.getSystemService(eq(Context.CARRIER_CONFIG_SERVICE))).thenReturn(
mMockCarrierConfigManager);
@@ -849,6 +932,8 @@
mCarrierConfigs[i]);
when(mTestSubscriptionManagerProxy.getSlotIndex(eq(i))).thenReturn(i);
when(mTestSubscriptionManagerProxy.getSubId(eq(i))).thenReturn(i);
+ when(mTestTelephonyManagerProxy.getSimState(any(Context.class), eq(i))).thenReturn(
+ TelephonyManager.SIM_STATE_READY);
}
mTestImsResolver = new ImsResolver(mMockContext, TEST_DEVICE_DEFAULT_NAME.getPackageName(),
@@ -865,6 +950,7 @@
mTestCarrierConfigReceiver = receiversCaptor.getAllValues().get(0);
mTestBootCompleteReceiver = receiversCaptor.getAllValues().get(1);
mTestImsResolver.setSubscriptionManagerProxy(mTestSubscriptionManagerProxy);
+ mTestImsResolver.setTelephonyManagerProxy(mTestTelephonyManagerProxy);
when(mMockQueryManagerFactory.create(any(Context.class),
any(ImsServiceFeatureQueryManager.Listener.class))).thenReturn(mMockQueryManager);
mTestImsResolver.setImsDynamicQueryManagerFactory(mMockQueryManagerFactory);
@@ -906,7 +992,11 @@
return controller;
}
- private void startBind() {
+ /**
+ * In this case, there is a CarrierConfig already set for the sub/slot combo when initializing.
+ * This automatically kicks off the binding internally.
+ */
+ private void startBindCarrierConfigAlreadySet() {
mTestImsResolver.initialize();
ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
@@ -915,6 +1005,23 @@
waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
}
+ /**
+ * In this case, there is no carrier config override, send CarrierConfig loaded intent to all
+ * slots, indicating that the SIMs are loaded and to bind the device default.
+ */
+ private void startBindNoCarrierConfig(int numSlots) {
+ mTestImsResolver.initialize();
+ ArgumentCaptor<ImsServiceFeatureQueryManager.Listener> queryManagerCaptor =
+ ArgumentCaptor.forClass(ImsServiceFeatureQueryManager.Listener.class);
+ verify(mMockQueryManagerFactory).create(any(Context.class), queryManagerCaptor.capture());
+ mDynamicQueryListener = queryManagerCaptor.getValue();
+ waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ // For ease of testing, slotId = subId
+ for (int i = 0; i < numSlots; i++) {
+ sendCarrierConfigChanged(i, i);
+ }
+ }
+
private void setupDynamicQueryFeatures(ComponentName name,
HashSet<ImsFeatureConfiguration.FeatureSlotPair> features, int times) {
// wait for schedule to happen
@@ -928,6 +1035,18 @@
waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
}
+ private void setupDynamicQueryFeaturesFailure(ComponentName name, int times) {
+ // wait for schedule to happen
+ waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ // ensure that startQuery was called
+ when(mMockQueryManager.startQuery(any(ComponentName.class), any(String.class)))
+ .thenReturn(true);
+ verify(mMockQueryManager, times(times)).startQuery(eq(name), any(String.class));
+ mDynamicQueryListener.onPermanentError(name);
+ // wait for handling of onPermanentError
+ waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ }
+
public void packageChanged(String packageName) {
// Tell the package manager that a new device feature is installed
Intent addPackageIntent = new Intent();
@@ -1004,6 +1123,14 @@
}
+ private void sendCarrierConfigChanged(int subId, int slotId) {
+ Intent carrierConfigIntent = new Intent();
+ carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SUBSCRIPTION_INDEX, subId);
+ carrierConfigIntent.putExtra(CarrierConfigManager.EXTRA_SLOT_INDEX, slotId);
+ mTestCarrierConfigReceiver.onReceive(null, carrierConfigIntent);
+ waitForHandlerAction(mTestImsResolver.getHandler(), TEST_TIMEOUT);
+ }
+
private void setConfigCarrierString(int subId, String packageName) {
mCarrierConfigs[subId].putString(
CarrierConfigManager.KEY_CONFIG_IMS_PACKAGE_OVERRIDE_STRING, packageName);
diff --git a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
index 7e1d71c..85a83ce 100644
--- a/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
+++ b/tests/telephonytests/src/com/android/internal/telephony/ims/ImsServiceControllerTest.java
@@ -295,6 +295,26 @@
}
/**
+ * Ensures that imsServiceBindPermanentError is called when the binder returns null.
+ */
+ @SmallTest
+ @Test
+ public void testBindServiceAndReturnedNull() throws RemoteException {
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures = new HashSet<>();
+ // Slot 1, MMTel
+ testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 1));
+ // Slot 1, RCS
+ testFeatures.add(new ImsFeatureConfiguration.FeatureSlotPair(1, 2));
+
+ bindAndNullServiceError(testFeatures);
+
+ verify(mMockCallbacks, never()).imsServiceFeatureCreated(anyInt(), anyInt(),
+ eq(mTestImsServiceController));
+ verify(mMockProxyCallbacks, never()).imsFeatureCreated(anyInt(), anyInt());
+ verify(mMockCallbacks).imsServiceBindPermanentError(eq(mTestComponentName));
+ }
+
+ /**
* Ensures ImsService and ImsResolver are notified when a feature is added.
*/
@SmallTest
@@ -499,16 +519,27 @@
verify(mMockContext, times(2)).bindService(any(), any(), anyInt());
}
+ private void bindAndNullServiceError(
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+ ServiceConnection connection = bindService(testFeatures);
+ connection.onNullBinding(mTestComponentName);
+ }
+
private ServiceConnection bindAndConnectService(
HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
+ ServiceConnection connection = bindService(testFeatures);
+ IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
+ when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
+ connection.onServiceConnected(mTestComponentName, controllerStub);
+ return connection;
+ }
+
+ private ServiceConnection bindService(
+ HashSet<ImsFeatureConfiguration.FeatureSlotPair> testFeatures) {
ArgumentCaptor<ServiceConnection> serviceCaptor =
ArgumentCaptor.forClass(ServiceConnection.class);
assertTrue(mTestImsServiceController.bind(testFeatures));
verify(mMockContext).bindService(any(), serviceCaptor.capture(), anyInt());
- IImsServiceController.Stub controllerStub = mock(IImsServiceController.Stub.class);
- when(controllerStub.queryLocalInterface(any())).thenReturn(mMockServiceControllerBinder);
- serviceCaptor.getValue().onServiceConnected(mTestComponentName,
- controllerStub);
return serviceCaptor.getValue();
}
}