Merge "Use new DEVICE_TO_DEVICE connection event to send received messages."
diff --git a/Android.bp b/Android.bp
index e7ca068..4357f12 100644
--- a/Android.bp
+++ b/Android.bp
@@ -15,6 +15,23 @@
// Build the Phone app which includes the emergency dialer. See Contacts
// for the 'other' dialer.
+package {
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "packages_services_Telephony_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "NOTICE",
+ ],
+}
+
android_app {
name: "TeleService",
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/apex/Android.bp b/apex/Android.bp
index 25a4909..a0e5713 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
apex_defaults {
name: "com.android.telephony-defaults",
diff --git a/apex/testing/Android.bp b/apex/testing/Android.bp
index 10455a4..1138b5e 100644
--- a/apex/testing/Android.bp
+++ b/apex/testing/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
apex {
name: "test_com.android.telephony",
visibility: [
@@ -22,4 +31,4 @@
file_contexts: ":com.android.telephony-file_contexts",
// Test APEX, should never be installed
installable: false,
-}
\ No newline at end of file
+}
diff --git a/ecc/conversion_toolset_v1/proto/Android.bp b/ecc/conversion_toolset_v1/proto/Android.bp
index e1e0643..632ab40 100644
--- a/ecc/conversion_toolset_v1/proto/Android.bp
+++ b/ecc/conversion_toolset_v1/proto/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
java_library_static {
name: "ecc-protos-lite",
proto: {
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/src/com/android/phone/ImsRcsController.java b/src/com/android/phone/ImsRcsController.java
index 9334078..701a759 100644
--- a/src/com/android/phone/ImsRcsController.java
+++ b/src/com/android/phone/ImsRcsController.java
@@ -16,6 +16,7 @@
package com.android.phone;
+import android.app.ActivityManager;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Binder;
@@ -270,7 +271,11 @@
@Override
public void requestCapabilities(int subId, String callingPackage, String callingFeatureId,
List<Uri> contactNumbers, IRcsUceControllerCallback c) {
- enforceReadPrivilegedPermission("requestCapabilities");
+ enforceAccessUserCapabilityExchangePermission("requestCapabilities");
+ enforceReadContactsPermission("requestCapabilities");
+ if (!isCallingProcessInForeground(Binder.getCallingUid())) {
+ throw new SecurityException("The caller is not in the foreground.");
+ }
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -290,7 +295,11 @@
@Override
public void requestAvailability(int subId, String callingPackage,
String callingFeatureId, Uri contactNumber, IRcsUceControllerCallback c) {
- enforceReadPrivilegedPermission("requestAvailability");
+ enforceAccessUserCapabilityExchangePermission("requestAvailability");
+ enforceReadContactsPermission("requestAvailability");
+ if (!isCallingProcessInForeground(Binder.getCallingUid())) {
+ throw new SecurityException("The caller is not in the foreground.");
+ }
final long token = Binder.clearCallingIdentity();
try {
UceControllerManager uceCtrlManager = getRcsFeatureController(subId).getFeature(
@@ -548,6 +557,39 @@
}
/**
+ * Make sure the caller has the ACCESS_RCS_USER_CAPABILITY_EXCHANGE permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
+ */
+ private void enforceAccessUserCapabilityExchangePermission(String message) {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.ACCESS_RCS_USER_CAPABILITY_EXCHANGE, message);
+ }
+
+ /**
+ * Make sure the caller has the READ_CONTACTS permission.
+ *
+ * @throws SecurityException if the caller does not have the required permission.
+ */
+ private void enforceReadContactsPermission(String message) {
+ mApp.enforceCallingOrSelfPermission(
+ android.Manifest.permission.READ_CONTACTS, message);
+ }
+
+ /**
+ * Check if the calling process is in the foreground.
+ *
+ * @return true if the caller is in the foreground.
+ */
+ private boolean isCallingProcessInForeground(int uid) {
+ ActivityManager am = mApp.getSystemService(ActivityManager.class);
+ boolean isCallingProcessForeground = am != null
+ && am.getUidImportance(uid)
+ == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND;
+ return isCallingProcessForeground;
+ }
+
+ /**
* Retrieve ImsPhone instance.
*
* @param subId the subscription ID
diff --git a/src/com/android/phone/PhoneInterfaceManager.java b/src/com/android/phone/PhoneInterfaceManager.java
index f1be951..eac13bb 100755
--- a/src/com/android/phone/PhoneInterfaceManager.java
+++ b/src/com/android/phone/PhoneInterfaceManager.java
@@ -51,6 +51,7 @@
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.ServiceSpecificException;
+import android.os.SystemClock;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.WorkSource;
@@ -87,7 +88,6 @@
import android.telephony.PhoneNumberRange;
import android.telephony.RadioAccessFamily;
import android.telephony.RadioAccessSpecifier;
-import android.telephony.RadioInterfaceCapabilities;
import android.telephony.ServiceState;
import android.telephony.SignalStrength;
import android.telephony.SignalStrengthUpdateRequest;
@@ -321,6 +321,7 @@
private static final int EVENT_SET_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE = 104;
private static final int CMD_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST = 105;
private static final int EVENT_CLEAR_SIGNAL_STRENGTH_UPDATE_REQUEST_DONE = 106;
+ private static final int CMD_PREPARE_UNATTENDED_REBOOT = 109;
// Parameters of select command.
private static final int SELECT_COMMAND = 0xA4;
@@ -371,6 +372,8 @@
public static final String RESET_NETWORK_ERASE_MODEM_CONFIG_ENABLED =
"reset_network_erase_modem_config_enabled";
+ private static final int SET_NETWORK_SELECTION_MODE_AUTOMATIC_TIMEOUT_MS = 2000; // 2 seconds
+
/**
* A request object to use for transmitting data to an ICC.
*/
@@ -1521,7 +1524,7 @@
PhoneConfigurationManager.getInstance()
.enablePhone(request.phone, enable, onCompleted);
break;
- case EVENT_ENABLE_MODEM_DONE:
+ case EVENT_ENABLE_MODEM_DONE: {
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
request.result = (ar.exception == null);
@@ -1536,6 +1539,7 @@
}
notifyRequester(request);
break;
+ }
case CMD_GET_MODEM_STATUS:
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_GET_MODEM_STATUS_DONE, request);
@@ -1674,27 +1678,44 @@
request = (MainThreadRequest) ar.userObj;
if (ar.exception == null) {
request.result = TelephonyManager.CHANGE_ICC_LOCK_SUCCESS;
+ // If the operation is successful, update the PIN storage
+ Pair<String, String> passwords = (Pair<String, String>) request.argument;
+ int phoneId = getPhoneFromRequest(request).getPhoneId();
+ UiccController.getInstance().getPinStorage()
+ .storePin(passwords.second, phoneId);
} else {
request.result = msg.arg1;
}
notifyRequester(request);
break;
- case CMD_SET_ICC_LOCK_ENABLED:
+ case CMD_SET_ICC_LOCK_ENABLED: {
request = (MainThreadRequest) msg.obj;
onCompleted = obtainMessage(EVENT_SET_ICC_LOCK_ENABLED_DONE, request);
Pair<Boolean, String> enabled = (Pair<Boolean, String>) request.argument;
getPhoneFromRequest(request).getIccCard().setIccLockEnabled(
enabled.first, enabled.second, onCompleted);
break;
+ }
case EVENT_SET_ICC_LOCK_ENABLED_DONE:
ar = (AsyncResult) msg.obj;
request = (MainThreadRequest) ar.userObj;
if (ar.exception == null) {
request.result = TelephonyManager.CHANGE_ICC_LOCK_SUCCESS;
+ // If the operation is successful, update the PIN storage
+ Pair<Boolean, String> enabled = (Pair<Boolean, String>) request.argument;
+ int phoneId = getPhoneFromRequest(request).getPhoneId();
+ if (enabled.first) {
+ UiccController.getInstance().getPinStorage()
+ .storePin(enabled.second, phoneId);
+ } else {
+ UiccController.getInstance().getPinStorage().clearPin(phoneId);
+ }
} else {
request.result = msg.arg1;
}
+
+
notifyRequester(request);
break;
@@ -1855,6 +1876,13 @@
break;
}
+ case CMD_PREPARE_UNATTENDED_REBOOT:
+ request = (MainThreadRequest) msg.obj;
+ request.result =
+ UiccController.getInstance().getPinStorage().prepareUnattendedReboot();
+ notifyRequester(request);
+ break;
+
default:
Log.w(LOG_TAG, "MainThreadHandler: unexpected message code: " + msg.what);
break;
@@ -1890,8 +1918,8 @@
* @see #sendRequestAsync
*/
private Object sendRequest(int command, Object argument) {
- return sendRequest(
- command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, null, null);
+ return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, null,
+ null, -1 /*timeoutInMs*/);
}
/**
@@ -1901,7 +1929,7 @@
*/
private Object sendRequest(int command, Object argument, WorkSource workSource) {
return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID,
- null, workSource);
+ null, workSource, -1 /*timeoutInMs*/);
}
/**
@@ -1910,7 +1938,18 @@
* @see #sendRequestAsync
*/
private Object sendRequest(int command, Object argument, Integer subId) {
- return sendRequest(command, argument, subId, null, null);
+ return sendRequest(command, argument, subId, null, null, -1 /*timeoutInMs*/);
+ }
+
+ /**
+ * Posts the specified command to be executed on the main thread,
+ * waits for the request to complete for at most {@code timeoutInMs}, and returns the result
+ * if not timeout or null otherwise.
+ * @see #sendRequestAsync
+ */
+ private @Nullable Object sendRequest(int command, Object argument, Integer subId,
+ long timeoutInMs) {
+ return sendRequest(command, argument, subId, null, null, timeoutInMs);
}
/**
@@ -1919,7 +1958,7 @@
* @see #sendRequestAsync
*/
private Object sendRequest(int command, Object argument, int subId, WorkSource workSource) {
- return sendRequest(command, argument, subId, null, workSource);
+ return sendRequest(command, argument, subId, null, workSource, -1 /*timeoutInMs*/);
}
/**
@@ -1928,17 +1967,18 @@
* @see #sendRequestAsync
*/
private Object sendRequest(int command, Object argument, Phone phone, WorkSource workSource) {
- return sendRequest(
- command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phone, workSource);
+ return sendRequest(command, argument, SubscriptionManager.INVALID_SUBSCRIPTION_ID, phone,
+ workSource, -1 /*timeoutInMs*/);
}
/**
- * Posts the specified command to be executed on the main thread,
- * waits for the request to complete, and returns the result.
+ * Posts the specified command to be executed on the main thread. If {@code timeoutInMs} is
+ * negative, waits for the request to complete, and returns the result. Otherwise, wait for
+ * maximum of {@code timeoutInMs} milliseconds, interrupt and return null.
* @see #sendRequestAsync
*/
- private Object sendRequest(
- int command, Object argument, Integer subId, Phone phone, WorkSource workSource) {
+ private @Nullable Object sendRequest(int command, Object argument, Integer subId, Phone phone,
+ WorkSource workSource, long timeoutInMs) {
if (Looper.myLooper() == mMainThreadHandler.getLooper()) {
throw new RuntimeException("This method will deadlock if called from the main thread.");
}
@@ -1955,16 +1995,36 @@
Message msg = mMainThreadHandler.obtainMessage(command, request);
msg.sendToTarget();
- // Wait for the request to complete
+
synchronized (request) {
- while (request.result == null) {
- try {
- request.wait();
- } catch (InterruptedException e) {
- // Do nothing, go back and wait until the request is complete
+ if (timeoutInMs >= 0) {
+ // Wait for at least timeoutInMs before returning null request result
+ long now = SystemClock.elapsedRealtime();
+ long deadline = now + timeoutInMs;
+ while (request == null && now < deadline) {
+ try {
+ request.wait(deadline - now);
+ } catch (InterruptedException e) {
+ // Do nothing, go back and check if request is completed or timeout
+ } finally {
+ now = SystemClock.elapsedRealtime();
+ }
+ }
+ } else {
+ // Wait for the request to complete
+ while (request.result == null) {
+ try {
+ request.wait();
+ } catch (InterruptedException e) {
+ // Do nothing, go back and wait until the request is complete
+ }
}
}
}
+ if (request.result == null) {
+ Log.wtf(LOG_TAG,
+ "sendRequest: Blocking command timed out. Something has gone terribly wrong.");
+ }
return request.result;
}
@@ -2183,7 +2243,8 @@
final long identity = Binder.clearCallingIdentity();
try {
- final UnlockSim checkSimPin = new UnlockSim(getPhone(subId).getIccCard());
+ Phone phone = getPhone(subId);
+ final UnlockSim checkSimPin = new UnlockSim(phone.getPhoneId(), phone.getIccCard());
checkSimPin.start();
return checkSimPin.unlockSim(null, pin);
} finally {
@@ -2196,7 +2257,8 @@
final long identity = Binder.clearCallingIdentity();
try {
- final UnlockSim checkSimPuk = new UnlockSim(getPhone(subId).getIccCard());
+ Phone phone = getPhone(subId);
+ final UnlockSim checkSimPuk = new UnlockSim(phone.getPhoneId(), phone.getIccCard());
checkSimPuk.start();
return checkSimPuk.unlockSim(puk, pin);
} finally {
@@ -2211,6 +2273,7 @@
private static class UnlockSim extends Thread {
private final IccCard mSimCard;
+ private final int mPhoneId;
private boolean mDone = false;
private int mResult = PhoneConstants.PIN_GENERAL_FAILURE;
@@ -2222,7 +2285,8 @@
// For async handler to identify request type
private static final int SUPPLY_PIN_COMPLETE = 100;
- public UnlockSim(IccCard simCard) {
+ UnlockSim(int phoneId, IccCard simCard) {
+ mPhoneId = phoneId;
mSimCard = simCard;
}
@@ -2304,6 +2368,11 @@
int[] resultArray = new int[2];
resultArray[0] = mResult;
resultArray[1] = mRetryCount;
+
+ if (mResult == PhoneConstants.PIN_RESULT_SUCCESS && pin.length() > 0) {
+ UiccController.getInstance().getPinStorage().storePin(pin, mPhoneId);
+ }
+
return resultArray;
}
}
@@ -3102,6 +3171,10 @@
mApp.enforceCallingOrSelfPermission(android.Manifest.permission.NETWORK_SETTINGS, null);
}
+ private void enforceRebootPermission() {
+ mApp.enforceCallingOrSelfPermission(android.Manifest.permission.REBOOT, null);
+ }
+
private String createTelUrl(String number) {
if (TextUtils.isEmpty(number)) {
return null;
@@ -5592,7 +5665,8 @@
return;
}
if (DBG) log("setNetworkSelectionModeAutomatic: subId " + subId);
- sendRequest(CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC, null, subId);
+ sendRequest(CMD_SET_NETWORK_SELECTION_MODE_AUTOMATIC, null, subId,
+ SET_NETWORK_SELECTION_MODE_AUTOMATIC_TIMEOUT_MS);
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -9330,12 +9404,12 @@
@Override
public boolean isRadioInterfaceCapabilitySupported(
@NonNull @TelephonyManager.RadioInterfaceCapability String capability) {
- RadioInterfaceCapabilities radioInterfaceCapabilities =
+ Set<String> radioInterfaceCapabilities =
mPhoneConfigurationManager.getRadioInterfaceCapabilities();
if (radioInterfaceCapabilities == null) {
throw new RuntimeException("radio interface capabilities are not available");
} else {
- return radioInterfaceCapabilities.isSupported(capability);
+ return radioInterfaceCapabilities.contains(capability);
}
}
@@ -9746,6 +9820,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
@@ -9973,4 +10069,21 @@
}
}
}
+
+ /**
+ * Prepare TelephonyManager for an unattended reboot. The reboot is
+ * required to be done shortly after the API is invoked.
+ */
+ @Override
+ @TelephonyManager.PrepareUnattendedRebootResult
+ public int prepareForUnattendedReboot() {
+ enforceRebootPermission();
+
+ final long identity = Binder.clearCallingIdentity();
+ try {
+ return (int) sendRequest(CMD_PREPARE_UNATTENDED_REBOOT, null);
+ } finally {
+ Binder.restoreCallingIdentity(identity);
+ }
+ }
}
diff --git a/src/com/android/phone/RcsProvisioningMonitor.java b/src/com/android/phone/RcsProvisioningMonitor.java
index 79310ef..13dddec 100644
--- a/src/com/android/phone/RcsProvisioningMonitor.java
+++ b/src/com/android/phone/RcsProvisioningMonitor.java
@@ -38,15 +38,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;
@@ -66,6 +71,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;
@@ -76,12 +82,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;
@@ -146,6 +155,7 @@
@Override
public void handleMessage(Message msg) {
+ logv("handleMessage: " + msg);
switch (msg.what) {
case EVENT_SUB_CHANGED:
onSubChanged();
@@ -177,6 +187,9 @@
onCarrierConfigChange();
}
break;
+ case EVENT_RESET:
+ reset();
+ break;
default:
loge("Unhandled event " + msg.what);
}
@@ -187,13 +200,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) {
@@ -205,7 +225,14 @@
}
void setConfig(byte[] config) {
- mConfig = config;
+ if (!Arrays.equals(mConfig, config)) {
+ mConfig = config;
+ if (mConfig != null) {
+ notifyRcsAutoConfigurationReceived();
+ } else {
+ notifyRcsAutoConfigurationRemoved();
+ }
+ }
}
byte[] getConfig() {
@@ -213,15 +240,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;
@@ -233,12 +259,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);
}
@@ -257,16 +282,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);
}
@@ -283,7 +381,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);
@@ -292,15 +444,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();
}
/**
@@ -312,7 +458,7 @@
HandlerThread handlerThread = new HandlerThread(TAG);
handlerThread.start();
sInstance = new RcsProvisioningMonitor(app, handlerThread.getLooper(),
- new RoleManagerAdapterImpl(app));
+ new RoleManagerAdapterImpl(app), RcsFeatureManager::getConnector);
}
return sInstance;
}
@@ -324,15 +470,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();
}
@@ -410,6 +585,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) {
@@ -462,61 +658,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) {
@@ -568,26 +732,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();
}
});
}
@@ -596,11 +759,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) {
@@ -608,9 +768,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) {
@@ -620,7 +781,14 @@
intent.putExtra(ProvisioningManager.EXTRA_SUBSCRIPTION_ID, subId);
intent.putExtra(ProvisioningManager.EXTRA_STATUS, capability);
logv("notify " + intent);
- mPhone.sendBroadcast(intent);
+ // 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);
+ mPhone.sendBroadcast(intent);
+ } else {
+ mPhone.sendBroadcast(intent);
+ }
}
private IImsConfig getIImsConfig(int subId, int feature) {
@@ -637,6 +805,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 af293ce..74d6b57 100644
--- a/src/com/android/phone/TelephonyShellCommand.java
+++ b/src/com/android/phone/TelephonyShellCommand.java
@@ -68,6 +68,7 @@
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";
+ private static final String UNATTENDED_REBOOT = "unattended-reboot";
private static final String CARRIER_CONFIG_SUBCOMMAND = "cc";
private static final String DATA_TEST_MODE = "data";
private static final String DATA_ENABLE = "enable";
@@ -101,6 +102,8 @@
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";
@@ -201,6 +204,8 @@
return handleSingleRegistrationConfigCommand();
case RESTART_MODEM:
return handleRestartModemCommand();
+ case UNATTENDED_REBOOT:
+ return handleUnattendedReboot();
default: {
return handleDefaultCommands(cmd);
}
@@ -231,6 +236,8 @@
pw.println(" RCS VoLTE Single Registration Config Commands.");
pw.println(" restart-modem");
pw.println(" Restart modem command.");
+ pw.println(" unattended-reboot");
+ pw.println(" Prepare for unattended reboot.");
onHelpIms();
onHelpUce();
onHelpEmergencyNumber();
@@ -412,6 +419,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).");
@@ -1456,6 +1468,20 @@
return result ? 0 : -1;
}
+ private int handleUnattendedReboot() {
+ // Verify that the user is allowed to run the command. Only allowed in rooted device in a
+ // non user build.
+ if (Binder.getCallingUid() != Process.ROOT_UID || TelephonyUtils.IS_USER) {
+ getErrPrintWriter().println("UnattendedReboot: Permission denied.");
+ return -1;
+ }
+
+ int result = TelephonyManager.getDefault().prepareForUnattendedReboot();
+ getOutPrintWriter().println("result: " + result);
+
+ return result != TelephonyManager.PREPARE_UNATTENDED_REBOOT_ERROR ? 0 : -1;
+ }
+
private int handleGbaCommand() {
String arg = getNextArg();
if (arg == null) {
@@ -1599,6 +1625,12 @@
}
switch (arg) {
+ case SRC_SET_TEST_ENABLED: {
+ return handleSrcSetTestEnabledCommand();
+ }
+ case SRC_GET_TEST_ENABLED: {
+ return handleSrcGetTestEnabledCommand();
+ }
case SRC_SET_DEVICE_ENABLED: {
return handleSrcSetDeviceEnabledCommand();
}
@@ -1718,6 +1750,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) {
diff --git a/src/com/android/services/telephony/CallQualityManager.java b/src/com/android/services/telephony/CallQualityManager.java
index 01b5bae..c8785d6 100644
--- a/src/com/android/services/telephony/CallQualityManager.java
+++ b/src/com/android/services/telephony/CallQualityManager.java
@@ -90,6 +90,11 @@
}
private void popUpNotification(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;
+ }
int iconId = android.R.drawable.stat_notify_error;
Notification notification = new Notification.Builder(mContext)
@@ -99,7 +104,7 @@
.setContentTitle(title)
.setContentText(details)
.setStyle(new Notification.BigTextStyle().bigText(details))
- .setOngoing(true)
+ .setAutoCancel(true)
.setChannelId(CALL_QUALITY_CHANNEL_ID)
.setOnlyAlertOnce(true)
.build();
diff --git a/testapps/EmbmsServiceTestApp/Android.bp b/testapps/EmbmsServiceTestApp/Android.bp
index e4a54cb..584e5bd 100644
--- a/testapps/EmbmsServiceTestApp/Android.bp
+++ b/testapps/EmbmsServiceTestApp/Android.bp
@@ -1,4 +1,13 @@
// Build the Sample Embms Services
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_app {
name: "EmbmsTestService",
srcs: ["src/**/*.java"],
diff --git a/testapps/EmbmsTestDownloadApp/Android.bp b/testapps/EmbmsTestDownloadApp/Android.bp
index 63f4e83..c1b9425 100644
--- a/testapps/EmbmsTestDownloadApp/Android.bp
+++ b/testapps/EmbmsTestDownloadApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
src_dirs = ["src"]
res_dirs = ["res"]
android_test {
diff --git a/testapps/EmbmsTestStreamingApp/Android.bp b/testapps/EmbmsTestStreamingApp/Android.bp
index 814c5ca..9f082ee 100644
--- a/testapps/EmbmsTestStreamingApp/Android.bp
+++ b/testapps/EmbmsTestStreamingApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_test {
name: "EmbmsTestStreamingApp",
srcs: ["src/**/*.java"],
diff --git a/testapps/GbaTestApp/Android.bp b/testapps/GbaTestApp/Android.bp
index cb6df4e..b3c45dd 100644
--- a/testapps/GbaTestApp/Android.bp
+++ b/testapps/GbaTestApp/Android.bp
@@ -12,6 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_test {
name: "GbaTestApp",
static_libs: [
diff --git a/testapps/ImsTestService/Android.bp b/testapps/ImsTestService/Android.bp
index a0b4edb..7073749 100644
--- a/testapps/ImsTestService/Android.bp
+++ b/testapps/ImsTestService/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_app {
name: "ImsTestApp",
static_libs: [
diff --git a/testapps/SmsManagerTestApp/Android.bp b/testapps/SmsManagerTestApp/Android.bp
index 5333eab..e451b2d 100644
--- a/testapps/SmsManagerTestApp/Android.bp
+++ b/testapps/SmsManagerTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_app {
name: "SmsManagerTestApp",
srcs: ["src/**/*.java"],
diff --git a/testapps/TelephonyManagerTestApp/Android.bp b/testapps/TelephonyManagerTestApp/Android.bp
index 8a37c99..e95d62f 100644
--- a/testapps/TelephonyManagerTestApp/Android.bp
+++ b/testapps/TelephonyManagerTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_test {
name: "TelephonyManagerTestApp",
srcs: ["src/**/*.java"],
diff --git a/testapps/TelephonyRegistryTestApp/Android.bp b/testapps/TelephonyRegistryTestApp/Android.bp
index fec5286..2439461 100644
--- a/testapps/TelephonyRegistryTestApp/Android.bp
+++ b/testapps/TelephonyRegistryTestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_test {
name: "TelephonyRegistryTestApp",
srcs: ["src/**/*.java"],
diff --git a/testapps/TestRcsApp/TestApp/Android.bp b/testapps/TestRcsApp/TestApp/Android.bp
index dfa1f2e..e63715b 100644
--- a/testapps/TestRcsApp/TestApp/Android.bp
+++ b/testapps/TestRcsApp/TestApp/Android.bp
@@ -1,3 +1,12 @@
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_app {
name: "TestRcsApp",
@@ -9,11 +18,10 @@
"androidx-constraintlayout_constraintlayout",
"aosp_test_rcs_client_base",
"androidx.appcompat_appcompat",
+ "libphonenumber-platform"
],
certificate: "platform",
sdk_version: "system_current",
min_sdk_version: "30",
}
-
-
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 6e52949..6bd5f32 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="3"
- android:versionName="1.0.2_UP1.0">
+ 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/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/Android.bp b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
index eef34c8..413b5e8 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/Android.bp
@@ -1,5 +1,24 @@
+package {
+ default_applicable_licenses: [
+ "packages_services_Telephony_testapps_TestRcsApp_aosp_test_rcsclient_license",
+ ],
+}
+
+// Added automatically by a large-scale-change
+// See: http://go/android-license-faq
+license {
+ name: "packages_services_Telephony_testapps_TestRcsApp_aosp_test_rcsclient_license",
+ visibility: [":__subpackages__"],
+ license_kinds: [
+ "SPDX-license-identifier-Apache-2.0",
+ ],
+ license_text: [
+ "LICENSE",
+ ],
+}
+
android_library {
name: "aosp_test_rcs_client_base",
@@ -23,5 +42,3 @@
sdk_version: "system_current",
min_sdk_version: "30",
}
-
-
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java
new file mode 100644
index 0000000..2dda33f
--- /dev/null
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessageTest.java
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.libraries.rcs.simpleclient.protocol.cpim;
+
+import static com.google.common.truth.Truth.assertThat;
+import static java.nio.charset.StandardCharsets.UTF_8;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class SimpleCpimMessageTest {
+ private static final String SAMPLE_CPIM =
+ "From: MR SANDERS <im:piglet@100akerwood.com>\r\n"
+ + "To: Depressed Donkey <im:eeyore@100akerwood.com>\r\n"
+ + "DateTime: 2000-12-13T13:40:00-08:00\r\n"
+ + "Subject: the weather will be fine today\r\n"
+ + "Subject:;lang=fr beau temps prevu pour aujourd'hui\r\n"
+ + "NS: MyFeatures <mid:MessageFeatures@id.foo.com>\r\n"
+ + "Require: MyFeatures.VitalMessageOption\r\n"
+ + "MyFeatures.VitalMessageOption: Confirmation-requested\r\n"
+ + "MyFeatures.WackyMessageOption: Use-silly-font\r\n"
+ + "\r\n"
+ + "Content-type: text/plain; charset=utf-8\r\n"
+ + "Content-ID: <1234567890@foo.com>\r\n"
+ + "\r\n"
+ + "body";
+
+ @Test
+ public void parse_successful() throws Exception {
+ SimpleCpimMessage cpim = SimpleCpimMessage.parse(SAMPLE_CPIM.getBytes(UTF_8));
+
+ assertThat(cpim.namespaces()).containsEntry("MyFeatures", "mid:MessageFeatures@id.foo.com");
+ assertThat(cpim.headers()).containsEntry("Require", "MyFeatures.VitalMessageOption");
+ assertThat(cpim.contentType()).isEqualTo("text/plain; charset=utf-8");
+ assertThat(cpim.content()).isEqualTo("body");
+ }
+}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
index 5c2e995..2723940 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/javatests/com/android/libraries/rcs/simpleclient/service/chat/SimpleChatSessionTest.java
@@ -35,14 +35,14 @@
import gov.nist.javax.sip.message.SIPRequest;
import gov.nist.javax.sip.message.SIPResponse;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
import java.util.List;
import javax.sip.message.Message;
import javax.sip.message.Request;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
@RunWith(AndroidJUnit4.class)
public class SimpleChatSessionTest {
private static final String LOCAL_URI = "tel:+1234567890";
@@ -126,6 +126,16 @@
public String getPlaniHeader() {
return "IEEE-802.11;i-wlan-node-id=PLANI01EB5B0";
}
+
+ @Override
+ public String getUserAgentHeader() {
+ return "Test-Client";
+ }
+
+ @Override
+ public int getMaxPayloadSizeOnUdp() {
+ return 0;
+ }
};
private final SimpleRcsClientContext context =
new SimpleRcsClientContext(
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
index b621257..6bb8eec 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/CpimUtils.java
@@ -16,8 +16,9 @@
package com.android.libraries.rcs.simpleclient.protocol.cpim;
-import java.time.LocalDate;
import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
import java.util.Random;
/** Collections of utility functions for CPIM */
@@ -28,6 +29,7 @@
private CpimUtils() {
}
+ @SuppressWarnings("AndroidJdkLibsChecker")
public static SimpleCpimMessage createForText(String text) {
return SimpleCpimMessage.newBuilder()
.addNamespace("imdn", "urn:ietf:params:imdn")
@@ -35,7 +37,8 @@
.addHeader("imdn.Disposition-Notification", "positive-delivery, display")
.addHeader("To", ANONYMOUS_URI)
.addHeader("From", ANONYMOUS_URI)
- .addHeader("DateTime", LocalDate.now(ZoneId.systemDefault()).toString())
+ .addHeader("DateTime", ZonedDateTime.now(ZoneId.systemDefault()).format(
+ DateTimeFormatter.ISO_INSTANT))
.setContentType("text/plain")
.setContent(text)
.build();
@@ -43,6 +46,6 @@
private static String generateImdnMessageId() {
Random random = new Random();
- return "Test_" + random.nextLong();
+ return "Test_" + random.nextInt(1000000);
}
}
diff --git a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
index aeb6b11..e2efafe 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/cpim/SimpleCpimMessage.java
@@ -16,11 +16,19 @@
package com.android.libraries.rcs.simpleclient.protocol.cpim;
+import android.text.TextUtils;
import com.google.auto.value.AutoValue;
+import com.google.common.base.Ascii;
import com.google.common.base.Utf8;
import com.google.common.collect.ImmutableMap;
-
+import com.google.common.io.CharStreams;
+import java.io.BufferedReader;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
/**
* The CPIM implementation as per RFC 3862. This class supports minimal fields that is required to
@@ -30,10 +38,9 @@
public abstract class SimpleCpimMessage {
private static final String CRLF = "\r\n";
private static final String COLSP = ": ";
-
- public static SimpleCpimMessage.Builder newBuilder() {
- return new AutoValue_SimpleCpimMessage.Builder();
- }
+ private static final Pattern NAMESPACE_HEADER_PATTERN =
+ Pattern.compile("NS:\\s+(\\S+)\\s+<(.+)>");
+ private static final Pattern HEADER_PATTERN = Pattern.compile("([^\\s:]+):\\s+(.+)");
public abstract ImmutableMap<String, String> namespaces();
@@ -61,13 +68,50 @@
builder.append(CRLF);
builder.append("Content-Type").append(COLSP).append(contentType());
- builder.append("Content-Length").append(COLSP).append(Utf8.encodedLength(content()));
builder.append(CRLF);
+ builder.append("Content-Length").append(COLSP).append(Utf8.encodedLength(content()));
+ builder.append(CRLF).append(CRLF);
builder.append(content());
return builder.toString();
}
+ public static SimpleCpimMessage parse(byte[] content) throws IOException {
+ BufferedReader reader =
+ new BufferedReader(new InputStreamReader(new ByteArrayInputStream(content)));
+ Builder builder = newBuilder();
+
+ String line = reader.readLine();
+ while (!TextUtils.isEmpty(line)) {
+ Matcher namespaceMatcher = NAMESPACE_HEADER_PATTERN.matcher(line);
+ Matcher headerMatcher = HEADER_PATTERN.matcher(line);
+ if (namespaceMatcher.matches()) {
+ builder.addNamespace(namespaceMatcher.group(1), namespaceMatcher.group(2));
+ } else if (headerMatcher.matches()) {
+ builder.addHeader(headerMatcher.group(1), headerMatcher.group(2));
+ }
+
+ line = reader.readLine();
+ }
+
+ line = reader.readLine();
+ while (!TextUtils.isEmpty(line)) {
+ Matcher headerMatcher = HEADER_PATTERN.matcher(line);
+ if (headerMatcher.matches()) {
+ if (Ascii.equalsIgnoreCase("content-type", headerMatcher.group(1))) {
+ builder.setContentType(headerMatcher.group(2));
+ }
+ }
+
+ line = reader.readLine();
+ }
+
+ String body = CharStreams.toString(reader);
+ builder.setContent(body);
+
+ return builder.build();
+ }
+
@AutoValue.Builder
public abstract static class Builder {
public abstract ImmutableMap.Builder<String, String> namespacesBuilder();
@@ -90,4 +134,8 @@
return this;
}
}
+
+ public static SimpleCpimMessage.Builder newBuilder() {
+ return new AutoValue_SimpleCpimMessage.Builder();
+ }
}
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/MsrpConstants.java b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
index ad1b98e..ba424c6 100644
--- a/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
+++ b/testapps/TestRcsApp/aosp_test_rcsclient/src/com/android/libraries/rcs/simpleclient/protocol/msrp/MsrpConstants.java
@@ -44,8 +44,12 @@
public static final String HEADER_TO_PATH = "To-Path";
public static final String HEADER_FROM_PATH = "From-Path";
public static final String HEADER_FAILURE_REPORT = "Failure-Report";
+ public static final String HEADER_SUCCESS_REPORT = "Success-Report";
+ public static final String REPORT_VALUE_YES = "yes";
+ public static final String REPORT_VALUE_NO = "no";
+
public static final int RESPONSE_CODE_OK = 200;
private MsrpConstants() {
}
-}
\ No newline at end of file
+}
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 2f95bef..7605fb5 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
@@ -22,7 +22,8 @@
import android.text.TextUtils;
import android.util.Log;
-import com.android.libraries.rcs.simpleclient.protocol.sdp.SdpUtils;
+import androidx.annotation.Nullable;
+
import com.android.libraries.rcs.simpleclient.protocol.sdp.SimpleSdpMessage;
import com.google.common.base.Ascii;
@@ -259,7 +260,7 @@
request.setCallId(invite.getCallId());
- Via via = (Via) request.getTopmostVia().clone();
+ Via via = (Via) invite.getTopmostVia().clone();
via.removeParameter("branch");
request.addHeader(via);
request.addHeader(
@@ -280,12 +281,13 @@
* @param code The status code of the response.
*/
public static SIPResponse buildInviteResponse(
- SipSessionConfiguration configuration, SIPRequest invite, int code)
+ SipSessionConfiguration configuration,
+ SIPRequest invite,
+ int code,
+ @Nullable SimpleSdpMessage sdp)
throws ParseException {
SIPResponse response = invite.createResponse(code);
if (code == Response.OK) {
- SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(),
- false);
response.setMessageContent(SDP_CONTENT_TYPE, SDP_CONTENT_SUB_TYPE, sdp.encode());
}
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 01a1061..b204de6 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
@@ -21,6 +21,8 @@
import android.text.TextUtils;
import android.util.Log;
+import androidx.annotation.Nullable;
+
import com.android.libraries.rcs.simpleclient.SimpleRcsClientContext;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpManager;
import com.android.libraries.rcs.simpleclient.protocol.sip.SipSession;
@@ -45,8 +47,6 @@
import javax.sip.message.Request;
import javax.sip.message.Response;
-import androidx.annotation.Nullable;
-
/**
* Minimal CPM chat session service that provides the interface creating a {@link SimpleChatSession}
* instance using {@link SipDelegateConnection}.
@@ -162,7 +162,8 @@
SipUtils.buildInviteResponse(
mContext.getSipSession().getSessionConfiguration(),
request,
- Response.METHOD_NOT_ALLOWED);
+ Response.METHOD_NOT_ALLOWED,
+ null);
sendSipResponse(response, /* session= */ null)
.addListener(() -> {
}, MoreExecutors.directExecutor());
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 74472d7..f66e230 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
@@ -31,6 +31,7 @@
import com.android.libraries.rcs.simpleclient.protocol.cpim.SimpleCpimMessage;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunk.Continuation;
+import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpChunkHeader;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpConstants;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpManager;
import com.android.libraries.rcs.simpleclient.protocol.msrp.MsrpSession;
@@ -109,6 +110,8 @@
// Build a new CPIM message and send it out through the MSRP session.
SimpleCpimMessage cpim = CpimUtils.createForText(msg);
+ Log.i(TAG, "Encoded CPIM:" + cpim.encode());
+
byte[] content = cpim.encode().getBytes(UTF_8);
MsrpChunk msrpChunk =
MsrpChunk.newBuilder()
@@ -118,12 +121,18 @@
.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_YES)
+ .addHeader(MsrpConstants.HEADER_SUCCESS_REPORT,
+ MsrpConstants.REPORT_VALUE_NO)
.addHeader(
MsrpConstants.HEADER_BYTE_RANGE,
String.format("1-%d/%d", content.length, content.length))
.addHeader(MsrpConstants.HEADER_MESSAGE_ID, MsrpUtils.generateRandomId())
.addHeader(MsrpConstants.HEADER_CONTENT_TYPE, CPIM_CONTENT_TYPE)
.build();
+
+ Log.i(TAG, "Send a MSRP chunk: " + msrpChunk);
Futures.addCallback(
session.send(msrpChunk),
new FutureCallback<MsrpChunk>() {
@@ -213,11 +222,13 @@
updateRemoteUri(mInviteRequest);
+ SipSessionConfiguration configuration = mContext.getSipSession().getSessionConfiguration();
+ SimpleSdpMessage sdp = SdpUtils.createSdpForMsrp(configuration.getLocalIpAddress(), false);
+
// Automatically reply back to the invite by building a pre-canned response.
try {
- SIPResponse response =
- SipUtils.buildInviteResponse(
- mContext.getSipSession().getSessionConfiguration(), invite, statusCode);
+ SIPResponse response = SipUtils.buildInviteResponse(configuration, invite, statusCode,
+ sdp);
return Futures.transform(
mService.sendSipResponse(response, this), result -> null,
MoreExecutors.directExecutor());
@@ -339,64 +350,79 @@
return;
}
+ SimpleSdpMessage sdp;
try {
- SimpleSdpMessage sdp =
- SimpleSdpMessage.parse(new ByteArrayInputStream(response.getRawContent()));
- startMsrpSession(sdp);
+ sdp = SimpleSdpMessage.parse(new ByteArrayInputStream(response.getRawContent()));
} catch (ParseException | IOException e) {
notifyFailure("Invalid SDP in INVITE", CODE_ERROR_UNSPECIFIED);
+ return;
}
- if (mInviteRequest != null) {
- SIPRequest ack = mInviteRequest.createAckRequest((To) response.getToHeader());
- Futures.addCallback(
- mService.sendSipRequest(ack, this),
- new FutureCallback<Boolean>() {
- @Override
- public void onSuccess(Boolean result) {
- if (result) {
- mStartFuture.set(null);
- mStartFuture = null;
- } else {
- notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
- }
- }
+ if (mInviteRequest == null) {
+ notifyFailure("No INVITE request sent out", CODE_ERROR_UNSPECIFIED);
+ return;
+ }
- @Override
- public void onFailure(Throwable t) {
+ SIPRequest ack = mInviteRequest.createAckRequest((To) response.getToHeader());
+ Futures.addCallback(
+ mService.sendSipRequest(ack, this),
+ new FutureCallback<Boolean>() {
+ @Override
+ public void onSuccess(Boolean result) {
+ if (result) {
+ startMsrpSession(sdp);
+ mRemoteSdp = sdp;
+ } else {
notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
}
- },
- MoreExecutors.directExecutor());
- }
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ notifyFailure("Failed to send ACK", CODE_ERROR_UNSPECIFIED);
+ }
+ },
+ MoreExecutors.directExecutor());
}
private void notifyFailure(String message, @ErrorCode int code) {
- mStartFuture.setException(new ChatServiceException(message, code));
- mStartFuture = null;
+ if (mStartFuture != null) {
+ mStartFuture.setException(new ChatServiceException(message, code));
+ mStartFuture = null;
+ }
+ }
+
+ private void notifySuccess() {
+ if (mStartFuture != null) {
+ mStartFuture.set(null);
+ mStartFuture = null;
+ }
}
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();
}
@Override
public void onFailure(Throwable t) {
Log.e(TAG, "Failed to create msrp session", t);
+ notifyFailure("Failed to establish msrp session",
+ CODE_ERROR_UNSPECIFIED);
terminate()
.addListener(
- () -> {
- Log.d(TAG, "Session terminated");
- },
+ () -> Log.d(TAG, "Session terminated"),
MoreExecutors.directExecutor());
}
},
@@ -406,13 +432,55 @@
}
}
+ 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);
- if (mListener != null) {
- // TODO(b/173186571): Parse CPIM and invoke onMessageReceived()
+
+ MsrpChunkHeader contentTypeHeader = chunk.header("Content-Type");
+ if (chunk.content().length == 0 || contentTypeHeader == null) {
+ Log.i(TAG, "No content or Content-Type header, drop it");
+ return;
+ }
+
+ String contentType = contentTypeHeader.value();
+ if ("message/cpim".equals(contentType)) {
+ try {
+ SimpleCpimMessage cpim = SimpleCpimMessage.parse(chunk.content());
+ if (mListener != null) {
+ mListener.onMessageReceived(cpim);
+ }
+ } catch (Exception e) {
+ Log.e(TAG, "Error while parsing cpim message.", e);
+ }
+ } else {
+ Log.w(TAG, contentType + " is not supported.");
}
}
+
/** Set new listener for this session. */
public void setListener(@Nullable ChatSessionListener listener) {
mListener = listener;
diff --git a/tests/Android.bp b/tests/Android.bp
index 4eacf6d..c180476 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -14,6 +14,15 @@
// limitations under the License.
//
+package {
+ // See: http://go/android-license-faq
+ // A large-scale-change added 'default_applicable_licenses' to import
+ // all of the 'license_kinds' from "packages_services_Telephony_license"
+ // to get the below license kinds:
+ // SPDX-license-identifier-Apache-2.0
+ default_applicable_licenses: ["packages_services_Telephony_license"],
+}
+
android_test {
name: "TeleServiceTests",
@@ -50,4 +59,3 @@
],
}
-
diff --git a/tests/src/com/android/phone/RcsProvisioningMonitorTest.java b/tests/src/com/android/phone/RcsProvisioningMonitorTest.java
index 6c36c2c..d72b348 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());
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);