Merge "[Telephony] Use TelephonyCallback instead of PhoneStateListener part4" into sc-dev
diff --git a/OWNERS b/OWNERS
index 3059d4d..e095b89 100644
--- a/OWNERS
+++ b/OWNERS
@@ -13,3 +13,4 @@
dbright@google.com
xiaotonj@google.com
+per-file *SimPhonebookProvider* = file:platform/packages/apps/Contacts:/OWNERS
diff --git a/res/values/config.xml b/res/values/config.xml
index 7ce141e..08a84f8 100644
--- a/res/values/config.xml
+++ b/res/values/config.xml
@@ -310,4 +310,7 @@
<!-- Whether or not to support device to device communication using RTP and DTMF communication
transports. -->
<bool name="config_use_device_to_device_communication">false</bool>
+
+ <!-- Whether or not to show notifications for when bluetooth connection is bad during a call -->
+ <bool name="enable_bluetooth_call_quality_notification">false</bool>
</resources>
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 1613ca8..4df6a52 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -2176,7 +2176,7 @@
<!-- details of the message popped up when there is
bad call quality caused by bluetooth connection-->
<string name="call_quality_notification_bluetooth_details">
- Suggestion: Improve Bluetooth connectivity</string>
+ Your bluetooth signal is weak. Try switching to speakerphone.</string>
<!-- name of the notification that pops up during
a phone call when there is bad call quality -->
<string name="call_quality_notification_name">Call Quality Notification</string>
diff --git a/src/com/android/phone/EmergencyCallbackModeExitDialog.java b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
index 591f435..adba850 100644
--- a/src/com/android/phone/EmergencyCallbackModeExitDialog.java
+++ b/src/com/android/phone/EmergencyCallbackModeExitDialog.java
@@ -16,6 +16,8 @@
package com.android.phone;
+import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
+
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
@@ -74,7 +76,7 @@
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
-
+ getWindow().addPrivateFlags(SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS);
mPhone = PhoneGlobals.getInstance().getPhoneInEcm();
// Check if phone is in Emergency Callback Mode. If not, exit.
if (mPhone == null || !mPhone.isInEcm()) {
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index 722f97b..24c55cf 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -18,6 +18,8 @@
import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_CDMA;
+import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_GSM;
import static com.android.internal.telephony.PhoneConstants.PHONE_TYPE_IMS;
import static com.android.internal.telephony.PhoneConstants.SUBSCRIPTION_KEY;
@@ -143,6 +145,7 @@
import com.android.internal.telephony.CommandsInterface;
import com.android.internal.telephony.DefaultPhoneNotifier;
import com.android.internal.telephony.GbaManager;
+import com.android.internal.telephony.GsmCdmaPhone;
import com.android.internal.telephony.HalVersion;
import com.android.internal.telephony.IBooleanConsumer;
import com.android.internal.telephony.ICallForwardingInfoCallback;
@@ -4642,7 +4645,7 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_UNKNOWN;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().getConfigInt(key);
+ return ImsManager.getInstance(mApp, slotId).getConfigInt(key);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "getImsProvisioningInt: ImsService is not available for subscription '"
+ subId + "' for key:" + key);
@@ -4667,7 +4670,7 @@
+ subId + "' for key:" + key);
return ProvisioningManager.STRING_QUERY_RESULT_ERROR_GENERIC;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().getConfigString(key);
+ return ImsManager.getInstance(mApp, slotId).getConfigString(key);
} catch (com.android.ims.ImsException e) {
Log.w(LOG_TAG, "getImsProvisioningString: ImsService is not available for sub '"
+ subId + "' for key:" + key);
@@ -4693,10 +4696,10 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().setConfig(key, value);
- } catch (com.android.ims.ImsException e) {
+ return ImsManager.getInstance(mApp, slotId).setConfig(key, value);
+ } catch (com.android.ims.ImsException | RemoteException e) {
Log.w(LOG_TAG, "setImsProvisioningInt: ImsService unavailable for sub '" + subId
- + "' for key:" + key);
+ + "' for key:" + key, e);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -4719,10 +4722,10 @@
+ subId + "' for key:" + key);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
}
- return ImsManager.getInstance(mApp, slotId).getConfigInterface().setConfig(key, value);
- } catch (com.android.ims.ImsException e) {
+ return ImsManager.getInstance(mApp, slotId).setConfig(key, value);
+ } catch (com.android.ims.ImsException | RemoteException e) {
Log.w(LOG_TAG, "setImsProvisioningString: ImsService unavailable for sub '" + subId
- + "' for key:" + key);
+ + "' for key:" + key, e);
return ImsConfigImplBase.CONFIG_RESULT_FAILED;
} finally {
Binder.restoreCallingIdentity(identity);
@@ -6982,15 +6985,6 @@
}
@Override
- public void setRadioCapability(RadioAccessFamily[] rafs) {
- try {
- ProxyController.getInstance().setRadioCapability(rafs);
- } catch (RuntimeException e) {
- Log.w(LOG_TAG, "setRadioCapability: Runtime Exception");
- }
- }
-
- @Override
public int getRadioAccessFamily(int phoneId, String callingPackage) {
Phone phone = PhoneFactory.getPhone(phoneId);
try {
@@ -8699,6 +8693,31 @@
}
/**
+ * Start emergency callback mode for GsmCdmaPhone for testing.
+ */
+ @Override
+ public void startEmergencyCallbackMode() {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "startEmergencyCallbackMode");
+ enforceModifyPermission();
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ for (Phone phone : PhoneFactory.getPhones()) {
+ Rlog.d(LOG_TAG, "startEmergencyCallbackMode phone type: " + phone.getPhoneType());
+ if (phone != null && ((phone.getPhoneType() == PHONE_TYPE_GSM)
+ || (phone.getPhoneType() == PHONE_TYPE_CDMA))) {
+ GsmCdmaPhone gsmCdmaPhone = (GsmCdmaPhone) phone;
+ gsmCdmaPhone.obtainMessage(
+ GsmCdmaPhone.EVENT_EMERGENCY_CALLBACK_MODE_ENTER).sendToTarget();
+ Rlog.d(LOG_TAG, "startEmergencyCallbackMode: triggered");
+ }
+ }
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
+
+ /**
* Update emergency number list for test mode.
*/
@Override
@@ -9911,6 +9930,28 @@
}
/**
+ * Enables or disables the test mode for RCS VoLTE single registration.
+ */
+ @Override
+ public void setRcsSingleRegistrationTestModeEnabled(boolean enabled) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setRcsSingleRegistrationTestModeEnabled");
+
+ RcsProvisioningMonitor.getInstance().setTestModeEnabled(enabled);
+ }
+
+ /**
+ * Gets the test mode for RCS VoLTE single registration.
+ */
+ @Override
+ public boolean getRcsSingleRegistrationTestModeEnabled() {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "getRcsSingleRegistrationTestModeEnabled");
+
+ return RcsProvisioningMonitor.getInstance().getTestModeEnabled();
+ }
+
+ /**
* Overrides the config of RCS VoLTE single registration enabled for the device.
*/
@Override
@@ -9933,7 +9974,7 @@
@Override
public void sendDeviceToDeviceMessage(int message, int value) {
TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
- "setCarrierSingleRegistrationEnabledOverride");
+ "sendDeviceToDeviceMessage");
enforceModifyPermission();
final long identity = Binder.clearCallingIdentity();
@@ -9950,6 +9991,29 @@
}
}
+ /**
+ * Sets the specified device to device transport active.
+ * @param transport The transport to set active.
+ */
+ @Override
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ TelephonyPermissions.enforceShellOnly(Binder.getCallingUid(),
+ "setActiveDeviceToDeviceTransport");
+ enforceModifyPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ TelephonyConnectionService service =
+ TelecomAccountRegistry.getInstance(null).getTelephonyConnectionService();
+ if (service == null) {
+ Rlog.e(LOG_TAG, "setActiveDeviceToDeviceTransport: not in a call.");
+ return;
+ }
+ service.setActiveDeviceToDeviceTransport(transport);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
/**
* Gets the config of RCS VoLTE single registration enabled for the device.
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 77f8a7b..dcf8e92 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -39,15 +39,20 @@
import android.telephony.ims.RcsConfig;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IRcsConfigCallback;
-import android.telephony.ims.feature.ImsFeature;
import android.text.TextUtils;
+import android.util.ArraySet;
+import android.util.SparseArray;
+import com.android.ims.FeatureConnector;
+import com.android.ims.FeatureUpdates;
+import com.android.ims.RcsFeatureManager;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.util.HandlerExecutor;
import com.android.internal.util.CollectionUtils;
import com.android.telephony.Rlog;
+import java.util.Arrays;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
@@ -67,6 +72,7 @@
private static final int EVENT_RECONFIG_REQUEST = 5;
private static final int EVENT_DEVICE_CONFIG_OVERRIDE = 6;
private static final int EVENT_CARRIER_CONFIG_OVERRIDE = 7;
+ private static final int EVENT_RESET = 8;
private final PhoneGlobals mPhone;
private final Handler mHandler;
@@ -77,12 +83,15 @@
private final HashMap<Integer, Boolean> mCarrierSingleRegistrationEnabledOverride =
new HashMap<>();
private String mDmaPackageName;
+ private final SparseArray<RcsFeatureListener> mRcsFeatureListeners = new SparseArray<>();
+ private volatile boolean mTestModeEnabled;
private final CarrierConfigManager mCarrierConfigManager;
private final DmaChangedListener mDmaChangedListener;
private final SubscriptionManager mSubscriptionManager;
private final TelephonyRegistryManager mTelephonyRegistryManager;
private final RoleManagerAdapter mRoleManager;
+ private FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
private static RcsProvisioningMonitor sInstance;
@@ -147,6 +156,7 @@
@Override
public void handleMessage(Message msg) {
+ logv("handleMessage: " + msg);
switch (msg.what) {
case EVENT_SUB_CHANGED:
onSubChanged();
@@ -178,6 +188,9 @@
onCarrierConfigChange();
}
break;
+ case EVENT_RESET:
+ reset();
+ break;
default:
loge("Unhandled event " + msg.what);
}
@@ -188,13 +201,20 @@
private int mSubId;
private volatile int mSingleRegistrationCapability;
private volatile byte[] mConfig;
- private HashSet<IRcsConfigCallback> mRcsConfigCallbacks;
+ private ArraySet<IRcsConfigCallback> mRcsConfigCallbacks;
+ private IImsConfig mIImsConfig;
+ private boolean mHasReconfigRequest;
RcsProvisioningInfo(int subId, int singleRegistrationCapability, byte[] config) {
mSubId = subId;
mSingleRegistrationCapability = singleRegistrationCapability;
mConfig = config;
- mRcsConfigCallbacks = new HashSet<>();
+ mRcsConfigCallbacks = new ArraySet<>();
+ registerRcsFeatureListener(this);
+ }
+
+ int getSubId() {
+ return mSubId;
}
void setSingleRegistrationCapability(int singleRegistrationCapability) {
@@ -206,7 +226,14 @@
}
void setConfig(byte[] config) {
- mConfig = config;
+ if (!Arrays.equals(mConfig, config)) {
+ mConfig = config;
+ if (mConfig != null) {
+ notifyRcsAutoConfigurationReceived();
+ } else {
+ notifyRcsAutoConfigurationRemoved();
+ }
+ }
}
byte[] getConfig() {
@@ -214,15 +241,14 @@
}
boolean addRcsConfigCallback(IRcsConfigCallback cb) {
- IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
- if (imsConfig == null) {
+ if (mIImsConfig == null) {
logd("fail to addRcsConfigCallback as imsConfig is null");
return false;
}
synchronized (mRcsConfigCallbacks) {
try {
- imsConfig.addRcsConfigCallback(cb);
+ mIImsConfig.addRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to addRcsConfigCallback due to " + e);
return false;
@@ -234,12 +260,11 @@
boolean removeRcsConfigCallback(IRcsConfigCallback cb) {
boolean result = true;
- IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
synchronized (mRcsConfigCallbacks) {
- if (imsConfig != null) {
+ if (mIImsConfig != null) {
try {
- imsConfig.removeRcsConfigCallback(cb);
+ mIImsConfig.removeRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to removeRcsConfigCallback due to " + e);
}
@@ -258,16 +283,89 @@
return result;
}
+ void triggerRcsReconfiguration() {
+ if (mIImsConfig != null) {
+ try {
+ logv("triggerRcsReconfiguration for sub:" + mSubId);
+ mIImsConfig.triggerRcsReconfiguration();
+ mHasReconfigRequest = false;
+ } catch (RemoteException e) {
+ loge("triggerRcsReconfiguration failed due to " + e);
+ }
+ } else {
+ logd("triggerRcsReconfiguration failed due to IImsConfig null.");
+ mHasReconfigRequest = true;
+ }
+ }
+
+ void destroy() {
+ unregisterRcsFeatureListener(this);
+ clear();
+ mIImsConfig = null;
+ mRcsConfigCallbacks = null;
+ }
+
void clear() {
setConfig(null);
+ clearCallbacks();
+ }
+
+ void onRcsStatusChanged(IImsConfig binder) {
+ logv("onRcsStatusChanged for sub:" + mSubId + ", IImsConfig?" + binder);
+ if (mIImsConfig != binder) {
+ mIImsConfig = binder;
+ if (mIImsConfig != null) {
+ if (mHasReconfigRequest) {
+ triggerRcsReconfiguration();
+ } else {
+ notifyRcsAutoConfigurationReceived();
+ }
+ } else {
+ // clear callbacks if rcs disconnected
+ clearCallbacks();
+ }
+ }
+ }
+
+ private void notifyRcsAutoConfigurationReceived() {
+ if (mConfig == null) {
+ logd("Rcs config is null for sub : " + mSubId);
+ return;
+ }
+
+ if (mIImsConfig != null) {
+ try {
+ logv("notifyRcsAutoConfigurationReceived for sub:" + mSubId);
+ mIImsConfig.notifyRcsAutoConfigurationReceived(mConfig, false);
+ } catch (RemoteException e) {
+ loge("notifyRcsAutoConfigurationReceived failed due to " + e);
+ }
+ } else {
+ logd("notifyRcsAutoConfigurationReceived failed due to IImsConfig null.");
+ }
+ }
+
+ private void notifyRcsAutoConfigurationRemoved() {
+ if (mIImsConfig != null) {
+ try {
+ logv("notifyRcsAutoConfigurationRemoved for sub:" + mSubId);
+ mIImsConfig.notifyRcsAutoConfigurationRemoved();
+ } catch (RemoteException e) {
+ loge("notifyRcsAutoConfigurationRemoved failed due to " + e);
+ }
+ } else {
+ logd("notifyRcsAutoConfigurationRemoved failed due to IImsConfig null.");
+ }
+ }
+
+ private void clearCallbacks() {
synchronized (mRcsConfigCallbacks) {
- IImsConfig imsConfig = getIImsConfig(mSubId, ImsFeature.FEATURE_RCS);
Iterator<IRcsConfigCallback> it = mRcsConfigCallbacks.iterator();
while (it.hasNext()) {
IRcsConfigCallback cb = it.next();
- if (imsConfig != null) {
+ if (mIImsConfig != null) {
try {
- imsConfig.removeRcsConfigCallback(cb);
+ mIImsConfig.removeRcsConfigCallback(cb);
} catch (RemoteException e) {
loge("fail to removeRcsConfigCallback due to " + e);
}
@@ -284,7 +382,61 @@
}
@VisibleForTesting
- public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager) {
+ public interface FeatureConnectorFactory<U extends FeatureUpdates> {
+ /**
+ * @return a {@link FeatureConnector} associated for the given {@link FeatureUpdates}
+ * and slot index.
+ */
+ FeatureConnector<U> create(Context context, int slotIndex,
+ FeatureConnector.Listener<U> listener, Executor executor, String logPrefix);
+ }
+
+ private final class RcsFeatureListener implements FeatureConnector.Listener<RcsFeatureManager> {
+ private final ArraySet<RcsProvisioningInfo> mRcsProvisioningInfos = new ArraySet<>();
+ private RcsFeatureManager mRcsFeatureManager;
+ private FeatureConnector<RcsFeatureManager> mConnector;
+
+ RcsFeatureListener(int slotId) {
+ mConnector = mFeatureFactory.create(
+ mPhone, slotId, this, new HandlerExecutor(mHandler), TAG);
+ mConnector.connect();
+ }
+
+ void destroy() {
+ mConnector.disconnect();
+ mConnector = null;
+ mRcsFeatureManager = null;
+ mRcsProvisioningInfos.clear();
+ }
+
+ void addRcsProvisioningInfo(RcsProvisioningInfo info) {
+ if (!mRcsProvisioningInfos.contains(info)) {
+ mRcsProvisioningInfos.add(info);
+ info.onRcsStatusChanged(mRcsFeatureManager == null ? null
+ : mRcsFeatureManager.getConfig());
+ }
+ }
+
+ void removeRcsProvisioningInfo(RcsProvisioningInfo info) {
+ mRcsProvisioningInfos.remove(info);
+ }
+
+ @Override
+ public void connectionReady(RcsFeatureManager manager) {
+ mRcsFeatureManager = manager;
+ mRcsProvisioningInfos.forEach(v -> v.onRcsStatusChanged(manager.getConfig()));
+ }
+
+ @Override
+ public void connectionUnavailable(int reason) {
+ mRcsFeatureManager = null;
+ mRcsProvisioningInfos.forEach(v -> v.onRcsStatusChanged(null));
+ }
+ }
+
+ @VisibleForTesting
+ public RcsProvisioningMonitor(PhoneGlobals app, Looper looper, RoleManagerAdapter roleManager,
+ FeatureConnectorFactory<RcsFeatureManager> factory) {
mPhone = app;
mHandler = new MyHandler(looper);
mCarrierConfigManager = mPhone.getSystemService(CarrierConfigManager.class);
@@ -293,15 +445,9 @@
mRoleManager = roleManager;
mDmaPackageName = getDmaPackageName();
logv("DMA is " + mDmaPackageName);
- IntentFilter filter = new IntentFilter();
- filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
- mPhone.registerReceiver(mReceiver, filter);
- mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
- mSubChangedListener, mSubChangedListener.getHandlerExecutor());
mDmaChangedListener = new DmaChangedListener();
- mDmaChangedListener.register();
- //initialize configs for all active sub
- onSubChanged();
+ mFeatureFactory = factory;
+ init();
}
/**
@@ -313,7 +459,7 @@
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
- new RoleManagerAdapterImpl(app));
+ new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
}
return sInstance;
}
@@ -325,15 +471,44 @@
return sInstance;
}
+ private void init() {
+ logd("init.");
+ IntentFilter filter = new IntentFilter();
+ filter.addAction(CarrierConfigManager.ACTION_CARRIER_CONFIG_CHANGED);
+ mPhone.registerReceiver(mReceiver, filter);
+ mTelephonyRegistryManager.addOnSubscriptionsChangedListener(
+ mSubChangedListener, mSubChangedListener.getHandlerExecutor());
+ mDmaChangedListener.register();
+ //initialize configs for all active sub
+ onSubChanged();
+ }
+
+ private void release() {
+ logd("release.");
+ mDmaChangedListener.unregister();
+ mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
+ mPhone.unregisterReceiver(mReceiver);
+ for (int i = 0; i < mRcsFeatureListeners.size(); i++) {
+ mRcsFeatureListeners.valueAt(i).destroy();
+ }
+ mRcsFeatureListeners.clear();
+ mRcsProvisioningInfos.forEach((k, v)->v.destroy());
+ mRcsProvisioningInfos.clear();
+ mCarrierSingleRegistrationEnabledOverride.clear();
+ }
+
+ private void reset() {
+ release();
+ init();
+ }
+
/**
* destroy the instance
*/
@VisibleForTesting
public void destroy() {
logd("destroy it.");
- mDmaChangedListener.unregister();
- mTelephonyRegistryManager.removeOnSubscriptionsChangedListener(mSubChangedListener);
- mPhone.unregisterReceiver(mReceiver);
+ release();
mHandler.getLooper().quit();
}
@@ -411,6 +586,27 @@
}
/**
+ * Enables or disables test mode.
+ *
+ * <p> If test mode is enabled, any rcs config change will not update the database.
+ */
+ public void setTestModeEnabled(boolean enabled) {
+ logv("setTestModeEnabled as " + enabled);
+ if (mTestModeEnabled != enabled) {
+ mTestModeEnabled = enabled;
+ mHandler.sendMessage(mHandler.obtainMessage(EVENT_RESET));
+ }
+ }
+
+
+ /**
+ * Returns whether the test mode is enabled.
+ */
+ public boolean getTestModeEnabled() {
+ return mTestModeEnabled;
+ }
+
+ /**
* override the device config whether single registration is enabled
*/
public void overrideDeviceSingleRegistrationEnabled(Boolean enabled) {
@@ -463,61 +659,29 @@
v.clear();
if (isAcsUsed(k)) {
logv("acs used, trigger to re-configure.");
- notifyRcsAutoConfigurationRemoved(k);
- triggerRcsReconfiguration(k);
+ updateConfigForSub(k, null, true);
+ v.triggerRcsReconfiguration();
} else {
+ logv("acs not used, set cached config and notify.");
v.setConfig(cachedConfig);
- logv("acs not used, notify.");
- notifyRcsAutoConfigurationReceived(k, v.getConfig(), false);
}
});
}
}
- private void notifyRcsAutoConfigurationReceived(int subId, byte[] config,
- boolean isCompressed) {
- if (config == null) {
- logd("Rcs config is null for sub : " + subId);
- return;
- }
-
- IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
- if (imsConfig != null) {
- try {
- imsConfig.notifyRcsAutoConfigurationReceived(config, isCompressed);
- } catch (RemoteException e) {
- loge("fail to notify rcs configuration received!");
- }
- } else {
- logd("getIImsConfig returns null.");
+ private void updateConfigForSub(int subId, byte[] config, boolean isCompressed) {
+ logv("updateConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+ if (!mTestModeEnabled) {
+ RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
}
}
- private void notifyRcsAutoConfigurationRemoved(int subId) {
- RcsConfig.updateConfigForSub(mPhone, subId, null, true);
- IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
- if (imsConfig != null) {
- try {
- imsConfig.notifyRcsAutoConfigurationRemoved();
- } catch (RemoteException e) {
- loge("fail to notify rcs configuration removed!");
- }
- } else {
- logd("getIImsConfig returns null.");
+ private byte[] loadConfigForSub(int subId) {
+ logv("loadConfigForSub, subId:" + subId + ", mTestModeEnabled:" + mTestModeEnabled);
+ if (!mTestModeEnabled) {
+ return RcsConfig.loadRcsConfigForSub(mPhone, subId, false);
}
- }
-
- private void triggerRcsReconfiguration(int subId) {
- IImsConfig imsConfig = getIImsConfig(subId, ImsFeature.FEATURE_RCS);
- if (imsConfig != null) {
- try {
- imsConfig.triggerRcsReconfiguration();
- } catch (RemoteException e) {
- loge("fail to trigger rcs reconfiguration!");
- }
- } else {
- logd("getIImsConfig returns null.");
- }
+ return null;
}
private boolean isAcsUsed(int subId) {
@@ -569,26 +733,25 @@
private void onSubChanged() {
final int[] activeSubs = mSubscriptionManager.getActiveSubscriptionIdList();
- final HashSet<Integer> subsToBeDeactivated = new HashSet<>(mRcsProvisioningInfos.keySet());
+ final ArraySet<Integer> subsToBeDeactivated =
+ new ArraySet<>(mRcsProvisioningInfos.keySet());
for (int i : activeSubs) {
subsToBeDeactivated.remove(i);
if (!mRcsProvisioningInfos.containsKey(i)) {
- byte[] data = RcsConfig.loadRcsConfigForSub(mPhone, i, false);
+ byte[] data = loadConfigForSub(i);
int capability = getSingleRegistrationCapableValue(i);
logv("new info is created for sub : " + i + ", single registration capability :"
+ capability + ", rcs config : " + data);
mRcsProvisioningInfos.put(i, new RcsProvisioningInfo(i, capability, data));
- notifyRcsAutoConfigurationReceived(i, data, false);
notifyDmaForSub(i, capability);
}
}
subsToBeDeactivated.forEach(i -> {
RcsProvisioningInfo info = mRcsProvisioningInfos.remove(i);
- notifyRcsAutoConfigurationRemoved(i);
if (info != null) {
- info.clear();
+ info.destroy();
}
});
}
@@ -597,11 +760,8 @@
logv("onConfigReceived, subId:" + subId + ", config:"
+ config + ", isCompressed:" + isCompressed);
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
- if (info != null) {
- info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
- }
- RcsConfig.updateConfigForSub(mPhone, subId, config, isCompressed);
- notifyRcsAutoConfigurationReceived(subId, config, isCompressed);
+ info.setConfig(isCompressed ? RcsConfig.decompressGzip(config) : config);
+ updateConfigForSub(subId, config, isCompressed);
}
private void onReconfigRequest(int subId) {
@@ -609,9 +769,10 @@
RcsProvisioningInfo info = mRcsProvisioningInfos.get(subId);
if (info != null) {
info.setConfig(null);
+ // clear rcs config stored in db
+ updateConfigForSub(subId, null, true);
+ info.triggerRcsReconfiguration();
}
- notifyRcsAutoConfigurationRemoved(subId);
- triggerRcsReconfiguration(subId);
}
private void notifyDmaForSub(int subId, int capability) {
@@ -621,13 +782,13 @@
intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
logv("notify " + intent);
- // Only send permission to the default sms app if it has the correct permissions.
- mPhone.sendBroadcast(intent, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
- }
-
- private IImsConfig getIImsConfig(int subId, int feature) {
- return mPhone.getImsResolver().getImsConfig(
- SubscriptionManager.getSlotIndex(subId), feature);
+ // Only send permission to the default sms app if it has the correct permissions
+ // except test mode enabled
+ if (!mTestModeEnabled) {
+ mPhone.sendBroadcast(intent, Manifest.permission.PERFORM_IMS_SINGLE_REGISTRATION);
+ } else {
+ mPhone.sendBroadcast(intent);
+ }
}
private String getDmaPackageName() {
@@ -639,6 +800,24 @@
}
}
+ void registerRcsFeatureListener(RcsProvisioningInfo info) {
+ int slotId = SubscriptionManager.getSlotIndex(info.getSubId());
+ RcsFeatureListener cb = mRcsFeatureListeners.get(slotId);
+ if (cb == null) {
+ cb = new RcsFeatureListener(slotId);
+ mRcsFeatureListeners.put(slotId, cb);
+ }
+ cb.addRcsProvisioningInfo(info);
+ }
+
+ void unregisterRcsFeatureListener(RcsProvisioningInfo info) {
+ int slotId = SubscriptionManager.getSlotIndex(info.getSubId());
+ RcsFeatureListener cb = mRcsFeatureListeners.get(slotId);
+ if (cb != null) {
+ cb.removeRcsProvisioningInfo(info);
+ }
+ }
+
private static boolean booleanEquals(Boolean val1, Boolean val2) {
return (val1 == null && val2 == null)
|| (Boolean.TRUE.equals(val1) && Boolean.TRUE.equals(val2))
diff --git a/src/com/android/phone/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 7a1e93c..6a27130 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -59,6 +59,7 @@
import java.util.Arrays;
import java.util.LinkedHashSet;
import java.util.List;
+import java.util.Objects;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
@@ -295,6 +296,9 @@
private Cursor queryElementaryFilesItem(PhonebookArgs args, String[] projection) {
validateProjection(ELEMENTARY_FILES_COLUMNS_SET, projection);
+ if (projection == null) {
+ projection = ELEMENTARY_FILES_ALL_COLUMNS;
+ }
MatrixCursor result = new MatrixCursor(projection);
try {
@@ -641,8 +645,8 @@
}
private boolean hasPermissionsForFdnWrite(PhonebookArgs args) {
- TelephonyManager telephonyManager = getContext().getSystemService(
- TelephonyManager.class);
+ TelephonyManager telephonyManager = Objects.requireNonNull(
+ getContext().getSystemService(TelephonyManager.class));
String callingPackage = getCallingPackage();
int granted = PackageManager.PERMISSION_DENIED;
if (callingPackage != null) {
@@ -701,7 +705,12 @@
String name = values.getAsString(SimRecords.NAME);
int length = getEncodedNameLength(name);
- int maxLength = AdnRecord.getMaxAlphaTagBytes(getRecordSize(getRecordsSizeForEf(args)));
+ int[] recordsSize = getRecordsSizeForEf(args);
+ if (recordsSize == null) {
+ throw new IllegalStateException(
+ "Failed to get " + ElementaryFiles.NAME_MAX_LENGTH + " from SIM");
+ }
+ int maxLength = AdnRecord.getMaxAlphaTagBytes(getRecordSize(recordsSize));
if (length > maxLength) {
throw new IllegalArgumentException(SimRecords.NAME + " is too long.");
@@ -740,7 +749,7 @@
private AdnRecord loadRecord(PhonebookArgs args) {
List<AdnRecord> records = loadRecordsForEf(args);
- if (args.recordNumber > records.size()) {
+ if (records == null || args.recordNumber > records.size()) {
return null;
}
AdnRecord result = records.get(args.recordNumber - 1);
diff --git a/src/com/android/phone/TelephonyShellCommand.java b/src/com/android/phone/TelephonyShellCommand.java
index 79dfbcf..d395aa2 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -71,6 +71,7 @@
private static final String CALL_COMPOSER_SUBCOMMAND = "callcomposer";
private static final String IMS_SUBCOMMAND = "ims";
private static final String NUMBER_VERIFICATION_SUBCOMMAND = "numverify";
+ private static final String EMERGENCY_CALLBACK_MODE = "emergency-callback-mode";
private static final String EMERGENCY_NUMBER_TEST_MODE = "emergency-number-test-mode";
private static final String END_BLOCK_SUPPRESSION = "end-block-suppression";
private static final String RESTART_MODEM = "restart-modem";
@@ -83,6 +84,7 @@
private static final String CALL_COMPOSER_TEST_MODE = "test-mode";
private static final String CALL_COMPOSER_SIMULATE_CALL = "simulate-outgoing-call";
+ private static final String CALL_COMPOSER_USER_SETTING = "user-setting";
private static final String IMS_SET_IMS_SERVICE = "set-ims-service";
private static final String IMS_GET_IMS_SERVICE = "get-ims-service";
@@ -110,9 +112,12 @@
private static final String SRC_GET_DEVICE_ENABLED = "get-device-enabled";
private static final String SRC_SET_CARRIER_ENABLED = "set-carrier-enabled";
private static final String SRC_GET_CARRIER_ENABLED = "get-carrier-enabled";
+ private static final String SRC_SET_TEST_ENABLED = "set-test-enabled";
+ private static final String SRC_GET_TEST_ENABLED = "get-test-enabled";
private static final String D2D_SUBCOMMAND = "d2d";
private static final String D2D_SEND = "send";
+ private static final String D2D_TRANSPORT = "transport";
private static final String RCS_UCE_COMMAND = "uce";
private static final String UCE_GET_EAB_CONTACT = "get-eab-contact";
@@ -193,6 +198,8 @@
return handleRcsUceCommand();
case NUMBER_VERIFICATION_SUBCOMMAND:
return handleNumberVerificationCommand();
+ case EMERGENCY_CALLBACK_MODE:
+ return handleEmergencyCallbackModeCommand();
case EMERGENCY_NUMBER_TEST_MODE:
return handleEmergencyNumberTestModeCommand();
case CARRIER_CONFIG_SUBCOMMAND: {
@@ -271,6 +278,9 @@
MESSAGE_DEVICE_BATTERY_STATE));
pw.println(" Type: " + MESSAGE_DEVICE_NETWORK_COVERAGE + " - "
+ Communicator.messageToString(MESSAGE_DEVICE_NETWORK_COVERAGE));
+ pw.println(" d2d transport TYPE");
+ pw.println(" Forces the specified D2D transport TYPE to be active. Use the");
+ pw.println(" short class name of the transport; i.e. DtmfTransport or RtpTransport.");
}
private void onHelpIms() {
@@ -427,6 +437,11 @@
private void onHelpSrc() {
PrintWriter pw = getOutPrintWriter();
pw.println("RCS VoLTE Single Registration Config Commands:");
+ pw.println(" src set-test-enabled true|false");
+ pw.println(" Sets the test mode enabled for RCS VoLTE single registration.");
+ pw.println(" The value could be true, false, or null(undefined).");
+ pw.println(" src get-test-enabled");
+ pw.println(" Gets the test mode for RCS VoLTE single registration.");
pw.println(" src set-device-enabled true|false|null");
pw.println(" Sets the device config for RCS VoLTE single registration to the value.");
pw.println(" The value could be true, false, or null(undefined).");
@@ -511,6 +526,19 @@
return 0;
}
+ private int handleEmergencyCallbackModeCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+ try {
+ mInterface.startEmergencyCallbackMode();
+ Log.d(LOG_TAG, "handleEmergencyCallbackModeCommand: triggered");
+ } catch (RemoteException ex) {
+ Log.w(LOG_TAG, "emergency-callback-mode error: " + ex.getMessage());
+ errPw.println("Exception: " + ex.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
private int handleEmergencyNumberTestModeCommand() {
PrintWriter errPw = getErrPrintWriter();
String opt = getNextOption();
@@ -634,6 +662,9 @@
case D2D_SEND: {
return handleD2dSendCommand();
}
+ case D2D_TRANSPORT: {
+ return handleD2dTransportCommand();
+ }
}
return -1;
@@ -641,11 +672,9 @@
private int handleD2dSendCommand() {
PrintWriter errPw = getErrPrintWriter();
- String opt;
int messageType = -1;
int messageValue = -1;
-
String arg = getNextArg();
if (arg == null) {
onHelpD2D();
@@ -681,6 +710,25 @@
return 0;
}
+ private int handleD2dTransportCommand() {
+ PrintWriter errPw = getErrPrintWriter();
+
+ String arg = getNextArg();
+ if (arg == null) {
+ onHelpD2D();
+ return 0;
+ }
+
+ try {
+ mInterface.setActiveDeviceToDeviceTransport(arg);
+ } catch (RemoteException e) {
+ Log.w(LOG_TAG, "d2d transport error: " + e.getMessage());
+ errPw.println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
// ims set-ims-service
private int handleImsSetServiceCommand() {
PrintWriter errPw = getErrPrintWriter();
@@ -1628,6 +1676,12 @@
}
switch (arg) {
+ case SRC_SET_TEST_ENABLED: {
+ return handleSrcSetTestEnabledCommand();
+ }
+ case SRC_GET_TEST_ENABLED: {
+ return handleSrcGetTestEnabledCommand();
+ }
case SRC_SET_DEVICE_ENABLED: {
return handleSrcSetDeviceEnabledCommand();
}
@@ -1747,6 +1801,40 @@
return 0;
}
+ private int handleSrcSetTestEnabledCommand() {
+ String enabledStr = getNextArg();
+ if (enabledStr == null) {
+ return -1;
+ }
+
+ try {
+ mInterface.setRcsSingleRegistrationTestModeEnabled(Boolean.parseBoolean(enabledStr));
+ if (VDBG) {
+ Log.v(LOG_TAG, "src set-test-enabled " + enabledStr + ", done");
+ }
+ getOutPrintWriter().println("Done");
+ } catch (NumberFormatException | RemoteException e) {
+ Log.w(LOG_TAG, "src set-test-enabled " + enabledStr + ", error" + e.getMessage());
+ getErrPrintWriter().println("Exception: " + e.getMessage());
+ return -1;
+ }
+ return 0;
+ }
+
+ private int handleSrcGetTestEnabledCommand() {
+ boolean result = false;
+ try {
+ result = mInterface.getRcsSingleRegistrationTestModeEnabled();
+ } catch (RemoteException e) {
+ return -1;
+ }
+ if (VDBG) {
+ Log.v(LOG_TAG, "src get-test-enabled, returned: " + result);
+ }
+ getOutPrintWriter().println(result);
+ return 0;
+ }
+
private int handleSrcSetDeviceEnabledCommand() {
String enabledStr = getNextArg();
if (enabledStr == null) {
@@ -1840,6 +1928,9 @@
pw.println(" callcomposer simulate-outgoing-call [subId] [UUID]");
pw.println(" Simulates an outgoing call being placed with the picture ID as");
pw.println(" the provided UUID. This triggers storage to the call log.");
+ pw.println(" callcomposer user-setting [subId] enable|disable|query");
+ pw.println(" Enables or disables the user setting for call composer, as set by");
+ pw.println(" TelephonyManager#setCallComposerStatus.");
}
private int handleCallComposerCommand() {
@@ -1883,6 +1974,29 @@
}
break;
}
+ case CALL_COMPOSER_USER_SETTING: {
+ try {
+ int subscriptionId = Integer.valueOf(getNextArg());
+ String enabledStr = getNextArg();
+ if (ENABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else if (DISABLE.equals(enabledStr)) {
+ mInterface.setCallComposerStatus(subscriptionId,
+ TelephonyManager.CALL_COMPOSER_STATUS_OFF);
+ } else if (QUERY.equals(enabledStr)) {
+ getOutPrintWriter().println(mInterface.getCallComposerStatus(subscriptionId)
+ == TelephonyManager.CALL_COMPOSER_STATUS_ON);
+ } else {
+ onHelpCallComposer();
+ return 1;
+ }
+ } catch (RemoteException e) {
+ e.printStackTrace(getOutPrintWriter());
+ return 1;
+ }
+ break;
+ }
}
return 0;
diff --git a/src/com/android/phone/callcomposer/CallComposerPictureManager.java b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
index b27a27c..3c9e27e 100644
--- a/src/com/android/phone/callcomposer/CallComposerPictureManager.java
+++ b/src/com/android/phone/callcomposer/CallComposerPictureManager.java
@@ -17,6 +17,7 @@
package com.android.phone.callcomposer;
import android.content.Context;
+import android.location.Location;
import android.net.Uri;
import android.os.OutcomeReceiver;
import android.os.UserHandle;
@@ -81,6 +82,13 @@
@VisibleForTesting
public static boolean sTestMode = false;
public static final String FAKE_SERVER_URL = "https://example.com/FAKE.png";
+ public static final String FAKE_SUBJECT = "This is a test call subject";
+ public static final Location FAKE_LOCATION = new Location("");
+ static {
+ // Meteor Crater, AZ
+ FAKE_LOCATION.setLatitude(35.027526);
+ FAKE_LOCATION.setLongitude(-111.021696);
+ }
public interface CallLogProxy {
default void storeCallComposerPictureAsUser(Context context,
diff --git a/src/com/android/services/telephony/CallQualityManager.java b/src/com/android/services/telephony/CallQualityManager.java
index 0e32ddc..262ce88 100644
--- a/src/com/android/services/telephony/CallQualityManager.java
+++ b/src/com/android/services/telephony/CallQualityManager.java
@@ -21,10 +21,12 @@
import android.app.NotificationManager;
import android.content.Context;
import android.os.Bundle;
+import android.os.SystemClock;
import android.telecom.BluetoothCallQualityReport;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.telephony.SlidingWindowEventCounter;
import com.android.phone.R;
/**
@@ -32,16 +34,20 @@
*/
public class CallQualityManager {
private static final String TAG = CallQualityManager.class.getCanonicalName();
- private static final String CALL_QUALITY_REPORT_CHANNEL = "call_quality_report_channel";
/** notification ids */
public static final int BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID = 700;
-
- public static final String CALL_QUALITY_CHANNEL_ID = "CallQualityNotification";
+ public static final String CALL_QUALITY_CHANNEL_ID = "CallQualityNotificationChannel";
+ public static final long NOTIFICATION_BACKOFF_TIME_MILLIS = 5L * 60 * 1000;
+ public static final int NUM_OCCURRENCES_THRESHOLD = 5;
+ public static final long TIME_WINDOW_MILLIS = 5 * 1000;
private final Context mContext;
private final NotificationChannel mNotificationChannel;
private final NotificationManager mNotificationManager;
+ private final SlidingWindowEventCounter mSlidingWindowEventCounter;
+
+ private long mNotificationLastTime;
public CallQualityManager(Context context) {
mContext = context;
@@ -51,6 +57,11 @@
mNotificationManager = (NotificationManager)
mContext.getSystemService(Context.NOTIFICATION_SERVICE);
mNotificationManager.createNotificationChannel(mNotificationChannel);
+ //making sure at the start we qualify to show notifications
+ mNotificationLastTime =
+ SystemClock.elapsedRealtime() - NOTIFICATION_BACKOFF_TIME_MILLIS - 1;
+ mSlidingWindowEventCounter =
+ new SlidingWindowEventCounter(TIME_WINDOW_MILLIS, NUM_OCCURRENCES_THRESHOLD);
}
/**
@@ -80,30 +91,49 @@
@VisibleForTesting
public void onChoppyVoice() {
String title = "Call Quality Improvement";
- //TODO: update call_quality_bluetooth_enhancement_suggestion with below before submitting:
-// "Voice is not being transmitted properly via your bluetooth device."
-// + "To improve, try:\n"
-// + "1. moving your phone closer to your bluetooth device\n"
-// + "2. using a different bluetooth device, or your phone's speaker\n";
- popUpNotification(title,
- mContext.getText(R.string.call_quality_notification_bluetooth_details));
+ Log.d(TAG, "Bluetooth choppy voice signal received.");
+ if (mSlidingWindowEventCounter.addOccurrence(SystemClock.elapsedRealtime())) {
+ timedNotify(title,
+ mContext.getText(R.string.call_quality_notification_bluetooth_details));
+ }
}
- private void popUpNotification(String title, CharSequence details) {
- int iconId = android.R.drawable.stat_notify_error;
+ // notify user only if you haven't in the last NOTIFICATION_BACKOFF_TIME_MILLIS milliseconds
+ private void timedNotify(String title, CharSequence details) {
+ if (!mContext.getResources().getBoolean(
+ R.bool.enable_bluetooth_call_quality_notification)) {
+ Log.d(TAG, "Bluetooth call quality notifications not enabled.");
+ return;
+ }
+ long now = SystemClock.elapsedRealtime();
+ if (now - mNotificationLastTime > NOTIFICATION_BACKOFF_TIME_MILLIS) {
+ int iconId = android.R.drawable.stat_notify_error;
- Notification notification = new Notification.Builder(mContext)
- .setSmallIcon(iconId)
- .setWhen(System.currentTimeMillis())
- .setAutoCancel(true)
- .setContentTitle(title)
- .setContentText(details)
- .setStyle(new Notification.BigTextStyle().bigText(details))
- .setAutoCancel(true)
- .setChannelId(CALL_QUALITY_CHANNEL_ID)
- .setOnlyAlertOnce(true)
- .build();
+ Notification notification = new Notification.Builder(mContext)
+ .setSmallIcon(iconId)
+ .setWhen(System.currentTimeMillis())
+ .setAutoCancel(true)
+ .setContentTitle(title)
+ .setContentText(details)
+ .setStyle(new Notification.BigTextStyle().bigText(details))
+ .setChannelId(CALL_QUALITY_CHANNEL_ID)
+ .setOnlyAlertOnce(true)
+ .build();
- mNotificationManager.notify(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID, notification);
+ mNotificationManager.notify(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID, notification);
+ mNotificationLastTime = now;
+ Log.d(TAG, "Call quality signal received, showing notification");
+ } else {
+ Log.d(TAG, "Call quality signal received, but not showing notification, "
+ + "as recently notified in the last "
+ + NOTIFICATION_BACKOFF_TIME_MILLIS / 1000 + " seconds");
+ }
+ }
+
+ /**
+ * close the notifications that have been emitted during the call
+ */
+ public void clearNotifications() {
+ mNotificationManager.cancel(TAG, BLUETOOTH_CHOPPY_VOICE_NOTIFICATION_ID);
}
}
diff --git a/src/com/android/services/telephony/PstnIncomingCallNotifier.java b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
index 235cbce..4191367 100644
--- a/src/com/android/services/telephony/PstnIncomingCallNotifier.java
+++ b/src/com/android/services/telephony/PstnIncomingCallNotifier.java
@@ -301,6 +301,12 @@
if (CallComposerPictureManager.sTestMode) {
imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_PICTURE_URL,
CallComposerPictureManager.FAKE_SERVER_URL);
+ imsCallProfile.setCallExtraInt(ImsCallProfile.EXTRA_PRIORITY,
+ TelecomManager.PRIORITY_URGENT);
+ imsCallProfile.setCallExtra(ImsCallProfile.EXTRA_CALL_SUBJECT,
+ CallComposerPictureManager.FAKE_SUBJECT);
+ imsCallProfile.setCallExtraParcelable(ImsCallProfile.EXTRA_LOCATION,
+ CallComposerPictureManager.FAKE_LOCATION);
}
extras.putInt(TelecomManager.EXTRA_PRIORITY,
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 7a59908..7000733 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -28,6 +28,7 @@
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
+import android.os.Messenger;
import android.os.PersistableBundle;
import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
@@ -95,6 +96,7 @@
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
/**
* Base class for CDMA and GSM connections.
@@ -133,6 +135,7 @@
private static final int MSG_ON_CONNECTION_EVENT = 19;
private static final int MSG_REDIAL_CONNECTION_CHANGED = 20;
private static final int MSG_REJECT = 21;
+ private static final int MSG_DTMF_DONE = 22;
private static final String JAPAN_COUNTRY_CODE_WITH_PLUS_SIGN = "+81";
private static final String JAPAN_ISO_COUNTRY_CODE = "JP";
@@ -303,6 +306,9 @@
int rejectReason = (int) msg.obj;
reject(rejectReason);
break;
+ case MSG_DTMF_DONE:
+ Log.i(this, "MSG_DTMF_DONE");
+ break;
case MSG_SET_CALL_RADIO_TECH:
int vrat = (int) msg.obj;
@@ -341,6 +347,8 @@
}
};
+ private final Messenger mHandlerMessenger = new Messenger(mHandler);
+
/**
* Handles {@link SuppServiceNotification}s pertinent to Telephony.
* @param ssn the notification.
@@ -510,7 +518,7 @@
@Override
public void onStateChanged(android.telecom.Connection c, int state) {
- mCommunicator.onStateChanged(c, state);
+ mCommunicator.onStateChanged(c.getTelecomCallId(), state);
}
}
@@ -742,6 +750,15 @@
extensionData.size());
mRtpTransport.onRtpHeaderExtensionsReceived(extensionData);
}
+
+ @Override
+ public void onReceivedDtmfDigit(char digit) {
+ if (mDtmfTransport == null) {
+ return;
+ }
+ Log.i(this, "onReceivedDtmfDigit: digit=%c", digit);
+ mDtmfTransport.onDtmfReceived(digit);
+ }
};
private TelephonyConnectionService mTelephonyConnectionService;
@@ -1139,24 +1156,33 @@
}
@Override
- public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
- CallScreeningService.CallResponse callScreeningResponse,
- boolean isResponseFromSystemDialer) {
+ public void onCallFilteringCompleted(CallFilteringCompletionInfo callFilteringCompletionInfo) {
// Check what the call screening service has to say, if it's a system dialer.
boolean isAllowedToDisplayPicture;
+ String callScreeningPackage =
+ callFilteringCompletionInfo.getCallScreeningComponent() == null
+ ? null
+ : callFilteringCompletionInfo.getCallScreeningComponent().getPackageName();
+ boolean isResponseFromSystemDialer =
+ Objects.equals(getPhone().getContext()
+ .getSystemService(TelecomManager.class).getSystemDialerPackage(),
+ callScreeningPackage);
+ CallScreeningService.CallResponse callScreeningResponse =
+ callFilteringCompletionInfo.getCallResponse();
+
if (isResponseFromSystemDialer && callScreeningResponse != null
&& callScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
isAllowedToDisplayPicture = (callScreeningResponse.getCallComposerAttachmentsToShow()
& CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PICTURE) != 0;
} else {
- isAllowedToDisplayPicture = isInContacts;
+ isAllowedToDisplayPicture = callFilteringCompletionInfo.isInContacts();
}
if (isImsConnection()) {
ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
if (imsPhone != null
&& imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
- && !isBlocked && isAllowedToDisplayPicture) {
+ && !callFilteringCompletionInfo.isBlocked() && isAllowedToDisplayPicture) {
ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
String serverUrl = CallComposerPictureManager.sTestMode
@@ -2351,7 +2377,7 @@
}
if (mCommunicator != null) {
- mCommunicator.onStateChanged(this, getState());
+ mCommunicator.onStateChanged(getTelecomCallId(), getState());
}
}
}
@@ -3144,6 +3170,9 @@
setDisconnected(disconnectCause);
notifyDisconnected(disconnectCause);
notifyStateChanged(getState());
+ if (mCallQualityManager != null) {
+ mCallQualityManager.clearNotifications();
+ }
}
/**
@@ -3284,13 +3313,18 @@
private void maybeConfigureDeviceToDeviceCommunication() {
if (!getPhone().getContext().getResources().getBoolean(
R.bool.config_use_device_to_device_communication)) {
- Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not using D2D.");
return;
}
if (!isImsConnection()) {
- Log.d(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: not an IMS connection.");
return;
}
+ if (mTreatAsEmergencyCall || mIsNetworkIdentifiedEmergencyCall) {
+ Log.i(this, "maybeConfigureDeviceToDeviceCommunication: emergency call; no D2D");
+ return;
+ }
+
// Implement abstracted out RTP functionality the RTP transport depends on.
RtpAdapter rtpAdapter = new RtpAdapter() {
@Override
@@ -3309,8 +3343,11 @@
if (!isImsConnection()) {
Log.w(TelephonyConnection.this, "sendRtpHeaderExtensions: not an ims conn.");
}
- Log.d(TelephonyConnection.this, "sendRtpHeaderExtensions: sending %d messages",
- rtpHeaderExtensions.size());
+
+ Log.i(TelephonyConnection.this, "sendRtpHeaderExtensions: sending: %s",
+ rtpHeaderExtensions.stream()
+ .map(r -> r.toString())
+ .collect(Collectors.joining(",")));
ImsPhoneConnection originalConnection =
(ImsPhoneConnection) mOriginalConnection;
originalConnection.sendRtpHeaderExtensions(rtpHeaderExtensions);
@@ -3322,10 +3359,12 @@
if (!isImsConnection()) {
Log.w(TelephonyConnection.this, "sendDtmf: not an ims conn.");
}
- Log.d(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
+ Log.i(TelephonyConnection.this, "sendDtmf: send digit %c", digit);
ImsPhoneConnection originalConnection =
(ImsPhoneConnection) mOriginalConnection;
- originalConnection.getImsCall().sendDtmf(digit, null /* result msg not needed */);
+ Message dtmfComplete = mHandler.obtainMessage(MSG_DTMF_DONE);
+ dtmfComplete.replyTo = mHandlerMessenger;
+ originalConnection.getImsCall().sendDtmf(digit, dtmfComplete);
};
ContentResolver cr = getPhone().getContext().getContentResolver();
mDtmfTransport = new DtmfTransport(dtmfAdapter, new Timeouts.Adapter(cr),
@@ -3350,14 +3389,13 @@
@Override
public void onMessagesReceived(@NonNull Set<Communicator.Message> messages) {
Log.i(this, "onMessagesReceived: got d2d messages: %s", messages);
- // TODO: Actually do something WITH the messages.
-
- // TODO: Remove this prior to launch.
- // This is just here for debug purposes; send as a connection event so that it
- // will be output in the Telecom logs.
+ // Send connection events up to Telecom so that we can relay the messages to a valid
+ // CallDiagnosticService which is bound.
for (Communicator.Message msg : messages) {
- sendConnectionEvent("D2D_" + Communicator.messageToString(msg.getType())
- + "_" + Communicator.valueToString(msg.getType(), msg.getValue()), null);
+ Bundle extras = new Bundle();
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_TYPE, msg.getType());
+ extras.putInt(Connection.EXTRA_DEVICE_TO_DEVICE_MESSAGE_VALUE, msg.getValue());
+ sendConnectionEvent(Connection.EVENT_DEVICE_TO_DEVICE_MESSAGE, extras);
}
}
diff --git a/src/com/android/services/telephony/TelephonyConnectionService.java b/src/com/android/services/telephony/TelephonyConnectionService.java
index 219c782..0803178 100644
--- a/src/com/android/services/telephony/TelephonyConnectionService.java
+++ b/src/com/android/services/telephony/TelephonyConnectionService.java
@@ -2538,6 +2538,26 @@
});
}
+ /**
+ * Overrides the current D2D transport, forcing the specified one to be active. Used for test.
+ * @param transport The class simple name of the transport to make active.
+ */
+ public void setActiveDeviceToDeviceTransport(@NonNull String transport) {
+ getAllConnections().stream()
+ .filter(f -> f instanceof TelephonyConnection)
+ .forEach(t -> {
+ TelephonyConnection tc = (TelephonyConnection) t;
+ Communicator c = tc.getCommunicator();
+ if (c == null) {
+ Log.w(this, "setActiveDeviceToDeviceTransport: D2D not enabled");
+ return;
+ }
+ Log.i(this, "setActiveDeviceToDeviceTransport: callId=%s, set to: %s",
+ tc.getTelecomCallId(), transport);
+ c.setTransportActive(transport);
+ });
+ }
+
private PhoneAccountHandle adjustAccountHandle(Phone phone,
PhoneAccountHandle origAccountHandle) {
int origSubId = PhoneUtils.getSubIdForPhoneAccountHandle(origAccountHandle);
diff --git a/src/com/android/services/telephony/rcs/UceControllerManager.java b/src/com/android/services/telephony/rcs/UceControllerManager.java
index 09288f1..20ea17b 100644
--- a/src/com/android/services/telephony/rcs/UceControllerManager.java
+++ b/src/com/android/services/telephony/rcs/UceControllerManager.java
@@ -23,12 +23,12 @@
import android.telephony.ims.RcsUceAdapter.PublishState;
import android.telephony.ims.aidl.IRcsUceControllerCallback;
import android.telephony.ims.aidl.IRcsUcePublishStateCallback;
+import android.util.IndentingPrintWriter;
import android.util.Log;
import com.android.ims.RcsFeatureManager;
import com.android.ims.rcs.uce.UceController;
import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.util.IndentingPrintWriter;
import java.io.PrintWriter;
import java.util.List;
@@ -253,8 +253,7 @@
IndentingPrintWriter pw = new IndentingPrintWriter(printWriter, " ");
pw.println("UceControllerManager" + "[" + mSlotId + "]:");
pw.increaseIndent();
- pw.println("UceController available = " + mUceController != null);
- //TODO: Add dump for UceController
+ mUceController.dump(pw);
pw.decreaseIndent();
}
}
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
index 2da47dd..4496a8e 100644
--- a/testapps/TestRcsApp/TestApp/Android.bp
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -18,6 +18,7 @@
"androidx-constraintlayout_constraintlayout",
"aosp_test_rcs_client_base",
"androidx.appcompat_appcompat",
+ "libphonenumber-platform"
],
certificate: "platform",
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 391d30d..2eea909 100644
--- a/testapps/TestRcsApp/TestApp/AndroidManifest.xml
+++ b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
@@ -19,8 +19,8 @@
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.sample.rcsclient"
- android:versionCode="4"
- android:versionName="1.0.3">
+ android:versionCode="6"
+ android:versionName="1.0.5">
<uses-sdk
android:minSdkVersion="30"
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
index 1db4af7..0531209 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ChatActivity.java
@@ -168,8 +168,10 @@
});
});
} catch (Exception e) {
- Log.e(TAG, e.getMessage());
+ Log.e(TAG, "Exception: " + e);
e.printStackTrace();
+ Toast.makeText(this, getResources().getString(R.string.session_failed),
+ Toast.LENGTH_SHORT).show();
}
}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
index 9d27fbc..0447d1a 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/ChatManager.java
@@ -91,6 +91,21 @@
mImsService.setListener((session) -> {
Log.i(TAG, "onIncomingSession()");
mContactSessionMap.put(session.getRemoteUri(), session);
+ session.setListener(
+ // implement onMessageReceived()
+ (message) -> {
+ mFixedThreadPool.execute(() -> {
+ String msg = message.content();
+ String phoneNumber = getNumberFromUri(
+ session.getRemoteUri().toString());
+ if (TextUtils.isEmpty(phoneNumber)) {
+ Log.i(TAG, "dest number is empty, uri:"
+ + session.getRemoteUri());
+ } else {
+ addNewMessage(msg, phoneNumber, SELF);
+ }
+ });
+ });
});
}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
index 72cbf3f..14d3b9c 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
@@ -17,8 +17,12 @@
package com.google.android.sample.rcsclient.util;
import android.content.Context;
-import android.telephony.PhoneNumberUtils;
import android.telephony.TelephonyManager;
+import android.util.Log;
+
+import com.android.i18n.phonenumbers.NumberParseException;
+import com.android.i18n.phonenumbers.PhoneNumberUtil;
+import com.android.i18n.phonenumbers.Phonenumber;
public class NumberUtils {
@@ -30,6 +34,13 @@
public static String formatNumber(Context context, String number) {
TelephonyManager manager = context.getSystemService(TelephonyManager.class);
String simCountryIso = manager.getSimCountryIso().toUpperCase();
- return PhoneNumberUtils.formatNumberToE164(number, simCountryIso);
+ PhoneNumberUtil util = PhoneNumberUtil.getInstance();
+ try {
+ Phonenumber.PhoneNumber phoneNumber = util.parse(number, simCountryIso);
+ return util.format(phoneNumber, PhoneNumberUtil.PhoneNumberFormat.E164);
+ } catch (NumberParseException e) {
+ Log.w("NumberUtils", "formatNumber: could not format " + number + ", error: " + e);
+ }
+ return null;
}
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
index 0011011..57bb75a 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/ImsPdnNetworkFetcher.java
@@ -82,7 +82,7 @@
return result;
}
- private ConnectivityManager getConnectivityManager() {
+ ConnectivityManager getConnectivityManager() {
return context.getSystemService(ConnectivityManager.class);
}
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
index 47326bd..81abe89 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpManager.java
@@ -17,6 +17,7 @@
package com.android.libraries.rcs.simpleclient.protocol.msrp;
import android.content.Context;
+import android.net.ConnectivityManager;
import android.net.Network;
import com.google.common.util.concurrent.Futures;
@@ -24,6 +25,7 @@
import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
+import java.net.InetAddress;
import java.net.Socket;
/** Provides creating and managing {@link MsrpSession} */
@@ -34,24 +36,27 @@
imsPdnNetworkFetcher = new ImsPdnNetworkFetcher(context);
}
- private static MsrpSession createMsrpSession(
- Network network, String host, int port, MsrpSessionListener listener)
- throws IOException {
- Socket socket = network.getSocketFactory().createSocket(host, port);
- MsrpSession msrpSession = new MsrpSession(socket, listener);
+ private static MsrpSession createMsrpSession(ConnectivityManager manager,
+ Network network, String host, int port, String localIp, int localPort,
+ MsrpSessionListener listener) throws IOException {
+ Socket socket = network.getSocketFactory().createSocket(host, port,
+ InetAddress.getByName(localIp), localPort);
+ MsrpSession msrpSession = new MsrpSession(manager,
+ network, socket, listener);
Thread thread = new Thread(msrpSession::run);
thread.start();
return msrpSession;
}
public ListenableFuture<MsrpSession> createMsrpSession(
- String host, int port, MsrpSessionListener listener) {
+ String host, int port, String localIp, int localPort, MsrpSessionListener listener) {
return Futures.transformAsync(
imsPdnNetworkFetcher.getImsPdnNetwork(),
network -> {
if (network != null) {
return Futures.immediateFuture(
- createMsrpSession(network, host, port, listener));
+ createMsrpSession(imsPdnNetworkFetcher.getConnectivityManager(),
+ network, host, port, localIp, localPort, listener));
} else {
return Futures.immediateFailedFuture(
new IllegalStateException("Network is null"));
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
index 96ca19c..3f8b986 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpSession.java
@@ -19,6 +19,16 @@
import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Method.SEND;
import static com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Method.UNKNOWN;
+import android.net.ConnectivityManager;
+import android.net.Network;
+import android.net.QosCallback;
+import android.net.QosCallbackException;
+import android.net.QosSession;
+import android.net.QosSessionAttributes;
+import android.net.QosSocketInfo;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
import androidx.concurrent.futures.CallbackToFutureAdapter;
import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
@@ -26,6 +36,7 @@
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
+import com.google.common.util.concurrent.MoreExecutors;
import java.io.IOException;
import java.io.InputStream;
@@ -38,6 +49,7 @@
* Provides MSRP sending and receiving messages ability.
*/
public class MsrpSession {
+ private final Network network;
private final Socket socket;
private final InputStream input;
private final OutputStream output;
@@ -45,13 +57,51 @@
private final ConcurrentHashMap<String, MsrpTransaction> transactions =
new ConcurrentHashMap<>();
private final MsrpSessionListener listener;
+ private final ConnectivityManager connectivityManager;
+ private final String LOG_TAG = MsrpSession.class.getSimpleName();
/** Creates a new MSRP session on the given listener and the provided streams. */
- MsrpSession(Socket socket, MsrpSessionListener listener) throws IOException {
+ MsrpSession(ConnectivityManager connectivityManager, Network network, Socket socket,
+ MsrpSessionListener listener) throws IOException {
+ this.connectivityManager = connectivityManager;
+ this.network = network;
this.socket = socket;
this.input = socket.getInputStream();
this.output = socket.getOutputStream();
this.listener = listener;
+
+ listenForBearer();
+ }
+
+ private final QosCallback qosCallback = new QosCallback() {
+ @Override
+ public void onError(@NonNull QosCallbackException exception) {
+ Log.e(LOG_TAG, "onError: " + exception.toString());
+ super.onError(exception);
+ }
+
+ @Override
+ public void onQosSessionAvailable(@NonNull QosSession session,
+ @NonNull QosSessionAttributes sessionAttributes) {
+ Log.d(LOG_TAG, "onQosSessionAvailable: " + session.toString() + ", "
+ + sessionAttributes.toString());
+ super.onQosSessionAvailable(session, sessionAttributes);
+ }
+
+ @Override
+ public void onQosSessionLost(@NonNull QosSession session) {
+ Log.e(LOG_TAG, "onQosSessionLost: " + session.toString());
+ super.onQosSessionLost(session);
+ }
+ };
+
+ private void listenForBearer() {
+ try {
+ connectivityManager.registerQosCallback(new QosSocketInfo(network, socket),
+ qosCallback, MoreExecutors.directExecutor());
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
}
/**
@@ -110,6 +160,7 @@
if (isOpen.getAndSet(false)) {
output.flush();
}
+ connectivityManager.unregisterQosCallback(qosCallback);
socket.close();
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
index 7605fb5..13fa53a 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/sip/SipUtils.java
@@ -54,6 +54,7 @@
import javax.sip.address.SipURI;
import javax.sip.address.URI;
import javax.sip.header.ContactHeader;
+import javax.sip.header.Header;
import javax.sip.header.HeaderFactory;
import javax.sip.header.ViaHeader;
import javax.sip.message.Request;
@@ -262,6 +263,7 @@
Via via = (Via) invite.getTopmostVia().clone();
via.removeParameter("branch");
+ via.setBranch(Utils.getInstance().generateBranchId());
request.addHeader(via);
request.addHeader(
sHeaderFactory.createFromHeader(invite.getFrom().getAddress(),
@@ -290,6 +292,27 @@
if (code == Response.OK) {
response.setMessageContent(SDP_CONTENT_TYPE, SDP_CONTENT_SUB_TYPE, sdp.encode());
}
+ response.setToTag(Utils.getInstance().generateTag());
+
+ // Set a Contact header.
+ response.setHeader(generateContactHeader(configuration));
+
+ // Set Conversation-ID and Contribution-ID
+ Header conversationIdHeader = invite.getHeader(CONVERSATION_ID_HEADER_NAME);
+ if (conversationIdHeader != null) {
+ response.setHeader((Header) conversationIdHeader.clone());
+ }
+ Header contributionIdHeader = invite.getHeader(CONTRIBUTION_ID_HEADER_NAME);
+ if (conversationIdHeader != null) {
+ response.setHeader((Header) contributionIdHeader.clone());
+ }
+
+ // Set P-Preferred-Identity
+ List<String> associatedUris = configuration.getAssociatedUris();
+ String preferredUri = Iterables.getFirst(associatedUris,
+ configuration.getPublicUserIdentity());
+ response.setHeader(
+ sHeaderFactory.createHeader(PPreferredIdentityHeader.NAME, preferredUri));
// Set PANI and PLANI if exists
if (configuration.getPaniHeader() != null) {
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java
index ac37110..f1868fc 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/registration/RegistrationControllerImpl.java
@@ -47,6 +47,7 @@
import com.google.common.util.concurrent.SettableFuture;
import java.text.ParseException;
+import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
@@ -152,6 +153,7 @@
new DelegateConnectionMessageCallback() {
@Override
public void onMessageReceived(@NonNull SipMessage message) {
+ message = repairHeaderSection(message);
SipSessionListener listener = sipSessionListener;
if (listener != null) {
try {
@@ -386,6 +388,9 @@
String serviceRoutes =
configuration.getString(
SipDelegateImsConfiguration.KEY_SIP_CONFIG_SERVICE_ROUTE_HEADER_STRING);
+ if (TextUtils.isEmpty(serviceRoutes)) {
+ return Collections.emptyList();
+ }
return Splitter.on(',').trimResults().splitToList(serviceRoutes);
}
@@ -424,6 +429,23 @@
return configuration.getInt(
SipDelegateImsConfiguration.KEY_SIP_CONFIG_MAX_PAYLOAD_SIZE_ON_UDP_INT, 1500);
}
+
+ /**
+ * There is a modem issue where "ia:" is returned back instead of "Via:". Fix that locally
+ * for now.
+ * @return A SipMessage with the corrected header section.
+ */
+ private static SipMessage repairHeaderSection(SipMessage message) {
+ String headers = message.getHeaderSection();
+
+ if (headers.startsWith("ia:")) {
+ headers = "V" + headers;
+ Log.i(TAG, "repairHeaderSection: detected malformed via: "
+ + message.getHeaderSection().substring(0, 10) + "->"
+ + headers.substring(0, 10));
+ }
+ return new SipMessage(message.getStartLine(), headers, message.getContent());
+ }
}
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
index b204de6..4b913f7 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/MinimalCpmChatService.java
@@ -54,7 +54,7 @@
public class MinimalCpmChatService implements ImsService {
public static final String CPM_SESSION_TAG =
"+g.3gpp.icsi-ref=\"urn%3Aurn-7%3A3gpp-service.ims.icsi.oma.cpm.session\"";
- private static final String TAG = SimpleChatSession.class.getSimpleName();
+ private static final String TAG = MinimalCpmChatService.class.getSimpleName();
private final Map<String, SimpleChatSession> mTransactions = new HashMap<>();
private final Map<String, SimpleChatSession> mDialogs = new HashMap<>();
@@ -112,7 +112,7 @@
}
ListenableFuture<Boolean> sendSipRequest(SIPRequest msg, SimpleChatSession session) {
- Log.i(TAG, "sendSipRequest");
+ Log.i(TAG, "sendSipRequest:\r\n" + msg);
if (!TextUtils.equals(msg.getMethod(), Request.ACK)) {
mTransactions.put(msg.getTransactionId(), session);
}
@@ -126,7 +126,7 @@
}
ListenableFuture<Boolean> sendSipResponse(SIPResponse msg, SimpleChatSession session) {
- Log.i(TAG, "sendSipRequest");
+ Log.i(TAG, "sendSipResponse:\r\n" + msg);
if (TextUtils.equals(msg.getCSeq().getMethod(), Request.BYE)) {
mDialogs.remove(msg.getDialogId(/* isServer= */ true));
} else if (TextUtils.equals(msg.getCSeq().getMethod(), Request.INVITE)
@@ -139,6 +139,7 @@
}
private void handleRequest(SIPRequest request) {
+ Log.i(TAG, "handleRequest:\r\n" + request);
String dialogId = request.getDialogId(/* isServer= */ true);
if (mDialogs.containsKey(dialogId)) {
SimpleChatSession session = mDialogs.get(dialogId);
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
index 6813c91..4cc474c 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSession.java
@@ -167,8 +167,10 @@
mStartFuture = future;
mRemoteUri = SipUtils.createUri(telUriContact);
try {
- SipSessionConfiguration configuration = mContext.getSipSession().getSessionConfiguration();
- SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(), false);
+ SipSessionConfiguration configuration =
+ mContext.getSipSession().getSessionConfiguration();
+ SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(),
+ false);
SIPRequest invite =
SipUtils.buildInvite(
mContext.getSipSession().getSessionConfiguration(),
@@ -229,6 +231,7 @@
try {
SIPResponse response = SipUtils.buildInviteResponse(configuration, invite, statusCode,
sdp);
+ mLocalSdp = sdp;
return Futures.transform(
mService.sendSipResponse(response, this), result -> null,
MoreExecutors.directExecutor());
@@ -402,14 +405,16 @@
private void startMsrpSession(SimpleSdpMessage remoteSdp) {
Log.d(TAG, "Start MSRP session: " + remoteSdp);
if (remoteSdp.getAddress().isPresent() && remoteSdp.getPort().isPresent()) {
+ String localIp = getLocalIp();
Futures.addCallback(
mMsrpManager.createMsrpSession(
- remoteSdp.getAddress().get(), remoteSdp.getPort().getAsInt(),
- this::receiveMsrpChunk),
+ remoteSdp.getAddress().get(), remoteSdp.getPort().getAsInt(), localIp,
+ 0 /* localPort */, this::receiveMsrpChunk),
new FutureCallback<MsrpSession>() {
@Override
public void onSuccess(MsrpSession result) {
mMsrpSession = result;
+ sendEmptyPacket();
notifySuccess();
}
@@ -430,6 +435,30 @@
}
}
+ private void sendEmptyPacket() {
+ MsrpChunk msrpChunk =
+ MsrpChunk.newBuilder()
+ .method(MsrpChunk.Method.SEND)
+ .transactionId(MsrpUtils.generateRandomId())
+ .continuation(Continuation.COMPLETE)
+ .addHeader(MsrpConstants.HEADER_TO_PATH, mRemoteSdp.getPath().get())
+ .addHeader(MsrpConstants.HEADER_FROM_PATH, mLocalSdp.getPath().get())
+ .addHeader(MsrpConstants.HEADER_FAILURE_REPORT,
+ MsrpConstants.REPORT_VALUE_NO)
+ .addHeader(MsrpConstants.HEADER_SUCCESS_REPORT,
+ MsrpConstants.REPORT_VALUE_NO)
+ .addHeader(MsrpConstants.HEADER_BYTE_RANGE, "1/0-0")
+ .addHeader(MsrpConstants.HEADER_MESSAGE_ID, MsrpUtils.generateRandomId())
+ .build();
+
+ mMsrpSession.send(msrpChunk);
+ }
+
+ private String getLocalIp() {
+ SipSessionConfiguration configuration = mContext.getSipSession().getSessionConfiguration();
+ return configuration.getLocalIpAddress();
+ }
+
private void receiveMsrpChunk(MsrpChunk chunk) {
Log.d(TAG, "Received msrp= " + chunk + " conversation=" + mConversationId);
@@ -441,6 +470,7 @@
String contentType = contentTypeHeader.value();
if ("message/cpim".equals(contentType)) {
+ Log.d(TAG, "Received CPIM: " + new String(chunk.content(), UTF_8));
try {
SimpleCpimMessage cpim = SimpleCpimMessage.parse(chunk.content());
if (mListener != null) {
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index f8c78d4..f77dd55 100644
--- a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
+++ b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
@@ -55,20 +55,21 @@
import android.telephony.ims.RcsConfig;
import android.telephony.ims.aidl.IImsConfig;
import android.telephony.ims.aidl.IRcsConfigCallback;
-import android.telephony.ims.feature.ImsFeature;
import android.test.mock.MockContentProvider;
import android.test.mock.MockContentResolver;
import android.test.suitebuilder.annotation.SmallTest;
import android.testing.TestableLooper;
import android.util.Log;
+import com.android.ims.FeatureConnector;
+import com.android.ims.RcsFeatureManager;
import com.android.internal.telephony.ITelephony;
-import com.android.internal.telephony.ims.ImsResolver;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.invocation.InvocationOnMock;
@@ -130,7 +131,13 @@
@Mock
private ITelephony.Stub mITelephony;
@Mock
- private ImsResolver mImsResolver;
+ private RcsFeatureManager mFeatureManager;
+ @Mock
+ private RcsProvisioningMonitor.FeatureConnectorFactory<RcsFeatureManager> mFeatureFactory;
+ @Mock
+ private FeatureConnector<RcsFeatureManager> mFeatureConnector;
+ @Captor
+ ArgumentCaptor<FeatureConnector.Listener<RcsFeatureManager>> mConnectorListener;
@Mock
private IImsConfig.Stub mIImsConfig;
@Mock
@@ -246,8 +253,7 @@
when(mCursor.getColumnIndexOrThrow(any())).thenReturn(1);
when(mCursor.getBlob(anyInt())).thenReturn(
RcsConfig.compressGzip(SAMPLE_CONFIG.getBytes()));
- when(mPhone.getImsResolver()).thenReturn(mImsResolver);
- when(mImsResolver.getImsConfig(anyInt(), anyInt())).thenReturn(mIImsConfig);
+
mHandlerThread = new HandlerThread("RcsProvisioningMonitorTest");
mHandlerThread.start();
}
@@ -268,8 +274,9 @@
@Test
@SmallTest
public void testInitWithSavedConfig() throws Exception {
- createMonitor(3);
ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
+ createMonitor(3);
+
for (int i = 0; i < 3; i++) {
assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
@@ -279,8 +286,6 @@
Intent capturedIntent = captorIntent.getAllValues().get(1);
assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
capturedIntent.getAction());
- PhoneGlobals.getInstance().getImsResolver();
- verify(mPhone, atLeastOnce()).getImsResolver();
verify(mIImsConfig, times(3)).notifyRcsAutoConfigurationReceived(any(), anyBoolean());
}
@@ -293,6 +298,7 @@
verify(mPhone, times(3)).sendBroadcast(captorIntent.capture(), any());
Intent capturedIntent = captorIntent.getAllValues().get(1);
+
assertEquals(ProvisioningManager.ACTION_RCS_SINGLE_REGISTRATION_CAPABILITY_UPDATE,
capturedIntent.getAction());
//Should not notify null config
@@ -304,8 +310,7 @@
public void testSubInfoChanged() throws Exception {
createMonitor(3);
ArgumentCaptor<Intent> captorIntent = ArgumentCaptor.forClass(Intent.class);
- mExecutor.execute(() -> mSubChangedListener.onSubscriptionsChanged());
- processAllMessages();
+
for (int i = 0; i < 3; i++) {
assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE + i)));
@@ -354,7 +359,7 @@
byte[] configCached = mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE);
assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), configCached));
- verify(mIImsConfig, never()).notifyRcsAutoConfigurationRemoved();
+ verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved();
// The api should be called 2 times, one happens when monitor is initilized,
// Another happens when DMS is changed.
verify(mIImsConfig, times(2))
@@ -419,8 +424,6 @@
mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
processAllMessages();
- verify(mImsResolver, atLeastOnce()).getImsConfig(
- anyInt(), eq(ImsFeature.FEATURE_RCS));
verify(mIImsConfig, atLeastOnce()).notifyRcsAutoConfigurationReceived(
argumentBytes.capture(), eq(false));
assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(), argumentBytes.getValue()));
@@ -434,8 +437,6 @@
mRcsProvisioningMonitor.requestReconfig(FAKE_SUB_ID_BASE);
processAllMessages();
- verify(mImsResolver, atLeastOnce()).getImsConfig(
- anyInt(), eq(ImsFeature.FEATURE_RCS));
verify(mIImsConfig, times(1)).notifyRcsAutoConfigurationRemoved();
verify(mIImsConfig, times(1)).triggerRcsReconfiguration();
}
@@ -519,19 +520,79 @@
verify(mCallback, times(1)).onRemoved();
}
- private void createMonitor(int subCount) {
+ @Test
+ @SmallTest
+ public void testRcsConnectedAndDisconnected() throws Exception {
+ createMonitor(1);
+ mRcsProvisioningMonitor.registerRcsProvisioningChangedCallback(
+ FAKE_SUB_ID_BASE, mCallback);
+
+ verify(mIImsConfig, times(1))
+ .notifyRcsAutoConfigurationReceived(any(), anyBoolean());
+
+ mConnectorListener.getValue().connectionUnavailable(0);
+
+ verify(mCallback, times(1)).onRemoved();
+ }
+
+ @Test
+ @SmallTest
+ public void testTestModeEnabledAndDisabled() throws Exception {
+ when(mCursor.getBlob(anyInt())).thenReturn(null);
+ createMonitor(1);
+
+ verify(mCursor, times(1)).getBlob(anyInt());
+
+ mRcsProvisioningMonitor.setTestModeEnabled(true);
+ processAllMessages();
+
+ //should not query db in test mode
+ verify(mCursor, times(1)).getBlob(anyInt());
+ assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
+ processAllMessages();
+
+ //config cahced in monitor should be updated, but db should not
+ assertNull(mProvider.getContentValues());
+ assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+ mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE)));
+
+ //verify if monitor goes back to normal mode
+ mRcsProvisioningMonitor.setTestModeEnabled(false);
+ processAllMessages();
+
+ verify(mCursor, times(2)).getBlob(anyInt());
+ assertNull(mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE));
+
+ mRcsProvisioningMonitor.updateConfig(FAKE_SUB_ID_BASE, SAMPLE_CONFIG.getBytes(), false);
+ processAllMessages();
+
+ assertTrue(Arrays.equals(SAMPLE_CONFIG.getBytes(),
+ mRcsProvisioningMonitor.getConfig(FAKE_SUB_ID_BASE)));
+ assertTrue(Arrays.equals(RcsConfig.compressGzip(SAMPLE_CONFIG.getBytes()),
+ (byte[]) mProvider.getContentValues().get(SimInfo.COLUMN_RCS_CONFIG)));
+ }
+
+ private void createMonitor(int subCount) throws Exception {
if (Looper.myLooper() == null) {
Looper.prepare();
}
makeFakeActiveSubIds(subCount);
+ when(mFeatureFactory.create(any(), anyInt(), mConnectorListener.capture(), any(), any()))
+ .thenReturn(mFeatureConnector);
+ when(mFeatureManager.getConfig()).thenReturn(mIImsConfig);
mRcsProvisioningMonitor = new RcsProvisioningMonitor(mPhone, mHandlerThread.getLooper(),
- mRoleManager);
+ mRoleManager, mFeatureFactory);
mHandler = mRcsProvisioningMonitor.getHandler();
try {
mLooper = new TestableLooper(mHandler.getLooper());
} catch (Exception e) {
logd("Unable to create looper from handler.");
}
+ mConnectorListener.getValue().connectionReady(mFeatureManager);
+
+ verify(mFeatureConnector, atLeastOnce()).connect();
}
private void broadcastCarrierConfigChange(int subId) {
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
index 8778529..4ab92a7 100644
--- a/tests/src/com/android/phone/SimPhonebookProviderTest.java
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -118,6 +118,14 @@
@Test
public void query_entityFiles_returnsCursorWithCorrectProjection() {
+ // Null projection
+ try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, null, null,
+ null)) {
+ assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+ .containsExactlyElementsIn(
+ SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
+ }
+
// Empty projection
try (Cursor cursor = mResolver.query(ElementaryFiles.CONTENT_URI, new String[0], null,
null)) {
@@ -211,18 +219,38 @@
}
@Test
+ public void query_entityFilesItem_nullProjection_returnsCursorWithCorrectProjection() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ // Null projection
+ try (Cursor cursor = mResolver.query(ElementaryFiles.getItemUri(1, EF_ADN), null, null,
+ null)) {
+ assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+ .containsExactlyElementsIn(
+ SimPhonebookProvider.ELEMENTARY_FILES_ALL_COLUMNS);
+ }
+ }
+
+ @Test
public void query_adnRecords_returnsCursorWithMatchingProjection() {
setupSimsWithSubscriptionIds(1);
mIccPhoneBook.makeAllEfsSupported(1);
Uri contentAdn = SimRecords.getContentUri(1, EF_ADN);
+ // Null projection
+ try (Cursor cursor = mResolver.query(contentAdn, null, null, null)) {
+ assertThat(Objects.requireNonNull(cursor).getColumnNames()).asList()
+ .containsExactlyElementsIn(SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
+ }
+
// Empty projection
try (Cursor cursor = mResolver.query(contentAdn, new String[0], null, null)) {
assertThat(cursor).hasColumnNames();
}
// Single column
- try (Cursor cursor = mResolver.query(contentAdn, new String[] {
+ try (Cursor cursor = mResolver.query(contentAdn, new String[]{
SimRecords.PHONE_NUMBER
}, null, null)) {
assertThat(cursor).hasColumnNames(SimRecords.PHONE_NUMBER);
@@ -531,6 +559,20 @@
}
@Test
+ public void query_itemUriNullProjection_returnsCursorWithAllColumns() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ try (Cursor cursor = mResolver.query(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
+ null, null, null)
+ ) {
+ assertThat(Objects.requireNonNull(
+ cursor).getColumnNames()).asList().containsExactlyElementsIn(
+ SimPhonebookProvider.SIM_RECORDS_ALL_COLUMNS);
+ }
+ }
+
+ @Test
public void query_itemUriEmptyRecord_returnsEmptyCursor() {
setupSimsWithSubscriptionIds(1);
mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 30);