Merge "Adds ALLOWED_NETWORK_TYPES_REASON_ENABLE_2G to PhoneInterfaceManager." into sc-dev
diff --git a/apex/Android.bp b/apex/Android.bp
index a7137d9..25a4909 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -9,6 +9,8 @@
key: "com.android.telephony.key",
certificate: ":com.android.telephony.certificate",
+
+ updatable: false,
}
apex {
diff --git a/src/com/android/phone/CarrierConfigLoader.java b/src/com/android/phone/CarrierConfigLoader.java
index 37a0618..831b537 100644
--- a/src/com/android/phone/CarrierConfigLoader.java
+++ b/src/com/android/phone/CarrierConfigLoader.java
@@ -278,6 +278,9 @@
obtainMessage(EVENT_BIND_DEFAULT_TIMEOUT, phoneId, -1),
BIND_TIMEOUT_MILLIS);
} else {
+ // Put a stub bundle in place so that the rest of the logic continues
+ // smoothly.
+ mConfigFromDefaultApp[phoneId] = new PersistableBundle();
// Send broadcast if bind fails.
notifySubscriptionInfoUpdater(phoneId);
// TODO: We *must* call unbindService even if bindService returns false.
@@ -359,6 +362,8 @@
unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
broadcastConfigChangedIntent(phoneId);
}
+ // Put a stub bundle in place so that the rest of the logic continues smoothly.
+ mConfigFromDefaultApp[phoneId] = new PersistableBundle();
notifySubscriptionInfoUpdater(phoneId);
break;
}
@@ -402,6 +407,9 @@
obtainMessage(EVENT_BIND_CARRIER_TIMEOUT, phoneId, -1),
BIND_TIMEOUT_MILLIS);
} else {
+ // Put a stub bundle in place so that the rest of the logic continues
+ // smoothly.
+ mConfigFromCarrierApp[phoneId] = new PersistableBundle();
// Send broadcast if bind fails.
broadcastConfigChangedIntent(phoneId);
loge("Bind to carrier app: " + carrierPackageName + " fails");
@@ -471,7 +479,8 @@
case EVENT_BIND_CARRIER_TIMEOUT:
case EVENT_FETCH_CARRIER_TIMEOUT: {
- loge("Bind/fetch from carrier app timeout");
+ loge("Bind/fetch from carrier app timeout, package="
+ + getCarrierPackageForPhoneId(phoneId));
removeMessages(EVENT_FETCH_CARRIER_TIMEOUT);
// If we attempted to bind to the app, but the service connection is null due to
// the race condition that clear config event happens before bind/fetch complete
@@ -482,6 +491,8 @@
unbindIfBound(mContext, mServiceConnection[phoneId], phoneId);
broadcastConfigChangedIntent(phoneId);
}
+ // Put a stub bundle in place so that the rest of the logic continues smoothly.
+ mConfigFromCarrierApp[phoneId] = new PersistableBundle();
notifySubscriptionInfoUpdater(phoneId);
break;
}
@@ -848,6 +859,7 @@
}
/** Returns the package name of a priveleged carrier app, or null if there is none. */
+ @Nullable
private String getCarrierPackageForPhoneId(int phoneId) {
List<String> carrierPackageNames = TelephonyManager.from(mContext)
.getCarrierPackageNamesForIntentAndPhone(
@@ -1167,25 +1179,27 @@
PersistableBundle config = mConfigFromDefaultApp[phoneId];
if (config != null) {
retConfig.putAll(config);
- if (getCarrierPackageForPhoneId(phoneId) == null) {
- retConfig.putBoolean(
- CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
- }
}
config = mConfigFromCarrierApp[phoneId];
if (config != null) {
retConfig.putAll(config);
- retConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
}
config = mPersistentOverrideConfigs[phoneId];
if (config != null) {
retConfig.putAll(config);
- retConfig.putBoolean(CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, true);
}
config = mOverrideConfigs[phoneId];
if (config != null) {
retConfig.putAll(config);
}
+ // Ignore the theoretical case of the default app not being present since that won't
+ // work in CarrierConfigLoader today.
+ final boolean allConfigsApplied =
+ (mConfigFromCarrierApp[phoneId] != null
+ || getCarrierPackageForPhoneId(phoneId) == null)
+ && mConfigFromDefaultApp[phoneId] != null;
+ retConfig.putBoolean(
+ CarrierConfigManager.KEY_CARRIER_CONFIG_APPLIED_BOOL, allConfigsApplied);
} else {
if (mNoSimConfig != null) {
retConfig.putAll(mNoSimConfig);
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/SimPhonebookProvider.java b/src/com/android/phone/SimPhonebookProvider.java
index 8307672..7a1e93c 100644
--- a/src/com/android/phone/SimPhonebookProvider.java
+++ b/src/com/android/phone/SimPhonebookProvider.java
@@ -59,7 +59,6 @@
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;
@@ -106,7 +105,6 @@
private static final int ELEMENTARY_FILES_ITEM = 101;
private static final int SIM_RECORDS = 200;
private static final int SIM_RECORDS_ITEM = 201;
- private static final int VALIDATE_NAME = 300;
static {
URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
@@ -120,10 +118,6 @@
SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*", SIM_RECORDS);
URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*/#", SIM_RECORDS_ITEM);
- URI_MATCHER.addURI(SimPhonebookContract.AUTHORITY,
- SimPhonebookContract.SUBSCRIPTION_ID_PATH_SEGMENT + "/#/*/"
- + SimRecords.VALIDATE_NAME_PATH_SEGMENT,
- VALIDATE_NAME);
}
// Only allow 1 write at a time to prevent races; the mutations are based on reads of the
@@ -209,6 +203,31 @@
return true;
}
+ @Nullable
+ @Override
+ public Bundle call(@NonNull String method, @Nullable String arg, @Nullable Bundle extras) {
+ if (SimRecords.GET_ENCODED_NAME_LENGTH_METHOD_NAME.equals(method)) {
+ // No permissions checks needed. This isn't leaking any sensitive information since the
+ // name we are checking is provided by the caller.
+ return callForEncodedNameLength(arg);
+ }
+ return super.call(method, arg, extras);
+ }
+
+ private Bundle callForEncodedNameLength(String name) {
+ Bundle result = new Bundle();
+ result.putInt(SimRecords.EXTRA_ENCODED_NAME_LENGTH, getEncodedNameLength(name));
+ return result;
+ }
+
+ private int getEncodedNameLength(String name) {
+ if (Strings.isNullOrEmpty(name)) {
+ return 0;
+ } else {
+ byte[] encoded = AdnRecord.encodeAlphaTag(name);
+ return encoded.length;
+ }
+ }
@Nullable
@Override
@@ -231,8 +250,6 @@
case SIM_RECORDS_ITEM:
return querySimRecordsItem(PhonebookArgs.forSimRecordsItem(uri, queryArgs),
projection);
- case VALIDATE_NAME:
- return queryValidateName(PhonebookArgs.forValidateName(uri, queryArgs), queryArgs);
default:
throw new IllegalArgumentException("Unsupported Uri " + uri);
}
@@ -402,21 +419,6 @@
return result;
}
- private Cursor queryValidateName(PhonebookArgs args, @Nullable Bundle queryArgs) {
- if (queryArgs == null) {
- throw new IllegalArgumentException(SimRecords.NAME + " is required.");
- }
- validateSubscriptionAndEf(args);
- String name = queryArgs.getString(SimRecords.NAME);
-
- // Cursor extras are used to return the result.
- Cursor result = new MatrixCursor(new String[0], 0);
- Bundle extras = new Bundle();
- extras.putParcelable(SimRecords.EXTRA_NAME_VALIDATION_RESULT, validateName(args, name));
- result.setExtras(extras);
- return result;
- }
-
@Nullable
@Override
public String getType(@NonNull Uri uri) {
@@ -665,23 +667,6 @@
}
}
- private SimRecords.NameValidationResult validateName(
- PhonebookArgs args, @Nullable String name) {
- name = Strings.nullToEmpty(name);
- int recordSize = getRecordSize(getRecordsSizeForEf(args));
- // Validating the name consists of encoding the record in the binary format that it is
- // stored on the SIM then decoding it and checking whether the decoded name is the same.
- // The AOSP implementation of AdnRecord replaces unsupported characters with spaces during
- // encoding.
- // TODO: It would be good to update AdnRecord to support UCS-2 on the encode path (it
- // supports it on the decode path). Right now it's not supported and so any non-latin
- // characters will not be valid (at least in the AOSP implementation).
- byte[] encodedName = AdnRecord.encodeAlphaTag(name);
- String sanitizedName = AdnRecord.decodeAlphaTag(encodedName, 0, encodedName.length);
- return new SimRecords.NameValidationResult(name, sanitizedName,
- encodedName.length, AdnRecord.getMaxAlphaTagBytes(recordSize));
- }
-
private void validatePhoneNumber(@Nullable String phoneNumber) {
if (phoneNumber == null || phoneNumber.isEmpty()) {
throw new IllegalArgumentException(SimRecords.PHONE_NUMBER + " is required.");
@@ -715,13 +700,11 @@
validatePhoneNumber(phoneNumber);
String name = values.getAsString(SimRecords.NAME);
- SimRecords.NameValidationResult result = validateName(args, name);
+ int length = getEncodedNameLength(name);
+ int maxLength = AdnRecord.getMaxAlphaTagBytes(getRecordSize(getRecordsSizeForEf(args)));
- if (result.getEncodedLength() > result.getMaxEncodedLength()) {
+ if (length > maxLength) {
throw new IllegalArgumentException(SimRecords.NAME + " is too long.");
- } else if (!Objects.equals(result.getName(), result.getSanitizedName())) {
- throw new IllegalArgumentException(
- SimRecords.NAME + " contains unsupported characters.");
}
}
@@ -895,19 +878,6 @@
queryArgs);
}
- /**
- * Pattern: subid/${subscriptionId}/${efName}/validate_name
- *
- * @see SimRecords#validateName(ContentResolver, int, int, String)
- * @see #VALIDATE_NAME
- */
- static PhonebookArgs forValidateName(Uri uri, Bundle queryArgs) {
- int subscriptionId = parseSubscriptionIdFromUri(uri, 1);
- String efName = uri.getPathSegments().get(2);
- return PhonebookArgs.createFromEfName(
- uri, subscriptionId, efName, -1, queryArgs);
- }
-
private static int parseSubscriptionIdFromUri(Uri uri, int pathIndex) {
if (pathIndex == -1) {
return SubscriptionManager.INVALID_SUBSCRIPTION_ID;
diff --git a/src/com/android/services/telephony/TelephonyConnection.java b/src/com/android/services/telephony/TelephonyConnection.java
index 1e97a6c..7a59908 100755
--- a/src/com/android/services/telephony/TelephonyConnection.java
+++ b/src/com/android/services/telephony/TelephonyConnection.java
@@ -31,6 +31,7 @@
import android.os.PersistableBundle;
import android.telecom.BluetoothCallQualityReport;
import android.telecom.CallAudioState;
+import android.telecom.CallScreeningService;
import android.telecom.Conference;
import android.telecom.Connection;
import android.telecom.ConnectionService;
@@ -1138,12 +1139,24 @@
}
@Override
- public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts) {
+ public void onCallFilteringCompleted(boolean isBlocked, boolean isInContacts,
+ CallScreeningService.CallResponse callScreeningResponse,
+ boolean isResponseFromSystemDialer) {
+ // Check what the call screening service has to say, if it's a system dialer.
+ boolean isAllowedToDisplayPicture;
+ if (isResponseFromSystemDialer && callScreeningResponse != null
+ && callScreeningResponse.getCallComposerAttachmentsToShow() >= 0) {
+ isAllowedToDisplayPicture = (callScreeningResponse.getCallComposerAttachmentsToShow()
+ & CallScreeningService.CallResponse.CALL_COMPOSER_ATTACHMENT_PICTURE) != 0;
+ } else {
+ isAllowedToDisplayPicture = isInContacts;
+ }
+
if (isImsConnection()) {
ImsPhone imsPhone = (getPhone() instanceof ImsPhone) ? (ImsPhone) getPhone() : null;
if (imsPhone != null
&& imsPhone.getCallComposerStatus() == TelephonyManager.CALL_COMPOSER_STATUS_ON
- && !isBlocked && isInContacts) {
+ && !isBlocked && isAllowedToDisplayPicture) {
ImsPhoneConnection originalConnection = (ImsPhoneConnection) mOriginalConnection;
ImsCallProfile profile = originalConnection.getImsCall().getCallProfile();
String serverUrl = CallComposerPictureManager.sTestMode
diff --git a/testapps/TestRcsApp/TestApp/AndroidManifest.xml b/testapps/TestRcsApp/TestApp/AndroidManifest.xml
index 2ff1df0..6e52949 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="2"
- android:versionName="1.0.1">
+ android:versionCode="3"
+ android:versionName="1.0.2_UP1.0">
<uses-sdk
android:minSdkVersion="30"
diff --git a/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml
index 374db9b..5dbf6b0 100644
--- a/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml
+++ b/testapps/TestRcsApp/TestApp/res/layout/chat_layout.xml
@@ -21,8 +21,9 @@
android:id="@+id/destNum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:inputType="number"
- android:text="16504483120" />
+ android:inputType="phone"
+ android:digits="0123456789+"
+ android:hint="+15555551212" />
</LinearLayout>
diff --git a/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml b/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml
index 5d71cd1..0390d51 100644
--- a/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml
+++ b/testapps/TestRcsApp/TestApp/res/layout/number_to_chat.xml
@@ -20,8 +20,9 @@
android:id="@+id/destNum"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:inputType="number"
- android:text="16504396583" />
+ android:inputType="phone"
+ android:digits="0123456789+"
+ android:hint="+15555551212" />
</LinearLayout>
<Button
diff --git a/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml b/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml
index 0174d71..305c88e 100644
--- a/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml
+++ b/testapps/TestRcsApp/TestApp/res/layout/uce_layout.xml
@@ -34,8 +34,9 @@
android:id="@+id/number_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
- android:inputType="number"
- android:text="16504483123, 16504489023" />
+ android:inputType="phone"
+ android:digits="0123456789+,"
+ android:hint="+16505551212,+16505551213" />
</LinearLayout>
<Button
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 1619f14..1db4af7 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
@@ -40,6 +40,7 @@
import com.google.android.sample.rcsclient.util.ChatManager;
import com.google.android.sample.rcsclient.util.ChatProvider;
+import com.google.android.sample.rcsclient.util.NumberUtils;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@@ -58,6 +59,7 @@
private boolean mSessionInitResult = false;
private Button mSend;
private String mDestNumber;
+ private TextView mDestNumberView;
private EditText mNewMessage;
private ChatObserver mChatObserver;
private Handler mHandler;
@@ -89,6 +91,7 @@
}
};
+ mDestNumberView = findViewById(R.id.destNum);
initDestNumber();
mChatObserver = new ChatObserver(mHandler);
}
@@ -96,8 +99,7 @@
private void initDestNumber() {
Intent intent = getIntent();
mDestNumber = intent.getStringExtra(EXTRA_REMOTE_PHONE_NUMBER);
- TextView destNumber = findViewById(R.id.destNum);
- destNumber.setText(mDestNumber);
+ mDestNumberView.setText(mDestNumber);
}
@Override
@@ -119,7 +121,12 @@
return;
}
try {
-
+ // Reformat so that the number matches the one sent to the network.
+ String formattedNumber = NumberUtils.formatNumber(this, mDestNumber);
+ if (formattedNumber != null) {
+ mDestNumber = formattedNumber;
+ }
+ mDestNumberView.setText(mDestNumber);
ChatManager.getInstance(getApplicationContext(), subId).initChatSession(
TELURI_PREFIX + mDestNumber, new SessionStateCallback() {
@Override
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/PhoneNumberActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/PhoneNumberActivity.java
index a277994..b432979 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/PhoneNumberActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/PhoneNumberActivity.java
@@ -22,10 +22,13 @@
import android.view.MenuItem;
import android.widget.Button;
import android.widget.EditText;
+import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.sample.rcsclient.util.NumberUtils;
+
/** An activity to let user input phone number to chat. */
public class PhoneNumberActivity extends AppCompatActivity {
@@ -43,11 +46,16 @@
mChatButton = this.findViewById(R.id.launch_chat_btn);
mPhoneNumber = findViewById(R.id.destNum);
mChatButton.setOnClickListener(view -> {
- Intent intent = new Intent(PhoneNumberActivity.this, ChatActivity.class);
- intent.putExtra(ChatActivity.EXTRA_REMOTE_PHONE_NUMBER,
+ String formattedNumber = NumberUtils.formatNumber(PhoneNumberActivity.this,
mPhoneNumber.getText().toString());
- PhoneNumberActivity.this.startActivity(intent);
-
+ if (formattedNumber != null) {
+ Intent intent = new Intent(PhoneNumberActivity.this, ChatActivity.class);
+ intent.putExtra(ChatActivity.EXTRA_REMOTE_PHONE_NUMBER, formattedNumber);
+ PhoneNumberActivity.this.startActivity(intent);
+ } else {
+ Toast.makeText(this, "Invalid Number format!",
+ Toast.LENGTH_LONG).show();
+ }
});
}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java
index da0cf39..aa90487 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/ProvisioningActivity.java
@@ -88,7 +88,7 @@
private static RcsClientConfiguration getDefaultClientConfiguration() {
return new RcsClientConfiguration(
/*rcsVersion=*/ "6.0",
- /*rcsProfile=*/ "UP_2.3",
+ /*rcsProfile=*/ "UP_1.0",
/*clientVendor=*/ "Goog",
/*clientVersion=*/ "RCSAndrd-1.0");
}
diff --git a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java
index 9edb817..10f588c 100644
--- a/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/UceActivity.java
@@ -35,16 +35,15 @@
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
+import com.google.android.sample.rcsclient.util.NumberUtils;
+
import java.util.ArrayList;
import java.util.List;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
/** An activity to verify UCE. */
public class UceActivity extends AppCompatActivity {
private static final String TAG = "TestRcsApp.UceActivity";
- private final ExecutorService mExecutorService = Executors.newSingleThreadExecutor();
private Button mCapabilityButton;
private Button mAvailabilityButton;
private TextView mCapabilityResult;
@@ -72,16 +71,16 @@
mCapabilityResult = findViewById(R.id.capability_result);
mAvailabilityResult = findViewById(R.id.capability_result);
- List<Uri> contactList = getContectList();
mImsRcsManager = getImsRcsManager(mDefaultSmsSubId);
mCapabilityButton.setOnClickListener(view -> {
+ List<Uri> contactList = getContectList();
if (contactList.size() == 0) {
Log.i(TAG, "empty contact list");
return;
}
mCapabilityResult.setText("pending...\n");
try {
- mImsRcsManager.getUceAdapter().requestCapabilities(contactList, mExecutorService,
+ mImsRcsManager.getUceAdapter().requestCapabilities(contactList, getMainExecutor(),
new RcsUceAdapter.CapabilitiesCallback() {
public void onCapabilitiesReceived(
List<RcsContactUceCapability> contactCapabilities) {
@@ -113,6 +112,7 @@
});
mAvailabilityButton.setOnClickListener(view -> {
+ List<Uri> contactList = getContectList();
if (contactList.size() == 0) {
Log.i(TAG, "empty contact list");
return;
@@ -120,7 +120,7 @@
mAvailabilityResult.setText("pending...\n");
try {
mImsRcsManager.getUceAdapter().requestAvailability(contactList.get(0),
- mExecutorService, new RcsUceAdapter.CapabilitiesCallback() {
+ getMainExecutor(), new RcsUceAdapter.CapabilitiesCallback() {
public void onCapabilitiesReceived(
List<RcsContactUceCapability> contactCapabilities) {
Log.i(TAG, "onCapabilitiesReceived()");
@@ -153,13 +153,18 @@
private List<Uri> getContectList() {
mNumbers = findViewById(R.id.number_list);
- String []numbers;
+ String[] numbers;
ArrayList<Uri> contactList = new ArrayList<>();
if (!TextUtils.isEmpty(mNumbers.getText().toString())) {
String numberList = mNumbers.getText().toString().trim();
numbers = numberList.split(",");
for (String number : numbers) {
- contactList.add(Uri.parse(ChatActivity.TELURI_PREFIX + number));
+ String formattedNumber = NumberUtils.formatNumber(this, number);
+ if (formattedNumber != null) {
+ contactList.add(Uri.parse(ChatActivity.TELURI_PREFIX + formattedNumber));
+ } else {
+ Log.w(TAG, "number formatted improperly, skipping: " + number);
+ }
}
}
@@ -206,6 +211,10 @@
if (t.getServiceCapabilities() != null) {
RcsContactPresenceTuple.ServiceCapabilities servCaps =
t.getServiceCapabilities();
+ b.append(", servCaps=(audio=");
+ b.append(servCaps.isAudioCapable());
+ b.append(", video=");
+ b.append(servCaps.isVideoCapable());
b.append(", servCaps=(supported=");
b.append(servCaps.getSupportedDuplexModes());
b.append("), servCaps=(unsupported=");
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
new file mode 100644
index 0000000..72cbf3f
--- /dev/null
+++ b/testapps/TestRcsApp/TestApp/src/com/google/android/sample/rcsclient/util/NumberUtils.java
@@ -0,0 +1,35 @@
+/*
+ * 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.google.android.sample.rcsclient.util;
+
+import android.content.Context;
+import android.telephony.PhoneNumberUtils;
+import android.telephony.TelephonyManager;
+
+public class NumberUtils {
+
+ /**
+ * Format a number in E164 format.
+ * <p>
+ * Note: if the number can not be formatted, this method will return null.
+ */
+ public static String formatNumber(Context context, String number) {
+ TelephonyManager manager = context.getSystemService(TelephonyManager.class);
+ String simCountryIso = manager.getSimCountryIso().toUpperCase();
+ return PhoneNumberUtils.formatNumberToE164(number, simCountryIso);
+ }
+}
diff --git a/tests/src/com/android/phone/SimPhonebookProviderTest.java b/tests/src/com/android/phone/SimPhonebookProviderTest.java
index 1d48694..8778529 100644
--- a/tests/src/com/android/phone/SimPhonebookProviderTest.java
+++ b/tests/src/com/android/phone/SimPhonebookProviderTest.java
@@ -78,10 +78,7 @@
@RunWith(AndroidJUnit4.class)
public final class SimPhonebookProviderTest {
- // Emojis aren't currently supported for the ADN record label.
private static final String EMOJI = new String(Character.toChars(0x1F642));
- private static final String UNSUPPORTED_NAME = ":)=" + EMOJI + ";ni=日;hon=本;";
- private static final String UNSUPPORTED_NAME2 = "日本" + EMOJI;
private static final Correspondence<AdnRecord, AdnRecord> ADN_RECORD_IS_EQUAL =
Correspondence.from(AdnRecord::isEqual, "isEqual");
@@ -674,6 +671,30 @@
}
@Test
+ public void insert_nameWithNonGsmCharacters_addsAdnRecord() {
+ setupSimsWithSubscriptionIds(1);
+ mIccPhoneBook.makeAllEfsSupported(1);
+
+ ContentValues values = new ContentValues();
+ String name = "abc日本" + EMOJI;
+ values.put(SimRecords.NAME, name);
+ values.put(SimRecords.PHONE_NUMBER, "8005550101");
+
+ Uri uri = mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values);
+
+ List<AdnRecord> records = mIccPhoneBook.getAdnRecordsInEfForSubscriber(
+ 1, IccConstants.EF_ADN).stream()
+ .filter(((Predicate<AdnRecord>) AdnRecord::isEmpty).negate())
+ .collect(Collectors.toList());
+
+ assertThat(records)
+ .comparingElementsUsing(ADN_RECORD_IS_EQUAL)
+ .containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, name, "8005550101"));
+
+ assertThat(uri).isEqualTo(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1));
+ }
+
+ @Test
public void insert_nullValues_returnsNull() {
setupSimsWithSubscriptionIds(1);
mIccPhoneBook.makeAllEfsSupported(1);
@@ -753,7 +774,7 @@
mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 25);
ContentValues values = new ContentValues();
- // Name is limited to 11 characters
+ // Name is limited to 11 characters when the max record size is 25
values.put(SimRecords.NAME, "1234567890ab");
values.put(SimRecords.PHONE_NUMBER, "8005550102");
@@ -761,6 +782,13 @@
() -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
+
+ // 2 bytes per character and 4 for the emoji. So this is 14 characters long.
+ values.put(SimRecords.NAME, "abc日本" + EMOJI);
+ e = assertThrows(IllegalArgumentException.class,
+ () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
+
+ assertThat(e).hasMessageThat().isEqualTo(SimRecords.NAME + " is too long.");
}
@Test
@@ -780,36 +808,22 @@
}
@Test
- public void insert_illegalCharacters_throwsCorrectException() {
+ public void insert_numberWithInvalidCharacters_throwsCorrectException() {
setupSimsWithSubscriptionIds(1);
mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 32);
ContentValues values = new ContentValues();
values.put(SimRecords.NAME, "Name");
- values.put(SimRecords.PHONE_NUMBER, "1800J550A0B");
+ values.put(SimRecords.PHONE_NUMBER, "(800)555-0190 x7777");
IllegalArgumentException e = assertThrows(IllegalArgumentException.class,
- () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
+ () -> mResolver.insert(SimRecords.getContentUri(1, ElementaryFiles.EF_ADN),
+ values,
+ null));
assertThat(e).hasMessageThat().isEqualTo(
SimRecords.PHONE_NUMBER + " contains unsupported characters.");
- values.put(SimRecords.NAME, UNSUPPORTED_NAME);
- values.put(SimRecords.PHONE_NUMBER, "18005550101");
-
- e = assertThrows(IllegalArgumentException.class,
- () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
- assertThat(e).hasMessageThat().isEqualTo(
- SimRecords.NAME + " contains unsupported characters.");
-
- values.put(SimRecords.NAME, UNSUPPORTED_NAME2);
- values.put(SimRecords.PHONE_NUMBER, "18005550101");
-
- e = assertThrows(IllegalArgumentException.class,
- () -> mResolver.insert(SimRecords.getContentUri(1, EF_ADN), values));
- assertThat(e).hasMessageThat().isEqualTo(
- SimRecords.NAME + " contains unsupported characters.");
-
- // The inserts didn't actually add any data.
+ // The insert didn't actually change the data.
assertThat(mIccPhoneBook.getAllValidRecords()).isEmpty();
}
@@ -996,7 +1010,7 @@
}
@Test
- public void update_nameOrNumberWithInvalidCharacters_throwsCorrectException() {
+ public void update_numberWithInvalidCharacters_throwsCorrectException() {
setupSimsWithSubscriptionIds(1);
mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 1, 32);
mIccPhoneBook.addRecord(1, IccConstants.EF_ADN, "Initial", "8005550101");
@@ -1012,18 +1026,7 @@
assertThat(e).hasMessageThat().isEqualTo(
SimRecords.PHONE_NUMBER + " contains unsupported characters.");
- // Unicode fffe is a unicode non-character
- values.put(SimRecords.NAME, UNSUPPORTED_NAME);
- values.put(SimRecords.PHONE_NUMBER, "18005550102");
-
- e = assertThrows(IllegalArgumentException.class,
- () -> mResolver.update(SimRecords.getItemUri(1, ElementaryFiles.EF_ADN, 1),
- values,
- null));
- assertThat(e).hasMessageThat().isEqualTo(
- SimRecords.NAME + " contains unsupported characters.");
-
- // The updates didn't actually change the data.
+ // The update didn't actually change the data.
assertThat(mIccPhoneBook.getAllValidRecords())
.comparingElementsUsing(Correspondence.from(AdnRecord::isEqual, "isEqual"))
.containsExactly(new AdnRecord(IccConstants.EF_ADN, 1, "Initial", "8005550101"));
@@ -1179,76 +1182,26 @@
}
@Test
- public void validateName_validName_returnsValueIsCorrect() {
- setupSimsWithSubscriptionIds(1);
- String validName = "First Last";
- // See AdnRecord#FOOTER_SIZE_BYTES
- mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 10, validName.length() + 14);
- SimRecords.NameValidationResult validationResult = SimRecords.validateName(mResolver, 1,
- EF_ADN, validName);
+ public void getEncodedNameLength_returnsValueIsCorrect() {
+ String name = "";
+ int length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(0);
- assertThat(validationResult.isValid()).isTrue();
- assertThat(validationResult.getName()).isEqualTo(validName);
- assertThat(validationResult.getSanitizedName()).isEqualTo(validName);
- assertThat(validationResult.getEncodedLength()).isEqualTo(validName.length());
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(validName.length());
+ name = "First Last";
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length());
- mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 10, 40);
- validationResult = SimRecords.validateName(mResolver, 1, EF_ADN, validName);
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(40 - 14);
- }
+ name = "日本";
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
- @Test
- public void validateName_nameTooLong_returnsValueIsCorrect() {
- setupSimsWithSubscriptionIds(1);
- String tooLongName = "First Last";
- mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 10, tooLongName.length() + 14 - 1);
- SimRecords.NameValidationResult validationResult = SimRecords.validateName(mResolver, 1,
- EF_ADN, tooLongName);
+ name = EMOJI;
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
- assertThat(validationResult.isValid()).isFalse();
- assertThat(validationResult.getName()).isEqualTo(tooLongName);
- assertThat(validationResult.getSanitizedName()).isEqualTo(tooLongName);
- assertThat(validationResult.getEncodedLength()).isEqualTo(tooLongName.length());
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(tooLongName.length() - 1);
- }
-
- @Test
- public void validateName_nameWithUnsupportedCharacters_returnsValueIsCorrect() {
- setupSimsWithSubscriptionIds(1);
- mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 10, 40);
- SimRecords.NameValidationResult validationResult = SimRecords.validateName(mResolver, 1,
- EF_ADN, UNSUPPORTED_NAME);
-
- assertThat(validationResult.isValid()).isFalse();
- assertThat(validationResult.getName()).isEqualTo(UNSUPPORTED_NAME);
- assertThat(validationResult.getSanitizedName()).isEqualTo(":)= ;ni= ;hon= ;");
- assertThat(validationResult.getEncodedLength()).isEqualTo(UNSUPPORTED_NAME.length());
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(
- AdnRecord.getMaxAlphaTagBytes(40));
- }
-
- @Test
- public void validateName_emptyString_returnsValueIsCorrect() {
- setupSimsWithSubscriptionIds(1);
- mIccPhoneBook.setRecordsSize(1, IccConstants.EF_ADN, 10, 40);
- SimRecords.NameValidationResult validationResult = SimRecords.validateName(mResolver, 1,
- EF_ADN, "");
-
- assertThat(validationResult.isValid()).isTrue();
- assertThat(validationResult.getName()).isEqualTo("");
- assertThat(validationResult.getSanitizedName()).isEqualTo("");
- assertThat(validationResult.getEncodedLength()).isEqualTo(0);
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(
- AdnRecord.getMaxAlphaTagBytes(40));
-
- // Null is equivalent to empty
- validationResult = SimRecords.validateName(mResolver, 1, EF_ADN, null);
- assertThat(validationResult.getName()).isEqualTo("");
- assertThat(validationResult.getSanitizedName()).isEqualTo("");
- assertThat(validationResult.getEncodedLength()).isEqualTo(0);
- assertThat(validationResult.getMaxEncodedLength()).isEqualTo(
- AdnRecord.getMaxAlphaTagBytes(40));
+ name = "abc日本" + EMOJI;
+ length = SimRecords.getEncodedNameLength(mResolver, name);
+ assertThat(length).isEqualTo(name.length() * 2 + 1);
}
private void setupSimsWithSubscriptionIds(int... subscriptionIds) {