Loading src/java/com/android/internal/telephony/imsphone/ImsPhone.java +57 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony.imsphone; import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE; Loading Loading @@ -70,6 +71,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UssdResponse; Loading Loading @@ -105,6 +107,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneNotifier; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyComponentFactory; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.dataconnection.TransportManager; Loading @@ -122,6 +125,7 @@ import com.android.telephony.Rlog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; Loading Loading @@ -2485,9 +2489,62 @@ public class ImsPhone extends ImsPhoneBase { public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) { if (DBG) logd("handleImsSubscriberAssociatedUriChanged"); setCurrentSubscriberUris(uris); setPhoneNumberForSourceIms(uris); } }; /** Sets the IMS phone number from IMS associated URIs, if any found. */ @VisibleForTesting public void setPhoneNumberForSourceIms(Uri[] uris) { String phoneNumber = extractPhoneNumberFromAssociatedUris(uris); if (phoneNumber == null) { return; } SubscriptionController subController = SubscriptionController.getInstance(); int subId = getSubId(); String countryIso = getCountryIso(subController, subId); // Format the number as one more defense to reject garbage values: // phoneNumber will become null. phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso); if (phoneNumber == null) { return; } subController.setSubscriptionProperty(subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, phoneNumber); } private static String getCountryIso(SubscriptionController subController, int subId) { SubscriptionInfo info = subController.getSubscriptionInfo(subId); String countryIso = info == null ? "" : info.getCountryIso(); // info.getCountryIso() may return null return countryIso == null ? "" : countryIso; } /** * Finds the phone number from associated URIs. * * <p>Associated URIs are public user identities, and phone number could be used: * see 3GPP TS 24.229 5.4.1.2 and 3GPP TS 23.003 13.4. This algotihm look for the * possible "global number" in E.164 format. */ private static String extractPhoneNumberFromAssociatedUris(Uri[] uris) { if (uris == null) { return null; } return Arrays.stream(uris) // Phone number is an opaque URI "tel:<phone-number>" or "sip:<phone-number>@<...>" .filter(u -> u != null && u.isOpaque()) .filter(u -> "tel".equalsIgnoreCase(u.getScheme()) || "sip".equalsIgnoreCase(u.getScheme())) .map(Uri::getSchemeSpecificPart) // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777" .filter(ssp -> ssp != null && ssp.startsWith("+")) // Remove whatever after "@" for sip URI .map(ssp -> ssp.split("@")[0]) // Returns the first winner .findFirst() .orElse(null); } public IccRecords getIccRecords() { return mDefaultPhone.getIccRecords(); } Loading tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java +96 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony.imsphone; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_ONLY; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED; import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY; Loading @@ -40,6 +42,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; Loading @@ -49,6 +52,7 @@ import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncResult; import android.os.Bundle; import android.os.Handler; Loading @@ -57,6 +61,7 @@ import android.os.PersistableBundle; import android.sysprop.TelephonyProperties; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsReasonInfo; Loading Loading @@ -976,6 +981,97 @@ public class ImsPhoneTest extends TelephonyTest { mImsPhoneUT.handleMessage(m); } @Test @SmallTest public void testSetPhoneNumberForSourceIms() { // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE); int subId = 1; doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); // 1. Two valid phone number; 1st is set. Uri[] associatedUris = new Uri[] { Uri.parse("sip:+447539447777@ims.x.com"), Uri.parse("tel:+447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539447777"); // 2. 1st invalid and 2nd valid: 2nd is set. associatedUris = new Uri[] { Uri.parse("sip:447539447777@ims.x.com"), Uri.parse("tel:+447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446666"); // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set. associatedUris = new Uri[] { Uri.parse("sip:john.doe@ims.x.com"), Uri.parse("tel:+447539446677"), Uri.parse("sip:+447539447766@ims.x.com") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446677"); // Clean up mContextFixture.addCallingOrSelfPermission(""); } @Test @SmallTest public void testSetPhoneNumberForSourceImsNegativeCases() { // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE); int subId = 1; doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); // 1. No valid phone number; do not set Uri[] associatedUris = new Uri[] { Uri.parse("sip:447539447777@ims.x.com"), Uri.parse("tel:447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 2. no URI; do not set associatedUris = new Uri[] {}; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 3. null URI; do not set associatedUris = new Uri[] { null }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 4. null pointer; do not set mImsPhoneUT.setPhoneNumberForSourceIms(null); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // Clean up mContextFixture.addCallingOrSelfPermission(""); } private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) { ServiceState ss = new ServiceState(); ss.setStateOutOfService(); Loading Loading
src/java/com/android/internal/telephony/imsphone/ImsPhone.java +57 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,7 @@ package com.android.internal.telephony.imsphone; import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_MESSAGE; import static android.telephony.ims.ImsManager.EXTRA_WFC_REGISTRATION_FAILURE_TITLE; Loading Loading @@ -70,6 +71,7 @@ import android.telephony.CarrierConfigManager; import android.telephony.NetworkRegistrationInfo; import android.telephony.PhoneNumberUtils; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; import android.telephony.UssdResponse; Loading Loading @@ -105,6 +107,7 @@ import com.android.internal.telephony.Phone; import com.android.internal.telephony.PhoneConstants; import com.android.internal.telephony.PhoneNotifier; import com.android.internal.telephony.ServiceStateTracker; import com.android.internal.telephony.SubscriptionController; import com.android.internal.telephony.TelephonyComponentFactory; import com.android.internal.telephony.TelephonyIntents; import com.android.internal.telephony.dataconnection.TransportManager; Loading @@ -122,6 +125,7 @@ import com.android.telephony.Rlog; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.function.Consumer; Loading Loading @@ -2485,9 +2489,62 @@ public class ImsPhone extends ImsPhoneBase { public void handleImsSubscriberAssociatedUriChanged(Uri[] uris) { if (DBG) logd("handleImsSubscriberAssociatedUriChanged"); setCurrentSubscriberUris(uris); setPhoneNumberForSourceIms(uris); } }; /** Sets the IMS phone number from IMS associated URIs, if any found. */ @VisibleForTesting public void setPhoneNumberForSourceIms(Uri[] uris) { String phoneNumber = extractPhoneNumberFromAssociatedUris(uris); if (phoneNumber == null) { return; } SubscriptionController subController = SubscriptionController.getInstance(); int subId = getSubId(); String countryIso = getCountryIso(subController, subId); // Format the number as one more defense to reject garbage values: // phoneNumber will become null. phoneNumber = PhoneNumberUtils.formatNumberToE164(phoneNumber, countryIso); if (phoneNumber == null) { return; } subController.setSubscriptionProperty(subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, phoneNumber); } private static String getCountryIso(SubscriptionController subController, int subId) { SubscriptionInfo info = subController.getSubscriptionInfo(subId); String countryIso = info == null ? "" : info.getCountryIso(); // info.getCountryIso() may return null return countryIso == null ? "" : countryIso; } /** * Finds the phone number from associated URIs. * * <p>Associated URIs are public user identities, and phone number could be used: * see 3GPP TS 24.229 5.4.1.2 and 3GPP TS 23.003 13.4. This algotihm look for the * possible "global number" in E.164 format. */ private static String extractPhoneNumberFromAssociatedUris(Uri[] uris) { if (uris == null) { return null; } return Arrays.stream(uris) // Phone number is an opaque URI "tel:<phone-number>" or "sip:<phone-number>@<...>" .filter(u -> u != null && u.isOpaque()) .filter(u -> "tel".equalsIgnoreCase(u.getScheme()) || "sip".equalsIgnoreCase(u.getScheme())) .map(Uri::getSchemeSpecificPart) // "Global number" should be in E.164 format starting with "+" e.g. "+447539447777" .filter(ssp -> ssp != null && ssp.startsWith("+")) // Remove whatever after "@" for sip URI .map(ssp -> ssp.split("@")[0]) // Returns the first winner .findFirst() .orElse(null); } public IccRecords getIccRecords() { return mDefaultPhone.getIccRecords(); } Loading
tests/telephonytests/src/com/android/internal/telephony/imsphone/ImsPhoneTest.java +96 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.internal.telephony.imsphone; import static android.Manifest.permission.MODIFY_PHONE_STATE; import static android.provider.Telephony.SimInfo.COLUMN_PHONE_NUMBER_SOURCE_IMS; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_ONLY; import static android.telephony.CarrierConfigManager.USSD_OVER_CS_PREFERRED; import static android.telephony.CarrierConfigManager.USSD_OVER_IMS_ONLY; Loading @@ -40,6 +42,7 @@ import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.times; Loading @@ -49,6 +52,7 @@ import android.app.Activity; import android.content.BroadcastReceiver; import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.AsyncResult; import android.os.Bundle; import android.os.Handler; Loading @@ -57,6 +61,7 @@ import android.os.PersistableBundle; import android.sysprop.TelephonyProperties; import android.telephony.CarrierConfigManager; import android.telephony.ServiceState; import android.telephony.SubscriptionInfo; import android.telephony.TelephonyManager; import android.telephony.ims.ImsCallProfile; import android.telephony.ims.ImsReasonInfo; Loading Loading @@ -976,6 +981,97 @@ public class ImsPhoneTest extends TelephonyTest { mImsPhoneUT.handleMessage(m); } @Test @SmallTest public void testSetPhoneNumberForSourceIms() { // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE); int subId = 1; doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); // 1. Two valid phone number; 1st is set. Uri[] associatedUris = new Uri[] { Uri.parse("sip:+447539447777@ims.x.com"), Uri.parse("tel:+447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539447777"); // 2. 1st invalid and 2nd valid: 2nd is set. associatedUris = new Uri[] { Uri.parse("sip:447539447777@ims.x.com"), Uri.parse("tel:+447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446666"); // 3. 1st sip-uri is not phone number and 2nd valid: 2nd is set. associatedUris = new Uri[] { Uri.parse("sip:john.doe@ims.x.com"), Uri.parse("tel:+447539446677"), Uri.parse("sip:+447539447766@ims.x.com") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController).setSubscriptionProperty( subId, COLUMN_PHONE_NUMBER_SOURCE_IMS, "+447539446677"); // Clean up mContextFixture.addCallingOrSelfPermission(""); } @Test @SmallTest public void testSetPhoneNumberForSourceImsNegativeCases() { // In reality the method under test runs in phone process so has MODIFY_PHONE_STATE mContextFixture.addCallingOrSelfPermission(MODIFY_PHONE_STATE); int subId = 1; doReturn(subId).when(mPhone).getSubId(); SubscriptionInfo subInfo = mock(SubscriptionInfo.class); doReturn("gb").when(subInfo).getCountryIso(); doReturn(subInfo).when(mSubscriptionController).getSubscriptionInfo(subId); // 1. No valid phone number; do not set Uri[] associatedUris = new Uri[] { Uri.parse("sip:447539447777@ims.x.com"), Uri.parse("tel:447539446666") }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 2. no URI; do not set associatedUris = new Uri[] {}; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 3. null URI; do not set associatedUris = new Uri[] { null }; mImsPhoneUT.setPhoneNumberForSourceIms(associatedUris); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // 4. null pointer; do not set mImsPhoneUT.setPhoneNumberForSourceIms(null); verify(mSubscriptionController, never()).setSubscriptionProperty( anyInt(), any(), any()); // Clean up mContextFixture.addCallingOrSelfPermission(""); } private ServiceState getServiceStateDataAndVoice(int rat, int regState, boolean isRoaming) { ServiceState ss = new ServiceState(); ss.setStateOutOfService(); Loading